How to stop pynput from listening to its own input - input

I have this issue:
Say I register a keyboard listener like this :
keyboard = pynput.keyboard.Controller()
def on_each_key_press(key):
print(key)
if(key==Key.enter):#make the pynput type a whenever Enter is pressed
keyboard.type("a")
if key == Key.esc:
# Stop listener
return False
def run():
with Listener(
on_press = on_each_key_press,
) as listener:
listener.join()
This is the output:
(I pressed the shift and # and ENTER keys myself)
Key.shift pressed
'#' pressed
Key.enter pressed
pynput types a
'a' pressed
Then the pynput will not only listen to the keys the user types, but it will also listen to the keys that pynput auto types (i.e the "a" after the ENTER key is pressed)
is there a way to stop pynput from tracking its own input?

Related

Multi-QComboBox Hide/Show Popup on LineEdit Press (PyQT5)

I have created a checkable QComboBox where all the options are checkboxes. Everything works fine, except I can no longer click on the QLineEdit to open/close the combobox pop-up, the way a regular QComboBox would work.
I have tried to apply an event filter to the QLineEdit, as shown below, that should ideally close the combobox pop-up if it is currently open, and open it if it is currently closed. But instead, clicking on QLineEdit only opens the pop-up everytime.
I believe this is because the mouse button press (QEvent.MouseButtonPress) closes the pop-up (hence setting the self.isPopup boolean to False), so the mouse button release (QEvent.MouseButtonRelease) will always open the pop-up. I've tried to get the QCombobox to ignore the MouseButtonPress event, but to no avail. I'm not sure where I've gone wrong here - if anyone has any suggestions, it would be much appreciated.
(Here's the relevant parts of the code)
class CustomComboBox(QtWidgets.QComboBox):
def __init__(self):
super(CustomComboBox, self).__init__()
self.setModel(QtGui.QStandardItemModel(self)) # setting up widget to make it checkable
self.setEditable(True)
self.lineEdit().setReadOnly(True)
self.lineEdit().setPlaceholderText("--Select Option--")
self.isPopup = False # bool to close or open pop up
self.lineEdit().installEventFilter(self) # event filter for lineedit presses
def hidePopup(self):
super().hidePopup()
self.isPopup = False
def showPopup(self):
super().shwoPopup()
self.isPopup = True
def eventFilter(self, widget, event):
if widget == self.lineEdit():
if event.type() == QtCore.QEvent.MouseButtonRelease:
if self.isPopup:
self.hidePopup()
else:
self.showPopup()
return True
elif event.type() == QtCore.QEvent.MouseButtonPress:
event.ignore()
return True
return super(CustomComboBox, self).eventFilter(widget, event)
The problem is caused by the fact that making the combo editable, you actually have two widgets that can handle mouse events, and since QComboBox handles mouse buttons in a specific way (to allow proper popup management) that makes things difficult, because the popup normally closes after the button press.
Since your requirement for the editable line edit is just to write custom text, then just override the paintEvent by slightly changing the default behavior:
class CustomComboBox(QtWidgets.QComboBox):
customText = ''
def setCustomText(self, text):
if self.customText != text:
self.customText = text
self.update()
def paintEvent(self, event):
if not self.customText:
super().paintEvent(event)
return
painter = QStylePainter(self)
painter.setPen(self.palette().color(QPalette.Text))
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
opt.text = self.customText
painter.drawControl(QStyle.CE_ComboBoxaLabel, opt)
With the code above, you don't need to make the combo editable, and therefore there is no event filtering.

How can I position the cursor where the mouse is clicked when editing a QTableWidget cell?

My app allows double-clicking on a QTableWidget cell to edit the content, but the cursor is always placed at the end of the existing content.
I've arranged for the content not to be selected on edit. How can I go about positioning the edit cursor where the mouse was clicked?
Here is a way to place the insertion point at the location where you clicked in the grid cell. There are a few things you need to put in the right place, as noted below. This uses a QTableView control instead of a QTableWidget. I'm not sure how much will translate across.
import PySide2.QtWidgets as qtw
import PySide2.QtGui as qgui
from PySide2.QtCore import Qt
# place the cursor where the mouse was clicked in a cell
# based on https://stackoverflow.com/a/72792962 and a comment
# from #musicamanta at https://stackoverflow.com/q/73346426
class ClickPositionDelegate(QStyledItemDelegate):
# override the createEditor behavior so we can capture the
# first `selectAll` that occurs automatically after the
# QLineEdit control is created.
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
# set margins so text in the control aligns with the grid (optional)
editor.setTextMargins(4, 2, 2, 4)
if isinstance(editor, qtw.QLineEdit):
def position_cursor():
# Catch the initial selectAll event via the selectionChanged
# signal; this ensures the position is calculated after the
# control is placed on screen, so cursorPositionAt will work
# correctly.
# Disconnect so setCursorPosition won't call this func again
editor.selectionChanged.disconnect(deselect)
# Get cursor position within the editor's coordinate system
gpos = qgui.QCursor.pos()
lpos = editor.mapFromGlobal(gpos)
editor.setCursorPosition(editor.cursorPositionAt(lpos))
# queue up the positioning function if and only if we got here
# via a simple mouse click (left mouse button is currently down
# with no modifiers)
if (
qgui.QGuiApplication.mouseButtons() == Qt.LeftButton
and qgui.QGuiApplication.keyboardModifiers() == Qt.NoModifier
):
editor.selectionChanged.connect(position_cursor)
return editor
class MainWindow(QMainWindow):
# Constructor
def __init__(self):
# Call the parent class's constructor
super().__init__()
...
# Create the data table
self.table = QTableView(self)
table_view = self.table
# start editing as soon as a cell is selected (no need for Enter
# or double-click)
self.table.setEditTriggers(QAbstractItemView.AllEditTriggers)
# or self.table.setEditTriggers(QTableView.CurrentChanged)
# call our special delegate to position the cursor correctly when
# clicking on a cell
self.table.setItemDelegate(ClickPositionDelegate(self.table))
# Set the central widget of the main window
self.setCentralWidget(self.table)
...
# be sure to run self.table.setModel(some_model) at some point
...
app = QApplication()
window = MainWindow()
window.show()
app.exec_()

PyQt5: Why does QPushButton.setDefault() ignore spacebar but work for enter/return?

I have a modal with two buttons, one Accept and one Cancel.
I set the cancel button to be the default with .setDefault() and .setAutoDefault()
Pressing return activates the cancel-button, but when I press spacebar the accept-button is activated.
Why is the application/accept-button ignoring the defaultness-configuration and activates on spacebar presses rather than the cancel button? It seems like the accept-button has focus or something despite there being a different default.
Why would the default not have focus?
If I call cancel_button.setFocus() just before showing the modal (but not earlier than that), even the spacebar will activate the Cancel-button instead of the Acccept-button, so that solves the underlying problem.
The question is why spacebar and enter do not both activate the default button.
Minimal example:
The modal shows up when the program is run, as well as when the user presses X.
Press ctrl+Q to close the application.
import sys
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QApplication, QMainWindow, QGroupBox, QHBoxLayout, QVBoxLayout, \
QWidget, QShortcut, QDialog, QPushButton
class Modal(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.resize(QSize(600, 300))
self.setParent(parent)
self.setWindowModality(True)
layout = QVBoxLayout()
self.setLayout(layout)
buttons = self.create_buttons()
layout.addWidget(buttons)
# This sets focus (when pressing spacebar), and makes the modal work as expected.
# The question is why is this needed to make spacebar default to activating Cancel?
# Why is spacebar activating Accept by default without this line?:
#self.cancel_button.setFocus()
def create_buttons(self):
button_groupbox = QGroupBox()
button_box_layout = QHBoxLayout()
button_groupbox.setLayout(button_box_layout)
# Despite setting the defaultness, pressing spacebar still activates the accept-button.
# Pressing return activates the cancel-button, however, and is expected behaviour.
# Why is the Accept-button being activated when space is pressed?
accept_button = QPushButton("Accept")
accept_button.clicked.connect(self.accept)
accept_button.setDefault(False)
accept_button.setAutoDefault(False)
self.accept_button = accept_button
cancel_button = QPushButton("Cancel")
cancel_button.clicked.connect(self.reject)
cancel_button.setDefault(True)
cancel_button.setAutoDefault(True)
self.cancel_button = cancel_button
# This does not set focus (when pressing spacebar), maybe because it has not been added yet?
#cancel_button.setFocus()
button_box_layout.addWidget(accept_button)
button_box_layout.addWidget(cancel_button)
return button_groupbox
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
shortcut.activated.connect(app.quit)
shortcut = QShortcut(QKeySequence("X"), self)
shortcut.activated.connect(self.run_modal)
self.resize(QSize(800, 600))
self.show()
def showEvent(self, event):
self.run_modal()
def run_modal(self):
self.modal = Modal(self)
self.modal.finished.connect(self.modal_finished)
self.modal.show()
def modal_finished(self, result):
if result == 0:
print("CANCEL")
elif result == 1:
print("ACCEPT")
else:
raise Exception("BAD RESULT")
if __name__ == '__main__':
app = QApplication(sys.argv)
mainwindow = MainWindow()
sys.exit(app.exec_())
By default, widgets receive focus based on the order in which they are added to a parent. When the top level window is shown, the first widget that accepts focus, following the order above, will receive input focus, meaning that any keyboard event will be sent to that widget first.
Note that when widgets are added to a layout, but were not created with the parent used for that layout, then the order follows that of the layout insertion.
The default property of QPushButtons, instead will "press" the button whenever the top level widget receives the Return or Enter keys are pressed, no matter of the current focused widget, and as long as the focused widget does not handle those keys.
In your case, the currently focused widget is the "Accept" button (since it's the first that has been added to the window), which results in the counter-intuitive behavior you're seeing.
If you want the cancel button to react to both Return/Enter keys (no matter what is the focused widget) and the space bar upon showing, then you have to explicitly call setFocus(). But there's a catch: since setFocus() sets the focus on a widget in the active window, it can only work as long as that widget already belongs to that window.
In your case, the cancel_button.setFocus() call done within create_buttons won't work because, at that point, the button doesn't belong to the top level window yet.
It does work when you do that after layout.addWidget(buttons), because then the button is part of the window.
So, considering the above:
if you want to set the focus on a widget, that widget must already belong to the top level widget before calling setFocus();
the default button will always be triggered upon Return/Enter keypress even if another button has focus;
With your current code, you either do what you already found out (using setFocus() on the instance attribute after adding the widget), or use a basic QTimer in the create_buttons function:
QTimer.singleShot(0, cancel_button.setFocus)
Note that:
while creating separate functions can help you to better organize your code, having a separate function that is just called once is often unnecessary (other than misleading and forcing the creation of instance attributes where they're not actually necessary); just separate code blocks with empty lines, unless those functions can be overridden by further subclasses;
setting a "Cancel" button that can be activated by Return/Enter is not a very good idea, as those keys are generally used for "Accept/Apply/Commit/Write/etc." purposes;
if you want to show a dialog as soon as its parent is shown, you shall only use a QTimer: QTimer.singleShot(0, self.run_modal); the paint event is certainly not a viable option (paint events occur very, very often, and in some systems even when the widget loses focus, which can cause recursion), nor is the showEvent() since that could happen when switching virtual desktops or unminimizing the window;

HTML textarea in Elm where pressing tab adds \t and doesn't change focus

I know how to listen for tab key presses in Elm. And I know how to stop the focus from being changed using onWithOptions:
textarea
[ onWithOptions "keydown" (Options False True) <| Decode.map KeyDown keyCode ] []
I can then check, in my update function, if the keyCode pressed was a 9, representing a tab. The problem is now the default behavior of a textarea doesn't work. Anything I type doesn't appear in the textarea. Easy enough, I simply add whatever I type to the model and make the value of the textarea the model. Now I have issues with the cursor and, more importantly, clipboard pasting doesn't work...
How do I get tabs to work properly with textareas in Elm? Normally, it would seem to make sense to only call preventDefault() if the tab key was pressed. How can I conditionally call preventDefault() in Elm?
Elm does support conditional event propagation through a Decoder that either succeeds or fails. Simply map the message type you want to react to in your update function:
succeededIfTabKey : Int -> Decode.Decoder Int
succeededIfTabKey key =
if key == 9 then
Decode.succeed key
else
Decode.fail "non-tab"
tabPressed : Decode.Decoder Msg
tabPressed =
Decode.andThen succeededIfTabKey keyCode
|> Decode.map (always TabPressed)
And then use this as your attribute for your input element:
onWithOptions "keydown" { defaultOptions | preventDefault = True } tabPressed
This isn't ideal for all situations. If you want some keydown events to not preventDefault(), and other keydown events to preventDefault(), then you're out of luck.

How to simulate "press and hold"(long press) key action in casperjs/phantomjs?

I'm using casperjs to test a grid-like widget. The grid contains large number of rows. The rows can multiple-selected by press and hold Shift key, then click two dirrent rows, then the rows among twice click will get selected. Here is my code segment to test this functionality:
casper.start(mytesturl);
this.then(function sendKeydown(){
//send Shift keydown event
this.page.sendEvent('keydown', '', null, null, 0x02000000);
});
this.then(function startClick(){
//click row 0
this.click('#row0', '50%', '50%');
});
this.then(function secondClick(){
//click row 3
this.click('#row3', '50%', '50%');
});
this.then(function sendKeyup(){
//send Shift keyup event
this.page.sendEvent('keyup', '', null, null, 0x02000000);
this.capture('afterKeyup.png');
});
I also tried "this.page.sendEvent('keyup', this.page.event.key.Shift);", but both failed, the afterKeyup.png alway shows only the row3 is selected, instead of rows 0 to 3 selected.
I guess the problem is that, the Casperjs or Phantomjs don't retain the keydown status when run into other steps, so each time I send keyup event, it acts as the Shift key is firstly pressed, test suite don't think the Shift key is now hold ("keydown").
So I want to know how should I do to achieve pressing and holding Shift key along with click event?
Thanks in advance!
Instead of triggering an event on the page, find which node is listening (Ex. with chrome dev tools) for the required events, then :
// node where event listeners are listening
var element = document.querySelector('foo#bar');
var evt = document.createEvent('HTMLEvents');
evt.initEvent('keydown', false, true);
element.dispatchEvent(evt);