initial commit

This commit is contained in:
rose-jinyang 2019-04-08 22:20:07 +08:00
parent cab4cd7eed
commit 821cee0db2
66 changed files with 13822 additions and 0 deletions

168
commandeditwnd.py Normal file
View File

@ -0,0 +1,168 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ui_commandeditwnd import Ui_CommandEditDialog
from keyactioneditwnd import KeyActionEditWnd
from mouseactioneditwnd import MouseActionEditWnd
from pauseactioneditwnd import PauseActionEditWnd
import json
import keyboard
class CommandEditWnd(QDialog):
def __init__(self, p_command, p_parent = None):
super().__init__(p_parent)
self.ui = Ui_CommandEditDialog()
self.ui.setupUi(self)
self.ui.deleteBut.clicked.connect(self.slotDelete)
self.ui.ok.clicked.connect(self.slotOK)
self.ui.cancel.clicked.connect(self.slotCancel)
self.ui.keyBut.clicked.connect(self.slotNewKeyEdit)
self.ui.mouseBut.clicked.connect(self.slotNewMouseEdit)
self.ui.pauseBut.clicked.connect(self.slotNewPauseEdit)
self.ui.upBut.clicked.connect(self.slotActionUp)
self.ui.downBut.clicked.connect(self.slotActionDown)
self.ui.editBut.clicked.connect(self.slotActionEdit)
self.ui.actionsListWidget.doubleClicked.connect(self.slotActionEdit)
w_otherMenu = QMenu()
w_otherMenu.addAction('Stop Another Command', self.slotStopAnotherCommand)
w_otherMenu.addAction('Execute Another Command', self.slotDoAnotherCommand)
self.ui.otherBut.setMenu(w_otherMenu)
self.m_command = {}
if p_command != None:
self.ui.say.setText(p_command['name'])
w_actions = p_command['actions']
for w_action in w_actions:
w_jsonAction = json.dumps(w_action)
w_item = QListWidgetItem(w_jsonAction)
w_item.setData(Qt.UserRole, w_jsonAction)
self.ui.actionsListWidget.addItem(w_item)
self.ui.asyncChk.setChecked(p_command['async'])
if p_command['repeat'] == -1:
self.ui.continueExe.setChecked(True)
elif p_command['repeat'] == 1:
self.ui.oneExe.setChecked(True)
else:
self.ui.repeatExe.setChecked(True)
self.ui.repeatCnt.setValue(p_command['repeat'])
else:
self.ui.asyncChk.setChecked(False)
self.ui.oneExe.setChecked(True)
def addAction(self, p_action):
w_jsonAction = json.dumps(p_action)
w_item = QListWidgetItem(w_jsonAction)
w_item.setData(Qt.UserRole, w_jsonAction)
self.ui.actionsListWidget.addItem(w_item)
def slotStopAnotherCommand(self):
text, okPressed = QInputDialog.getText(self, "Get Command Name", "Another command name:", QLineEdit.Normal, "")
if okPressed and text != '':
w_commandStopAction = {}
w_commandStopAction['name'] = 'command stop action'
w_commandStopAction['command name'] = text
self.addAction(w_commandStopAction)
def slotDoAnotherCommand(self):
text, okPressed = QInputDialog.getText(self, "Get Command Name", "Another command name:", QLineEdit.Normal, "")
if okPressed and text != '':
w_commandDoAction = {}
w_commandDoAction['name'] = 'command execute action'
w_commandDoAction['command name'] = text
self.addAction(w_commandDoAction)
def slotNewKeyEdit(self):
w_keyEditWnd = KeyActionEditWnd(None, self)
if w_keyEditWnd.exec() == QDialog.Accepted:
self.addAction(w_keyEditWnd.m_keyAction)
def slotNewMouseEdit(self):
w_mouseEditWnd = MouseActionEditWnd(None, self)
if w_mouseEditWnd.exec() == QDialog.Accepted:
self.addAction(w_mouseEditWnd.m_mouseAction)
def slotNewPauseEdit(self):
w_pauseEditWnd = PauseActionEditWnd(None, self)
if w_pauseEditWnd.exec() == QDialog.Accepted:
self.addAction(w_pauseEditWnd.m_pauseAction)
def slotActionUp(self):
currentIndex = self.ui.actionsListWidget.currentRow()
currentItem = self.ui.actionsListWidget.takeItem(currentIndex);
self.ui.actionsListWidget.insertItem(currentIndex - 1, currentItem);
self.ui.actionsListWidget.setCurrentRow(currentIndex - 1);
def slotActionDown(self):
currentIndex = self.ui.actionsListWidget.currentRow();
currentItem = self.ui.actionsListWidget.takeItem(currentIndex);
self.ui.actionsListWidget.insertItem(currentIndex + 1, currentItem);
self.ui.actionsListWidget.setCurrentRow(currentIndex + 1);
def slotActionEdit(self):
w_listItems = self.ui.actionsListWidget.selectedItems()
if not w_listItems: return
w_action = {}
for w_item in w_listItems:
w_jsonAction = w_item.data(Qt.UserRole)
w_action = json.loads(w_jsonAction)
break
if w_action['name'] == 'key action':
w_keyEditWnd = KeyActionEditWnd(w_action, self)
if w_keyEditWnd.exec() == QDialog.Accepted:
w_jsonAction = json.dumps(w_keyEditWnd.m_keyAction)
elif w_action['name'] == 'mouse click action' \
or w_action['name'] == 'mouse move action'\
or w_action['name'] == 'mouse scroll action':
w_mouseEditWnd = MouseActionEditWnd(w_action, self)
if w_mouseEditWnd.exec() == QDialog.Accepted:
w_jsonAction = json.dumps(w_mouseEditWnd.m_mouseAction)
elif w_action['name'] == 'pause action':
w_pauseEditWnd = PauseActionEditWnd(w_action, self)
if w_pauseEditWnd.exec() == QDialog.Accepted:
w_jsonAction = json.dumps(w_pauseEditWnd.m_pauseAction)
elif w_action['name'] == 'command stop action' \
or w_action['name'] == 'command execute action':
text, okPressed = QInputDialog.getText(self, "Get Command Name", "Another command name:", QLineEdit.Normal,
w_action['command name'])
if okPressed and text != '':
w_action['command name'] = text
w_jsonAction = json.dumps(w_action)
w_item.setText(w_jsonAction)
w_item.setData(Qt.UserRole, w_jsonAction)
def slotDelete(self):
w_listItems = self.ui.actionsListWidget.selectedItems()
if not w_listItems: return
for w_item in w_listItems:
self.ui.actionsListWidget.takeItem(self.ui.actionsListWidget.row(w_item))
def saveCommand(self):
w_actionCnt = self.ui.actionsListWidget.count()
self.m_command['name'] = self.ui.say.text()
w_actions = []
for w_idx in range(w_actionCnt):
w_jsonAction = self.ui.actionsListWidget.item(w_idx).data(Qt.UserRole)
w_action = json.loads(w_jsonAction)
w_actions.append(w_action)
self.m_command['actions'] = w_actions
self.m_command['async'] = self.ui.asyncChk.isChecked()
if self.ui.oneExe.isChecked():
self.m_command['repeat'] = 1
elif self.ui.continueExe.isChecked():
self.m_command['repeat'] = -1
elif self.ui.repeatExe.isChecked():
self.m_command['repeat'] = self.ui.repeatCnt.value()
def slotOK(self):
self.saveCommand()
super().accept()
def slotCancel(self):
super().reject()

390
commandeditwnd.ui Normal file
View File

@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CommandEditDialog</class>
<widget class="QDialog" name="CommandEditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>927</width>
<height>419</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Command Edit Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>25</number>
</property>
<property name="topMargin">
<number>20</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item row="4" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>When this command excutes, do the following sequence:</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="8">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>When I say :</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="say"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QRadioButton" name="oneExe">
<property name="text">
<string>This command executes once</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="7" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QRadioButton" name="continueExe">
<property name="text">
<string>This command repeats continuously</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="8" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QRadioButton" name="repeatExe">
<property name="text">
<string>This command repeats</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="repeatCnt">
<property name="minimum">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>times</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0" colspan="7">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QPushButton" name="keyBut">
<property name="text">
<string>Key Press</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mouseBut">
<property name="text">
<string>Mouse</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pauseBut">
<property name="text">
<string>Pause</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="otherBut">
<property name="text">
<string>Other</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="actionsListWidget">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QPushButton" name="upBut">
<property name="text">
<string>Up</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="downBut">
<property name="text">
<string>Down</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editBut">
<property name="text">
<string>Edit</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteBut">
<property name="text">
<string>Delete</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="5" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QCheckBox" name="asyncChk">
<property name="text">
<string>Allow other commands to execute while this one is running</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="12" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<property name="spacing">
<number>20</number>
</property>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

152
keyactioneditwnd.py Normal file
View File

@ -0,0 +1,152 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ui_keyactioneditwnd import Ui_KeyActionEditDialog
import json
import keyboard
import time
import threading
class KeyActionEditWnd(QDialog):
def __init__(self, p_keyAction, p_parent = None):
super().__init__(p_parent)
self.ui = Ui_KeyActionEditDialog()
self.ui.setupUi(self)
self.ui.ctrlBut.clicked.connect(self.slotCtrlClicked)
self.ui.altBut.clicked.connect(self.slotAltClicked)
self.ui.shiftBut.clicked.connect(self.slotShiftClicked)
self.ui.winBut.clicked.connect(self.slotWinClicked)
self.ui.ok.clicked.connect(self.slotOK)
self.ui.cancel.clicked.connect(self.slotCancel)
self.m_ctrlState = 0
self.m_altState = 0
self.m_shiftState = 0
self.m_winState = 0
self.m_keyAction = {}
self.ui.press_releaseKey.setChecked(True)
t = threading.Thread(target=self.keyInput)
t.daemon = True
t.start()
if p_keyAction == None:
return
w_hotKey = p_keyAction['key']
w_keys = w_hotKey.split('+')
for w_key in w_keys:
if w_key == 'left ctrl':
self.m_ctrlState = 1
elif w_key == 'right ctrl':
self.m_ctrlState = 2
elif w_key == 'left alt':
self.m_altState = 1
elif w_key == 'right alt':
self.m_altState = 2
elif w_key == 'left shift':
self.m_shiftState = 1
elif w_key == 'right shift':
self.m_shiftState = 2
elif w_key == 'left windows':
self.m_winState = 1
elif w_key == 'right windows':
self.m_winState = 2
else:
self.ui.keyEdit.setText(w_key)
w_stateText = ['Ctrl', 'Left Ctrl', 'Right Ctrl']
self.ui.ctrlBut.setText(w_stateText[self.m_ctrlState])
w_stateText = ['Alt', 'Left Alt', 'Right Alt']
self.ui.altBut.setText(w_stateText[self.m_altState])
w_stateText = ['Shift', 'Left Shift', 'Right Shift']
self.ui.shiftBut.setText(w_stateText[self.m_shiftState])
w_stateText = ['Win', 'Left Win', 'Right Win']
self.ui.winBut.setText(w_stateText[self.m_winState])
if p_keyAction['type'] == 10:
self.ui.press_releaseKey.setChecked(True)
elif p_keyAction['type'] == 0:
self.ui.releaseKey.setChecked(True)
elif p_keyAction['type'] == 1:
self.ui.pressKey.setChecked(True)
def slotOK(self):
w_ctrlStateText = ['', 'left ctrl', 'right ctrl']
w_altStateText = ['', 'left alt', 'right alt']
w_shiftStateText = ['', 'left shift', 'right shift']
w_winStateText = ['', 'left windows', 'right windows']
w_hotKey = ''
if self.m_ctrlState != 0:
w_hotKey = w_ctrlStateText[self.m_ctrlState]
if self.m_altState != 0:
if w_hotKey != '':
w_hotKey = w_hotKey + '+'
w_hotKey = w_hotKey + w_altStateText[self.m_altState]
if self.m_shiftState != 0:
if w_hotKey != '':
w_hotKey = w_hotKey + '+'
w_hotKey = w_hotKey + w_shiftStateText[self.m_shiftState]
if self.m_winState != 0:
if w_hotKey != '':
w_hotKey = w_hotKey + '+'
w_hotKey = w_hotKey + w_winStateText[self.m_winState]
if self.ui.keyEdit.text() != '':
if w_hotKey != '':
w_hotKey = w_hotKey + '+'
w_hotKey = w_hotKey + self.ui.keyEdit.text()
if w_hotKey == '':
return
self.m_keyAction = {}
self.m_keyAction['name'] = 'key action'
self.m_keyAction['key'] = w_hotKey
if self.ui.press_releaseKey.isChecked():
self.m_keyAction['type'] = 10
elif self.ui.pressKey.isChecked():
self.m_keyAction['type'] = 1
elif self.ui.releaseKey.isChecked():
self.m_keyAction['type'] = 0
return super().accept()
def slotCancel(self):
return super().reject()
def slotCtrlClicked(self):
self.m_ctrlState = (self.m_ctrlState + 1) % 3
w_stateText = ['Ctrl', 'Left Ctrl', 'Right Ctrl']
self.ui.ctrlBut.setText(w_stateText[self.m_ctrlState])
def slotAltClicked(self):
self.m_altState = (self.m_altState + 1) % 3
w_stateText = ['Alt', 'Left Alt', 'Right Alt']
self.ui.altBut.setText(w_stateText[self.m_altState])
def slotShiftClicked(self):
self.m_shiftState = (self.m_shiftState + 1) % 3
w_stateText = ['Shift', 'Left Shift', 'Right Shift']
self.ui.shiftBut.setText(w_stateText[self.m_shiftState])
def slotWinClicked(self):
self.m_winState = (self.m_winState + 1) % 3
w_stateText = ['Win', 'Left Win', 'Right Win']
self.ui.winBut.setText(w_stateText[self.m_winState])
def keyInput(self):
try:
while True:
self.ui.keyEdit.setText(keyboard.read_hotkey(False))
except:
pass

177
keyactioneditwnd.ui Normal file
View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KeyActionEditDialog</class>
<widget class="QDialog" name="KeyActionEditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>471</width>
<height>164</height>
</rect>
</property>
<property name="windowTitle">
<string>Key Action Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Key (Combination):</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>20</number>
</property>
<item>
<widget class="QRadioButton" name="press_releaseKey">
<property name="text">
<string>Press and Release Key(s)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="pressKey">
<property name="text">
<string>Press Key(s)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="releaseKey">
<property name="text">
<string>Release Key(s)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok">
<property name="text">
<string>OK</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="ctrlBut">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
<strikeout>false</strikeout>
</font>
</property>
<property name="text">
<string>Ctrl</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="altBut">
<property name="text">
<string>Alt</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="shiftBut">
<property name="text">
<string>Shift</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="winBut">
<property name="text">
<string>Win</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="keyEdit">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

1155
keyboard/__init__.py Normal file

File diff suppressed because it is too large Load Diff

13
keyboard/__main__.py Normal file
View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
import keyboard
import fileinput
import json
import sys
def print_event_json(event):
print(event.to_json(ensure_ascii=sys.stdout.encoding != 'utf-8'))
sys.stdout.flush()
keyboard.hook(print_event_json)
parse_event_json = lambda line: keyboard.KeyboardEvent(**json.loads(line))
keyboard.play(parse_event_json(line) for line in fileinput.input())

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1246
keyboard/_canonical_names.py Normal file

File diff suppressed because it is too large Load Diff

442
keyboard/_darwinkeyboard.py Normal file
View File

@ -0,0 +1,442 @@
import ctypes
import ctypes.util
import Quartz
import time
import os
import threading
from AppKit import NSEvent
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP
from ._canonical_names import normalize_name
try: # Python 2/3 compatibility
unichr
except NameError:
unichr = chr
Carbon = ctypes.cdll.LoadLibrary(ctypes.util.find_library('Carbon'))
class KeyMap(object):
non_layout_keys = dict((vk, normalize_name(name)) for vk, name in {
# Layout specific keys from https://stackoverflow.com/a/16125341/252218
# Unfortunately no source for layout-independent keys was found.
0x24: 'return',
0x30: 'tab',
0x31: 'space',
0x33: 'delete',
0x35: 'escape',
0x37: 'command',
0x38: 'shift',
0x39: 'capslock',
0x3a: 'option',
0x3b: 'control',
0x3c: 'right shift',
0x3d: 'right option',
0x3e: 'right control',
0x3f: 'function',
0x40: 'f17',
0x48: 'volume up',
0x49: 'volume down',
0x4a: 'mute',
0x4f: 'f18',
0x50: 'f19',
0x5a: 'f20',
0x60: 'f5',
0x61: 'f6',
0x62: 'f7',
0x63: 'f3',
0x64: 'f8',
0x65: 'f9',
0x67: 'f11',
0x69: 'f13',
0x6a: 'f16',
0x6b: 'f14',
0x6d: 'f10',
0x6f: 'f12',
0x71: 'f15',
0x72: 'help',
0x73: 'home',
0x74: 'page up',
0x75: 'forward delete',
0x76: 'f4',
0x77: 'end',
0x78: 'f2',
0x79: 'page down',
0x7a: 'f1',
0x7b: 'left',
0x7c: 'right',
0x7d: 'down',
0x7e: 'up',
}.items())
layout_specific_keys = {}
def __init__(self):
# Virtual key codes are usually the same for any given key, unless you have a different
# keyboard layout. The only way I've found to determine the layout relies on (supposedly
# deprecated) Carbon APIs. If there's a more modern way to do this, please update this
# section.
# Set up data types and exported values:
CFTypeRef = ctypes.c_void_p
CFDataRef = ctypes.c_void_p
CFIndex = ctypes.c_uint64
OptionBits = ctypes.c_uint32
UniCharCount = ctypes.c_uint8
UniChar = ctypes.c_uint16
UniChar4 = UniChar * 4
class CFRange(ctypes.Structure):
_fields_ = [('loc', CFIndex),
('len', CFIndex)]
kTISPropertyUnicodeKeyLayoutData = ctypes.c_void_p.in_dll(Carbon, 'kTISPropertyUnicodeKeyLayoutData')
shiftKey = 0x0200
alphaKey = 0x0400
optionKey = 0x0800
controlKey = 0x1000
kUCKeyActionDisplay = 3
kUCKeyTranslateNoDeadKeysBit = 0
# Set up function calls:
Carbon.CFDataGetBytes.argtypes = [CFDataRef] #, CFRange, UInt8
Carbon.CFDataGetBytes.restype = None
Carbon.CFDataGetLength.argtypes = [CFDataRef]
Carbon.CFDataGetLength.restype = CFIndex
Carbon.CFRelease.argtypes = [CFTypeRef]
Carbon.CFRelease.restype = None
Carbon.LMGetKbdType.argtypes = []
Carbon.LMGetKbdType.restype = ctypes.c_uint32
Carbon.TISCopyCurrentKeyboardInputSource.argtypes = []
Carbon.TISCopyCurrentKeyboardInputSource.restype = ctypes.c_void_p
Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource.argtypes = []
Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource.restype = ctypes.c_void_p
Carbon.TISGetInputSourceProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
Carbon.TISGetInputSourceProperty.restype = ctypes.c_void_p
Carbon.UCKeyTranslate.argtypes = [ctypes.c_void_p,
ctypes.c_uint16,
ctypes.c_uint16,
ctypes.c_uint32,
ctypes.c_uint32,
OptionBits, # keyTranslateOptions
ctypes.POINTER(ctypes.c_uint32), # deadKeyState
UniCharCount, # maxStringLength
ctypes.POINTER(UniCharCount), # actualStringLength
UniChar4]
Carbon.UCKeyTranslate.restype = ctypes.c_uint32
# Get keyboard layout
klis = Carbon.TISCopyCurrentKeyboardInputSource()
k_layout = Carbon.TISGetInputSourceProperty(klis, kTISPropertyUnicodeKeyLayoutData)
if k_layout is None:
klis = Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource()
k_layout = Carbon.TISGetInputSourceProperty(klis, kTISPropertyUnicodeKeyLayoutData)
k_layout_size = Carbon.CFDataGetLength(k_layout)
k_layout_buffer = ctypes.create_string_buffer(k_layout_size) # TODO - Verify this works instead of initializing with empty string
Carbon.CFDataGetBytes(k_layout, CFRange(0, k_layout_size), ctypes.byref(k_layout_buffer))
# Generate character representations of key codes
for key_code in range(0, 128):
# TODO - Possibly add alt modifier to key map
non_shifted_char = UniChar4()
shifted_char = UniChar4()
keys_down = ctypes.c_uint32()
char_count = UniCharCount()
retval = Carbon.UCKeyTranslate(k_layout_buffer,
key_code,
kUCKeyActionDisplay,
0, # No modifier
Carbon.LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
ctypes.byref(keys_down),
4,
ctypes.byref(char_count),
non_shifted_char)
non_shifted_key = u''.join(unichr(non_shifted_char[i]) for i in range(char_count.value))
retval = Carbon.UCKeyTranslate(k_layout_buffer,
key_code,
kUCKeyActionDisplay,
shiftKey >> 8, # Shift
Carbon.LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
ctypes.byref(keys_down),
4,
ctypes.byref(char_count),
shifted_char)
shifted_key = u''.join(unichr(shifted_char[i]) for i in range(char_count.value))
self.layout_specific_keys[key_code] = (non_shifted_key, shifted_key)
# Cleanup
Carbon.CFRelease(klis)
def character_to_vk(self, character):
""" Returns a tuple of (scan_code, modifiers) where ``scan_code`` is a numeric scan code
and ``modifiers`` is an array of string modifier names (like 'shift') """
for vk in self.non_layout_keys:
if self.non_layout_keys[vk] == character.lower():
return (vk, [])
for vk in self.layout_specific_keys:
if self.layout_specific_keys[vk][0] == character:
return (vk, [])
elif self.layout_specific_keys[vk][1] == character:
return (vk, ['shift'])
raise ValueError("Unrecognized character: {}".format(character))
def vk_to_character(self, vk, modifiers=[]):
""" Returns a character corresponding to the specified scan code (with given
modifiers applied) """
if vk in self.non_layout_keys:
# Not a character
return self.non_layout_keys[vk]
elif vk in self.layout_specific_keys:
if 'shift' in modifiers:
return self.layout_specific_keys[vk][1]
return self.layout_specific_keys[vk][0]
else:
# Invalid vk
raise ValueError("Invalid scan code: {}".format(vk))
class KeyController(object):
def __init__(self):
self.key_map = KeyMap()
self.current_modifiers = {
"shift": False,
"caps": False,
"alt": False,
"ctrl": False,
"cmd": False,
}
self.media_keys = {
'KEYTYPE_SOUND_UP': 0,
'KEYTYPE_SOUND_DOWN': 1,
'KEYTYPE_BRIGHTNESS_UP': 2,
'KEYTYPE_BRIGHTNESS_DOWN': 3,
'KEYTYPE_CAPS_LOCK': 4,
'KEYTYPE_HELP': 5,
'POWER_KEY': 6,
'KEYTYPE_MUTE': 7,
'UP_ARROW_KEY': 8,
'DOWN_ARROW_KEY': 9,
'KEYTYPE_NUM_LOCK': 10,
'KEYTYPE_CONTRAST_UP': 11,
'KEYTYPE_CONTRAST_DOWN': 12,
'KEYTYPE_LAUNCH_PANEL': 13,
'KEYTYPE_EJECT': 14,
'KEYTYPE_VIDMIRROR': 15,
'KEYTYPE_PLAY': 16,
'KEYTYPE_NEXT': 17,
'KEYTYPE_PREVIOUS': 18,
'KEYTYPE_FAST': 19,
'KEYTYPE_REWIND': 20,
'KEYTYPE_ILLUMINATION_UP': 21,
'KEYTYPE_ILLUMINATION_DOWN': 22,
'KEYTYPE_ILLUMINATION_TOGGLE': 23
}
def press(self, key_code):
""" Sends a 'down' event for the specified scan code """
if key_code >= 128:
# Media key
ev = NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
14, # type
(0, 0), # location
0xa00, # flags
0, # timestamp
0, # window
0, # ctx
8, # subtype
((key_code-128) << 16) | (0xa << 8), # data1
-1 # data2
)
Quartz.CGEventPost(0, ev.CGEvent())
else:
# Regular key
# Apply modifiers if necessary
event_flags = 0
if self.current_modifiers["shift"]:
event_flags += Quartz.kCGEventFlagMaskShift
if self.current_modifiers["caps"]:
event_flags += Quartz.kCGEventFlagMaskAlphaShift
if self.current_modifiers["alt"]:
event_flags += Quartz.kCGEventFlagMaskAlternate
if self.current_modifiers["ctrl"]:
event_flags += Quartz.kCGEventFlagMaskControl
if self.current_modifiers["cmd"]:
event_flags += Quartz.kCGEventFlagMaskCommand
# Update modifiers if necessary
if key_code == 0x37: # cmd
self.current_modifiers["cmd"] = True
elif key_code == 0x38 or key_code == 0x3C: # shift or right shift
self.current_modifiers["shift"] = True
elif key_code == 0x39: # caps lock
self.current_modifiers["caps"] = True
elif key_code == 0x3A: # alt
self.current_modifiers["alt"] = True
elif key_code == 0x3B: # ctrl
self.current_modifiers["ctrl"] = True
event = Quartz.CGEventCreateKeyboardEvent(None, key_code, True)
Quartz.CGEventSetFlags(event, event_flags)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
time.sleep(0.01)
def release(self, key_code):
""" Sends an 'up' event for the specified scan code """
if key_code >= 128:
# Media key
ev = NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
14, # type
(0, 0), # location
0xb00, # flags
0, # timestamp
0, # window
0, # ctx
8, # subtype
((key_code-128) << 16) | (0xb << 8), # data1
-1 # data2
)
Quartz.CGEventPost(0, ev.CGEvent())
else:
# Regular key
# Update modifiers if necessary
if key_code == 0x37: # cmd
self.current_modifiers["cmd"] = False
elif key_code == 0x38 or key_code == 0x3C: # shift or right shift
self.current_modifiers["shift"] = False
elif key_code == 0x39: # caps lock
self.current_modifiers["caps"] = False
elif key_code == 0x3A: # alt
self.current_modifiers["alt"] = False
elif key_code == 0x3B: # ctrl
self.current_modifiers["ctrl"] = False
# Apply modifiers if necessary
event_flags = 0
if self.current_modifiers["shift"]:
event_flags += Quartz.kCGEventFlagMaskShift
if self.current_modifiers["caps"]:
event_flags += Quartz.kCGEventFlagMaskAlphaShift
if self.current_modifiers["alt"]:
event_flags += Quartz.kCGEventFlagMaskAlternate
if self.current_modifiers["ctrl"]:
event_flags += Quartz.kCGEventFlagMaskControl
if self.current_modifiers["cmd"]:
event_flags += Quartz.kCGEventFlagMaskCommand
event = Quartz.CGEventCreateKeyboardEvent(None, key_code, False)
Quartz.CGEventSetFlags(event, event_flags)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
time.sleep(0.01)
def map_char(self, character):
if character in self.media_keys:
return (128+self.media_keys[character],[])
else:
return self.key_map.character_to_vk(character)
def map_scan_code(self, scan_code):
if scan_code >= 128:
character = [k for k, v in enumerate(self.media_keys) if v == scan_code-128]
if len(character):
return character[0]
return None
else:
return self.key_map.vk_to_character(scan_code)
class KeyEventListener(object):
def __init__(self, callback, blocking=False):
self.blocking = blocking
self.callback = callback
self.listening = True
self.tap = None
def run(self):
""" Creates a listener and loops while waiting for an event. Intended to run as
a background thread. """
self.tap = Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionDefault,
Quartz.CGEventMaskBit(Quartz.kCGEventKeyDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventKeyUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventFlagsChanged),
self.handler,
None)
loopsource = Quartz.CFMachPortCreateRunLoopSource(None, self.tap, 0)
loop = Quartz.CFRunLoopGetCurrent()
Quartz.CFRunLoopAddSource(loop, loopsource, Quartz.kCFRunLoopDefaultMode)
Quartz.CGEventTapEnable(self.tap, True)
while self.listening:
Quartz.CFRunLoopRunInMode(Quartz.kCFRunLoopDefaultMode, 5, False)
def handler(self, proxy, e_type, event, refcon):
scan_code = Quartz.CGEventGetIntegerValueField(event, Quartz.kCGKeyboardEventKeycode)
key_name = name_from_scancode(scan_code)
flags = Quartz.CGEventGetFlags(event)
event_type = ""
is_keypad = (flags & Quartz.kCGEventFlagMaskNumericPad)
if e_type == Quartz.kCGEventKeyDown:
event_type = "down"
elif e_type == Quartz.kCGEventKeyUp:
event_type = "up"
elif e_type == Quartz.kCGEventFlagsChanged:
if key_name.endswith("shift") and (flags & Quartz.kCGEventFlagMaskShift):
event_type = "down"
elif key_name == "caps lock" and (flags & Quartz.kCGEventFlagMaskAlphaShift):
event_type = "down"
elif (key_name.endswith("option") or key_name.endswith("alt")) and (flags & Quartz.kCGEventFlagMaskAlternate):
event_type = "down"
elif key_name == "ctrl" and (flags & Quartz.kCGEventFlagMaskControl):
event_type = "down"
elif key_name == "command" and (flags & Quartz.kCGEventFlagMaskCommand):
event_type = "down"
else:
event_type = "up"
if self.blocking:
return None
self.callback(KeyboardEvent(event_type, scan_code, name=key_name, is_keypad=is_keypad))
return event
key_controller = KeyController()
""" Exported functions below """
def init():
key_controller = KeyController()
def press(scan_code):
""" Sends a 'down' event for the specified scan code """
key_controller.press(scan_code)
def release(scan_code):
""" Sends an 'up' event for the specified scan code """
key_controller.release(scan_code)
def map_name(name):
""" Returns a tuple of (scan_code, modifiers) where ``scan_code`` is a numeric scan code
and ``modifiers`` is an array of string modifier names (like 'shift') """
yield key_controller.map_char(name)
def name_from_scancode(scan_code):
""" Returns the name or character associated with the specified key code """
return key_controller.map_scan_code(scan_code)
def listen(callback):
if not os.geteuid() == 0:
raise OSError("Error 13 - Must be run as administrator")
KeyEventListener(callback).run()
def type_unicode(character):
OUTPUT_SOURCE = Quartz.CGEventSourceCreate(Quartz.kCGEventSourceStateHIDSystemState)
# Key down
event = Quartz.CGEventCreateKeyboardEvent(OUTPUT_SOURCE, 0, True)
Quartz.CGEventKeyboardSetUnicodeString(event, len(character.encode('utf-16-le')) // 2, character)
Quartz.CGEventPost(Quartz.kCGSessionEventTap, event)
# Key up
event = Quartz.CGEventCreateKeyboardEvent(OUTPUT_SOURCE, 0, False)
Quartz.CGEventKeyboardSetUnicodeString(event, len(character.encode('utf-16-le')) // 2, character)
Quartz.CGEventPost(Quartz.kCGSessionEventTap, event)

173
keyboard/_darwinmouse.py Normal file
View File

@ -0,0 +1,173 @@
import os
import datetime
import threading
import Quartz
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN
_button_mapping = {
LEFT: (Quartz.kCGMouseButtonLeft, Quartz.kCGEventLeftMouseDown, Quartz.kCGEventLeftMouseUp, Quartz.kCGEventLeftMouseDragged),
RIGHT: (Quartz.kCGMouseButtonRight, Quartz.kCGEventRightMouseDown, Quartz.kCGEventRightMouseUp, Quartz.kCGEventRightMouseDragged),
MIDDLE: (Quartz.kCGMouseButtonCenter, Quartz.kCGEventOtherMouseDown, Quartz.kCGEventOtherMouseUp, Quartz.kCGEventOtherMouseDragged)
}
_button_state = {
LEFT: False,
RIGHT: False,
MIDDLE: False
}
_last_click = {
"time": None,
"button": None,
"position": None,
"click_count": 0
}
class MouseEventListener(object):
def __init__(self, callback, blocking=False):
self.blocking = blocking
self.callback = callback
self.listening = True
def run(self):
""" Creates a listener and loops while waiting for an event. Intended to run as
a background thread. """
self.tap = Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionDefault,
Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved) |
Quartz.CGEventMaskBit(Quartz.kCGEventScrollWheel),
self.handler,
None)
loopsource = Quartz.CFMachPortCreateRunLoopSource(None, self.tap, 0)
loop = Quartz.CFRunLoopGetCurrent()
Quartz.CFRunLoopAddSource(loop, loopsource, Quartz.kCFRunLoopDefaultMode)
Quartz.CGEventTapEnable(self.tap, True)
while self.listening:
Quartz.CFRunLoopRunInMode(Quartz.kCFRunLoopDefaultMode, 5, False)
def handler(self, proxy, e_type, event, refcon):
# TODO Separate event types by button/wheel/move
scan_code = Quartz.CGEventGetIntegerValueField(event, Quartz.kCGKeyboardEventKeycode)
key_name = name_from_scancode(scan_code)
flags = Quartz.CGEventGetFlags(event)
event_type = ""
is_keypad = (flags & Quartz.kCGEventFlagMaskNumericPad)
if e_type == Quartz.kCGEventKeyDown:
event_type = "down"
elif e_type == Quartz.kCGEventKeyUp:
event_type = "up"
if self.blocking:
return None
self.callback(KeyboardEvent(event_type, scan_code, name=key_name, is_keypad=is_keypad))
return event
# Exports
def init():
""" Initializes mouse state """
pass
def listen(queue):
""" Appends events to the queue (ButtonEvent, WheelEvent, and MoveEvent). """
if not os.geteuid() == 0:
raise OSError("Error 13 - Must be run as administrator")
listener = MouseEventListener(lambda e: queue.put(e) or is_allowed(e.name, e.event_type == KEY_UP))
t = threading.Thread(target=listener.run, args=())
t.daemon = True
t.start()
def press(button=LEFT):
""" Sends a down event for the specified button, using the provided constants """
location = get_position()
button_code, button_down, _, _ = _button_mapping[button]
e = Quartz.CGEventCreateMouseEvent(
None,
button_down,
location,
button_code)
# Check if this is a double-click (same location within the last 300ms)
if _last_click["time"] is not None and datetime.datetime.now() - _last_click["time"] < datetime.timedelta(seconds=0.3) and _last_click["button"] == button and _last_click["position"] == location:
# Repeated Click
_last_click["click_count"] = min(3, _last_click["click_count"]+1)
else:
# Not a double-click - Reset last click
_last_click["click_count"] = 1
Quartz.CGEventSetIntegerValueField(
e,
Quartz.kCGMouseEventClickState,
_last_click["click_count"])
Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
_button_state[button] = True
_last_click["time"] = datetime.datetime.now()
_last_click["button"] = button
_last_click["position"] = location
def release(button=LEFT):
""" Sends an up event for the specified button, using the provided constants """
location = get_position()
button_code, _, button_up, _ = _button_mapping[button]
e = Quartz.CGEventCreateMouseEvent(
None,
button_up,
location,
button_code)
if _last_click["time"] is not None and _last_click["time"] > datetime.datetime.now() - datetime.timedelta(microseconds=300000) and _last_click["button"] == button and _last_click["position"] == location:
# Repeated Click
Quartz.CGEventSetIntegerValueField(
e,
Quartz.kCGMouseEventClickState,
_last_click["click_count"])
Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
_button_state[button] = False
def wheel(delta=1):
""" Sends a wheel event for the provided number of clicks. May be negative to reverse
direction. """
location = get_position()
e = Quartz.CGEventCreateMouseEvent(
None,
Quartz.kCGEventScrollWheel,
location,
Quartz.kCGMouseButtonLeft)
e2 = Quartz.CGEventCreateScrollWheelEvent(
None,
Quartz.kCGScrollEventUnitLine,
1,
delta)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, e2)
def move_to(x, y):
""" Sets the mouse's location to the specified coordinates. """
for b in _button_state:
if _button_state[b]:
e = Quartz.CGEventCreateMouseEvent(
None,
_button_mapping[b][3], # Drag Event
(x, y),
_button_mapping[b][0])
break
else:
e = Quartz.CGEventCreateMouseEvent(
None,
Quartz.kCGEventMouseMoved,
(x, y),
Quartz.kCGMouseButtonLeft)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
def get_position():
""" Returns the mouse's location as a tuple of (x, y). """
e = Quartz.CGEventCreate(None)
point = Quartz.CGEventGetLocation(e)
return (point.x, point.y)

73
keyboard/_generic.py Normal file
View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
from threading import Thread, Lock
import traceback
import functools
try:
from queue import Queue
except ImportError:
from Queue import Queue
class GenericListener(object):
lock = Lock()
def __init__(self):
self.handlers = []
self.listening = False
self.queue = Queue()
def invoke_handlers(self, event):
for handler in self.handlers:
try:
if handler(event):
# Stop processing this hotkey.
return 1
except Exception as e:
traceback.print_exc()
def start_if_necessary(self):
"""
Starts the listening thread if it wans't already.
"""
self.lock.acquire()
try:
if not self.listening:
self.init()
self.listening = True
self.listening_thread = Thread(target=self.listen)
self.listening_thread.daemon = True
self.listening_thread.start()
self.processing_thread = Thread(target=self.process)
self.processing_thread.daemon = True
self.processing_thread.start()
finally:
self.lock.release()
def pre_process_event(self, event):
raise NotImplementedError('This method should be implemented in the child class.')
def process(self):
"""
Loops over the underlying queue of events and processes them in order.
"""
assert self.queue is not None
while True:
event = self.queue.get()
if self.pre_process_event(event):
self.invoke_handlers(event)
self.queue.task_done()
def add_handler(self, handler):
"""
Adds a function to receive each event captured, starting the capturing
process if necessary.
"""
self.start_if_necessary()
self.handlers.append(handler)
def remove_handler(self, handler):
""" Removes a previously added event handler. """
while handler in self.handlers:
self.handlers.remove(handler)

View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
from time import time as now
import json
from ._canonical_names import canonical_names, normalize_name
try:
basestring
except NameError:
basestring = str
KEY_DOWN = 'down'
KEY_UP = 'up'
class KeyboardEvent(object):
event_type = None
scan_code = None
name = None
time = None
device = None
modifiers = None
is_keypad = None
def __init__(self, event_type, scan_code, name=None, time=None, device=None, modifiers=None, is_keypad=None):
self.event_type = event_type
self.scan_code = scan_code
self.time = now() if time is None else time
self.device = device
self.is_keypad = is_keypad
self.modifiers = modifiers
if name:
self.name = normalize_name(name)
def to_json(self, ensure_ascii=False):
attrs = dict(
(attr, getattr(self, attr)) for attr in ['event_type', 'scan_code', 'name', 'time', 'device', 'is_keypad']
if not attr.startswith('_') and getattr(self, attr) is not None
)
return json.dumps(attrs, ensure_ascii=ensure_ascii)
def __repr__(self):
return 'KeyboardEvent({} {})'.format(self.name or 'Unknown {}'.format(self.scan_code), self.event_type)
def __eq__(self, other):
return (
isinstance(other, KeyboardEvent)
and self.event_type == other.event_type
and (
not self.scan_code or not other.scan_code or self.scan_code == other.scan_code
) and (
not self.name or not other.name or self.name == other.name
)
)

827
keyboard/_keyboard_tests.py Normal file
View File

@ -0,0 +1,827 @@
# -*- coding: utf-8 -*-
"""
Side effects are avoided using two techniques:
- Low level OS requests (keyboard._os_keyboard) are mocked out by rewriting
the functions at that namespace. This includes a list of dummy keys.
- Events are pumped manually by the main test class, and accepted events
are tested against expected values.
Fake user events are appended to `input_events`, passed through
keyboard,_listener.direct_callback, then, if accepted, appended to
`output_events`. Fake OS events (keyboard.press) are processed
and added to `output_events` immediately, mimicking real functionality.
"""
from __future__ import print_function
import unittest
import time
import keyboard
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP
dummy_keys = {
'space': [(0, [])],
'a': [(1, [])],
'b': [(2, [])],
'c': [(3, [])],
'A': [(1, ['shift']), (-1, [])],
'B': [(2, ['shift']), (-2, [])],
'C': [(3, ['shift']), (-3, [])],
'alt': [(4, [])],
'left alt': [(4, [])],
'left shift': [(5, [])],
'right shift': [(6, [])],
'left ctrl': [(7, [])],
'backspace': [(8, [])],
'caps lock': [(9, [])],
'+': [(10, [])],
',': [(11, [])],
'_': [(12, [])],
'none': [],
'duplicated': [(20, []), (20, [])],
}
def make_event(event_type, name, scan_code=None, time=0):
return KeyboardEvent(event_type=event_type, scan_code=scan_code or dummy_keys[name][0][0], name=name, time=time)
# Used when manually pumping events.
input_events = []
output_events = []
def send_instant_event(event):
if keyboard._listener.direct_callback(event):
output_events.append(event)
# Mock out side effects.
keyboard._os_keyboard.init = lambda: None
keyboard._os_keyboard.listen = lambda callback: None
keyboard._os_keyboard.map_name = dummy_keys.__getitem__
keyboard._os_keyboard.press = lambda scan_code: send_instant_event(make_event(KEY_DOWN, None, scan_code))
keyboard._os_keyboard.release = lambda scan_code: send_instant_event(make_event(KEY_UP, None, scan_code))
keyboard._os_keyboard.type_unicode = lambda char: output_events.append(KeyboardEvent(event_type=KEY_DOWN, scan_code=999, name=char))
# Shortcuts for defining test inputs and expected outputs.
# Usage: d_shift + d_a + u_a + u_shift
d_a = [make_event(KEY_DOWN, 'a')]
u_a = [make_event(KEY_UP, 'a')]
du_a = d_a+u_a
d_b = [make_event(KEY_DOWN, 'b')]
u_b = [make_event(KEY_UP, 'b')]
du_b = d_b+u_b
d_c = [make_event(KEY_DOWN, 'c')]
u_c = [make_event(KEY_UP, 'c')]
du_c = d_c+u_c
d_ctrl = [make_event(KEY_DOWN, 'left ctrl')]
u_ctrl = [make_event(KEY_UP, 'left ctrl')]
du_ctrl = d_ctrl+u_ctrl
d_shift = [make_event(KEY_DOWN, 'left shift')]
u_shift = [make_event(KEY_UP, 'left shift')]
du_shift = d_shift+u_shift
d_alt = [make_event(KEY_DOWN, 'alt')]
u_alt = [make_event(KEY_UP, 'alt')]
du_alt = d_alt+u_alt
du_backspace = [make_event(KEY_DOWN, 'backspace'), make_event(KEY_UP, 'backspace')]
du_capslock = [make_event(KEY_DOWN, 'caps lock'), make_event(KEY_UP, 'caps lock')]
d_space = [make_event(KEY_DOWN, 'space')]
u_space = [make_event(KEY_UP, 'space')]
du_space = [make_event(KEY_DOWN, 'space'), make_event(KEY_UP, 'space')]
trigger = lambda e=None: keyboard.press(999)
triggered_event = [KeyboardEvent(KEY_DOWN, scan_code=999)]
class TestKeyboard(unittest.TestCase):
def tearDown(self):
keyboard.unhook_all()
#self.assertEquals(keyboard._hooks, {})
#self.assertEquals(keyboard._hotkeys, {})
def setUp(self):
#keyboard._hooks.clear()
#keyboard._hotkeys.clear()
del input_events[:]
del output_events[:]
keyboard._recording = None
keyboard._pressed_events.clear()
keyboard._physically_pressed_keys.clear()
keyboard._logically_pressed_keys.clear()
keyboard._hotkeys.clear()
keyboard._listener.init()
keyboard._word_listeners = {}
def do(self, manual_events, expected=None):
input_events.extend(manual_events)
while input_events:
event = input_events.pop(0)
if keyboard._listener.direct_callback(event):
output_events.append(event)
if expected is not None:
to_names = lambda es: '+'.join(('d' if e.event_type == KEY_DOWN else 'u') + '_' + str(e.scan_code) for e in es)
self.assertEqual(to_names(output_events), to_names(expected))
del output_events[:]
keyboard._listener.queue.join()
def test_event_json(self):
event = make_event(KEY_DOWN, u'á \'"', 999)
import json
self.assertEqual(event, KeyboardEvent(**json.loads(event.to_json())))
def test_is_modifier_name(self):
for name in keyboard.all_modifiers:
self.assertTrue(keyboard.is_modifier(name))
def test_is_modifier_scan_code(self):
for i in range(10):
self.assertEqual(keyboard.is_modifier(i), i in [4, 5, 6, 7])
def test_key_to_scan_codes_brute(self):
for name, entries in dummy_keys.items():
if name in ['none', 'duplicated']: continue
expected = tuple(scan_code for scan_code, modifiers in entries)
self.assertEqual(keyboard.key_to_scan_codes(name), expected)
def test_key_to_scan_code_from_scan_code(self):
for i in range(10):
self.assertEqual(keyboard.key_to_scan_codes(i), (i,))
def test_key_to_scan_code_from_letter(self):
self.assertEqual(keyboard.key_to_scan_codes('a'), (1,))
self.assertEqual(keyboard.key_to_scan_codes('A'), (1,-1))
def test_key_to_scan_code_from_normalized(self):
self.assertEqual(keyboard.key_to_scan_codes('shift'), (5,6))
self.assertEqual(keyboard.key_to_scan_codes('SHIFT'), (5,6))
self.assertEqual(keyboard.key_to_scan_codes('ctrl'), keyboard.key_to_scan_codes('CONTROL'))
def test_key_to_scan_code_from_sided_modifier(self):
self.assertEqual(keyboard.key_to_scan_codes('left shift'), (5,))
self.assertEqual(keyboard.key_to_scan_codes('right shift'), (6,))
def test_key_to_scan_code_underscores(self):
self.assertEqual(keyboard.key_to_scan_codes('_'), (12,))
self.assertEqual(keyboard.key_to_scan_codes('right_shift'), (6,))
def test_key_to_scan_code_error_none(self):
with self.assertRaises(ValueError):
keyboard.key_to_scan_codes(None)
def test_key_to_scan_code_error_empty(self):
with self.assertRaises(ValueError):
keyboard.key_to_scan_codes('')
def test_key_to_scan_code_error_other(self):
with self.assertRaises(ValueError):
keyboard.key_to_scan_codes({})
def test_key_to_scan_code_list(self):
self.assertEqual(keyboard.key_to_scan_codes([10, 5, 'a']), (10, 5, 1))
def test_key_to_scan_code_empty(self):
with self.assertRaises(ValueError):
keyboard.key_to_scan_codes('none')
def test_key_to_scan_code_duplicated(self):
self.assertEqual(keyboard.key_to_scan_codes('duplicated'), (20,))
def test_parse_hotkey_simple(self):
self.assertEqual(keyboard.parse_hotkey('a'), (((1,),),))
self.assertEqual(keyboard.parse_hotkey('A'), (((1,-1),),))
def test_parse_hotkey_separators(self):
self.assertEqual(keyboard.parse_hotkey('+'), keyboard.parse_hotkey('plus'))
self.assertEqual(keyboard.parse_hotkey(','), keyboard.parse_hotkey('comma'))
def test_parse_hotkey_keys(self):
self.assertEqual(keyboard.parse_hotkey('left shift + a'), (((5,), (1,),),))
self.assertEqual(keyboard.parse_hotkey('left shift+a'), (((5,), (1,),),))
def test_parse_hotkey_simple_steps(self):
self.assertEqual(keyboard.parse_hotkey('a,b'), (((1,),),((2,),)))
self.assertEqual(keyboard.parse_hotkey('a, b'), (((1,),),((2,),)))
def test_parse_hotkey_steps(self):
self.assertEqual(keyboard.parse_hotkey('a+b, b+c'), (((1,),(2,)),((2,),(3,))))
def test_parse_hotkey_example(self):
alt_codes = keyboard.key_to_scan_codes('alt')
shift_codes = keyboard.key_to_scan_codes('shift')
a_codes = keyboard.key_to_scan_codes('a')
b_codes = keyboard.key_to_scan_codes('b')
c_codes = keyboard.key_to_scan_codes('c')
self.assertEqual(keyboard.parse_hotkey("alt+shift+a, alt+b, c"), ((alt_codes, shift_codes, a_codes), (alt_codes, b_codes), (c_codes,)))
def test_parse_hotkey_list_scan_codes(self):
self.assertEqual(keyboard.parse_hotkey([1, 2, 3]), (((1,), (2,), (3,)),))
def test_parse_hotkey_deep_list_scan_codes(self):
result = keyboard.parse_hotkey('a')
self.assertEqual(keyboard.parse_hotkey(result), (((1,),),))
def test_parse_hotkey_list_names(self):
self.assertEqual(keyboard.parse_hotkey(['a', 'b', 'c']), (((1,), (2,), (3,)),))
def test_is_pressed_none(self):
self.assertFalse(keyboard.is_pressed('a'))
def test_is_pressed_true(self):
self.do(d_a)
self.assertTrue(keyboard.is_pressed('a'))
def test_is_pressed_true_scan_code_true(self):
self.do(d_a)
self.assertTrue(keyboard.is_pressed(1))
def test_is_pressed_true_scan_code_false(self):
self.do(d_a)
self.assertFalse(keyboard.is_pressed(2))
def test_is_pressed_true_scan_code_invalid(self):
self.do(d_a)
self.assertFalse(keyboard.is_pressed(-1))
def test_is_pressed_false(self):
self.do(d_a+u_a+d_b)
self.assertFalse(keyboard.is_pressed('a'))
self.assertTrue(keyboard.is_pressed('b'))
def test_is_pressed_hotkey_true(self):
self.do(d_shift+d_a)
self.assertTrue(keyboard.is_pressed('shift+a'))
def test_is_pressed_hotkey_false(self):
self.do(d_shift+d_a+u_a)
self.assertFalse(keyboard.is_pressed('shift+a'))
def test_is_pressed_multi_step_fail(self):
self.do(u_a+d_a)
with self.assertRaises(ValueError):
keyboard.is_pressed('a, b')
def test_send_single_press_release(self):
keyboard.send('a', do_press=True, do_release=True)
self.do([], d_a+u_a)
def test_send_single_press(self):
keyboard.send('a', do_press=True, do_release=False)
self.do([], d_a)
def test_send_single_release(self):
keyboard.send('a', do_press=False, do_release=True)
self.do([], u_a)
def test_send_single_none(self):
keyboard.send('a', do_press=False, do_release=False)
self.do([], [])
def test_press(self):
keyboard.press('a')
self.do([], d_a)
def test_release(self):
keyboard.release('a')
self.do([], u_a)
def test_press_and_release(self):
keyboard.press_and_release('a')
self.do([], d_a+u_a)
def test_send_modifier_press_release(self):
keyboard.send('ctrl+a', do_press=True, do_release=True)
self.do([], d_ctrl+d_a+u_a+u_ctrl)
def test_send_modifiers_release(self):
keyboard.send('ctrl+shift+a', do_press=False, do_release=True)
self.do([], u_a+u_shift+u_ctrl)
def test_call_later(self):
triggered = []
def fn(arg1, arg2):
assert arg1 == 1 and arg2 == 2
triggered.append(True)
keyboard.call_later(fn, (1, 2), 0.01)
self.assertFalse(triggered)
time.sleep(0.05)
self.assertTrue(triggered)
def test_hook_nonblocking(self):
self.i = 0
def count(e):
self.assertEqual(e.name, 'a')
self.i += 1
hook = keyboard.hook(count, suppress=False)
self.do(d_a+u_a, d_a+u_a)
self.assertEqual(self.i, 2)
keyboard.unhook(hook)
self.do(d_a+u_a, d_a+u_a)
self.assertEqual(self.i, 2)
keyboard.hook(count, suppress=False)
self.do(d_a+u_a, d_a+u_a)
self.assertEqual(self.i, 4)
keyboard.unhook_all()
self.do(d_a+u_a, d_a+u_a)
self.assertEqual(self.i, 4)
def test_hook_blocking(self):
self.i = 0
def count(e):
self.assertIn(e.name, ['a', 'b'])
self.i += 1
return e.name == 'b'
hook = keyboard.hook(count, suppress=True)
self.do(d_a+d_b, d_b)
self.assertEqual(self.i, 2)
keyboard.unhook(hook)
self.do(d_a+d_b, d_a+d_b)
self.assertEqual(self.i, 2)
keyboard.hook(count, suppress=True)
self.do(d_a+d_b, d_b)
self.assertEqual(self.i, 4)
keyboard.unhook_all()
self.do(d_a+d_b, d_a+d_b)
self.assertEqual(self.i, 4)
def test_on_press_nonblocking(self):
keyboard.on_press(lambda e: self.assertEqual(e.name, 'a') and self.assertEqual(e.event_type, KEY_DOWN))
self.do(d_a+u_a)
def test_on_press_blocking(self):
keyboard.on_press(lambda e: e.scan_code == 1, suppress=True)
self.do([make_event(KEY_DOWN, 'A', -1)] + d_a, d_a)
def test_on_release(self):
keyboard.on_release(lambda e: self.assertEqual(e.name, 'a') and self.assertEqual(e.event_type, KEY_UP))
self.do(d_a+u_a)
def test_hook_key_invalid(self):
with self.assertRaises(ValueError):
keyboard.hook_key('invalid', lambda e: None)
def test_hook_key_nonblocking(self):
self.i = 0
def count(event):
self.i += 1
hook = keyboard.hook_key('A', count)
self.do(d_a)
self.assertEqual(self.i, 1)
self.do(u_a+d_b)
self.assertEqual(self.i, 2)
self.do([make_event(KEY_DOWN, 'A', -1)])
self.assertEqual(self.i, 3)
keyboard.unhook_key(hook)
self.do(d_a)
self.assertEqual(self.i, 3)
def test_hook_key_blocking(self):
self.i = 0
def count(event):
self.i += 1
return event.scan_code == 1
hook = keyboard.hook_key('A', count, suppress=True)
self.do(d_a, d_a)
self.assertEqual(self.i, 1)
self.do(u_a+d_b, u_a+d_b)
self.assertEqual(self.i, 2)
self.do([make_event(KEY_DOWN, 'A', -1)], [])
self.assertEqual(self.i, 3)
keyboard.unhook_key(hook)
self.do([make_event(KEY_DOWN, 'A', -1)], [make_event(KEY_DOWN, 'A', -1)])
self.assertEqual(self.i, 3)
def test_on_press_key_nonblocking(self):
keyboard.on_press_key('A', lambda e: self.assertEqual(e.name, 'a') and self.assertEqual(e.event_type, KEY_DOWN))
self.do(d_a+u_a+d_b+u_b)
def test_on_press_key_blocking(self):
keyboard.on_press_key('A', lambda e: e.scan_code == 1, suppress=True)
self.do([make_event(KEY_DOWN, 'A', -1)] + d_a, d_a)
def test_on_release_key(self):
keyboard.on_release_key('a', lambda e: self.assertEqual(e.name, 'a') and self.assertEqual(e.event_type, KEY_UP))
self.do(d_a+u_a)
def test_block_key(self):
blocked = keyboard.block_key('a')
self.do(d_a+d_b, d_b)
self.do([make_event(KEY_DOWN, 'A', -1)], [make_event(KEY_DOWN, 'A', -1)])
keyboard.unblock_key(blocked)
self.do(d_a+d_b, d_a+d_b)
def test_block_key_ambiguous(self):
keyboard.block_key('A')
self.do(d_a+d_b, d_b)
self.do([make_event(KEY_DOWN, 'A', -1)], [])
def test_remap_key_simple(self):
mapped = keyboard.remap_key('a', 'b')
self.do(d_a+d_c+u_a, d_b+d_c+u_b)
keyboard.unremap_key(mapped)
self.do(d_a+d_c+u_a, d_a+d_c+u_a)
def test_remap_key_ambiguous(self):
keyboard.remap_key('A', 'b')
self.do(d_a+d_b, d_b+d_b)
self.do([make_event(KEY_DOWN, 'A', -1)], d_b)
def test_remap_key_multiple(self):
mapped = keyboard.remap_key('a', 'shift+b')
self.do(d_a+d_c+u_a, d_shift+d_b+d_c+u_b+u_shift)
keyboard.unremap_key(mapped)
self.do(d_a+d_c+u_a, d_a+d_c+u_a)
def test_stash_state(self):
self.do(d_a+d_shift)
self.assertEqual(sorted(keyboard.stash_state()), [1, 5])
self.do([], u_a+u_shift)
def test_restore_state(self):
self.do(d_b)
keyboard.restore_state([1, 5])
self.do([], u_b+d_a+d_shift)
def test_restore_modifieres(self):
self.do(d_b)
keyboard.restore_modifiers([1, 5])
self.do([], u_b+d_shift)
def test_write_simple(self):
keyboard.write('a', exact=False)
self.do([], d_a+u_a)
def test_write_multiple(self):
keyboard.write('ab', exact=False)
self.do([], d_a+u_a+d_b+u_b)
def test_write_modifiers(self):
keyboard.write('Ab', exact=False)
self.do([], d_shift+d_a+u_a+u_shift+d_b+u_b)
# restore_state_after has been removed after the introduction of `restore_modifiers`.
#def test_write_stash_not_restore(self):
# self.do(d_shift)
# keyboard.write('a', restore_state_after=False, exact=False)
# self.do([], u_shift+d_a+u_a)
def test_write_stash_restore(self):
self.do(d_shift)
keyboard.write('a', exact=False)
self.do([], u_shift+d_a+u_a+d_shift)
def test_write_multiple(self):
last_time = time.time()
keyboard.write('ab', delay=0.01, exact=False)
self.do([], d_a+u_a+d_b+u_b)
self.assertGreater(time.time() - last_time, 0.015)
def test_write_unicode_explicit(self):
keyboard.write('ab', exact=True)
self.do([], [KeyboardEvent(event_type=KEY_DOWN, scan_code=999, name='a'), KeyboardEvent(event_type=KEY_DOWN, scan_code=999, name='b')])
def test_write_unicode_fallback(self):
keyboard.write(u'áb', exact=False)
self.do([], [KeyboardEvent(event_type=KEY_DOWN, scan_code=999, name=u'á')]+d_b+u_b)
def test_start_stop_recording(self):
keyboard.start_recording()
self.do(d_a+u_a)
self.assertEqual(keyboard.stop_recording(), d_a+u_a)
def test_stop_recording_error(self):
with self.assertRaises(ValueError):
keyboard.stop_recording()
def test_record(self):
queue = keyboard._queue.Queue()
def process():
queue.put(keyboard.record('space', suppress=True))
from threading import Thread
t = Thread(target=process)
t.daemon = True
t.start()
# 0.01s sleep failed once already. Better solutions?
time.sleep(0.01)
self.do(du_a+du_b+du_space, du_a+du_b)
self.assertEqual(queue.get(timeout=0.5), du_a+du_b+du_space)
def test_play_nodelay(self):
keyboard.play(d_a+u_a, 0)
self.do([], d_a+u_a)
def test_play_stash(self):
self.do(d_ctrl)
keyboard.play(d_a+u_a, 0)
self.do([], u_ctrl+d_a+u_a+d_ctrl)
def test_play_delay(self):
last_time = time.time()
events = [make_event(KEY_DOWN, 'a', 1, 100), make_event(KEY_UP, 'a', 1, 100.01)]
keyboard.play(events, 1)
self.do([], d_a+u_a)
self.assertGreater(time.time() - last_time, 0.005)
def test_get_typed_strings_simple(self):
events = du_a+du_b+du_backspace+d_shift+du_a+u_shift+du_space+du_ctrl+du_a
self.assertEqual(list(keyboard.get_typed_strings(events)), ['aA ', 'a'])
def test_get_typed_strings_backspace(self):
events = du_a+du_b+du_backspace
self.assertEqual(list(keyboard.get_typed_strings(events)), ['a'])
events = du_backspace+du_a+du_b
self.assertEqual(list(keyboard.get_typed_strings(events)), ['ab'])
def test_get_typed_strings_shift(self):
events = d_shift+du_a+du_b+u_shift+du_space+du_ctrl+du_a
self.assertEqual(list(keyboard.get_typed_strings(events)), ['AB ', 'a'])
def test_get_typed_strings_all(self):
events = du_a+du_b+du_backspace+d_shift+du_a+du_capslock+du_b+u_shift+du_space+du_ctrl+du_a
self.assertEqual(list(keyboard.get_typed_strings(events)), ['aAb ', 'A'])
def test_get_hotkey_name_simple(self):
self.assertEqual(keyboard.get_hotkey_name(['a']), 'a')
def test_get_hotkey_name_modifiers(self):
self.assertEqual(keyboard.get_hotkey_name(['a', 'shift', 'ctrl']), 'ctrl+shift+a')
def test_get_hotkey_name_normalize(self):
self.assertEqual(keyboard.get_hotkey_name(['SHIFT', 'left ctrl']), 'ctrl+shift')
def test_get_hotkey_name_plus(self):
self.assertEqual(keyboard.get_hotkey_name(['+']), 'plus')
def test_get_hotkey_name_duplicated(self):
self.assertEqual(keyboard.get_hotkey_name(['+', 'plus']), 'plus')
def test_get_hotkey_name_full(self):
self.assertEqual(keyboard.get_hotkey_name(['+', 'left ctrl', 'shift', 'WIN', 'right alt']), 'ctrl+alt+shift+windows+plus')
def test_get_hotkey_name_multiple(self):
self.assertEqual(keyboard.get_hotkey_name(['ctrl', 'b', '!', 'a']), 'ctrl+!+a+b')
def test_get_hotkey_name_from_pressed(self):
self.do(du_c+d_ctrl+d_a+d_b)
self.assertEqual(keyboard.get_hotkey_name(), 'ctrl+a+b')
def test_read_hotkey(self):
queue = keyboard._queue.Queue()
def process():
queue.put(keyboard.read_hotkey())
from threading import Thread
t = Thread(target=process)
t.daemon = True
t.start()
time.sleep(0.01)
self.do(d_ctrl+d_a+d_b+u_ctrl)
self.assertEqual(queue.get(timeout=0.5), 'ctrl+a+b')
def test_read_event(self):
queue = keyboard._queue.Queue()
def process():
queue.put(keyboard.read_event(suppress=True))
from threading import Thread
t = Thread(target=process)
t.daemon = True
t.start()
time.sleep(0.01)
self.do(d_a, [])
self.assertEqual(queue.get(timeout=0.5), d_a[0])
def test_read_key(self):
queue = keyboard._queue.Queue()
def process():
queue.put(keyboard.read_key(suppress=True))
from threading import Thread
t = Thread(target=process)
t.daemon = True
t.start()
time.sleep(0.01)
self.do(d_a, [])
self.assertEqual(queue.get(timeout=0.5), 'a')
def test_wait_infinite(self):
self.triggered = False
def process():
keyboard.wait()
self.triggered = True
from threading import Thread
t = Thread(target=process)
t.daemon = True # Yep, we are letting this thread loose.
t.start()
time.sleep(0.01)
self.assertFalse(self.triggered)
def test_wait_until_success(self):
queue = keyboard._queue.Queue()
def process():
queue.put(keyboard.wait(queue.get(timeout=0.5), suppress=True) or True)
from threading import Thread
t = Thread(target=process)
t.daemon = True
t.start()
queue.put('a')
time.sleep(0.01)
self.do(d_a, [])
self.assertTrue(queue.get(timeout=0.5))
def test_wait_until_fail(self):
def process():
keyboard.wait('a', suppress=True)
self.fail()
from threading import Thread
t = Thread(target=process)
t.daemon = True # Yep, we are letting this thread loose.
t.start()
time.sleep(0.01)
self.do(d_b)
def test_add_hotkey_single_step_suppress_allow(self):
keyboard.add_hotkey('a', lambda: trigger() or True, suppress=True)
self.do(d_a, triggered_event+d_a)
def test_add_hotkey_single_step_suppress_args_allow(self):
arg = object()
keyboard.add_hotkey('a', lambda a: self.assertIs(a, arg) or trigger() or True, args=(arg,), suppress=True)
self.do(d_a, triggered_event+d_a)
def test_add_hotkey_single_step_suppress_single(self):
keyboard.add_hotkey('a', trigger, suppress=True)
self.do(d_a, triggered_event)
def test_add_hotkey_single_step_suppress_removed(self):
keyboard.remove_hotkey(keyboard.add_hotkey('a', trigger, suppress=True))
self.do(d_a, d_a)
def test_add_hotkey_single_step_suppress_removed(self):
keyboard.remove_hotkey(keyboard.add_hotkey('ctrl+a', trigger, suppress=True))
self.do(d_ctrl+d_a, d_ctrl+d_a)
self.assertEqual(keyboard._listener.filtered_modifiers[dummy_keys['left ctrl'][0][0]], 0)
def test_remove_hotkey_internal(self):
remove = keyboard.add_hotkey('shift+a', trigger, suppress=True)
self.assertTrue(all(keyboard._listener.blocking_hotkeys.values()))
self.assertTrue(all(keyboard._listener.filtered_modifiers.values()))
self.assertNotEqual(keyboard._hotkeys, {})
remove()
self.assertTrue(not any(keyboard._listener.filtered_modifiers.values()))
self.assertTrue(not any(keyboard._listener.blocking_hotkeys.values()))
self.assertEqual(keyboard._hotkeys, {})
def test_remove_hotkey_internal_multistep_start(self):
remove = keyboard.add_hotkey('shift+a, b', trigger, suppress=True)
self.assertTrue(all(keyboard._listener.blocking_hotkeys.values()))
self.assertTrue(all(keyboard._listener.filtered_modifiers.values()))
self.assertNotEqual(keyboard._hotkeys, {})
remove()
self.assertTrue(not any(keyboard._listener.filtered_modifiers.values()))
self.assertTrue(not any(keyboard._listener.blocking_hotkeys.values()))
self.assertEqual(keyboard._hotkeys, {})
def test_remove_hotkey_internal_multistep_end(self):
remove = keyboard.add_hotkey('shift+a, b', trigger, suppress=True)
self.do(d_shift+du_a+u_shift)
self.assertTrue(any(keyboard._listener.blocking_hotkeys.values()))
self.assertTrue(not any(keyboard._listener.filtered_modifiers.values()))
self.assertNotEqual(keyboard._hotkeys, {})
remove()
self.assertTrue(not any(keyboard._listener.filtered_modifiers.values()))
self.assertTrue(not any(keyboard._listener.blocking_hotkeys.values()))
self.assertEqual(keyboard._hotkeys, {})
def test_add_hotkey_single_step_suppress_with_modifiers(self):
keyboard.add_hotkey('ctrl+shift+a', trigger, suppress=True)
self.do(d_ctrl+d_shift+d_a, triggered_event)
def test_add_hotkey_single_step_suppress_with_modifiers_fail_unrelated_modifier(self):
keyboard.add_hotkey('ctrl+shift+a', trigger, suppress=True)
self.do(d_ctrl+d_shift+u_shift+d_a, d_shift+u_shift+d_ctrl+d_a)
def test_add_hotkey_single_step_suppress_with_modifiers_fail_unrelated_key(self):
keyboard.add_hotkey('ctrl+shift+a', trigger, suppress=True)
self.do(d_ctrl+d_shift+du_b, d_shift+d_ctrl+du_b)
def test_add_hotkey_single_step_suppress_with_modifiers_unrelated_key(self):
keyboard.add_hotkey('ctrl+shift+a', trigger, suppress=True)
self.do(d_ctrl+d_shift+du_b+d_a, d_shift+d_ctrl+du_b+triggered_event)
def test_add_hotkey_single_step_suppress_with_modifiers_release(self):
keyboard.add_hotkey('ctrl+shift+a', trigger, suppress=True)
self.do(d_ctrl+d_shift+du_b+d_a+u_ctrl+u_shift, d_shift+d_ctrl+du_b+triggered_event+u_ctrl+u_shift)
def test_add_hotkey_single_step_suppress_with_modifiers_out_of_order(self):
keyboard.add_hotkey('ctrl+shift+a', trigger, suppress=True)
self.do(d_shift+d_ctrl+d_a, triggered_event)
def test_add_hotkey_single_step_suppress_with_modifiers_repeated(self):
keyboard.add_hotkey('ctrl+a', trigger, suppress=True)
self.do(d_ctrl+du_a+du_b+du_a, triggered_event+d_ctrl+du_b+triggered_event)
def test_add_hotkey_single_step_suppress_with_modifiers_release(self):
keyboard.add_hotkey('ctrl+a', trigger, suppress=True, trigger_on_release=True)
self.do(d_ctrl+du_a+du_b+du_a, triggered_event+d_ctrl+du_b+triggered_event)
def test_add_hotkey_single_step_suppress_with_modifier_superset_release(self):
keyboard.add_hotkey('ctrl+a', trigger, suppress=True, trigger_on_release=True)
self.do(d_ctrl+d_shift+du_a+u_shift+u_ctrl, d_ctrl+d_shift+du_a+u_shift+u_ctrl)
def test_add_hotkey_single_step_suppress_with_modifier_superset(self):
keyboard.add_hotkey('ctrl+a', trigger, suppress=True)
self.do(d_ctrl+d_shift+du_a+u_shift+u_ctrl, d_ctrl+d_shift+du_a+u_shift+u_ctrl)
def test_add_hotkey_single_step_timeout(self):
keyboard.add_hotkey('a', trigger, timeout=1, suppress=True)
self.do(du_a, triggered_event)
def test_add_hotkey_multi_step_first_timeout(self):
keyboard.add_hotkey('a, b', trigger, timeout=0.01, suppress=True)
time.sleep(0.03)
self.do(du_a+du_b, triggered_event)
def test_add_hotkey_multi_step_last_timeout(self):
keyboard.add_hotkey('a, b', trigger, timeout=0.01, suppress=True)
self.do(du_a, [])
time.sleep(0.05)
self.do(du_b, du_a+du_b)
def test_add_hotkey_multi_step_success_timeout(self):
keyboard.add_hotkey('a, b', trigger, timeout=0.05, suppress=True)
self.do(du_a, [])
time.sleep(0.01)
self.do(du_b, triggered_event)
def test_add_hotkey_multi_step_suffix_timeout(self):
keyboard.add_hotkey('a, b, a', trigger, timeout=0.01, suppress=True)
self.do(du_a+du_b, [])
time.sleep(0.05)
self.do(du_a, du_a+du_b)
self.do(du_b+du_a, triggered_event)
def test_add_hotkey_multi_step_allow(self):
keyboard.add_hotkey('a, b', lambda: trigger() or True, suppress=True)
self.do(du_a+du_b, triggered_event+du_a+du_b)
def test_add_hotkey_single_step_nonsuppress(self):
queue = keyboard._queue.Queue()
keyboard.add_hotkey('ctrl+shift+a+b', lambda: queue.put(True), suppress=False)
self.do(d_shift+d_ctrl+d_a+d_b)
self.assertTrue(queue.get(timeout=0.5))
def test_add_hotkey_single_step_nonsuppress_repeated(self):
queue = keyboard._queue.Queue()
keyboard.add_hotkey('ctrl+shift+a+b', lambda: queue.put(True), suppress=False)
self.do(d_shift+d_ctrl+d_a+d_b)
self.do(d_shift+d_ctrl+d_a+d_b)
self.assertTrue(queue.get(timeout=0.5))
self.assertTrue(queue.get(timeout=0.5))
def test_add_hotkey_single_step_nosuppress_with_modifiers_out_of_order(self):
queue = keyboard._queue.Queue()
keyboard.add_hotkey('ctrl+shift+a', lambda: queue.put(True), suppress=False)
self.do(d_shift+d_ctrl+d_a)
self.assertTrue(queue.get(timeout=0.5))
def test_add_hotkey_single_step_suppress_regression_1(self):
keyboard.add_hotkey('a', trigger, suppress=True)
self.do(d_c+d_a+u_c+u_a, d_c+d_a+u_c+u_a)
def test_remap_hotkey_single(self):
keyboard.remap_hotkey('a', 'b')
self.do(d_a+u_a, d_b+u_b)
def test_remap_hotkey_complex_dst(self):
keyboard.remap_hotkey('a', 'ctrl+b, c')
self.do(d_a+u_a, d_ctrl+du_b+u_ctrl+du_c)
def test_remap_hotkey_modifiers(self):
keyboard.remap_hotkey('ctrl+shift+a', 'b')
self.do(d_ctrl+d_shift+d_a+u_a, du_b)
def test_remap_hotkey_modifiers_repeat(self):
keyboard.remap_hotkey('ctrl+shift+a', 'b')
self.do(d_ctrl+d_shift+du_a+du_a, du_b+du_b)
def test_remap_hotkey_modifiers_state(self):
keyboard.remap_hotkey('ctrl+shift+a', 'b')
self.do(d_ctrl+d_shift+du_c+du_a+du_a, d_shift+d_ctrl+du_c+u_shift+u_ctrl+du_b+d_ctrl+d_shift+u_shift+u_ctrl+du_b+d_ctrl+d_shift)
def test_remap_hotkey_release_incomplete(self):
keyboard.remap_hotkey('a', 'b', trigger_on_release=True)
self.do(d_a, [])
def test_remap_hotkey_release_complete(self):
keyboard.remap_hotkey('a', 'b', trigger_on_release=True)
self.do(du_a, du_b)
def test_parse_hotkey_combinations_scan_code(self):
self.assertEqual(keyboard.parse_hotkey_combinations(30), (((30,),),))
def test_parse_hotkey_combinations_single(self):
self.assertEqual(keyboard.parse_hotkey_combinations('a'), (((1,),),))
def test_parse_hotkey_combinations_single_modifier(self):
self.assertEqual(keyboard.parse_hotkey_combinations('shift+a'), (((1, 5), (1, 6)),))
def test_parse_hotkey_combinations_single_modifiers(self):
self.assertEqual(keyboard.parse_hotkey_combinations('shift+ctrl+a'), (((1, 5, 7), (1, 6, 7)),))
def test_parse_hotkey_combinations_multi(self):
self.assertEqual(keyboard.parse_hotkey_combinations('a, b'), (((1,),), ((2,),)))
def test_parse_hotkey_combinations_multi_modifier(self):
self.assertEqual(keyboard.parse_hotkey_combinations('shift+a, b'), (((1, 5), (1, 6)), ((2,),)))
def test_parse_hotkey_combinations_list_list(self):
self.assertEqual(keyboard.parse_hotkey_combinations(keyboard.parse_hotkey_combinations('a, b')), keyboard.parse_hotkey_combinations('a, b'))
def test_parse_hotkey_combinations_fail_empty(self):
with self.assertRaises(ValueError):
keyboard.parse_hotkey_combinations('')
def test_add_hotkey_multistep_suppress_incomplete(self):
keyboard.add_hotkey('a, b', trigger, suppress=True)
self.do(du_a, [])
self.assertEqual(keyboard._listener.blocking_hotkeys[(1,)], [])
self.assertEqual(len(keyboard._listener.blocking_hotkeys[(2,)]), 1)
def test_add_hotkey_multistep_suppress_incomplete(self):
keyboard.add_hotkey('a, b', trigger, suppress=True)
self.do(du_a+du_b, triggered_event)
def test_add_hotkey_multistep_suppress_modifier(self):
keyboard.add_hotkey('shift+a, b', trigger, suppress=True)
self.do(d_shift+du_a+u_shift+du_b, triggered_event)
def test_add_hotkey_multistep_suppress_fail(self):
keyboard.add_hotkey('a, b', trigger, suppress=True)
self.do(du_a+du_c, du_a+du_c)
def test_add_hotkey_multistep_suppress_three_steps(self):
keyboard.add_hotkey('a, b, c', trigger, suppress=True)
self.do(du_a+du_b+du_c, triggered_event)
def test_add_hotkey_multistep_suppress_repeated_prefix(self):
keyboard.add_hotkey('a, a, c', trigger, suppress=True, trigger_on_release=True)
self.do(du_a+du_a+du_c, triggered_event)
def test_add_hotkey_multistep_suppress_repeated_key(self):
keyboard.add_hotkey('a, b', trigger, suppress=True)
self.do(du_a+du_a+du_b, du_a+triggered_event)
self.assertEqual(keyboard._listener.blocking_hotkeys[(2,)], [])
self.assertEqual(len(keyboard._listener.blocking_hotkeys[(1,)]), 1)
def test_add_hotkey_multi_step_suppress_regression_1(self):
keyboard.add_hotkey('a, b', trigger, suppress=True)
self.do(d_c+d_a+u_c+u_a+du_c, d_c+d_a+u_c+u_a+du_c)
def test_add_hotkey_multi_step_suppress_replays(self):
keyboard.add_hotkey('a, b, c', trigger, suppress=True)
self.do(du_a+du_b+du_a+du_b+du_space, du_a+du_b+du_a+du_b+du_space)
def test_add_word_listener_success(self):
queue = keyboard._queue.Queue()
def free():
queue.put(1)
keyboard.add_word_listener('abc', free)
self.do(du_a+du_b+du_c+du_space)
self.assertTrue(queue.get(timeout=0.5))
def test_add_word_listener_no_trigger_fail(self):
queue = keyboard._queue.Queue()
def free():
queue.put(1)
keyboard.add_word_listener('abc', free)
self.do(du_a+du_b+du_c)
with self.assertRaises(keyboard._queue.Empty):
queue.get(timeout=0.01)
def test_add_word_listener_timeout_fail(self):
queue = keyboard._queue.Queue()
def free():
queue.put(1)
keyboard.add_word_listener('abc', free, timeout=1)
self.do(du_a+du_b+du_c+[make_event(KEY_DOWN, name='space', time=2)])
with self.assertRaises(keyboard._queue.Empty):
queue.get(timeout=0.01)
def test_duplicated_word_listener(self):
keyboard.add_word_listener('abc', trigger)
keyboard.add_word_listener('abc', trigger)
def test_add_word_listener_remove(self):
queue = keyboard._queue.Queue()
def free():
queue.put(1)
keyboard.add_word_listener('abc', free)
keyboard.remove_word_listener('abc')
self.do(du_a+du_b+du_c+du_space)
with self.assertRaises(keyboard._queue.Empty):
queue.get(timeout=0.01)
def test_add_word_listener_suffix_success(self):
queue = keyboard._queue.Queue()
def free():
queue.put(1)
keyboard.add_word_listener('abc', free, match_suffix=True)
self.do(du_a+du_a+du_b+du_c+du_space)
self.assertTrue(queue.get(timeout=0.5))
def test_add_word_listener_suffix_fail(self):
queue = keyboard._queue.Queue()
def free():
queue.put(1)
keyboard.add_word_listener('abc', free)
self.do(du_a+du_a+du_b+du_c)
with self.assertRaises(keyboard._queue.Empty):
queue.get(timeout=0.01)
#def test_add_abbreviation(self):
# keyboard.add_abbreviation('abc', 'aaa')
# self.do(du_a+du_b+du_c+du_space, [])
if __name__ == '__main__':
unittest.main()

20
keyboard/_mouse_event.py Normal file
View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from collections import namedtuple
LEFT = 'left'
RIGHT = 'right'
MIDDLE = 'middle'
WHEEL = 'wheel'
X = 'x'
X2 = 'x2'
UP = 'up'
DOWN = 'down'
DOUBLE = 'double'
VERTICAL = 'vertical'
HORIZONTAL = 'horizontal'
ButtonEvent = namedtuple('ButtonEvent', ['event_type', 'button', 'time'])
WheelEvent = namedtuple('WheelEvent', ['delta', 'time'])
MoveEvent = namedtuple('MoveEvent', ['x', 'y', 'time'])

271
keyboard/_mouse_tests.py Normal file
View File

@ -0,0 +1,271 @@
# -*- coding: utf-8 -*-
import unittest
import time
from ._mouse_event import MoveEvent, ButtonEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
from keyboard import mouse
class FakeOsMouse(object):
def __init__(self):
self.append = None
self.position = (0, 0)
self.queue = None
self.init = lambda: None
def listen(self, queue):
self.listening = True
self.queue = queue
def press(self, button):
self.append((DOWN, button))
def release(self, button):
self.append((UP, button))
def get_position(self):
return self.position
def move_to(self, x, y):
self.append(('move', (x, y)))
self.position = (x, y)
def wheel(self, delta):
self.append(('wheel', delta))
def move_relative(self, x, y):
self.position = (self.position[0] + x, self.position[1] + y)
class TestMouse(unittest.TestCase):
@staticmethod
def setUpClass():
mouse._os_mouse= FakeOsMouse()
mouse._listener.start_if_necessary()
assert mouse._os_mouse.listening
def setUp(self):
self.events = []
mouse._pressed_events.clear()
mouse._os_mouse.append = self.events.append
def tearDown(self):
mouse.unhook_all()
# Make sure there's no spill over between tests.
self.wait_for_events_queue()
def wait_for_events_queue(self):
mouse._listener.queue.join()
def flush_events(self):
self.wait_for_events_queue()
events = list(self.events)
# Ugly, but requried to work in Python2. Python3 has list.clear
del self.events[:]
return events
def press(self, button=LEFT):
mouse._os_mouse.queue.put(ButtonEvent(DOWN, button, time.time()))
self.wait_for_events_queue()
def release(self, button=LEFT):
mouse._os_mouse.queue.put(ButtonEvent(UP, button, time.time()))
self.wait_for_events_queue()
def double_click(self, button=LEFT):
mouse._os_mouse.queue.put(ButtonEvent(DOUBLE, button, time.time()))
self.wait_for_events_queue()
def click(self, button=LEFT):
self.press(button)
self.release(button)
def wheel(self, delta=1):
mouse._os_mouse.queue.put(WheelEvent(delta, time.time()))
self.wait_for_events_queue()
def move(self, x=0, y=0):
mouse._os_mouse.queue.put(MoveEvent(x, y, time.time()))
self.wait_for_events_queue()
def test_hook(self):
events = []
self.press()
mouse.hook(events.append)
self.press()
mouse.unhook(events.append)
self.press()
self.assertEqual(len(events), 1)
def test_is_pressed(self):
self.assertFalse(mouse.is_pressed())
self.press()
self.assertTrue(mouse.is_pressed())
self.release()
self.press(X2)
self.assertFalse(mouse.is_pressed())
self.assertTrue(mouse.is_pressed(X2))
self.press(X2)
self.assertTrue(mouse.is_pressed(X2))
self.release(X2)
self.release(X2)
self.assertFalse(mouse.is_pressed(X2))
def test_buttons(self):
mouse.press()
self.assertEqual(self.flush_events(), [(DOWN, LEFT)])
mouse.release()
self.assertEqual(self.flush_events(), [(UP, LEFT)])
mouse.click()
self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT)])
mouse.double_click()
self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT), (DOWN, LEFT), (UP, LEFT)])
mouse.right_click()
self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)])
mouse.click(RIGHT)
self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)])
mouse.press(X2)
self.assertEqual(self.flush_events(), [(DOWN, X2)])
def test_position(self):
self.assertEqual(mouse.get_position(), mouse._os_mouse.get_position())
def test_move(self):
mouse.move(0, 0)
self.assertEqual(mouse._os_mouse.get_position(), (0, 0))
mouse.move(100, 500)
self.assertEqual(mouse._os_mouse.get_position(), (100, 500))
mouse.move(1, 2, False)
self.assertEqual(mouse._os_mouse.get_position(), (101, 502))
mouse.move(0, 0)
mouse.move(100, 499, True, duration=0.01)
self.assertEqual(mouse._os_mouse.get_position(), (100, 499))
mouse.move(100, 1, False, duration=0.01)
self.assertEqual(mouse._os_mouse.get_position(), (200, 500))
mouse.move(0, 0, False, duration=0.01)
self.assertEqual(mouse._os_mouse.get_position(), (200, 500))
def triggers(self, fn, events, **kwargs):
self.triggered = False
def callback():
self.triggered = True
handler = fn(callback, **kwargs)
for event_type, arg in events:
if event_type == DOWN:
self.press(arg)
elif event_type == UP:
self.release(arg)
elif event_type == DOUBLE:
self.double_click(arg)
elif event_type == 'WHEEL':
self.wheel()
mouse._listener.remove_handler(handler)
return self.triggered
def test_on_button(self):
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)]))
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, RIGHT)]))
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, X)]))
self.assertFalse(self.triggers(mouse.on_button, [('WHEEL', '')]))
self.assertFalse(self.triggers(mouse.on_button, [(DOWN, X)], buttons=MIDDLE))
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE))
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE))
self.assertFalse(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE, types=UP))
self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=MIDDLE, types=UP))
self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
self.assertFalse(self.triggers(mouse.on_button, [(UP, X)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
def test_ons(self):
self.assertTrue(self.triggers(mouse.on_click, [(UP, LEFT)]))
self.assertFalse(self.triggers(mouse.on_click, [(UP, RIGHT)]))
self.assertFalse(self.triggers(mouse.on_click, [(DOWN, LEFT)]))
self.assertFalse(self.triggers(mouse.on_click, [(DOWN, RIGHT)]))
self.assertTrue(self.triggers(mouse.on_double_click, [(DOUBLE, LEFT)]))
self.assertFalse(self.triggers(mouse.on_double_click, [(DOUBLE, RIGHT)]))
self.assertFalse(self.triggers(mouse.on_double_click, [(DOWN, RIGHT)]))
self.assertTrue(self.triggers(mouse.on_right_click, [(UP, RIGHT)]))
self.assertTrue(self.triggers(mouse.on_middle_click, [(UP, MIDDLE)]))
def test_wait(self):
# If this fails it blocks. Unfortunately, but I see no other way of testing.
from threading import Thread, Lock
lock = Lock()
lock.acquire()
def t():
mouse.wait()
lock.release()
Thread(target=t).start()
self.press()
lock.acquire()
def test_record_play(self):
from threading import Thread, Lock
lock = Lock()
lock.acquire()
def t():
self.recorded = mouse.record(RIGHT)
lock.release()
Thread(target=t).start()
self.click()
self.wheel(5)
self.move(100, 50)
self.press(RIGHT)
lock.acquire()
self.assertEqual(len(self.recorded), 5)
self.assertEqual(self.recorded[0]._replace(time=None), ButtonEvent(DOWN, LEFT, None))
self.assertEqual(self.recorded[1]._replace(time=None), ButtonEvent(UP, LEFT, None))
self.assertEqual(self.recorded[2]._replace(time=None), WheelEvent(5, None))
self.assertEqual(self.recorded[3]._replace(time=None), MoveEvent(100, 50, None))
self.assertEqual(self.recorded[4]._replace(time=None), ButtonEvent(DOWN, RIGHT, None))
mouse.play(self.recorded, speed_factor=0)
events = self.flush_events()
self.assertEqual(len(events), 5)
self.assertEqual(events[0], (DOWN, LEFT))
self.assertEqual(events[1], (UP, LEFT))
self.assertEqual(events[2], ('wheel', 5))
self.assertEqual(events[3], ('move', (100, 50)))
self.assertEqual(events[4], (DOWN, RIGHT))
mouse.play(self.recorded)
events = self.flush_events()
self.assertEqual(len(events), 5)
self.assertEqual(events[0], (DOWN, LEFT))
self.assertEqual(events[1], (UP, LEFT))
self.assertEqual(events[2], ('wheel', 5))
self.assertEqual(events[3], ('move', (100, 50)))
self.assertEqual(events[4], (DOWN, RIGHT))
mouse.play(self.recorded, include_clicks=False)
events = self.flush_events()
self.assertEqual(len(events), 2)
self.assertEqual(events[0], ('wheel', 5))
self.assertEqual(events[1], ('move', (100, 50)))
mouse.play(self.recorded, include_moves=False)
events = self.flush_events()
self.assertEqual(len(events), 4)
self.assertEqual(events[0], (DOWN, LEFT))
self.assertEqual(events[1], (UP, LEFT))
self.assertEqual(events[2], ('wheel', 5))
self.assertEqual(events[3], (DOWN, RIGHT))
mouse.play(self.recorded, include_wheel=False)
events = self.flush_events()
self.assertEqual(len(events), 4)
self.assertEqual(events[0], (DOWN, LEFT))
self.assertEqual(events[1], (UP, LEFT))
self.assertEqual(events[2], ('move', (100, 50)))
self.assertEqual(events[3], (DOWN, RIGHT))
if __name__ == '__main__':
unittest.main()

174
keyboard/_nixcommon.py Normal file
View File

@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
import struct
import os
import atexit
from time import time as now
from threading import Thread
from glob import glob
try:
from queue import Queue
except ImportError:
from Queue import Queue
event_bin_format = 'llHHI'
# Taken from include/linux/input.h
# https://www.kernel.org/doc/Documentation/input/event-codes.txt
EV_SYN = 0x00
EV_KEY = 0x01
EV_REL = 0x02
EV_ABS = 0x03
EV_MSC = 0x04
def make_uinput():
if not os.path.exists('/dev/uinput'):
raise IOError('No uinput module found.')
import fcntl, struct
# Requires uinput driver, but it's usually available.
uinput = open("/dev/uinput", 'wb')
UI_SET_EVBIT = 0x40045564
fcntl.ioctl(uinput, UI_SET_EVBIT, EV_KEY)
UI_SET_KEYBIT = 0x40045565
for i in range(256):
fcntl.ioctl(uinput, UI_SET_KEYBIT, i)
BUS_USB = 0x03
uinput_user_dev = "80sHHHHi64i64i64i64i"
axis = [0] * 64 * 4
uinput.write(struct.pack(uinput_user_dev, b"Virtual Keyboard", BUS_USB, 1, 1, 1, 0, *axis))
uinput.flush() # Without this you may get Errno 22: Invalid argument.
UI_DEV_CREATE = 0x5501
fcntl.ioctl(uinput, UI_DEV_CREATE)
UI_DEV_DESTROY = 0x5502
#fcntl.ioctl(uinput, UI_DEV_DESTROY)
return uinput
class EventDevice(object):
def __init__(self, path):
self.path = path
self._input_file = None
self._output_file = None
@property
def input_file(self):
if self._input_file is None:
try:
self._input_file = open(self.path, 'rb')
except IOError as e:
if e.strerror == 'Permission denied':
print('Permission denied ({}). You must be sudo to access global events.'.format(self.path))
exit()
def try_close():
try:
self._input_file.close
except:
pass
atexit.register(try_close)
return self._input_file
@property
def output_file(self):
if self._output_file is None:
self._output_file = open(self.path, 'wb')
atexit.register(self._output_file.close)
return self._output_file
def read_event(self):
data = self.input_file.read(struct.calcsize(event_bin_format))
seconds, microseconds, type, code, value = struct.unpack(event_bin_format, data)
return seconds + microseconds / 1e6, type, code, value, self.path
def write_event(self, type, code, value):
integer, fraction = divmod(now(), 1)
seconds = int(integer)
microseconds = int(fraction * 1e6)
data_event = struct.pack(event_bin_format, seconds, microseconds, type, code, value)
# Send a sync event to ensure other programs update.
sync_event = struct.pack(event_bin_format, seconds, microseconds, EV_SYN, 0, 0)
self.output_file.write(data_event + sync_event)
self.output_file.flush()
class AggregatedEventDevice(object):
def __init__(self, devices, output=None):
self.event_queue = Queue()
self.devices = devices
self.output = output or self.devices[0]
def start_reading(device):
while True:
self.event_queue.put(device.read_event())
for device in self.devices:
thread = Thread(target=start_reading, args=[device])
thread.setDaemon(True)
thread.start()
def read_event(self):
return self.event_queue.get(block=True)
def write_event(self, type, code, value):
self.output.write_event(type, code, value)
import re
from collections import namedtuple
DeviceDescription = namedtuple('DeviceDescription', 'event_file is_mouse is_keyboard')
device_pattern = r"""N: Name="([^"]+?)".+?H: Handlers=([^\n]+)"""
def list_devices_from_proc(type_name):
try:
with open('/proc/bus/input/devices') as f:
description = f.read()
except FileNotFoundError:
return
devices = {}
for name, handlers in re.findall(device_pattern, description, re.DOTALL):
path = '/dev/input/event' + re.search(r'event(\d+)', handlers).group(1)
if type_name in handlers:
yield EventDevice(path)
def list_devices_from_by_id(name_suffix, by_id=True):
for path in glob('/dev/input/{}/*-event-{}'.format('by-id' if by_id else 'by-path', name_suffix)):
yield EventDevice(path)
def aggregate_devices(type_name):
# Some systems have multiple keyboards with different range of allowed keys
# on each one, like a notebook with a "keyboard" device exclusive for the
# power button. Instead of figuring out which keyboard allows which key to
# send events, we create a fake device and send all events through there.
try:
uinput = make_uinput()
fake_device = EventDevice('uinput Fake Device')
fake_device._input_file = uinput
fake_device._output_file = uinput
except IOError as e:
import warnings
warnings.warn('Failed to create a device file using `uinput` module. Sending of events may be limited or unavailable depending on plugged-in devices.', stacklevel=2)
fake_device = None
# We don't aggregate devices from different sources to avoid
# duplicates.
devices_from_proc = list(list_devices_from_proc(type_name))
if devices_from_proc:
return AggregatedEventDevice(devices_from_proc, output=fake_device)
# breaks on mouse for virtualbox
# was getting /dev/input/by-id/usb-VirtualBox_USB_Tablet-event-mouse
devices_from_by_id = list(list_devices_from_by_id(type_name)) or list(list_devices_from_by_id(type_name, by_id=False))
if devices_from_by_id:
return AggregatedEventDevice(devices_from_by_id, output=fake_device)
# If no keyboards were found we can only use the fake device to send keys.
assert fake_device
return fake_device
def ensure_root():
if os.geteuid() != 0:
raise ImportError('You must be root to use this library on linux.')

183
keyboard/_nixkeyboard.py Normal file
View File

@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
import struct
import traceback
from time import time as now
from collections import namedtuple
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP
from ._canonical_names import all_modifiers, normalize_name
from ._nixcommon import EV_KEY, aggregate_devices, ensure_root
# TODO: start by reading current keyboard state, as to not missing any already pressed keys.
# See: http://stackoverflow.com/questions/3649874/how-to-get-keyboard-state-in-linux
def cleanup_key(name):
""" Formats a dumpkeys format to our standard. """
name = name.lstrip('+')
is_keypad = name.startswith('KP_')
for mod in ('Meta_', 'Control_', 'dead_', 'KP_'):
if name.startswith(mod):
name = name[len(mod):]
# Dumpkeys is weird like that.
if name == 'Remove':
name = 'Delete'
elif name == 'Delete':
name = 'Backspace'
if name.endswith('_r'):
name = 'right ' + name[:-2]
if name.endswith('_l'):
name = 'left ' + name[:-2]
return normalize_name(name), is_keypad
def cleanup_modifier(modifier):
modifier = normalize_name(modifier)
if modifier in all_modifiers:
return modifier
if modifier[:-1] in all_modifiers:
return modifier[:-1]
raise ValueError('Unknown modifier {}'.format(modifier))
"""
Use `dumpkeys --keys-only` to list all scan codes and their names. We
then parse the output and built a table. For each scan code and modifiers we
have a list of names and vice-versa.
"""
from subprocess import check_output
from collections import defaultdict
import re
to_name = defaultdict(list)
from_name = defaultdict(list)
keypad_scan_codes = set()
def register_key(key_and_modifiers, name):
if name not in to_name[key_and_modifiers]:
to_name[key_and_modifiers].append(name)
if key_and_modifiers not in from_name[name]:
from_name[name].append(key_and_modifiers)
def build_tables():
if to_name and from_name: return
ensure_root()
modifiers_bits = {
'shift': 1,
'alt gr': 2,
'ctrl': 4,
'alt': 8,
}
keycode_template = r'^keycode\s+(\d+)\s+=(.*?)$'
dump = check_output(['dumpkeys', '--keys-only'], universal_newlines=True)
for str_scan_code, str_names in re.findall(keycode_template, dump, re.MULTILINE):
scan_code = int(str_scan_code)
for i, str_name in enumerate(str_names.strip().split()):
modifiers = tuple(sorted(modifier for modifier, bit in modifiers_bits.items() if i & bit))
name, is_keypad = cleanup_key(str_name)
register_key((scan_code, modifiers), name)
if is_keypad:
keypad_scan_codes.add(scan_code)
register_key((scan_code, modifiers), 'keypad ' + name)
# dumpkeys consistently misreports the Windows key, sometimes
# skipping it completely or reporting as 'alt. 125 = left win,
# 126 = right win.
if (125, ()) not in to_name or to_name[(125, ())] == 'alt':
register_key((125, ()), 'windows')
if (126, ()) not in to_name or to_name[(126, ())] == 'alt':
register_key((126, ()), 'windows')
# The menu key is usually skipped altogether, so we also add it manually.
if (127, ()) not in to_name:
register_key((127, ()), 'menu')
synonyms_template = r'^(\S+)\s+for (.+)$'
dump = check_output(['dumpkeys', '--long-info'], universal_newlines=True)
for synonym_str, original_str in re.findall(synonyms_template, dump, re.MULTILINE):
synonym, _ = cleanup_key(synonym_str)
original, _ = cleanup_key(original_str)
if synonym != original:
from_name[original].extend(from_name[synonym])
from_name[synonym].extend(from_name[original])
device = None
def build_device():
global device
if device: return
ensure_root()
device = aggregate_devices('kbd')
def init():
build_device()
build_tables()
pressed_modifiers = set()
def listen(callback):
build_device()
build_tables()
while True:
time, type, code, value, device_id = device.read_event()
if type != EV_KEY:
continue
scan_code = code
event_type = KEY_DOWN if value else KEY_UP # 0 = UP, 1 = DOWN, 2 = HOLD
pressed_modifiers_tuple = tuple(sorted(pressed_modifiers))
names = to_name[(scan_code, pressed_modifiers_tuple)] or to_name[(scan_code, ())] or ['unknown']
name = names[0]
if name in all_modifiers:
if event_type == KEY_DOWN:
pressed_modifiers.add(name)
else:
pressed_modifiers.discard(name)
is_keypad = scan_code in keypad_scan_codes
callback(KeyboardEvent(event_type=event_type, scan_code=scan_code, name=name, time=time, device=device_id, is_keypad=is_keypad, modifiers=pressed_modifiers_tuple))
def write_event(scan_code, is_down):
build_device()
device.write_event(EV_KEY, scan_code, int(is_down))
def map_name(name):
build_tables()
for entry in from_name[name]:
yield entry
parts = name.split(' ', 1)
if len(parts) > 1 and parts[0] in ('left', 'right'):
for entry in from_name[parts[1]]:
yield entry
def press(scan_code):
write_event(scan_code, True)
def release(scan_code):
write_event(scan_code, False)
def type_unicode(character):
codepoint = ord(character)
hexadecimal = hex(codepoint)[len('0x'):]
for key in ['ctrl', 'shift', 'u']:
scan_code, _ = next(map_name(key))
press(scan_code)
for key in hexadecimal:
scan_code, _ = next(map_name(key))
press(scan_code)
release(scan_code)
for key in ['ctrl', 'shift', 'u']:
scan_code, _ = next(map_name(key))
release(scan_code)
if __name__ == '__main__':
def p(e):
print(e)
listen(p)

130
keyboard/_nixmouse.py Normal file
View File

@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
import struct
from subprocess import check_output
import re
from ._nixcommon import EV_KEY, EV_REL, EV_MSC, EV_SYN, EV_ABS, aggregate_devices, ensure_root
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN
import ctypes
import ctypes.util
from ctypes import c_uint32, c_uint, c_int, byref
display = None
window = None
x11 = None
def build_display():
global display, window, x11
if display and window and x11: return
x11 = ctypes.cdll.LoadLibrary(ctypes.util.find_library('X11'))
# Required because we will have multiple threads calling x11,
# such as the listener thread and then main using "move_to".
x11.XInitThreads()
display = x11.XOpenDisplay(None)
# Known to cause segfault in Fedora 23 64bits, no known workarounds.
# http://stackoverflow.com/questions/35137007/get-mouse-position-on-linux-pure-python
window = x11.XDefaultRootWindow(display)
def get_position():
build_display()
root_id, child_id = c_uint32(), c_uint32()
root_x, root_y, win_x, win_y = c_int(), c_int(), c_int(), c_int()
mask = c_uint()
ret = x11.XQueryPointer(display, c_uint32(window), byref(root_id), byref(child_id),
byref(root_x), byref(root_y),
byref(win_x), byref(win_y), byref(mask))
return root_x.value, root_y.value
def move_to(x, y):
build_display()
x11.XWarpPointer(display, None, window, 0, 0, 0, 0, x, y)
x11.XFlush(display)
REL_X = 0x00
REL_Y = 0x01
REL_Z = 0x02
REL_HWHEEL = 0x06
REL_WHEEL = 0x08
ABS_X = 0x00
ABS_Y = 0x01
BTN_MOUSE = 0x110
BTN_LEFT = 0x110
BTN_RIGHT = 0x111
BTN_MIDDLE = 0x112
BTN_SIDE = 0x113
BTN_EXTRA = 0x114
button_by_code = {
BTN_LEFT: LEFT,
BTN_RIGHT: RIGHT,
BTN_MIDDLE: MIDDLE,
BTN_SIDE: X,
BTN_EXTRA: X2,
}
code_by_button = {button: code for code, button in button_by_code.items()}
device = None
def build_device():
global device
if device: return
ensure_root()
device = aggregate_devices('mouse')
init = build_device
def listen(queue):
build_device()
while True:
time, type, code, value, device_id = device.read_event()
if type == EV_SYN or type == EV_MSC:
continue
event = None
arg = None
if type == EV_KEY:
event = ButtonEvent(DOWN if value else UP, button_by_code.get(code, '?'), time)
elif type == EV_REL:
value, = struct.unpack('i', struct.pack('I', value))
if code == REL_WHEEL:
event = WheelEvent(value, time)
elif code in (REL_X, REL_Y):
x, y = get_position()
event = MoveEvent(x, y, time)
if event is None:
# Unknown event type.
continue
queue.put(event)
def press(button=LEFT):
build_device()
device.write_event(EV_KEY, code_by_button[button], 0x01)
def release(button=LEFT):
build_device()
device.write_event(EV_KEY, code_by_button[button], 0x00)
def move_relative(x, y):
build_device()
# Note relative events are not in terms of pixels, but millimeters.
if x < 0:
x += 2**32
if y < 0:
y += 2**32
device.write_event(EV_REL, REL_X, x)
device.write_event(EV_REL, REL_Y, y)
def wheel(delta=1):
build_device()
if delta < 0:
delta += 2**32
device.write_event(EV_REL, REL_WHEEL, delta)
if __name__ == '__main__':
#listen(print)
move_to(100, 200)

620
keyboard/_winkeyboard.py Normal file
View File

@ -0,0 +1,620 @@
# -*- coding: utf-8 -*-
"""
This is the Windows backend for keyboard events, and is implemented by
invoking the Win32 API through the ctypes module. This is error prone
and can introduce very unpythonic failure modes, such as segfaults and
low level memory leaks. But it is also dependency-free, very performant
well documented on Microsoft's webstie and scattered examples.
# TODO:
- Keypad numbers still print as numbers even when numlock is off.
- No way to specify if user wants a keypad key or not in `map_char`.
"""
from __future__ import unicode_literals
import re
import atexit
import traceback
from threading import Lock
from collections import defaultdict
from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP
from ._canonical_names import normalize_name
try:
# Force Python2 to convert to unicode and not to str.
chr = unichr
except NameError:
pass
# This part is just declaring Win32 API structures using ctypes. In C
# this would be simply #include "windows.h".
import ctypes
from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, Structure, CFUNCTYPE, POINTER
from ctypes.wintypes import WORD, DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM, LONG, HMODULE, LPCWSTR, HINSTANCE, HWND
LPMSG = POINTER(MSG)
ULONG_PTR = POINTER(DWORD)
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
GetModuleHandleW = kernel32.GetModuleHandleW
GetModuleHandleW.restype = HMODULE
GetModuleHandleW.argtypes = [LPCWSTR]
#https://github.com/boppreh/mouse/issues/1
#user32 = ctypes.windll.user32
user32 = ctypes.WinDLL('user32', use_last_error = True)
VK_PACKET = 0xE7
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2
KEYEVENTF_KEYUP = 0x02
KEYEVENTF_UNICODE = 0x04
class KBDLLHOOKSTRUCT(Structure):
_fields_ = [("vk_code", DWORD),
("scan_code", DWORD),
("flags", DWORD),
("time", c_int),
("dwExtraInfo", ULONG_PTR)]
# Included for completeness.
class MOUSEINPUT(ctypes.Structure):
_fields_ = (('dx', LONG),
('dy', LONG),
('mouseData', DWORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))
class KEYBDINPUT(ctypes.Structure):
_fields_ = (('wVk', WORD),
('wScan', WORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR))
class HARDWAREINPUT(ctypes.Structure):
_fields_ = (('uMsg', DWORD),
('wParamL', WORD),
('wParamH', WORD))
class _INPUTunion(ctypes.Union):
_fields_ = (('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT))
class INPUT(ctypes.Structure):
_fields_ = (('type', DWORD),
('union', _INPUTunion))
LowLevelKeyboardProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(KBDLLHOOKSTRUCT))
SetWindowsHookEx = user32.SetWindowsHookExW
SetWindowsHookEx.argtypes = [c_int, LowLevelKeyboardProc, HINSTANCE , DWORD]
SetWindowsHookEx.restype = HHOOK
CallNextHookEx = user32.CallNextHookEx
#CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(KBDLLHOOKSTRUCT)]
CallNextHookEx.restype = c_int
UnhookWindowsHookEx = user32.UnhookWindowsHookEx
UnhookWindowsHookEx.argtypes = [HHOOK]
UnhookWindowsHookEx.restype = BOOL
GetMessage = user32.GetMessageW
GetMessage.argtypes = [LPMSG, HWND, c_uint, c_uint]
GetMessage.restype = BOOL
TranslateMessage = user32.TranslateMessage
TranslateMessage.argtypes = [LPMSG]
TranslateMessage.restype = BOOL
DispatchMessage = user32.DispatchMessageA
DispatchMessage.argtypes = [LPMSG]
keyboard_state_type = c_uint8 * 256
GetKeyboardState = user32.GetKeyboardState
GetKeyboardState.argtypes = [keyboard_state_type]
GetKeyboardState.restype = BOOL
GetKeyNameText = user32.GetKeyNameTextW
GetKeyNameText.argtypes = [c_long, LPWSTR, c_int]
GetKeyNameText.restype = c_int
MapVirtualKey = user32.MapVirtualKeyW
MapVirtualKey.argtypes = [c_uint, c_uint]
MapVirtualKey.restype = c_uint
ToUnicode = user32.ToUnicode
ToUnicode.argtypes = [c_uint, c_uint, keyboard_state_type, LPWSTR, c_int, c_uint]
ToUnicode.restype = c_int
SendInput = user32.SendInput
SendInput.argtypes = [c_uint, POINTER(INPUT), c_int]
SendInput.restype = c_uint
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646307(v=vs.85).aspx
MAPVK_VK_TO_CHAR = 2
MAPVK_VK_TO_VSC = 0
MAPVK_VSC_TO_VK = 1
MAPVK_VK_TO_VSC_EX = 4
MAPVK_VSC_TO_VK_EX = 3
VkKeyScan = user32.VkKeyScanW
VkKeyScan.argtypes = [WCHAR]
VkKeyScan.restype = c_short
LLKHF_INJECTED = 0x00000010
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_SYSKEYDOWN = 0x104 # Used for ALT key
WM_SYSKEYUP = 0x105
# This marks the end of Win32 API declarations. The rest is ours.
keyboard_event_types = {
WM_KEYDOWN: KEY_DOWN,
WM_KEYUP: KEY_UP,
WM_SYSKEYDOWN: KEY_DOWN,
WM_SYSKEYUP: KEY_UP,
}
# List taken from the official documentation, but stripped of the OEM-specific keys.
# Keys are virtual key codes, values are pairs (name, is_keypad).
official_virtual_keys = {
0x03: ('control-break processing', False),
0x08: ('backspace', False),
0x09: ('tab', False),
0x0c: ('clear', False),
0x0d: ('enter', False),
0x10: ('shift', False),
0x11: ('ctrl', False),
0x12: ('alt', False),
0x13: ('pause', False),
0x14: ('caps lock', False),
0x15: ('ime kana mode', False),
0x15: ('ime hanguel mode', False),
0x15: ('ime hangul mode', False),
0x17: ('ime junja mode', False),
0x18: ('ime final mode', False),
0x19: ('ime hanja mode', False),
0x19: ('ime kanji mode', False),
0x1b: ('esc', False),
0x1c: ('ime convert', False),
0x1d: ('ime nonconvert', False),
0x1e: ('ime accept', False),
0x1f: ('ime mode change request', False),
0x20: ('spacebar', False),
0x21: ('page up', False),
0x22: ('page down', False),
0x23: ('end', False),
0x24: ('home', False),
0x25: ('left', False),
0x26: ('up', False),
0x27: ('right', False),
0x28: ('down', False),
0x29: ('select', False),
0x2a: ('print', False),
0x2b: ('execute', False),
0x2c: ('print screen', False),
0x2d: ('insert', False),
0x2e: ('delete', False),
0x2f: ('help', False),
0x30: ('0', False),
0x31: ('1', False),
0x32: ('2', False),
0x33: ('3', False),
0x34: ('4', False),
0x35: ('5', False),
0x36: ('6', False),
0x37: ('7', False),
0x38: ('8', False),
0x39: ('9', False),
0x41: ('a', False),
0x42: ('b', False),
0x43: ('c', False),
0x44: ('d', False),
0x45: ('e', False),
0x46: ('f', False),
0x47: ('g', False),
0x48: ('h', False),
0x49: ('i', False),
0x4a: ('j', False),
0x4b: ('k', False),
0x4c: ('l', False),
0x4d: ('m', False),
0x4e: ('n', False),
0x4f: ('o', False),
0x50: ('p', False),
0x51: ('q', False),
0x52: ('r', False),
0x53: ('s', False),
0x54: ('t', False),
0x55: ('u', False),
0x56: ('v', False),
0x57: ('w', False),
0x58: ('x', False),
0x59: ('y', False),
0x5a: ('z', False),
0x5b: ('left windows', False),
0x5c: ('right windows', False),
0x5d: ('applications', False),
0x5f: ('sleep', False),
0x60: ('0', True),
0x61: ('1', True),
0x62: ('2', True),
0x63: ('3', True),
0x64: ('4', True),
0x65: ('5', True),
0x66: ('6', True),
0x67: ('7', True),
0x68: ('8', True),
0x69: ('9', True),
0x6a: ('*', True),
0x6b: ('+', True),
0x6c: ('separator', True),
0x6d: ('-', True),
0x6e: ('decimal', True),
0x6f: ('/', True),
0x70: ('f1', False),
0x71: ('f2', False),
0x72: ('f3', False),
0x73: ('f4', False),
0x74: ('f5', False),
0x75: ('f6', False),
0x76: ('f7', False),
0x77: ('f8', False),
0x78: ('f9', False),
0x79: ('f10', False),
0x7a: ('f11', False),
0x7b: ('f12', False),
0x7c: ('f13', False),
0x7d: ('f14', False),
0x7e: ('f15', False),
0x7f: ('f16', False),
0x80: ('f17', False),
0x81: ('f18', False),
0x82: ('f19', False),
0x83: ('f20', False),
0x84: ('f21', False),
0x85: ('f22', False),
0x86: ('f23', False),
0x87: ('f24', False),
0x90: ('num lock', False),
0x91: ('scroll lock', False),
0xa0: ('left shift', False),
0xa1: ('right shift', False),
0xa2: ('left ctrl', False),
0xa3: ('right ctrl', False),
0xa4: ('left menu', False),
0xa5: ('right menu', False),
0xa6: ('browser back', False),
0xa7: ('browser forward', False),
0xa8: ('browser refresh', False),
0xa9: ('browser stop', False),
0xaa: ('browser search key', False),
0xab: ('browser favorites', False),
0xac: ('browser start and home', False),
0xad: ('volume mute', False),
0xae: ('volume down', False),
0xaf: ('volume up', False),
0xb0: ('next track', False),
0xb1: ('previous track', False),
0xb2: ('stop media', False),
0xb3: ('play/pause media', False),
0xb4: ('start mail', False),
0xb5: ('select media', False),
0xb6: ('start application 1', False),
0xb7: ('start application 2', False),
0xbb: ('+', False),
0xbc: (',', False),
0xbd: ('-', False),
0xbe: ('.', False),
#0xbe:('/', False), # Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?.
0xe5: ('ime process', False),
0xf6: ('attn', False),
0xf7: ('crsel', False),
0xf8: ('exsel', False),
0xf9: ('erase eof', False),
0xfa: ('play', False),
0xfb: ('zoom', False),
0xfc: ('reserved ', False),
0xfd: ('pa1', False),
0xfe: ('clear', False),
}
tables_lock = Lock()
to_name = defaultdict(list)
from_name = defaultdict(list)
scan_code_to_vk = {}
distinct_modifiers = [
(),
('shift',),
('alt gr',),
('num lock',),
('shift', 'num lock'),
('caps lock',),
('shift', 'caps lock'),
('alt gr', 'num lock'),
]
name_buffer = ctypes.create_unicode_buffer(32)
unicode_buffer = ctypes.create_unicode_buffer(32)
keyboard_state = keyboard_state_type()
def get_event_names(scan_code, vk, is_extended, modifiers):
is_keypad = (scan_code, vk, is_extended) in keypad_keys
is_official = vk in official_virtual_keys
if is_keypad and is_official:
yield official_virtual_keys[vk][0]
keyboard_state[0x10] = 0x80 * ('shift' in modifiers)
keyboard_state[0x11] = 0x80 * ('alt gr' in modifiers)
keyboard_state[0x12] = 0x80 * ('alt gr' in modifiers)
keyboard_state[0x14] = 0x01 * ('caps lock' in modifiers)
keyboard_state[0x90] = 0x01 * ('num lock' in modifiers)
keyboard_state[0x91] = 0x01 * ('scroll lock' in modifiers)
unicode_ret = ToUnicode(vk, scan_code, keyboard_state, unicode_buffer, len(unicode_buffer), 0)
if unicode_ret and unicode_buffer.value:
yield unicode_buffer.value
# unicode_ret == -1 -> is dead key
# ToUnicode has the side effect of setting global flags for dead keys.
# Therefore we need to call it twice to clear those flags.
# If your 6 and 7 keys are named "^6" and "^7", this is the reason.
ToUnicode(vk, scan_code, keyboard_state, unicode_buffer, len(unicode_buffer), 0)
name_ret = GetKeyNameText(scan_code << 16 | is_extended << 24, name_buffer, 1024)
if name_ret and name_buffer.value:
yield name_buffer.value
char = user32.MapVirtualKeyW(vk, MAPVK_VK_TO_CHAR) & 0xFF
if char != 0:
yield chr(char)
if not is_keypad and is_official:
yield official_virtual_keys[vk][0]
def _setup_name_tables():
"""
Ensures the scan code/virtual key code/name translation tables are
filled.
"""
with tables_lock:
if to_name: return
# Go through every possible scan code, and map them to virtual key codes.
# Then vice-versa.
all_scan_codes = [(sc, user32.MapVirtualKeyExW(sc, MAPVK_VSC_TO_VK_EX, 0)) for sc in range(0x100)]
all_vks = [(user32.MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, 0), vk) for vk in range(0x100)]
for scan_code, vk in all_scan_codes + all_vks:
# `to_name` and `from_name` entries will be a tuple (scan_code, vk, extended, shift_state).
if (scan_code, vk, 0, 0, 0) in to_name:
continue
if scan_code not in scan_code_to_vk:
scan_code_to_vk[scan_code] = vk
# Brute force all combinations to find all possible names.
for extended in [0, 1]:
for modifiers in distinct_modifiers:
entry = (scan_code, vk, extended, modifiers)
# Get key names from ToUnicode, GetKeyNameText, MapVirtualKeyW and official virtual keys.
names = list(get_event_names(*entry))
if names:
# Also map lowercased key names, but only after the properly cased ones.
lowercase_names = [name.lower() for name in names]
to_name[entry] = names + lowercase_names
# Remember the "id" of the name, as the first techniques
# have better results and therefore priority.
for i, name in enumerate(map(normalize_name, names + lowercase_names)):
from_name[name].append((i, entry))
# TODO: single quotes on US INTL is returning the dead key (?), and therefore
# not typing properly.
# Alt gr is way outside the usual range of keys (0..127) and on my
# computer is named as 'ctrl'. Therefore we add it manually and hope
# Windows is consistent in its inconsistency.
for extended in [0, 1]:
for modifiers in distinct_modifiers:
to_name[(541, 162, extended, modifiers)] = ['alt gr']
from_name['alt gr'].append((1, (541, 162, extended, modifiers)))
modifiers_preference = defaultdict(lambda: 10)
modifiers_preference.update({(): 0, ('shift',): 1, ('alt gr',): 2, ('ctrl',): 3, ('alt',): 4})
def order_key(line):
i, entry = line
scan_code, vk, extended, modifiers = entry
return modifiers_preference[modifiers], i, extended, vk, scan_code
for name, entries in list(from_name.items()):
from_name[name] = sorted(set(entries), key=order_key)
# Called by keyboard/__init__.py
init = _setup_name_tables
# List created manually.
keypad_keys = [
# (scan_code, virtual_key_code, is_extended)
(126, 194, 0),
(126, 194, 0),
(28, 13, 1),
(28, 13, 1),
(53, 111, 1),
(53, 111, 1),
(55, 106, 0),
(55, 106, 0),
(69, 144, 1),
(69, 144, 1),
(71, 103, 0),
(71, 36, 0),
(72, 104, 0),
(72, 38, 0),
(73, 105, 0),
(73, 33, 0),
(74, 109, 0),
(74, 109, 0),
(75, 100, 0),
(75, 37, 0),
(76, 101, 0),
(76, 12, 0),
(77, 102, 0),
(77, 39, 0),
(78, 107, 0),
(78, 107, 0),
(79, 35, 0),
(79, 97, 0),
(80, 40, 0),
(80, 98, 0),
(81, 34, 0),
(81, 99, 0),
(82, 45, 0),
(82, 96, 0),
(83, 110, 0),
(83, 46, 0),
]
shift_is_pressed = False
altgr_is_pressed = False
ignore_next_right_alt = False
shift_vks = set([0x10, 0xa0, 0xa1])
def prepare_intercept(callback):
"""
Registers a Windows low level keyboard hook. The provided callback will
be invoked for each high-level keyboard event, and is expected to return
True if the key event should be passed to the next program, or False if
the event is to be blocked.
No event is processed until the Windows messages are pumped (see
start_intercept).
"""
_setup_name_tables()
def process_key(event_type, vk, scan_code, is_extended):
global shift_is_pressed, altgr_is_pressed, ignore_next_right_alt
#print(event_type, vk, scan_code, is_extended)
# Pressing alt-gr also generates an extra "right alt" event
if vk == 0xA5 and ignore_next_right_alt:
ignore_next_right_alt = False
return True
modifiers = (
('shift',) * shift_is_pressed +
('alt gr',) * altgr_is_pressed +
('num lock',) * (user32.GetKeyState(0x90) & 1) +
('caps lock',) * (user32.GetKeyState(0x14) & 1) +
('scroll lock',) * (user32.GetKeyState(0x91) & 1)
)
entry = (scan_code, vk, is_extended, modifiers)
if entry not in to_name:
to_name[entry] = list(get_event_names(*entry))
names = to_name[entry]
name = names[0] if names else None
# TODO: inaccurate when holding multiple different shifts.
if vk in shift_vks:
shift_is_pressed = event_type == KEY_DOWN
if scan_code == 541 and vk == 162:
ignore_next_right_alt = True
altgr_is_pressed = event_type == KEY_DOWN
is_keypad = (scan_code, vk, is_extended) in keypad_keys
return callback(KeyboardEvent(event_type=event_type, scan_code=scan_code or -vk, name=name, is_keypad=is_keypad))
def low_level_keyboard_handler(nCode, wParam, lParam):
try:
vk = lParam.contents.vk_code
# Ignore the second `alt` DOWN observed in some cases.
fake_alt = (LLKHF_INJECTED | 0x20)
# Ignore events generated by SendInput with Unicode.
if vk != VK_PACKET and lParam.contents.flags & fake_alt != fake_alt:
event_type = keyboard_event_types[wParam]
is_extended = lParam.contents.flags & 1
scan_code = lParam.contents.scan_code
should_continue = process_key(event_type, vk, scan_code, is_extended)
if not should_continue:
return -1
except Exception as e:
print('Error in keyboard hook:')
traceback.print_exc()
return CallNextHookEx(None, nCode, wParam, lParam)
WH_KEYBOARD_LL = c_int(13)
keyboard_callback = LowLevelKeyboardProc(low_level_keyboard_handler)
handle = GetModuleHandleW(None)
thread_id = DWORD(0)
keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_callback, handle, thread_id)
# Register to remove the hook when the interpreter exits. Unfortunately a
# try/finally block doesn't seem to work here.
atexit.register(UnhookWindowsHookEx, keyboard_callback)
def listen(callback):
prepare_intercept(callback)
msg = LPMSG()
while not GetMessage(msg, 0, 0, 0):
TranslateMessage(msg)
DispatchMessage(msg)
def map_name(name):
_setup_name_tables()
entries = from_name.get(name)
if not entries:
raise ValueError('Key name {} is not mapped to any known key.'.format(repr(name)))
for i, entry in entries:
scan_code, vk, is_extended, modifiers = entry
yield scan_code or -vk, modifiers
def _send_event(code, event_type):
if code == 541:
# Alt-gr is made of ctrl+alt. Just sending even 541 doesn't do anything.
user32.keybd_event(0x11, code, event_type, 0)
user32.keybd_event(0x12, code, event_type, 0)
elif code > 0:
vk = scan_code_to_vk.get(code, 0)
user32.keybd_event(vk, code, event_type, 0)
else:
# Negative scan code is a way to indicate we don't have a scan code,
# and the value actually contains the Virtual key code.
user32.keybd_event(-code, 0, event_type, 0)
def press(code):
_send_event(code, 0)
def release(code):
_send_event(code, 2)
def type_unicode(character):
# This code and related structures are based on
# http://stackoverflow.com/a/11910555/252218
surrogates = bytearray(character.encode('utf-16le'))
presses = []
releases = []
for i in range(0, len(surrogates), 2):
higher, lower = surrogates[i:i+2]
structure = KEYBDINPUT(0, (lower << 8) + higher, KEYEVENTF_UNICODE, 0, None)
presses.append(INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure)))
structure = KEYBDINPUT(0, (lower << 8) + higher, KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, None)
releases.append(INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure)))
inputs = presses + releases
nInputs = len(inputs)
LPINPUT = INPUT * nInputs
pInputs = LPINPUT(*inputs)
cbSize = c_int(ctypes.sizeof(INPUT))
SendInput(nInputs, pInputs, cbSize)
if __name__ == '__main__':
_setup_name_tables()
import pprint
pprint.pprint(to_name)
pprint.pprint(from_name)
#listen(lambda e: print(e.to_json()) or True)

201
keyboard/_winmouse.py Normal file
View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
import ctypes
import time
from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, byref, Structure, CFUNCTYPE, POINTER
from ctypes.wintypes import DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM
LPMSG = POINTER(MSG)
import atexit
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE, WHEEL, HORIZONTAL, VERTICAL
#https://github.com/boppreh/mouse/issues/1
#user32 = ctypes.windll.user32
user32 = ctypes.WinDLL('user32', use_last_error = True)
class MSLLHOOKSTRUCT(Structure):
_fields_ = [("x", c_long),
("y", c_long),
('data', c_int32),
('reserved', c_int32),
("flags", DWORD),
("time", c_int),
]
LowLevelMouseProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(MSLLHOOKSTRUCT))
SetWindowsHookEx = user32.SetWindowsHookExA
#SetWindowsHookEx.argtypes = [c_int, LowLevelMouseProc, c_int, c_int]
SetWindowsHookEx.restype = HHOOK
CallNextHookEx = user32.CallNextHookEx
#CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(MSLLHOOKSTRUCT)]
CallNextHookEx.restype = c_int
UnhookWindowsHookEx = user32.UnhookWindowsHookEx
UnhookWindowsHookEx.argtypes = [HHOOK]
UnhookWindowsHookEx.restype = BOOL
GetMessage = user32.GetMessageW
GetMessage.argtypes = [LPMSG, c_int, c_int, c_int]
GetMessage.restype = BOOL
TranslateMessage = user32.TranslateMessage
TranslateMessage.argtypes = [LPMSG]
TranslateMessage.restype = BOOL
DispatchMessage = user32.DispatchMessageA
DispatchMessage.argtypes = [LPMSG]
# Beware, as of 2016-01-30 the official docs have a very incomplete list.
# This one was compiled from experience and may be incomplete.
WM_MOUSEMOVE = 0x200
WM_LBUTTONDOWN = 0x201
WM_LBUTTONUP = 0x202
WM_LBUTTONDBLCLK = 0x203
WM_RBUTTONDOWN = 0x204
WM_RBUTTONUP = 0x205
WM_RBUTTONDBLCLK = 0x206
WM_MBUTTONDOWN = 0x207
WM_MBUTTONUP = 0x208
WM_MBUTTONDBLCLK = 0x209
WM_MOUSEWHEEL = 0x20A
WM_XBUTTONDOWN = 0x20B
WM_XBUTTONUP = 0x20C
WM_XBUTTONDBLCLK = 0x20D
WM_NCXBUTTONDOWN = 0x00AB
WM_NCXBUTTONUP = 0x00AC
WM_NCXBUTTONDBLCLK = 0x00AD
WM_MOUSEHWHEEL = 0x20E
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
buttons_by_wm_code = {
WM_LBUTTONDOWN: (DOWN, LEFT),
WM_LBUTTONUP: (UP, LEFT),
WM_LBUTTONDBLCLK: (DOUBLE, LEFT),
WM_RBUTTONDOWN: (DOWN, RIGHT),
WM_RBUTTONUP: (UP, RIGHT),
WM_RBUTTONDBLCLK: (DOUBLE, RIGHT),
WM_MBUTTONDOWN: (DOWN, MIDDLE),
WM_MBUTTONUP: (UP, MIDDLE),
WM_MBUTTONDBLCLK: (DOUBLE, MIDDLE),
WM_XBUTTONDOWN: (DOWN, X),
WM_XBUTTONUP: (UP, X),
WM_XBUTTONDBLCLK: (DOUBLE, X),
}
MOUSEEVENTF_ABSOLUTE = 0x8000
MOUSEEVENTF_MOVE = 0x1
MOUSEEVENTF_WHEEL = 0x800
MOUSEEVENTF_HWHEEL = 0x1000
MOUSEEVENTF_LEFTDOWN = 0x2
MOUSEEVENTF_LEFTUP = 0x4
MOUSEEVENTF_RIGHTDOWN = 0x8
MOUSEEVENTF_RIGHTUP = 0x10
MOUSEEVENTF_MIDDLEDOWN = 0x20
MOUSEEVENTF_MIDDLEUP = 0x40
MOUSEEVENTF_XDOWN = 0x0080
MOUSEEVENTF_XUP = 0x0100
simulated_mouse_codes = {
(WHEEL, HORIZONTAL): MOUSEEVENTF_HWHEEL,
(WHEEL, VERTICAL): MOUSEEVENTF_WHEEL,
(DOWN, LEFT): MOUSEEVENTF_LEFTDOWN,
(UP, LEFT): MOUSEEVENTF_LEFTUP,
(DOWN, RIGHT): MOUSEEVENTF_RIGHTDOWN,
(UP, RIGHT): MOUSEEVENTF_RIGHTUP,
(DOWN, MIDDLE): MOUSEEVENTF_MIDDLEDOWN,
(UP, MIDDLE): MOUSEEVENTF_MIDDLEUP,
(DOWN, X): MOUSEEVENTF_XDOWN,
(UP, X): MOUSEEVENTF_XUP,
}
NULL = c_int(0)
WHEEL_DELTA = 120
init = lambda: None
def listen(queue):
def low_level_mouse_handler(nCode, wParam, lParam):
struct = lParam.contents
# Can't use struct.time because it's usually zero.
t = time.time()
if wParam == WM_MOUSEMOVE:
event = MoveEvent(struct.x, struct.y, t)
elif wParam == WM_MOUSEWHEEL:
event = WheelEvent(struct.data / (WHEEL_DELTA * (2<<15)), t)
elif wParam in buttons_by_wm_code:
type, button = buttons_by_wm_code.get(wParam, ('?', '?'))
if wParam >= WM_XBUTTONDOWN:
button = {0x10000: X, 0x20000: X2}[struct.data]
event = ButtonEvent(type, button, t)
queue.put(event)
return CallNextHookEx(NULL, nCode, wParam, lParam)
WH_MOUSE_LL = c_int(14)
mouse_callback = LowLevelMouseProc(low_level_mouse_handler)
mouse_hook = SetWindowsHookEx(WH_MOUSE_LL, mouse_callback, NULL, NULL)
# Register to remove the hook when the interpreter exits. Unfortunately a
# try/finally block doesn't seem to work here.
atexit.register(UnhookWindowsHookEx, mouse_hook)
msg = LPMSG()
while not GetMessage(msg, NULL, NULL, NULL):
TranslateMessage(msg)
DispatchMessage(msg)
def _translate_button(button):
if button == X or button == X2:
return X, {X: 0x10000, X2: 0x20000}[button]
else:
return button, 0
def press(button=LEFT):
button, data = _translate_button(button)
code = simulated_mouse_codes[(DOWN, button)]
user32.mouse_event(code, 0, 0, data, 0)
def release(button=LEFT):
button, data = _translate_button(button)
code = simulated_mouse_codes[(UP, button)]
user32.mouse_event(code, 0, 0, data, 0)
def wheel(delta=1):
code = simulated_mouse_codes[(WHEEL, VERTICAL)]
user32.mouse_event(code, 0, 0, int(delta * WHEEL_DELTA), 0)
def move_to(x, y):
user32.SetCursorPos(int(x), int(y))
def move_relative(x, y):
user32.mouse_event(MOUSEEVENTF_MOVE, int(x), int(y), 0, 0)
class POINT(Structure):
_fields_ = [("x", c_long), ("y", c_long)]
def get_position():
point = POINT()
user32.GetCursorPos(byref(point))
return (point.x, point.y)
if __name__ == '__main__':
def p(e):
print(e)
listen(p)

232
keyboard/mouse.py Normal file
View File

@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
import warnings
warnings.simplefilter('always', DeprecationWarning)
warnings.warn('The mouse sub-library is deprecated and will be removed in future versions. Please use the standalone package `mouse`.', DeprecationWarning, stacklevel=2)
import time as _time
import platform as _platform
if _platform.system() == 'Windows':
from. import _winmouse as _os_mouse
elif _platform.system() == 'Linux':
from. import _nixmouse as _os_mouse
elif _platform.system() == 'Darwin':
from. import _darwinmouse as _os_mouse
else:
raise OSError("Unsupported platform '{}'".format(_platform.system()))
from ._mouse_event import ButtonEvent, MoveEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
from ._generic import GenericListener as _GenericListener
_pressed_events = set()
class _MouseListener(_GenericListener):
def init(self):
_os_mouse.init()
def pre_process_event(self, event):
if isinstance(event, ButtonEvent):
if event.event_type in (UP, DOUBLE):
_pressed_events.discard(event.button)
else:
_pressed_events.add(event.button)
return True
def listen(self):
_os_mouse.listen(self.queue)
_listener = _MouseListener()
def is_pressed(button=LEFT):
""" Returns True if the given button is currently pressed. """
_listener.start_if_necessary()
return button in _pressed_events
def press(button=LEFT):
""" Presses the given button (but doesn't release). """
_os_mouse.press(button)
def release(button=LEFT):
""" Releases the given button. """
_os_mouse.release(button)
def click(button=LEFT):
""" Sends a click with the given button. """
_os_mouse.press(button)
_os_mouse.release(button)
def double_click(button=LEFT):
""" Sends a double click with the given button. """
click(button)
click(button)
def right_click():
""" Sends a right click with the given button. """
click(RIGHT)
def wheel(delta=1):
""" Scrolls the wheel `delta` clicks. Sign indicates direction. """
_os_mouse.wheel(delta)
def move(x, y, absolute=True, duration=0):
"""
Moves the mouse. If `absolute`, to position (x, y), otherwise move relative
to the current position. If `duration` is non-zero, animates the movement.
"""
x = int(x)
y = int(y)
# Requires an extra system call on Linux, but `move_relative` is measured
# in millimiters so we would lose precision.
position_x, position_y = get_position()
if not absolute:
x = position_x + x
y = position_y + y
if duration:
start_x = position_x
start_y = position_y
dx = x - start_x
dy = y - start_y
if dx == 0 and dy == 0:
_time.sleep(duration)
else:
# 120 movements per second.
# Round and keep float to ensure float division in Python 2
steps = max(1.0, float(int(duration * 120.0)))
for i in range(int(steps)+1):
move(start_x + dx*i/steps, start_y + dy*i/steps)
_time.sleep(duration/steps)
else:
_os_mouse.move_to(x, y)
def drag(start_x, start_y, end_x, end_y, absolute=True, duration=0):
"""
Holds the left mouse button, moving from start to end position, then
releases. `absolute` and `duration` are parameters regarding the mouse
movement.
"""
if is_pressed():
release()
move(start_x, start_y, absolute, 0)
press()
move(end_x, end_y, absolute, duration)
release()
def on_button(callback, args=(), buttons=(LEFT, MIDDLE, RIGHT, X, X2), types=(UP, DOWN, DOUBLE)):
""" Invokes `callback` with `args` when the specified event happens. """
if not isinstance(buttons, (tuple, list)):
buttons = (buttons,)
if not isinstance(types, (tuple, list)):
types = (types,)
def handler(event):
if isinstance(event, ButtonEvent):
if event.event_type in types and event.button in buttons:
callback(*args)
_listener.add_handler(handler)
return handler
def on_click(callback, args=()):
""" Invokes `callback` with `args` when the left button is clicked. """
return on_button(callback, args, [LEFT], [UP])
def on_double_click(callback, args=()):
"""
Invokes `callback` with `args` when the left button is double clicked.
"""
return on_button(callback, args, [LEFT], [DOUBLE])
def on_right_click(callback, args=()):
""" Invokes `callback` with `args` when the right button is clicked. """
return on_button(callback, args, [RIGHT], [UP])
def on_middle_click(callback, args=()):
""" Invokes `callback` with `args` when the middle button is clicked. """
return on_button(callback, args, [MIDDLE], [UP])
def wait(button=LEFT, target_types=(UP, DOWN, DOUBLE)):
"""
Blocks program execution until the given button performs an event.
"""
from threading import Lock
lock = Lock()
lock.acquire()
handler = on_button(lock.release, (), [button], target_types)
lock.acquire()
_listener.remove_handler(handler)
def get_position():
""" Returns the (x, y) mouse position. """
return _os_mouse.get_position()
def hook(callback):
"""
Installs a global listener on all available mouses, invoking `callback`
each time it is moved, a key status changes or the wheel is spun. A mouse
event is passed as argument, with type either `mouse.ButtonEvent`,
`mouse.WheelEvent` or `mouse.MoveEvent`.
Returns the given callback for easier development.
"""
_listener.add_handler(callback)
return callback
def unhook(callback):
"""
Removes a previously installed hook.
"""
_listener.remove_handler(callback)
def unhook_all():
"""
Removes all hooks registered by this application. Note this may include
hooks installed by high level functions, such as `record`.
"""
del _listener.handlers[:]
def record(button=RIGHT, target_types=(DOWN,)):
"""
Records all mouse events until the user presses the given button.
Then returns the list of events recorded. Pairs well with `play(events)`.
Note: this is a blocking function.
Note: for more details on the mouse hook and events see `hook`.
"""
recorded = []
hook(recorded.append)
wait(button=button, target_types=target_types)
unhook(recorded.append)
return recorded
def play(events, speed_factor=1.0, include_clicks=True, include_moves=True, include_wheel=True):
"""
Plays a sequence of recorded events, maintaining the relative time
intervals. If speed_factor is <= 0 then the actions are replayed as fast
as the OS allows. Pairs well with `record()`.
The parameters `include_*` define if events of that type should be inluded
in the replay or ignored.
"""
last_time = None
for event in events:
if speed_factor > 0 and last_time is not None:
_time.sleep((event.time - last_time) / speed_factor)
last_time = event.time
if isinstance(event, ButtonEvent) and include_clicks:
if event.event_type == UP:
_os_mouse.release(event.button)
else:
_os_mouse.press(event.button)
elif isinstance(event, MoveEvent) and include_moves:
_os_mouse.move_to(event.x, event.y)
elif isinstance(event, WheelEvent) and include_wheel:
_os_mouse.wheel(event.delta)
replay = play
hold = press
if __name__ == '__main__':
print('Recording... Double click to stop and replay.')
play(record())

231
main.py Normal file
View File

@ -0,0 +1,231 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ui_mainwnd import Ui_MainWidget
from profileeditwnd import ProfileEditWnd
import json
from profileexecutor import ProfileExecutor
import sys
import pickle
import time
import keyboard
import threading
class MainWnd(QWidget):
def __init__(self, p_parent = None):
super().__init__(p_parent)
self.ui = Ui_MainWidget()
self.ui.setupUi(self)
self.m_profileExecutor = ProfileExecutor()
self.ui.profileCbx.currentIndexChanged.connect(self.slotProfileChanged)
self.ui.addBut.clicked.connect(self.slotAddNewProfile)
self.ui.editBut.clicked.connect(self.slotEditProfile)
self.ui.removeBut.clicked.connect(self.slotRemoveProfile)
self.ui.listeningChk.stateChanged.connect(self.slotListeningEnabled)
self.ui.ok.clicked.connect(self.slotOK)
self.ui.cancel.clicked.connect(self.slotCancel)
self.m_profileExecutor.start()
if self.loadFromDatabase() > 0 :
# if self.loadTestProfiles() > 0:
w_jsonProfile = self.ui.profileCbx.itemData(0)
if w_jsonProfile != None:
self.m_activeProfile = json.loads(w_jsonProfile)
self.m_profileExecutor.setProfile(self.m_activeProfile)
def saveToDatabase(self):
w_profiles = []
w_profileCnt = self.ui.profileCbx.count()
for w_idx in range(w_profileCnt):
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
if w_jsonProfile == None:
continue
w_profile = json.loads(w_jsonProfile)
w_profiles.append(w_profile)
with open("profiles.dat", "wb") as f:
pickle.dump(w_profiles, f, pickle.HIGHEST_PROTOCOL)
def loadFromDatabase(self):
w_profiles = []
with open("profiles.dat", "rb") as f:
w_profiles = pickle.load(f)
for w_profile in w_profiles:
self.ui.profileCbx.addItem(w_profile['name'])
w_jsonProfile = json.dumps(w_profile)
self.ui.profileCbx.setItemData(self.ui.profileCbx.count() - 1, w_jsonProfile)
return len(w_profiles)
def loadTestProfiles(self):
w_carProfileDict = {
"name": "car game",
"commands": [
{'name': 'up',
'actions': [
{'name': 'key action', 'key': 'up', 'type': 1},
{'name': 'pause action', 'time': 0.03}
],
'repeat': 1,
'async': False
},
{'name': 'left',
'actions': [{'name': 'key action', 'key': 'right', 'type': 0},
{'name': 'pause action', 'time': 0.03},
{'name': 'key action', 'key': 'left', 'type': 1},
{'name': 'pause action', 'time': 0.03}
],
'repeat': 1,
'async': False
},
{'name': 'right',
'actions': [{'name': 'key action', 'key': 'left', 'type': 0},
{'name': 'pause action', 'time': 0.03},
{'name': 'key action', 'key': 'right', 'type': 1},
{'name': 'pause action', 'time': 0.03}
],
'repeat': 1,
'async': False
},
{'name': 'stop',
'actions': [
{'name': 'key action', 'key': 'left', 'type': 0},
{'name': 'pause action', 'time': 0.03},
{'name': 'key action', 'key': 'right', 'type': 0},
{'name': 'pause action', 'time': 0.03},
{'name': 'key action', 'key': 'up', 'type': 0},
{'name': 'pause action', 'time': 0.03}
],
'repeat': 1,
'async': False
}
]
}
w_airplaneProfileDict = {
"name": "airplane game",
"commands": [
{'name': 'up',
'actions': [
{'name': 'command stop action', 'command name': 'down'},
{'name': 'mouse move action', 'x': 0, 'y': -5, 'absolute': False},
{'name': 'pause action', 'time': 0.01}
],
'repeat': -1,
'async': True
},
{'name': 'left',
'actions': [
{'name': 'command stop action', 'command name': 'right'},
{'name': 'mouse move action', 'x': -5, 'y': 0, 'absolute': False},
{'name': 'pause action', 'time': 0.005}
],
'repeat': -1,
'async': True
},
{'name': 'right',
'actions': [
{'name': 'command stop action', 'command name': 'left'},
{'name': 'mouse move action', 'x': 5, 'y': 0, 'absolute': False},
{'name': 'pause action', 'time': 0.005}
],
'repeat': -1,
'async': True
},
{'name': 'down',
'actions': [
{'name': 'command stop action', 'command name': 'up'},
{'name': 'mouse move action', 'x': 0, 'y': 5, 'absolute': False},
{'name': 'pause action', 'time': 0.005}
],
'repeat': -1,
'async': True
},
{'name': 'shoot',
'actions': [
{'name': 'mouse click action', 'button': 'left', 'type': 1},
{'name': 'pause action', 'time': 0.03}
],
'repeat': 1,
'async': False
},
{'name': 'stop',
'actions': [
{'name': 'command stop action', 'command name': 'up'},
{'name': 'command stop action', 'command name': 'left'},
{'name': 'command stop action', 'command name': 'right'},
{'name': 'command stop action', 'command name': 'down'},
{'name': 'mouse click action', 'button': 'left', 'type': 0}
],
'repeat': 1,
'async': False
}
]
}
w_profiles = []
w_profiles.append(w_carProfileDict)
w_profiles.append(w_airplaneProfileDict)
i = 0
for w_profile in w_profiles:
self.ui.profileCbx.addItem(w_profile['name'])
w_jsonProfile = json.dumps(w_profile)
self.ui.profileCbx.setItemData(i, w_jsonProfile)
i = i + 1
return i
def slotProfileChanged(self, p_idx):
w_jsonProfile = self.ui.profileCbx.itemData(p_idx)
if w_jsonProfile != None:
self.m_activeProfile = json.loads(w_jsonProfile)
self.m_profileExecutor.setProfile(self.m_activeProfile)
def slotAddNewProfile(self):
w_profileEditWnd = ProfileEditWnd(None, self)
if w_profileEditWnd.exec() == QDialog.Accepted:
w_profile = w_profileEditWnd.m_profile
self.ui.profileCbx.addItem(w_profile['name'])
w_jsonProfile = json.dumps(w_profile)
self.ui.profileCbx.setItemData(self.ui.profileCbx.count()-1, w_jsonProfile)
def slotEditProfile(self):
w_idx = self.ui.profileCbx.currentIndex()
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
w_profile = json.loads(w_jsonProfile)
w_profileEditWnd = ProfileEditWnd(w_profile, self)
if w_profileEditWnd.exec() == QDialog.Accepted:
w_profile = w_profileEditWnd.m_profile
self.ui.profileCbx.setItemText(w_idx, w_profile['name'])
w_jsonProfile = json.dumps(w_profile)
self.ui.profileCbx.setItemData(w_idx, w_jsonProfile)
def slotRemoveProfile(self):
w_curIdx = self.ui.profileCbx.currentIndex()
if w_curIdx >= 0:
self.ui.profileCbx.removeItem(w_curIdx)
def slotListeningEnabled(self, p_enabled):
if p_enabled:
self.m_profileExecutor.setEnableListening(True)
else:
self.m_profileExecutor.setEnableListening(False)
def slotOK(self):
self.saveToDatabase()
self.close()
def slotCancel(self):
self.close()
exit()
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWnd = MainWnd()
mainWnd.show()
sys.exit(app.exec_())

167
mainwnd.ui Normal file
View File

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWidget</class>
<widget class="QWidget" name="MainWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>820</width>
<height>166</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="windowTitle">
<string>LinVAM</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="topMargin">
<number>20</number>
</property>
<property name="verticalSpacing">
<number>20</number>
</property>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>20</number>
</property>
<item>
<widget class="QCheckBox" name="listeningChk">
<property name="text">
<string>Enable Listening</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>20</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Profile:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="profileCbx">
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addBut">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editBut">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeBut">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

117
mouseactioneditwnd.py Normal file
View File

@ -0,0 +1,117 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ui_mouseactioneditwnd import Ui_MouseActionEditDialog
import json
class MouseActionEditWnd(QDialog):
def __init__(self, p_mouseAction, p_parent = None):
super().__init__(p_parent)
self.ui = Ui_MouseActionEditDialog()
self.ui.setupUi(self)
self.ui.ok.clicked.connect(self.slotOK)
self.ui.cancel.clicked.connect(super().reject)
self.m_mouseAction = {}
if p_mouseAction == None:
return
if p_mouseAction['name'] == 'mouse click action':
self.ui.mouseActionTabWidget.setCurrentIndex(0)
if p_mouseAction['button'] == 'left':
if p_mouseAction['type'] == 10: # click
self.ui.leftClick.setChecked(True)
elif p_mouseAction['type'] == 1: # down
self.ui.leftDown.setChecked(True)
elif p_mouseAction['type'] == 0: # up
self.ui.leftUp.setChecked(True)
elif p_mouseAction['type'] == 11: # double-click
self.ui.leftDclick.setChecked(True)
elif p_mouseAction['button'] == 'right':
if p_mouseAction['type'] == 10:
self.ui.rightClick.setChecked(True)
elif p_mouseAction['type'] == 1:
self.ui.rightDown.setChecked(True)
elif p_mouseAction['type'] == 0:
self.ui.rightUp.setChecked(True)
elif p_mouseAction['type'] == 11: # double-click
self.ui.rightDclick.setChecked(True)
elif p_mouseAction['button'] == 'middle':
if p_mouseAction['type'] == 10:
self.ui.middleClick.setChecked(True)
elif p_mouseAction['type'] == 1:
self.ui.middleDown.setChecked(True)
elif p_mouseAction['type'] == 0:
self.ui.middleUp.setChecked(True)
elif p_mouseAction['type'] == 11: # double-click
self.ui.middleDclick.setChecked(True)
elif p_mouseAction['name'] == 'mouse move action':
self.ui.mouseActionTabWidget.setCurrentIndex(1)
if p_mouseAction['absolute'] == True:
self.ui.moveTo.setChecked(True)
self.ui.xEdit.setText(str(p_mouseAction['x']))
self.ui.yEdit.setText(str(p_mouseAction['y']))
else:
self.ui.moveOffset.setChecked(True)
self.ui.xOffsetEdit.setText(str(p_mouseAction['x']))
self.ui.yOffsetEdit.setText(str(p_mouseAction['y']))
elif p_mouseAction['name'] == 'mouse scroll action':
self.ui.mouseActionTabWidget.setCurrentIndex(1)
if p_mouseAction['delta'] < 0:
self.ui.scrollUp.setChecked(True)
self.ui.scrollUpEdit.setText(str(-p_mouseAction['delta']))
else:
self.ui.scrollDown.setChecked(True)
self.ui.scrollDownEdit.setText(str(p_mouseAction['delta']))
def slotOK(self):
if self.ui.mouseActionTabWidget.currentIndex() == 0:
if self.ui.leftClick.isChecked():
self.m_mouseAction = {'name':'mouse click action', 'button':'left', 'type':10}
elif self.ui.rightClick.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'right', 'type': 10}
elif self.ui.middleClick.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'middle', 'type': 10}
elif self.ui.leftDclick.isChecked():
self.m_mouseAction = {'name':'mouse click action', 'button':'left', 'type':11}
elif self.ui.rightDclick.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'right', 'type': 11}
elif self.ui.middleDclick.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'middle', 'type': 11}
elif self.ui.leftDown.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'left', 'type': 1}
elif self.ui.rightDown.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'right', 'type': 1}
elif self.ui.middleDown.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'middle', 'type': 1}
elif self.ui.leftUp.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'left', 'type': 0}
elif self.ui.rightUp.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'right', 'type': 0}
elif self.ui.middleUp.isChecked():
self.m_mouseAction = {'name': 'mouse click action', 'button': 'middle', 'type': 0}
else:
if self.ui.moveTo.isChecked():
self.m_mouseAction['name'] = 'mouse move action'
self.m_mouseAction['x'] = int(self.ui.xEdit.text())
self.m_mouseAction['y'] = int(self.ui.yEdit.text())
self.m_mouseAction['absolute'] = True
elif self.ui.moveOffset.isChecked():
self.m_mouseAction['name'] = 'mouse move action'
self.m_mouseAction['x'] = int(self.ui.xOffsetEdit.text())
self.m_mouseAction['y'] = int(self.ui.yOffsetEdit.text())
self.m_mouseAction['absolute'] = False
elif self.ui.scrollUp.isChecked():
self.m_mouseAction['name'] = 'mouse scroll action'
self.m_mouseAction['delta'] = -(abs(int(self.ui.scrollUpEdit.text())))
elif self.ui.scrollDown.isChecked():
self.m_mouseAction['name'] = 'mouse scroll action'
self.m_mouseAction['delta'] = abs(int(self.ui.scrollDownEdit.text()))
super().accept()

402
mouseactioneditwnd.ui Normal file
View File

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MouseActionEditDialog</class>
<widget class="QDialog" name="MouseActionEditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>651</width>
<height>268</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="verticalSpacing">
<number>20</number>
</property>
<item row="0" column="0">
<widget class="QTabWidget" name="mouseActionTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="clickActionTab">
<attribute name="title">
<string>Click</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="topMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item row="0" column="0">
<widget class="QRadioButton" name="leftClick">
<property name="text">
<string>Click left button</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="rightClick">
<property name="text">
<string>Click right button</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QRadioButton" name="middleClick">
<property name="text">
<string>Click middle button</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="leftDclick">
<property name="text">
<string>Double-click left button</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="rightDclick">
<property name="text">
<string>Double-click right button</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QRadioButton" name="middleDclick">
<property name="text">
<string>Double-click middle button</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="leftDown">
<property name="text">
<string>left button down</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QRadioButton" name="rightDown">
<property name="text">
<string>right button down</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QRadioButton" name="middleDown">
<property name="text">
<string>middle button down</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QRadioButton" name="leftUp">
<property name="text">
<string>left button up</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QRadioButton" name="rightUp">
<property name="text">
<string>right button up</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QRadioButton" name="middleUp">
<property name="text">
<string>middle button up</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="moveActionTab">
<attribute name="title">
<string>Move</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<property name="topMargin">
<number>20</number>
</property>
<item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="moveTo">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Move to:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>X</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="xEdit"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Y</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="yEdit"/>
</item>
</layout>
</item>
<item row="2" column="0" colspan="6">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QRadioButton" name="moveOffset">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Move offset</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>X</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="xOffsetEdit"/>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>pixels</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Y</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="yOffsetEdit"/>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>pixels</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QRadioButton" name="scrollUp">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Scroll up</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="scrollUpEdit"/>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>pixels</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="scrollDown">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Scroll down</string>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="scrollDownEdit"/>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>pixels</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>20</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

27
pauseactioneditwnd.py Normal file
View File

@ -0,0 +1,27 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ui_pauseactioneditwnd import Ui_PauseActionEditDialog
import json
class PauseActionEditWnd(QDialog):
def __init__(self, p_pauseAction, p_parent = None):
super().__init__(p_parent)
self.ui = Ui_PauseActionEditDialog()
self.ui.setupUi(self)
self.ui.ok.clicked.connect(self.slotOK)
self.ui.cancel.clicked.connect(super().reject)
self.m_pauseAction = {}
if p_pauseAction == None:
return
self.ui.secondEdit.setText(str(p_pauseAction['time']))
def slotOK(self):
self.m_pauseAction['name'] = 'pause action'
self.m_pauseAction['time'] = float(self.ui.secondEdit.text())
super().accept()

93
pauseactioneditwnd.ui Normal file
View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PauseActionEditDialog</class>
<widget class="QDialog" name="PauseActionEditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>271</width>
<height>133</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Pause Action Edit Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Pause for </string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="secondEdit"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>20</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

221
profileeditwnd.py Normal file
View File

@ -0,0 +1,221 @@
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ui_profileeditwnd import Ui_ProfileEditDialog
from commandeditwnd import CommandEditWnd
import json
from profileexecutor import *
import sys
class ProfileEditWnd(QDialog):
def __init__(self, p_profile, p_parent = None):
super().__init__(p_parent)
self.m_profiles = []
self.ui = Ui_ProfileEditDialog()
self.ui.setupUi(self)
self.m_profile = {}
self.ui.cmdTable.setHorizontalHeaderLabels(('Spoken command', 'Actions'))
self.ui.cmdTable.setSelectionBehavior(QAbstractItemView.SelectRows)
self.ui.cmdTable.setSelectionMode(QAbstractItemView.SingleSelection)
self.ui.cmdTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Interactive)
self.ui.cmdTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
# define actions here
self.ui.newCmd.clicked.connect(self.slotNewCmd)
self.ui.editCmd.clicked.connect(self.slotEditCmd)
self.ui.cmdTable.doubleClicked.connect(self.slotEditCmd)
self.ui.deleteCmd.clicked.connect(self.slotDeleteCmd)
self.ui.ok.clicked.connect(self.slotOK)
self.ui.cancel.clicked.connect(self.slotCancel)
if p_profile == None or p_profile == {}:
self.ui.cmdTable.setRowCount(0)
return
self.ui.profileNameEdit.setText(p_profile['name'])
w_commands = p_profile['commands']
self.ui.cmdTable.setRowCount(len(w_commands))
i = 0
for w_command in w_commands:
self.ui.cmdTable.setItem(i, 0, QTableWidgetItem(w_command['name']))
w_text = json.dumps(w_command)
w_item = QTableWidgetItem(w_text)
w_item.setData(Qt.UserRole, json.dumps(w_command))
self.ui.cmdTable.setItem(i, 1, w_item)
i = i + 1
QTimer.singleShot(100, self.ui.cmdTable.resizeRowsToContents)
# def activateProfile(self, p_profile):
# if p_profile == None or p_profile == {}:
# self.ui.cmdTable.setRowCount(0)
# return
#
# w_commands = p_profile['commands']
# self.ui.cmdTable.setRowCount(len(w_commands))
# i = 0
# for w_command in w_commands:
# self.ui.cmdTable.setItem(i, 0, QTableWidgetItem(w_command['name']))
# w_text = json.dumps(w_command)
# w_item = QTableWidgetItem(w_text)
# w_item.setData(Qt.UserRole, json.dumps(w_command))
# self.ui.cmdTable.setItem(i, 1, w_item)
# i = i + 1
# self.ui.cmdTable.resizeRowsToContents()
# def updateActiveProfile(self):
# w_idx = self.m_curProfileIdx
# if w_idx < 0:
# return
#
# w_profile = {}
# w_profile['name'] = self.ui.profileCbx.itemText(w_idx)
# w_commands = []
#
# w_commandCnt = self.ui.cmdTable.rowCount()
# for w_i in range(w_commandCnt):
# w_jsonCommand = self.ui.cmdTable.item(w_i, 1).data(Qt.UserRole)
# w_command = json.loads(w_jsonCommand)
# w_commands.append(w_command)
# w_profile['commands'] = w_commands
# w_jsonProfile = json.dumps(w_profile)
# self.ui.profileCbx.setItemData(w_idx, w_jsonProfile)
# def slotSelChanged(self, p_idx):
# self.updateActiveProfile()
# w_jsonProfile = self.ui.profileCbx.itemData(p_idx)
#
# w_profile = {}
# if w_jsonProfile != None:
# w_profile = json.loads(w_jsonProfile)
#
# self.activateProfile(w_profile)
#
# self.m_curProfileIdx = p_idx
# def importProfile(self, p_profile, p_update = True):
# if p_profile == None or p_profile == {}:
# return
#
# w_profileCnt = self.ui.profileCbx.count()
# for w_idx in range(w_profileCnt):
# w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
# if w_jsonProfile == None:
# continue
#
# w_profile = json.loads(w_jsonProfile)
# if p_profile['name'] == w_profile['name']:
# if p_update:
# w_jsonProfile = json.dumps(p_profile)
# self.ui.profileCbx.setItemData(w_idx, w_jsonProfile)
# return True
# return False
#
# self.ui.profileCbx.addItem(p_profile['name'])
# w_jsonProfile = json.dumps(p_profile)
# self.ui.profileCbx.setItemData(w_profileCnt, w_jsonProfile)
# return True
# def slotAddNewProfile(self):
# text, okPressed = QInputDialog.getText(self, "Get Profile Name", "Profile name:", QLineEdit.Normal, "")
# if okPressed and text != '':
# w_profile = {}
# w_profile['name'] = text
# w_profile['commands'] = []
# if self.importProfile(w_profile, False) == False:
# QMessageBox.critical(None, 'Error', 'Adding a new profile was failed')
# return
# self.ui.profileCbx.setCurrentIndex(self.ui.profileCbx.count()-1)
# def slotRemoveProfile(self):
# w_curIdx = self.ui.profileCbx.currentIndex()
# if w_curIdx >= 0:
# self.ui.cmdTable.setRowCount(0)
# self.m_curProfileIdx = -1
# self.ui.profileCbx.removeItem(w_curIdx)
#
# def slotRenameProfile(self):
# w_curIdx = self.ui.profileCbx.currentIndex()
# if w_curIdx >= 0:
# text, okPressed = QInputDialog.getText(self, "Input Dialog", "Profile name:",
# QLineEdit.Normal, self.ui.profileCbx.itemText(w_curIdx))
# if okPressed and text != '':
# self.ui.profileCbx.setItemText(w_curIdx, text)
# w_jsonProfile = self.ui.profileCbx.itemData(w_curIdx)
# w_profile = json.loads(w_jsonProfile)
# w_profile['name'] = text
# w_jsonProfile = json.dumps(w_profile)
# self.ui.profileCbx.setItemData(w_curIdx, w_jsonProfile)
def importCommand(self, p_command, p_update):
w_commandCnt = self.ui.cmdTable.rowCount()
for w_i in range(w_commandCnt):
w_jsonCommand = self.ui.cmdTable.item(w_i, 1).data(Qt.UserRole)
w_command = json.loads(w_jsonCommand)
if w_command['name'] == p_command['name']:
if p_update:
w_text = json.dumps(p_command)
w_item = QTableWidgetItem(w_text)
w_item.setData(Qt.UserRole, json.dumps(p_command))
self.ui.cmdTable.setItem(w_i, 1, w_item)
self.ui.cmdTable.resizeRowsToContents()
return True
return False
w_rowCnt = self.ui.cmdTable.rowCount()
self.ui.cmdTable.setRowCount(w_rowCnt + 1)
self.ui.cmdTable.setItem(w_rowCnt, 0, QTableWidgetItem(p_command['name']))
w_text = json.dumps(p_command)
w_item = QTableWidgetItem(w_text)
w_item.setData(Qt.UserRole, json.dumps(p_command))
self.ui.cmdTable.setItem(w_rowCnt, 1, w_item)
self.ui.cmdTable.resizeRowsToContents()
def slotNewCmd(self):
w_cmdEditWnd = CommandEditWnd(None, self)
if w_cmdEditWnd.exec_() == QDialog.Accepted:
if self.importCommand(w_cmdEditWnd.m_command, False) == False:
QMessageBox.critical(None, 'Error', 'Adding a new command was failed')
return
self.ui.cmdTable.selectRow(self.ui.cmdTable.rowCount() - 1)
self.ui.cmdTable.setFocus()
def slotEditCmd(self):
w_modelIdxs = self.ui.cmdTable.selectionModel().selectedRows()
if len(w_modelIdxs) == 0:
return
w_modelIdx = w_modelIdxs[0]
w_jsonCommand = self.ui.cmdTable.item(w_modelIdx.row(), 1).data(Qt.UserRole)
w_command = json.loads(w_jsonCommand)
w_cmdEditWnd = CommandEditWnd(w_command, self)
if w_cmdEditWnd.exec_() == QDialog.Accepted:
self.importCommand(w_cmdEditWnd.m_command, True)
self.ui.cmdTable.resizeRowsToContents()
def slotDeleteCmd(self):
w_modelIdxs = self.ui.cmdTable.selectionModel().selectedRows()
i = 0
for w_modelIdx in sorted(w_modelIdxs):
self.ui.cmdTable.removeRow(w_modelIdx.row() - i)
i = i + 1
self.ui.cmdTable.setFocus()
def slotOK(self):
self.m_profile = {}
self.m_profile['name'] = self.ui.profileNameEdit.text()
w_commands = []
w_commandCnt = self.ui.cmdTable.rowCount()
for w_i in range(w_commandCnt):
w_jsonCommand = self.ui.cmdTable.item(w_i, 1).data(Qt.UserRole)
w_command = json.loads(w_jsonCommand)
w_commands.append(w_command)
self.m_profile['commands'] = w_commands
super().accept()
def slotCancel(self):
super().reject()

219
profileeditwnd.ui Normal file
View File

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProfileEditDialog</class>
<widget class="QDialog" name="ProfileEditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1191</width>
<height>591</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Profile:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="profileNameEdit"/>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Commands</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTableWidget" name="cmdTable">
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>160</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>160</number>
</attribute>
<column/>
<column/>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>20</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="newCmd">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editCmd">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteCmd">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>20</number>
</property>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

318
profileexecutor.py Normal file
View File

@ -0,0 +1,318 @@
import keyboard
from pynput.mouse import Button, Controller
import time
import threading
import copy
import json
class ProfileExecutor(threading.Thread):
mouse = Controller()
def __init__(self, p_profile = None):
threading.Thread.__init__(self)
self.m_profile = p_profile
self.m_stop = False
self.m_listening = True
self.m_cmdThreads = {}
def setProfile(self, p_profile):
self.m_profile = p_profile
def setEnableListening(self, p_enable):
self.m_listening = p_enable
def run(self):
while self.m_stop != True:
keyboard.wait('ctrl+alt')
if self.m_listening != True:
continue
for i in range(3):
self.doCommand('up')
time.sleep(0.5)
self.doCommand('left')
time.sleep(0.5)
self.doCommand('right')
time.sleep(0.5)
self.doCommand('stop')
def stop(self):
self.m_stop = True
def doAction(self, p_action):
# {'name': 'key action', 'key': 'left', 'type': 0}
# {'name': 'pause action', 'time': 0.03}
# {'name': 'command stop action', 'command name': 'down'}
# {'name': 'mouse move action', 'x':5, 'y':0, 'absolute': False}
# {'name': 'mouse click action', 'button': 'left', 'type': 0}
# {'name': 'mouse wheel action', 'delta':10}
w_actionName = p_action['name']
if w_actionName == 'key action':
w_key = p_action['key']
w_type = p_action['type']
if w_type == 1:
keyboard.press(w_key)
print("pressed key: ", w_key)
elif w_type == 0:
keyboard.release(w_key)
print("released key: ", w_key)
elif w_type == 10:
keyboard.press(w_key)
keyboard.release(w_key)
print("pressed and released key: ", w_key)
elif w_actionName == 'pause action':
time.sleep(p_action['time'])
elif w_actionName == 'command stop action':
self.stopCommand(p_action['command name'])
elif w_actionName == 'mouse move action':
if p_action['absolute']:
ProfileExecutor.mouse.position([p_action['x'], p_action['y']])
else:
ProfileExecutor.mouse.move(p_action['x'], p_action['y'])
elif w_actionName == 'mouse click action':
w_type = p_action['type']
w_button = p_action['button']
if w_type == 1:
if w_button == 'left':
ProfileExecutor.mouse.press(Button.left)
elif w_button == 'middle':
ProfileExecutor.mouse.press(Button.middle)
elif w_button == 'right':
ProfileExecutor.mouse.press(Button.right)
print("pressed mouse button: ", w_button)
elif w_type == 0:
if w_button == 'left':
ProfileExecutor.mouse.release(Button.left)
elif w_button == 'middle':
ProfileExecutor.mouse.release(Button.middle)
elif w_button == 'right':
ProfileExecutor.mouse.release(Button.right)
print("released mouse button: ", w_button)
elif w_type == 10:
if w_button == 'left':
ProfileExecutor.mouse.click(Button.left)
elif w_button == 'middle':
ProfileExecutor.mouse.click(Button.middle)
elif w_button == 'right':
ProfileExecutor.mouse.click(Button.right)
print("pressed and released mouse button: ", w_button)
elif w_actionName == 'mouse scroll action':
ProfileExecutor.mouse.scroll(0, p_action['delta'])
class CommandThread(threading.Thread):
def __init__(self, p_ProfileExecutor, p_actions, p_repeat):
threading.Thread.__init__(self)
self.ProfileExecutor = p_ProfileExecutor
self.m_actions = p_actions
self.m_repeat = p_repeat
self.m_stop = False
def run(self):
w_repeat = self.m_repeat
while self.m_stop != True:
for w_action in self.m_actions:
self.ProfileExecutor.doAction(w_action)
w_repeat = w_repeat - 1
if w_repeat == 0:
break
def stop(self):
self.m_stop = True
threading.Thread.join(self)
def doCommand(self, p_cmdName):
if self.m_profile == None:
return
w_commands = self.m_profile['commands']
flag = False
for w_command in w_commands:
if w_command['name'] == p_cmdName:
flag = True
break
if flag == False:
return
w_actions = w_command['actions']
w_async = w_command['async']
if w_async == False:
w_repeat = w_command['repeat']
if w_repeat < 1:
w_repeat = 1
while True:
for w_action in w_command['actions']:
self.doAction(w_action)
w_repeat = w_repeat - 1
if w_repeat == 0:
break
else:
w_cmdThread = ProfileExecutor.CommandThread(self, w_actions, w_command['repeat'])
w_cmdThread.start()
self.m_cmdThreads[p_cmdName] = w_cmdThread
def stopCommand(self, p_cmdName):
if p_cmdName in self.m_cmdThreads.keys():
self.m_cmdThreads[p_cmdName].stop()
del self.m_cmdThreads[p_cmdName]
# def carGameTest():
# w_profileDict = {
# "name": "car game",
# "commands": [
# {'name': 'up',
# 'actions': [
# {'name': 'key action', 'key': 'up', 'type': 1},
# {'name': 'pause action', 'time': 0.03}
# ],
# 'repeat': 1,
# 'async': False
# },
# {'name': 'left',
# 'actions': [{'name': 'key action', 'key': 'right', 'type': 0},
# {'name': 'pause action', 'time': 0.03},
# {'name': 'key action', 'key': 'left', 'type': 1},
# {'name': 'pause action', 'time': 0.03}
# ],
# 'repeat': 1,
# 'async': False
# },
# {'name': 'right',
# 'actions': [{'name': 'key action', 'key': 'left', 'type': 0},
# {'name': 'pause action', 'time': 0.03},
# {'name': 'key action', 'key': 'right', 'type': 1},
# {'name': 'pause action', 'time': 0.03}
# ],
# 'repeat': 1,
# 'async': False
# },
# {'name': 'stop',
# 'actions': [
# {'name': 'key action', 'key': 'left', 'type': 0},
# {'name': 'pause action', 'time': 0.03},
# {'name': 'key action', 'key': 'right', 'type': 0},
# {'name': 'pause action', 'time': 0.03},
# {'name': 'key action', 'key': 'up', 'type': 0},
# {'name': 'pause action', 'time': 0.03}
# ],
# 'repeat': 1,
# 'async': False
# }
# ]
# }
#
# w_ProfileExecutor = ProfileExecutor(w_profileDict)
#
# print("Move to the target window and press spacebar")
# keyboard.wait('space')
#
# print("Started !")
#
# for i in range(5):
# w_ProfileExecutor.doCommand('up')
# time.sleep(0.5)
#
# w_ProfileExecutor.doCommand('left')
# time.sleep(0.5)
#
# w_ProfileExecutor.doCommand('right')
# time.sleep(0.5)
#
# w_ProfileExecutor.doCommand('stop')
#
# def airplaneGameTest():
# w_profileDict = {
# "name": "airplane game",
# "commands": [
# {'name': 'up',
# 'actions': [
# {'name': 'command stop action', 'command name': 'down'},
# {'name': 'mouse move action', 'x':0, 'y':-5, 'absolute': False},
# {'name': 'pause action', 'time': 0.01}
# ],
# 'repeat': -1,
# 'async': True
# },
# {'name': 'left',
# 'actions': [
# {'name': 'command stop action', 'command name':'right'},
# {'name': 'mouse move action', 'x':-5, 'y':0, 'absolute': False},
# {'name': 'pause action', 'time': 0.005}
# ],
# 'repeat': -1,
# 'async': True
# },
# {'name': 'right',
# 'actions': [
# {'name': 'command stop action', 'command name': 'left'},
# {'name': 'mouse move action', 'x':5, 'y':0, 'absolute': False},
# {'name': 'pause action', 'time': 0.005}
# ],
# 'repeat': -1,
# 'async': True
# },
# {'name': 'down',
# 'actions': [
# {'name': 'command stop action', 'command name': 'up'},
# {'name': 'mouse move action', 'x':0, 'y':5, 'absolute': False},
# {'name': 'pause action', 'time': 0.005}
# ],
# 'repeat': -1,
# 'async': True
# },
# {'name': 'shoot',
# 'actions': [
# {'name': 'mouse click action', 'button':'left', 'type': 1},
# {'name': 'pause action', 'time': 0.03}
# ],
# 'repeat': 1,
# 'async': False
# },
# {'name': 'stop',
# 'actions': [
# {'name': 'command stop action', 'command name': 'up'},
# {'name': 'command stop action', 'command name': 'left'},
# {'name': 'command stop action', 'command name': 'right'},
# {'name': 'command stop action', 'command name': 'down'},
# {'name': 'mouse click action', 'button': 'left', 'type': 0}
# ],
# 'repeat': 1,
# 'async': False
# }
# ]
# }
#
# w_ProfileExecutor = ProfileExecutor(w_profileDict)
#
# print("Move to the target window and press the spacebar to start")
# keyboard.wait('space')
#
# time.sleep(1)
#
# print("Started !")
#
# for i in range(3):
# w_ProfileExecutor.doCommand('shoot')
# time.sleep(1)
#
# w_ProfileExecutor.doCommand('up')
# time.sleep(1)
#
# w_ProfileExecutor.doCommand('down')
# time.sleep(0.5)
#
# w_ProfileExecutor.stopCommand('down')
#
# w_ProfileExecutor.doCommand('left')
# time.sleep(0.5)
#
# w_ProfileExecutor.doCommand('right')
# time.sleep(0.5)
#
# w_ProfileExecutor.doCommand('stop')
#
# if __name__ == "__main__":
# carGameTest()
# # airplaneGameTest()

BIN
profiles.dat Normal file

Binary file not shown.

41
pynput/__init__.py Normal file
View File

@ -0,0 +1,41 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The main *pynput* module.
This module imports ``keyboard`` and ``mouse``.
"""
def _logger(cls):
"""Creates a logger with a name suitable for a specific class.
This function takes into account that implementations for classes reside in
platform dependent modules, and thus removes the final part of the module
name.
:param type cls: The class for which to create a logger.
:return: a logger
"""
import logging
return logging.getLogger('{}.{}'.format(
'.'.join(cls.__module__.split('.', 2)[:2]),
cls.__name__))
# from . import keyboard
from . import mouse

Binary file not shown.

19
pynput/_info.py Normal file
View File

@ -0,0 +1,19 @@
# coding=utf-8
# pystray
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
__author__ = u'Moses Palmér'
__version__ = (1, 4, 2)

296
pynput/_util/__init__.py Normal file
View File

@ -0,0 +1,296 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
General utility functions and classes.
"""
# pylint: disable=R0903
# We implement minimal mixins
# pylint: disable=W0212
# We implement an internal API
import contextlib
import functools
import sys
import threading
import six
from six.moves import queue
class AbstractListener(threading.Thread):
"""A class implementing the basic behaviour for event listeners.
Instances of this class can be used as context managers. This is equivalent
to the following code::
listener.start()
listener.wait()
try:
with_statements()
finally:
listener.stop()
Actual implementations of this class must set the attribute ``_log``, which
must be an instance of :class:`logging.Logger`.
:param bool suppress: Whether to suppress events. Setting this to ``True``
will prevent the input events from being passed to the rest of the
system.
:param kwargs: A mapping from callback attribute to callback handler. All
handlers will be wrapped in a function reading the return value of the
callback, and if it ``is False``, raising :class:`StopException`.
Any callback that is falsy will be ignored.
"""
class StopException(Exception):
"""If an event listener callback raises this exception, the current
listener is stopped.
"""
pass
#: Exceptions that are handled outside of the emitter and should thus not be
#: passed through the queue
_HANDLED_EXCEPTIONS = tuple()
def __init__(self, suppress=False, **kwargs):
super(AbstractListener, self).__init__()
def wrapper(f):
def inner(*args):
if f(*args) is False:
raise self.StopException()
return inner
self._suppress = suppress
self._running = False
self._thread = threading.current_thread()
self._condition = threading.Condition()
self._ready = False
# Allow multiple calls to stop
self._queue = queue.Queue(10)
self.daemon = True
for name, callback in kwargs.items():
setattr(self, name, wrapper(callback or (lambda *a: None)))
@property
def suppress(self):
"""Whether to suppress events.
"""
return self._suppress
@property
def running(self):
"""Whether the listener is currently running.
"""
return self._running
def stop(self):
"""Stops listening for events.
When this method returns, no more events will be delivered.
"""
if self._running:
self._running = False
self._queue.put(None)
self._stop_platform()
def __enter__(self):
self.start()
self.wait()
return self
def __exit__(self, exc_type, value, traceback):
self.stop()
def wait(self):
"""Waits for this listener to become ready.
"""
self._condition.acquire()
while not self._ready:
self._condition.wait()
self._condition.release()
def run(self):
"""The thread runner method.
"""
self._running = True
self._thread = threading.current_thread()
self._run()
# Make sure that the queue contains something
self._queue.put(None)
@classmethod
def _emitter(cls, f):
"""A decorator to mark a method as the one emitting the callbacks.
This decorator will wrap the method and catch exception. If a
:class:`StopException` is caught, the listener will be stopped
gracefully. If any other exception is caught, it will be propagated to
the thread calling :meth:`join` and reraised there.
"""
@functools.wraps(f)
def inner(self, *args, **kwargs):
# pylint: disable=W0702; we want to catch all exception
try:
return f(self, *args, **kwargs)
except Exception as e:
if not isinstance(e, self._HANDLED_EXCEPTIONS):
if not isinstance(e, AbstractListener.StopException):
self._log.exception(
'Unhandled exception in listener callback')
self._queue.put(
None if isinstance(e, cls.StopException)
else sys.exc_info())
self.stop()
raise
# pylint: enable=W0702
return inner
def _mark_ready(self):
"""Marks this listener as ready to receive events.
This method must be called from :meth:`_run`. :meth:`wait` will block
until this method is called.
"""
self._condition.acquire()
self._ready = True
self._condition.notify()
self._condition.release()
def _run(self):
"""The implementation of the :meth:`run` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _stop_platform(self):
"""The implementation of the :meth:`stop` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def join(self, *args):
super(AbstractListener, self).join(*args)
# Reraise any exceptions
try:
exc_type, exc_value, exc_traceback = self._queue.get()
except TypeError:
return
six.reraise(exc_type, exc_value, exc_traceback)
class NotifierMixin(object):
"""A mixin for notifiers of fake events.
This mixin can be used for controllers on platforms where sending fake
events does not cause a listener to receive a notification.
"""
def _emit(self, action, *args):
"""Sends a notification to all registered listeners.
This method will ensure that listeners that raise
:class:`StopException` are stopped.
:param str action: The name of the notification.
:param args: The arguments to pass.
"""
stopped = []
for listener in self._listeners():
try:
getattr(listener, action)(*args)
except listener.StopException:
stopped.append(listener)
for listener in stopped:
listener.stop()
@classmethod
def _receiver(cls, listener_class):
"""A decorator to make a class able to receive fake events from a
controller.
This decorator will add the method ``_receive`` to the decorated class.
This method is a context manager which ensures that all calls to
:meth:`_emit` will invoke the named method in the listener instance
while the block is active.
"""
@contextlib.contextmanager
def receive(self):
"""Executes a code block with this listener instance registered as
a receiver of fake input events.
"""
self._controller_class._add_listener(self)
try:
yield
finally:
self._controller_class._remove_listener(self)
listener_class._receive = receive
listener_class._controller_class = cls
# Make sure this class has the necessary attributes
if not hasattr(cls, '_listener_cache'):
cls._listener_cache = set()
cls._listener_lock = threading.Lock()
return listener_class
@classmethod
def _listeners(cls):
"""Iterates over the set of running listeners.
This method will quit without acquiring the lock if the set is empty,
so there is potential for race conditions. This is an optimisation,
since :class:`Controller` will need to call this method for every
control event.
"""
if not cls._listener_cache:
return
with cls._listener_lock:
for listener in cls._listener_cache:
yield listener
@classmethod
def _add_listener(cls, listener):
"""Adds a listener to the set of running listeners.
:param listener: The listener for fake events.
"""
with cls._listener_lock:
cls._listener_cache.add(listener)
@classmethod
def _remove_listener(cls, listener):
"""Removes this listener from the set of running listeners.
:param listener: The listener for fake events.
"""
with cls._listener_lock:
cls._listener_cache.remove(listener)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

273
pynput/_util/darwin.py Normal file
View File

@ -0,0 +1,273 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Utility functions and classes for the *Darwin* backend.
"""
# pylint: disable=C0103
# pylint: disable=R0903
# This module contains wrapper classes
import contextlib
import ctypes
import ctypes.util
import six
import objc
import CoreFoundation
import Quartz
from . import AbstractListener
#: The objc module as a library handle
OBJC = ctypes.PyDLL(objc._objc.__file__)
OBJC.PyObjCObject_New.restype = ctypes.py_object
OBJC.PyObjCObject_New.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
def _wrap_value(value):
"""Converts a pointer to a *Python objc* value.
:param value: The pointer to convert.
:return: a wrapped value
"""
return OBJC.PyObjCObject_New(value, 0, 1)
@contextlib.contextmanager
def _wrapped(value):
"""A context manager that converts a raw pointer to a *Python objc* value.
When the block is exited, the value is released.
:param value: The raw value to wrap.
"""
wrapped_value = _wrap_value(value)
try:
yield value
finally:
CoreFoundation.CFRelease(wrapped_value)
class CarbonExtra(object):
"""A class exposing some missing functionality from *Carbon* as class
attributes.
"""
_Carbon = ctypes.cdll.LoadLibrary(ctypes.util.find_library('Carbon'))
_Carbon.TISCopyCurrentKeyboardInputSource.argtypes = []
_Carbon.TISCopyCurrentKeyboardInputSource.restype = ctypes.c_void_p
_Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource.argtypes = []
_Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource.restype = \
ctypes.c_void_p
_Carbon.TISGetInputSourceProperty.argtypes = [
ctypes.c_void_p, ctypes.c_void_p]
_Carbon.TISGetInputSourceProperty.restype = ctypes.c_void_p
_Carbon.LMGetKbdType.argtypes = []
_Carbon.LMGetKbdType.restype = ctypes.c_uint32
_Carbon.UCKeyTranslate.argtypes = [
ctypes.c_void_p,
ctypes.c_uint16,
ctypes.c_uint16,
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
ctypes.c_uint8,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_uint16 * 4]
_Carbon.UCKeyTranslate.restype = ctypes.c_uint32
TISCopyCurrentKeyboardInputSource = \
_Carbon.TISCopyCurrentKeyboardInputSource
TISCopyCurrentASCIICapableKeyboardLayoutInputSource = \
_Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource
kTISPropertyUnicodeKeyLayoutData = ctypes.c_void_p.in_dll(
_Carbon, 'kTISPropertyUnicodeKeyLayoutData')
TISGetInputSourceProperty = \
_Carbon.TISGetInputSourceProperty
LMGetKbdType = \
_Carbon.LMGetKbdType
kUCKeyActionDisplay = 3
kUCKeyTranslateNoDeadKeysBit = 0
UCKeyTranslate = \
_Carbon.UCKeyTranslate
@contextlib.contextmanager
def keycode_context():
"""Returns an opaque value representing a context for translating keycodes
to strings.
"""
keyboard_type, layout_data = None, None
for source in [
CarbonExtra.TISCopyCurrentKeyboardInputSource,
CarbonExtra.TISCopyCurrentASCIICapableKeyboardLayoutInputSource]:
with _wrapped(source()) as keyboard:
keyboard_type = CarbonExtra.LMGetKbdType()
layout = _wrap_value(CarbonExtra.TISGetInputSourceProperty(
keyboard,
CarbonExtra.kTISPropertyUnicodeKeyLayoutData))
layout_data = layout.bytes().tobytes() if layout else None
if keyboard is not None and layout_data is not None:
break
yield (keyboard_type, layout_data)
def keycode_to_string(context, keycode, modifier_state=0):
"""Converts a keycode to a string.
"""
LENGTH = 4
keyboard_type, layout_data = context
dead_key_state = ctypes.c_uint32()
length = ctypes.c_uint8()
unicode_string = (ctypes.c_uint16 * LENGTH)()
CarbonExtra.UCKeyTranslate(
layout_data,
keycode,
CarbonExtra.kUCKeyActionDisplay,
modifier_state,
keyboard_type,
CarbonExtra.kUCKeyTranslateNoDeadKeysBit,
ctypes.byref(dead_key_state),
LENGTH,
ctypes.byref(length),
unicode_string)
return u''.join(
six.unichr(unicode_string[i])
for i in range(length.value))
def get_unicode_to_keycode_map():
"""Returns a mapping from unicode strings to virtual key codes.
:return: a dict mapping key codes to strings
"""
with keycode_context() as context:
return {
keycode_to_string(context, keycode): keycode
for keycode in range(128)}
class ListenerMixin(object):
"""A mixin for *Quartz* event listeners.
Subclasses should set a value for :attr:`_EVENTS` and implement
:meth:`_handle`.
"""
#: The events that we listen to
_EVENTS = tuple()
def _run(self):
self._loop = None
try:
tap = self._create_event_tap()
if tap is None:
self._mark_ready()
return
loop_source = Quartz.CFMachPortCreateRunLoopSource(
None, tap, 0)
self._loop = Quartz.CFRunLoopGetCurrent()
Quartz.CFRunLoopAddSource(
self._loop, loop_source, Quartz.kCFRunLoopDefaultMode)
Quartz.CGEventTapEnable(tap, True)
self._mark_ready()
# pylint: disable=W0702; we want to silence errors
try:
while self.running:
result = Quartz.CFRunLoopRunInMode(
Quartz.kCFRunLoopDefaultMode, 1, False)
try:
if result != Quartz.kCFRunLoopRunTimedOut:
break
except AttributeError:
# This happens during teardown of the virtual machine
break
except:
# This exception will have been passed to the main thread
pass
# pylint: enable=W0702
finally:
self._loop = None
def _stop_platform(self):
# The base class sets the running flag to False; this will cause the
# loop around run loop invocations to terminate and set this event
try:
if self._loop is not None:
Quartz.CFRunLoopStop(self._loop)
except AttributeError:
# The loop may not have been created
pass
def _create_event_tap(self):
"""Creates the event tap used by the listener.
:return: an event tap
"""
return Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionListenOnly if (True
and not self.suppress
and self._intercept is None)
else Quartz.kCGEventTapOptionDefault,
self._EVENTS,
self._handler,
None)
@AbstractListener._emitter
def _handler(self, proxy, event_type, event, refcon):
"""The callback registered with *Mac OSX* for mouse events.
This method will call the callbacks registered on initialisation.
"""
self._handle(proxy, event_type, event, refcon)
if self._intercept is not None:
return self._intercept(event_type, event)
elif self.suppress:
return None
def _handle(self, proxy, event_type, event, refcon):
"""The device specific callback handler.
This method calls the appropriate callback registered when this
listener was created based on the event.
"""
raise NotImplementedError()

641
pynput/_util/win32.py Normal file
View File

@ -0,0 +1,641 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Utility functions and classes for the *win32* backend.
"""
# pylint: disable=C0103
# We want to make it obvious how structs are related
# pylint: disable=R0903
# This module contains a number of structs
import contextlib
import ctypes
import threading
from ctypes import (
windll,
wintypes)
import six
from . import AbstractListener
# LPDWORD is not in ctypes.wintypes on Python 2
if not hasattr(wintypes, 'LPDWORD'):
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
class MOUSEINPUT(ctypes.Structure):
"""Contains information about a simulated mouse event.
"""
MOVE = 0x0001
LEFTDOWN = 0x0002
LEFTUP = 0x0004
RIGHTDOWN = 0x0008
RIGHTUP = 0x0010
MIDDLEDOWN = 0x0020
MIDDLEUP = 0x0040
XDOWN = 0x0080
XUP = 0x0100
WHEEL = 0x0800
HWHEEL = 0x1000
ABSOLUTE = 0x8000
XBUTTON1 = 0x0001
XBUTTON2 = 0x0002
_fields_ = [
('dx', wintypes.LONG),
('dy', wintypes.LONG),
('mouseData', wintypes.DWORD),
('dwFlags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
class KEYBDINPUT(ctypes.Structure):
"""Contains information about a simulated keyboard event.
"""
EXTENDEDKEY = 0x0001
KEYUP = 0x0002
SCANCODE = 0x0008
UNICODE = 0x0004
_fields_ = [
('wVk', wintypes.WORD),
('wScan', wintypes.WORD),
('dwFlags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
class HARDWAREINPUT(ctypes.Structure):
"""Contains information about a simulated message generated by an input
device other than a keyboard or mouse.
"""
_fields_ = [
('uMsg', wintypes.DWORD),
('wParamL', wintypes.WORD),
('wParamH', wintypes.WORD)]
class INPUT_union(ctypes.Union):
"""Represents the union of input types in :class:`INPUT`.
"""
_fields_ = [
('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT)]
class INPUT(ctypes.Structure):
"""Used by :attr:`SendInput` to store information for synthesizing input
events such as keystrokes, mouse movement, and mouse clicks.
"""
MOUSE = 0
KEYBOARD = 1
HARDWARE = 2
_fields_ = [
('type', wintypes.DWORD),
('value', INPUT_union)]
LPINPUT = ctypes.POINTER(INPUT)
VkKeyScan = windll.user32.VkKeyScanW
VkKeyScan.argtypes = (
wintypes.WCHAR,)
SendInput = windll.user32.SendInput
SendInput.argtypes = (
wintypes.UINT,
LPINPUT,
ctypes.c_int)
GetCurrentThreadId = windll.kernel32.GetCurrentThreadId
GetCurrentThreadId.restype = wintypes.DWORD
class MessageLoop(object):
"""A class representing a message loop.
"""
#: The message that signals this loop to terminate
WM_STOP = 0x0401
_LPMSG = ctypes.POINTER(wintypes.MSG)
_GetMessage = windll.user32.GetMessageW
_GetMessage.argtypes = (
_LPMSG,
wintypes.HWND,
wintypes.UINT,
wintypes.UINT)
_PeekMessage = windll.user32.PeekMessageW
_PeekMessage.argtypes = (
_LPMSG,
wintypes.HWND,
wintypes.UINT,
wintypes.UINT,
wintypes.UINT)
_PostThreadMessage = windll.user32.PostThreadMessageW
_PostThreadMessage.argtypes = (
wintypes.DWORD,
wintypes.UINT,
wintypes.WPARAM,
wintypes.LPARAM)
PM_NOREMOVE = 0
def __init__(self):
self._threadid = None
self._event = threading.Event()
self.thread = None
def __iter__(self):
"""Initialises the message loop and yields all messages until
:meth:`stop` is called.
:raises AssertionError: if :meth:`start` has not been called
"""
assert self._threadid is not None
try:
# Pump messages until WM_STOP
while True:
msg = wintypes.MSG()
lpmsg = ctypes.byref(msg)
r = self._GetMessage(lpmsg, None, 0, 0)
if r <= 0 or msg.message == self.WM_STOP:
break
else:
yield msg
finally:
self._threadid = None
self.thread = None
def start(self):
"""Starts the message loop.
This method must be called before iterating over messages, and it must
be called from the same thread.
"""
self._threadid = GetCurrentThreadId()
self.thread = threading.current_thread()
# Create the message loop
msg = wintypes.MSG()
lpmsg = ctypes.byref(msg)
self._PeekMessage(lpmsg, None, 0x0400, 0x0400, self.PM_NOREMOVE)
# Set the event to signal to other threads that the loop is created
self._event.set()
def stop(self):
"""Stops the message loop.
"""
self._event.wait()
if self._threadid:
self.post(self.WM_STOP, 0, 0)
def post(self, msg, wparam, lparam):
"""Posts a message to this message loop.
:param ctypes.wintypes.UINT msg: The message.
:param ctypes.wintypes.WPARAM wparam: The value of ``wParam``.
:param ctypes.wintypes.LPARAM lparam: The value of ``lParam``.
"""
self._PostThreadMessage(self._threadid, msg, wparam, lparam)
class SystemHook(object):
"""A class to handle Windows hooks.
"""
#: The hook action value for actions we should check
HC_ACTION = 0
_HOOKPROC = ctypes.WINFUNCTYPE(
wintypes.LPARAM,
ctypes.c_int32, wintypes.WPARAM, wintypes.LPARAM)
_SetWindowsHookEx = windll.user32.SetWindowsHookExW
_SetWindowsHookEx.argtypes = (
ctypes.c_int,
_HOOKPROC,
wintypes.HINSTANCE,
wintypes.DWORD)
_UnhookWindowsHookEx = windll.user32.UnhookWindowsHookEx
_UnhookWindowsHookEx.argtypes = (
wintypes.HHOOK,)
_CallNextHookEx = windll.user32.CallNextHookEx
_CallNextHookEx.argtypes = (
wintypes.HHOOK,
ctypes.c_int,
wintypes.WPARAM,
wintypes.LPARAM)
#: The registered hook procedures
_HOOKS = {}
class SuppressException(Exception):
"""An exception raised by a hook callback to suppress further
propagation of events.
"""
pass
def __init__(self, hook_id, on_hook=lambda code, msg, lpdata: None):
self.hook_id = hook_id
self.on_hook = on_hook
self._hook = None
def __enter__(self):
key = threading.current_thread().ident
assert key not in self._HOOKS
# Add ourself to lookup table and install the hook
self._HOOKS[key] = self
self._hook = self._SetWindowsHookEx(
self.hook_id,
self._handler,
None,
0)
return self
def __exit__(self, exc_type, value, traceback):
key = threading.current_thread().ident
assert key in self._HOOKS
if self._hook is not None:
# Uninstall the hook and remove ourself from lookup table
self._UnhookWindowsHookEx(self._hook)
del self._HOOKS[key]
@staticmethod
@_HOOKPROC
def _handler(code, msg, lpdata):
key = threading.current_thread().ident
self = SystemHook._HOOKS.get(key, None)
if self:
# pylint: disable=W0702; we want to silence errors
try:
self.on_hook(code, msg, lpdata)
except self.SuppressException:
# Return non-zero to stop event propagation
return 1
except:
# Ignore any errors
pass
# pylint: enable=W0702
return SystemHook._CallNextHookEx(0, code, msg, lpdata)
class ListenerMixin(object):
"""A mixin for *win32* event listeners.
Subclasses should set a value for :attr:`_EVENTS` and implement
:meth:`_handle`.
Subclasses must also be decorated with a decorator compatible with
:meth:`pynput._util.NotifierMixin._receiver` or implement the method
``_receive()``.
"""
#: The Windows hook ID for the events to capture
_EVENTS = None
#: The window message used to signal that an even should be handled
_WM_PROCESS = 0x410
def suppress_event(self):
"""Causes the currently filtered event to be suppressed.
This has a system wide effect and will generally result in no
applications receiving the event.
This method will raise an undefined exception.
"""
raise SystemHook.SuppressException()
def _run(self):
self._message_loop = MessageLoop()
with self._receive():
self._mark_ready()
self._message_loop.start()
# pylint: disable=W0702; we want to silence errors
try:
with SystemHook(self._EVENTS, self._handler):
# Just pump messages
for msg in self._message_loop:
if not self.running:
break
if msg.message == self._WM_PROCESS:
self._process(msg.wParam, msg.lParam)
except:
# This exception will have been passed to the main thread
pass
# pylint: enable=W0702
def _stop_platform(self):
try:
self._message_loop.stop()
except AttributeError:
# The loop may not have been created
pass
@AbstractListener._emitter
def _handler(self, code, msg, lpdata):
"""The callback registered with *Windows* for events.
This method will post the message :attr:`_WM_HANDLE` to the message loop
started with this listener using :meth:`MessageLoop.post`. The
parameters are retrieved with a call to :meth:`_handle`.
"""
try:
converted = self._convert(code, msg, lpdata)
if converted is not None:
self._message_loop.post(self._WM_PROCESS, *converted)
except NotImplementedError:
self._handle(code, msg, lpdata)
if self.suppress:
self.suppress_event()
def _convert(self, code, msg, lpdata):
"""The device specific callback handler.
This method converts a low-level message and data to a
``WPARAM`` / ``LPARAM`` pair.
"""
raise NotImplementedError()
def _process(self, wparam, lparam):
"""The device specific callback handler.
This method performs the actual dispatching of events.
"""
raise NotImplementedError()
def _handle(self, code, msg, lpdata):
"""The device specific callback handler.
This method calls the appropriate callback registered when this
listener was created based on the event.
This method is only called if :meth:`_convert` is not implemented.
"""
raise NotImplementedError()
class KeyTranslator(object):
"""A class to translate virtual key codes to characters.
"""
_AttachThreadInput = ctypes.windll.user32.AttachThreadInput
_AttachThreadInput.argtypes = (
wintypes.DWORD,
wintypes.DWORD,
wintypes.BOOL)
_GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow
_GetKeyboardLayout = ctypes.windll.user32.GetKeyboardLayout
_GetKeyboardLayout.argtypes = (
wintypes.DWORD,)
_GetKeyboardState = ctypes.windll.user32.GetKeyboardState
_GetKeyboardState.argtypes = (
ctypes.c_voidp,)
_GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
_GetWindowThreadProcessId.argtypes = (
wintypes.HWND,
wintypes.LPDWORD)
_MapVirtualKeyEx = ctypes.windll.user32.MapVirtualKeyExW
_MapVirtualKeyEx.argtypes = (
wintypes.UINT,
wintypes.UINT,
wintypes.HKL)
_ToUnicodeEx = ctypes.windll.user32.ToUnicodeEx
_ToUnicodeEx.argtypes = (
wintypes.UINT,
wintypes.UINT,
ctypes.c_voidp,
ctypes.c_voidp,
ctypes.c_int,
wintypes.UINT,
wintypes.HKL)
_MAPVK_VK_TO_VSC = 0
_MAPVK_VK_TO_CHAR = 2
def __init__(self):
self.__state = (ctypes.c_ubyte * 255)()
self._cache = {}
self._reinject_arguments = None
def __call__(self, vk, is_press):
"""Converts a virtual key code to a string.
:param int vk: The virtual key code.
:param bool is_press: Whether this is a press. Because the *win32*
functions used to translate the key modifies internal kernel state,
some cleanup must be performed for key presses.
:return: parameters suitable for the :class:`pynput.keyboard.KeyCode`
constructor
:raises OSError: if a call to any *win32* function fails
"""
# Get the keyboard state and layout
state, layout = self._get_state_and_layout()
# Get the scan code for the virtual key
scan = self._to_scan(vk, layout)
# Try to reuse the previous key in the cache
try:
if is_press:
return self._cache[vk]
else:
return self._cache.pop(vk)
except KeyError:
pass
# Get a string representation of the key
char, is_dead = self._to_char(vk, layout)
modified_char = self._to_char_with_modifiers(vk, layout, scan, state)
# Clear the keyboard state if the key was a dead key
if is_dead:
self._reset_state(vk, layout, scan)
# If the previous key handled was a dead key, we reinject it
if self._reinject_arguments:
self._reinject(*self._reinject_arguments)
self._reinject_arguments = None
# If the current key is a dead key, we store the current state to be
# able to reinject later
elif is_dead:
self._reinject_arguments = (
vk,
layout,
scan,
(ctypes.c_ubyte * 255)(*state))
# Otherwise we just clear any previous dead key state
else:
self._reinject_arguments = None
# Update the cache
self._cache[vk] = {
'char': modified_char or char,
'is_dead': is_dead,
'vk': vk}
return self._cache[vk]
def _get_state_and_layout(self):
"""Returns the keyboard state and layout.
The state is read from the currently active window if possible. It is
kept in a cache, so any call to this method will invalidate return
values from previous invocations.
:return: the tuple ``(state, layout)``
"""
# Get the state of the keyboard attached to the active window
with self._thread_input() as active_thread:
if not self._GetKeyboardState(ctypes.byref(self.__state)):
raise OSError(
'GetKeyboardState failed: %d',
ctypes.wintypes.get_last_error())
# Get the keyboard layout for the thread for which we retrieved the
# state
layout = self._GetKeyboardLayout(active_thread)
return (self.__state, layout)
def _to_scan(self, vk, layout):
"""Retrieves the scan code for a virtual key code.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:return: the scan code
"""
return self._MapVirtualKeyEx(
vk, self._MAPVK_VK_TO_VSC, layout)
def _to_char(self, vk, layout):
"""Converts a virtual key by simply mapping it through the keyboard
layout.
This method is stateless, so any active shift state or dead keys are
ignored.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:return: the string representation of the key, or ``None``, and whether
was dead as the tuple ``(char, is_dead)``
"""
# MapVirtualKeyEx will yield a string representation for dead keys
flags_and_codepoint = self._MapVirtualKeyEx(
vk, self._MAPVK_VK_TO_CHAR, layout)
if flags_and_codepoint:
return (
six.unichr(flags_and_codepoint & 0xFFFF),
bool(flags_and_codepoint & (1 << 31)))
else:
return (None, None)
def _to_char_with_modifiers(self, vk, layout, scan, state):
"""Converts a virtual key by mapping it through the keyboard layout and
internal kernel keyboard state.
This method is stateful, so any active shift state and dead keys are
applied. Currently active dead keys will be removed from the internal
kernel keyboard state.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:param int scan: The scan code of the key.
:param state: The keyboard state.
:return: the string representation of the key, or ``None``
"""
# This will apply any dead keys and modify the internal kernel keyboard
# state
out = (ctypes.wintypes.WCHAR * 5)()
count = self._ToUnicodeEx(
vk, scan, ctypes.byref(state), ctypes.byref(out),
len(out), 0, layout)
return out[0] if count > 0 else None
def _reset_state(self, vk, layout, scan):
"""Clears the internal kernel keyboard state.
This method will remove all dead keys from the internal state.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:param int scan: The scan code of the key.
"""
state = (ctypes.c_byte * 255)()
out = (ctypes.wintypes.WCHAR * 5)()
while self._ToUnicodeEx(
vk, scan, ctypes.byref(state), ctypes.byref(out),
len(out), 0, layout) < 0:
pass
def _reinject(self, vk, layout, scan, state):
"""Reinjects the previous dead key.
This must be called if ``ToUnicodeEx`` has been called, and the
previous key was a dead one.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:param int scan: The scan code of the key.
:param state: The keyboard state.
"""
out = (ctypes.wintypes.WCHAR * 5)()
self._ToUnicodeEx(
vk, scan, ctypes.byref(state), ctypes.byref(out),
len(out), 0, layout)
@contextlib.contextmanager
def _thread_input(self):
"""Yields the current thread ID.
"""
yield GetCurrentThreadId()

179
pynput/_util/win32_vks.py Normal file
View File

@ -0,0 +1,179 @@
# coding: utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# pylint: disable=C0111,C0302
LBUTTON = 1
RBUTTON = 2
CANCEL = 3
MBUTTON = 4
XBUTTON1 = 5
XBUTTON2 = 6
BACK = 8
TAB = 9
CLEAR = 12
RETURN = 13
SHIFT = 16
CONTROL = 17
MENU = 18
PAUSE = 19
CAPITAL = 20
KANA = 21
HANGEUL = 21
HANGUL = 21
JUNJA = 23
FINAL = 24
HANJA = 25
KANJI = 25
ESCAPE = 27
CONVERT = 28
NONCONVERT = 29
ACCEPT = 30
MODECHANGE = 31
SPACE = 32
PRIOR = 33
NEXT = 34
END = 35
HOME = 36
LEFT = 37
UP = 38
RIGHT = 39
DOWN = 40
SELECT = 41
PRINT = 42
EXECUTE = 43
SNAPSHOT = 44
INSERT = 45
DELETE = 46
HELP = 47
LWIN = 91
RWIN = 92
APPS = 93
SLEEP = 95
NUMPAD0 = 96
NUMPAD1 = 97
NUMPAD2 = 98
NUMPAD3 = 99
NUMPAD4 = 100
NUMPAD5 = 101
NUMPAD6 = 102
NUMPAD7 = 103
NUMPAD8 = 104
NUMPAD9 = 105
MULTIPLY = 106
ADD = 107
SEPARATOR = 108
SUBTRACT = 109
DECIMAL = 110
DIVIDE = 111
F1 = 112
F2 = 113
F3 = 114
F4 = 115
F5 = 116
F6 = 117
F7 = 118
F8 = 119
F9 = 120
F10 = 121
F11 = 122
F12 = 123
F13 = 124
F14 = 125
F15 = 126
F16 = 127
F17 = 128
F18 = 129
F19 = 130
F20 = 131
F21 = 132
F22 = 133
F23 = 134
F24 = 135
NUMLOCK = 144
SCROLL = 145
OEM_NEC_EQUAL = 146
OEM_FJ_JISHO = 146
OEM_FJ_MASSHOU = 147
OEM_FJ_TOUROKU = 148
OEM_FJ_LOYA = 149
OEM_FJ_ROYA = 150
LSHIFT = 160
RSHIFT = 161
LCONTROL = 162
RCONTROL = 163
LMENU = 164
RMENU = 165
BROWSER_BACK = 166
BROWSER_FORWARD = 167
BROWSER_REFRESH = 168
BROWSER_STOP = 169
BROWSER_SEARCH = 170
BROWSER_FAVORITES = 171
BROWSER_HOME = 172
VOLUME_MUTE = 173
VOLUME_DOWN = 174
VOLUME_UP = 175
MEDIA_NEXT_TRACK = 176
MEDIA_PREV_TRACK = 177
MEDIA_STOP = 178
MEDIA_PLAY_PAUSE = 179
LAUNCH_MAIL = 180
LAUNCH_MEDIA_SELECT = 181
LAUNCH_APP1 = 182
LAUNCH_APP2 = 183
OEM_1 = 186
OEM_PLUS = 187
OEM_COMMA = 188
OEM_MINUS = 189
OEM_PERIOD = 190
OEM_2 = 191
OEM_3 = 192
OEM_4 = 219
OEM_5 = 220
OEM_6 = 221
OEM_7 = 222
OEM_8 = 223
OEM_AX = 225
OEM_102 = 226
ICO_HELP = 227
ICO_00 = 228
PROCESSKEY = 229
ICO_CLEAR = 230
PACKET = 231
OEM_RESET = 233
OEM_JUMP = 234
OEM_PA1 = 235
OEM_PA2 = 236
OEM_PA3 = 237
OEM_WSCTRL = 238
OEM_CUSEL = 239
OEM_ATTN = 240
OEM_FINISH = 241
OEM_COPY = 242
OEM_AUTO = 243
OEM_ENLW = 244
OEM_BACKTAB = 245
ATTN = 246
CRSEL = 247
EXSEL = 248
EREOF = 249
PLAY = 250
ZOOM = 251
NONAME = 252
PA1 = 253
OEM_CLEAR = 254

480
pynput/_util/xorg.py Normal file
View File

@ -0,0 +1,480 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Utility functions and classes for the *Xorg* backend.
"""
# pylint: disable=R0903
# We implement stubs
import contextlib
import functools
import itertools
import operator
import Xlib.display
import Xlib.threaded
import Xlib.XK
from . import AbstractListener
from .xorg_keysyms import SYMBOLS
# Create a display to verify that we have an X connection
def _check():
display = Xlib.display.Display()
display.close()
_check()
del _check
class X11Error(Exception):
"""An error that is thrown at the end of a code block managed by a
:func:`display_manager` if an *X11* error occurred.
"""
pass
@contextlib.contextmanager
def display_manager(display):
"""Traps *X* errors and raises an :class:``X11Error`` at the end if any
error occurred.
This handler also ensures that the :class:`Xlib.display.Display` being
managed is sync'd.
:param Xlib.display.Display display: The *X* display.
:return: the display
:rtype: Xlib.display.Display
"""
errors = []
def handler(*args):
"""The *Xlib* error handler.
"""
errors.append(args)
old_handler = display.set_error_handler(handler)
try:
yield display
display.sync()
finally:
display.set_error_handler(old_handler)
if errors:
raise X11Error(errors)
def _find_mask(display, symbol):
"""Returns the mode flags to use for a modifier symbol.
:param Xlib.display.Display display: The *X* display.
:param str symbol: The name of the symbol.
:return: the modifier mask
"""
# Get the key code for the symbol
modifier_keycode = display.keysym_to_keycode(
Xlib.XK.string_to_keysym(symbol))
for index, keycodes in enumerate(display.get_modifier_mapping()):
for keycode in keycodes:
if keycode == modifier_keycode:
return 1 << index
return 0
def alt_mask(display):
"""Returns the *alt* mask flags.
The first time this function is called for a display, the value is cached.
Subsequent calls will return the cached value.
:param Xlib.display.Display display: The *X* display.
:return: the modifier mask
"""
if not hasattr(display, '__alt_mask'):
display.__alt_mask = _find_mask(display, 'Alt_L')
return display.__alt_mask
def alt_gr_mask(display):
"""Returns the *alt* mask flags.
The first time this function is called for a display, the value is cached.
Subsequent calls will return the cached value.
:param Xlib.display.Display display: The *X* display.
:return: the modifier mask
"""
if not hasattr(display, '__altgr_mask'):
display.__altgr_mask = _find_mask(display, 'Mode_switch')
return display.__altgr_mask
def numlock_mask(display):
"""Returns the *numlock* mask flags.
The first time this function is called for a display, the value is cached.
Subsequent calls will return the cached value.
:param Xlib.display.Display display: The *X* display.
:return: the modifier mask
"""
if not hasattr(display, '__numlock_mask'):
display.__numlock_mask = _find_mask(display, 'Num_Lock')
return display.__numlock_mask
def keysym_is_latin_upper(keysym):
"""Determines whether a *keysym* is an upper case *latin* character.
This is true only if ``XK_A`` <= ``keysym`` <= ` XK_Z``.
:param in keysym: The *keysym* to check.
"""
return Xlib.XK.XK_A <= keysym <= Xlib.XK.XK_Z
def keysym_is_latin_lower(keysym):
"""Determines whether a *keysym* is a lower case *latin* character.
This is true only if ``XK_a`` <= ``keysym`` <= ` XK_z``.
:param in keysym: The *keysym* to check.
"""
return Xlib.XK.XK_a <= keysym <= Xlib.XK.XK_z
def keysym_group(ks1, ks2):
"""Generates a group from two *keysyms*.
The implementation of this function comes from:
Within each group, if the second element of the group is ``NoSymbol``,
then the group should be treated as if the second element were the same
as the first element, except when the first element is an alphabetic
*KeySym* ``K`` for which both lowercase and uppercase forms are
defined.
In that case, the group should be treated as if the first element were
the lowercase form of ``K`` and the second element were the uppercase
form of ``K``.
This function assumes that *alphabetic* means *latin*; this assumption
appears to be consistent with observations of the return values from
``XGetKeyboardMapping``.
:param ks1: The first *keysym*.
:param ks2: The second *keysym*.
:return: a tuple conforming to the description above
"""
if ks2 == Xlib.XK.NoSymbol:
if keysym_is_latin_upper(ks1):
return (Xlib.XK.XK_a + ks1 - Xlib.XK.XK_A, ks1)
elif keysym_is_latin_lower(ks1):
return (ks1, Xlib.XK.XK_A + ks1 - Xlib.XK.XK_a)
else:
return (ks1, ks1)
else:
return (ks1, ks2)
def keysym_normalize(keysym):
"""Normalises a list of *keysyms*.
The implementation of this function comes from:
If the list (ignoring trailing ``NoSymbol`` entries) is a single
*KeySym* ``K``, then the list is treated as if it were the list
``K NoSymbol K NoSymbol``.
If the list (ignoring trailing ``NoSymbol`` entries) is a pair of
*KeySyms* ``K1 K2``, then the list is treated as if it were the list
``K1 K2 K1 K2``.
If the list (ignoring trailing ``NoSymbol`` entries) is a triple of
*KeySyms* ``K1 K2 K3``, then the list is treated as if it were the list
``K1 K2 K3 NoSymbol``.
This function will also group the *keysyms* using :func:`keysym_group`.
:param keysyms: A list of keysyms.
:return: the tuple ``(group_1, group_2)`` or ``None``
"""
# Remove trailing NoSymbol
stripped = list(reversed(list(
itertools.dropwhile(
lambda n: n == Xlib.XK.NoSymbol,
reversed(keysym)))))
if not stripped:
return
elif len(stripped) == 1:
return (
keysym_group(stripped[0], Xlib.XK.NoSymbol),
keysym_group(stripped[0], Xlib.XK.NoSymbol))
elif len(stripped) == 2:
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[0], stripped[1]))
elif len(stripped) == 3:
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[2], Xlib.XK.NoSymbol))
elif len(stripped) >= 6:
# TODO: Find out why this is necessary; using only the documented
# behaviour may lead to only a US layout being used?
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[4], stripped[5]))
else:
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[2], stripped[3]))
def index_to_shift(display, index):
"""Converts an index in a *key code* list to the corresponding shift state.
:param Xlib.display.Display display: The display for which to retrieve the
shift mask.
:param int index: The keyboard mapping *key code* index.
:return: a shift mask
"""
return (
(1 << 0 if index & 1 else 0) |
(alt_gr_mask(display) if index & 2 else 0))
def shift_to_index(display, shift):
"""Converts an index in a *key code* list to the corresponding shift state.
:param Xlib.display.Display display: The display for which to retrieve the
shift mask.
:param int index: The keyboard mapping *key code* index.
:retur: a shift mask
"""
return (
(1 if shift & 1 else 0) +
(2 if shift & alt_gr_mask(display) else 0))
def keyboard_mapping(display):
"""Generates a mapping from *keysyms* to *key codes* and required
modifier shift states.
:param Xlib.display.Display display: The display for which to retrieve the
keyboard mapping.
:return: the keyboard mapping
"""
mapping = {}
shift_mask = 1 << 0
group_mask = alt_gr_mask(display)
# Iterate over all keysym lists in the keyboard mapping
min_keycode = display.display.info.min_keycode
keycode_count = display.display.info.max_keycode - min_keycode + 1
for index, keysyms in enumerate(display.get_keyboard_mapping(
min_keycode, keycode_count)):
key_code = index + min_keycode
# Normalise the keysym list to yield a tuple containing the two groups
normalized = keysym_normalize(keysyms)
if not normalized:
continue
# Iterate over the groups to extract the shift and modifier state
for groups, group in zip(normalized, (False, True)):
for keysym, shift in zip(groups, (False, True)):
if not keysym:
continue
shift_state = 0 \
| (shift_mask if shift else 0) \
| (group_mask if group else 0)
# Prefer already known lesser shift states
if keysym in mapping and mapping[keysym][1] < shift_state:
continue
mapping[keysym] = (key_code, shift_state)
return mapping
def symbol_to_keysym(symbol):
"""Converts a symbol name to a *keysym*.
:param str symbol: The name of the symbol.
:return: the corresponding *keysym*, or ``0`` if it cannot be found
"""
# First try simple translation
keysym = Xlib.XK.string_to_keysym(symbol)
if keysym:
return keysym
# If that fails, try checking a module attribute of Xlib.keysymdef.xkb
if not keysym:
try:
return getattr(Xlib.keysymdef.xkb, 'XK_' + symbol, 0)
except AttributeError:
return SYMBOLS.get(symbol, (0,))[0]
class ListenerMixin(object):
"""A mixin for *X* event listeners.
Subclasses should set a value for :attr:`_EVENTS` and implement
:meth:`_handle`.
"""
#: The events for which to listen
_EVENTS = tuple()
#: We use this instance for parsing the binary data
_EVENT_PARSER = Xlib.protocol.rq.EventField(None)
def _run(self):
self._display_stop = Xlib.display.Display()
self._display_record = Xlib.display.Display()
with display_manager(self._display_stop) as dm:
self._context = dm.record_create_context(
0,
[Xlib.ext.record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': self._EVENTS,
'errors': (0, 0),
'client_started': False,
'client_died': False}])
# pylint: disable=W0702; we want to silence errors
try:
self._initialize(self._display_stop)
self._mark_ready()
if self.suppress:
with display_manager(self._display_record) as dm:
self._suppress_start(dm)
self._display_record.record_enable_context(
self._context, self._handler)
except:
# This exception will have been passed to the main thread
pass
finally:
if self.suppress:
with display_manager(self._display_stop) as dm:
self._suppress_stop(dm)
self._display_record.record_free_context(self._context)
self._display_stop.close()
self._display_record.close()
# pylint: enable=W0702
def _stop_platform(self):
if not hasattr(self, '_context'):
self.wait()
# pylint: disable=W0702; we must ignore errors
try:
with display_manager(self._display_stop) as dm:
dm.record_disable_context(self._context)
except:
pass
# pylint: enable=W0702
def _suppress_start(self, display):
"""Starts suppressing events.
:param Xlib.display.Display display: The display for which to suppress
events.
"""
raise NotImplementedError()
def _suppress_stop(self, display):
"""Starts suppressing events.
:param Xlib.display.Display display: The display for which to suppress
events.
"""
raise NotImplementedError()
@property
def _event_mask(self):
"""The event mask.
"""
return functools.reduce(operator.__or__, self._EVENTS, 0)
@AbstractListener._emitter
def _handler(self, events):
"""The callback registered with *X* for mouse events.
This method will parse the response and call the callbacks registered
on initialisation.
:param events: The events passed by *X*. This is a binary block
parsable by :attr:`_EVENT_PARSER`.
"""
if not self.running:
raise self.StopException()
data = events.data
while data and len(data):
event, data = self._EVENT_PARSER.parse_binary_value(
data, self._display_record.display, None, None)
self._handle(self._display_stop, event)
def _initialize(self, display):
"""Initialises this listener.
This method is called immediately before the event loop, from the
handler thread.
:param display: The display being used.
"""
pass
def _handle(self, display, event):
"""The device specific callback handler.
This method calls the appropriate callback registered when this
listener was created based on the event.
:param display: The display being used.
:param event: The event.
"""
pass

1715
pynput/_util/xorg_keysyms.py Normal file

File diff suppressed because it is too large Load Diff

56
pynput/mouse/__init__.py Normal file
View File

@ -0,0 +1,56 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The module containing mouse classes.
See the documentation for more information.
"""
# pylint: disable=C0103
# Button, Controller and Listener are not constants
import os
import sys
if os.environ.get('__PYNPUT_GENERATE_DOCUMENTATION') == 'yes':
from ._base import Button, Controller, Listener
else:
Button = None
Controller = None
Listener = None
if sys.platform == 'darwin':
if not Button and not Controller and not Listener:
from ._darwin import Button, Controller, Listener
elif sys.platform == 'win32':
if not Button and not Controller and not Listener:
from ._win32 import Button, Controller, Listener
else:
if not Button and not Controller and not Listener:
try:
from ._xorg import Button, Controller, Listener
except ImportError:
# For now, since we only support Xlib anyway, we re-raise these
# errors to allow users to determine the cause of failures to import
raise
if not Button or not Controller or not Listener:
raise ImportError('this platform is not supported')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

263
pynput/mouse/_base.py Normal file
View File

@ -0,0 +1,263 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module contains the base implementation.
The actual interface to mouse classes is defined here, but the implementation
is located in a platform dependent module.
"""
# pylint: disable=R0903
# We implement stubs
import enum
from pynput._util import AbstractListener
from pynput import _logger
class Button(enum.Enum):
"""The various buttons.
The actual values for these items differ between platforms. Some
platforms may have additional buttons, but these are guaranteed to be
present everywhere.
"""
#: An unknown button was pressed
unknown = 0
#: The left button
left = 1
#: The middle button
middle = 2
#: The right button
right = 3
class Controller(object):
"""A controller for sending virtual mouse events to the system.
"""
def __init__(self):
self._log = _logger(self.__class__)
@property
def position(self):
"""The current position of the mouse pointer.
This is the tuple ``(x, y)``, and setting it will move the pointer.
"""
return self._position_get()
@position.setter
def position(self, pos):
self._position_set(pos)
def scroll(self, dx, dy):
"""Sends scroll events.
:param int dx: The horizontal scroll. The units of scrolling is
undefined.
:param int dy: The vertical scroll. The units of scrolling is
undefined.
:raises ValueError: if the values are invalid, for example out of
bounds
"""
self._scroll(dx, dy)
def press(self, button):
"""Emits a button press event at the current position.
:param Button button: The button to press.
"""
self._press(button)
def release(self, button):
"""Emits a button release event at the current position.
:param Button button: The button to release.
"""
self._release(button)
def move(self, dx, dy):
"""Moves the mouse pointer a number of pixels from its current
position.
:param int x: The horizontal offset.
:param int dy: The vertical offset.
:raises ValueError: if the values are invalid, for example out of
bounds
"""
self.position = tuple(sum(i) for i in zip(self.position, (dx, dy)))
def click(self, button, count=1):
"""Emits a button click event at the current position.
The default implementation sends a series of press and release events.
:param Button button: The button to click.
:param int count: The number of clicks to send.
"""
with self as controller:
for _ in range(count):
controller.press(button)
controller.release(button)
def __enter__(self):
"""Begins a series of clicks.
In the default :meth:`click` implementation, the return value of this
method is used for the calls to :meth:`press` and :meth:`release`
instead of ``self``.
The default implementation is a no-op.
"""
return self
def __exit__(self, exc_type, value, traceback):
"""Ends a series of clicks.
"""
pass
def _position_get(self):
"""The implementation of the getter for :attr:`position`.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _position_set(self, pos):
"""The implementation of the setter for :attr:`position`.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _scroll(self, dx, dy):
"""The implementation of the :meth:`scroll` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _press(self, button):
"""The implementation of the :meth:`press` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _release(self, button):
"""The implementation of the :meth:`release` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
# pylint: disable=W0223; This is also an abstract class
class Listener(AbstractListener):
"""A listener for mouse events.
Instances of this class can be used as context managers. This is equivalent
to the following code::
listener.start()
try:
listener.wait()
with_statements()
finally:
listener.stop()
This class inherits from :class:`threading.Thread` and supports all its
methods. It will set :attr:`daemon` to ``True`` when created.
:param callable on_move: The callback to call when mouse move events occur.
It will be called with the arguments ``(x, y)``, which is the new
pointer position. If this callback raises :class:`StopException` or
returns ``False``, the listener is stopped.
:param callable on_click: The callback to call when a mouse button is
clicked.
It will be called with the arguments ``(x, y, button, pressed)``,
where ``(x, y)`` is the new pointer position, ``button`` is one of the
:class:`Button` values and ``pressed`` is whether the button was
pressed.
If this callback raises :class:`StopException` or returns ``False``,
the listener is stopped.
:param callable on_scroll: The callback to call when mouse scroll
events occur.
It will be called with the arguments ``(x, y, dx, dy)``, where
``(x, y)`` is the new pointer position, and ``(dx, dy)`` is the scroll
vector.
If this callback raises :class:`StopException` or returns ``False``,
the listener is stopped.
:param bool suppress: Whether to suppress events. Setting this to ``True``
will prevent the input events from being passed to the rest of the
system.
:param kwargs: Any non-standard platform dependent options. These should be
prefixed with the platform name thus: ``darwin_``, ``xorg_`` or
``win32_``.
Supported values are:
``darwin_intercept``
A callable taking the arguments ``(event_type, event)``, where
``event_type`` is any mouse related event type constant, and
``event`` is a ``CGEventRef``.
This callable can freely modify the event using functions like
``Quartz.CGEventSetIntegerValueField``. If this callable does not
return the event, the event is suppressed system wide.
``win32_event_filter``
A callable taking the arguments ``(msg, data)``, where ``msg`` is
the current message, and ``data`` associated data as a
`MSLLHOOKSTRUCT <https://msdn.microsoft.com/en-us/library/windows/desktop/ms644970(v=vs.85).aspx>`_.
If this callback returns ``False``, the event will not
be propagated to the listener callback.
If ``self.suppress_event()`` is called, the event is suppressed
system wide.
"""
def __init__(self, on_move=None, on_click=None, on_scroll=None,
suppress=False, **kwargs):
self._log = _logger(self.__class__)
prefix = self.__class__.__module__.rsplit('.', 1)[-1][1:] + '_'
self._options = {
key[len(prefix):]: value
for key, value in kwargs.items()
if key.startswith(prefix)}
super(Listener, self).__init__(
on_move=on_move, on_click=on_click, on_scroll=on_scroll,
suppress=suppress)
# pylint: enable=W0223

215
pynput/mouse/_darwin.py Normal file
View File

@ -0,0 +1,215 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The mouse implementation for *OSX*.
"""
# pylint: disable=C0111
# The documentation is extracted from the base classes
# pylint: disable=R0903
# We implement stubs
import enum
import Quartz
from AppKit import NSEvent
from pynput._util.darwin import (
ListenerMixin)
from . import _base
def _button_value(base_name, mouse_button):
"""Generates the value tuple for a :class:`Button` value.
:param str base_name: The base name for the button. This shuld be a string
like ``'kCGEventLeftMouse'``.
:param int mouse_button: The mouse button ID.
:return: a value tuple
"""
return (
tuple(
getattr(Quartz, '%sMouse%s' % (base_name, name))
for name in ('Down', 'Up', 'Dragged')),
mouse_button)
class Button(enum.Enum):
"""The various buttons.
"""
unknown = None
left = _button_value('kCGEventLeft', 0)
middle = _button_value('kCGEventOther', 2)
right = _button_value('kCGEventRight', 1)
class Controller(_base.Controller):
#: The scroll speed
_SCROLL_SPEED = 5
def __init__(self, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
self._click = None
self._drag_button = None
def _position_get(self):
pos = NSEvent.mouseLocation()
return pos.x, Quartz.CGDisplayPixelsHigh(0) - pos.y
def _position_set(self, pos):
try:
(_, _, mouse_type), mouse_button = self._drag_button.value
except AttributeError:
mouse_type = Quartz.kCGEventMouseMoved
mouse_button = 0
Quartz.CGEventPost(
Quartz.kCGHIDEventTap,
Quartz.CGEventCreateMouseEvent(
None,
mouse_type,
pos,
mouse_button))
def _scroll(self, dx, dy):
while dx != 0 or dy != 0:
xval = 1 if dx > 0 else -1 if dx < 0 else 0
dx -= xval
yval = 1 if dy > 0 else -1 if dy < 0 else 0
dy -= yval
Quartz.CGEventPost(
Quartz.kCGHIDEventTap,
Quartz.CGEventCreateScrollWheelEvent(
None,
Quartz.kCGScrollEventUnitPixel,
2,
yval * self._SCROLL_SPEED,
xval * self._SCROLL_SPEED))
def _press(self, button):
(press, _, _), mouse_button = button.value
event = Quartz.CGEventCreateMouseEvent(
None,
press,
self.position,
mouse_button)
# If we are performing a click, we need to set this state flag
if self._click is not None:
self._click += 1
Quartz.CGEventSetIntegerValueField(
event,
Quartz.kCGMouseEventClickState,
self._click)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
# Store the button to enable dragging
self._drag_button = button
def _release(self, button):
(_, release, _), mouse_button = button.value
event = Quartz.CGEventCreateMouseEvent(
None,
release,
self.position,
mouse_button)
# If we are performing a click, we need to set this state flag
if self._click is not None:
Quartz.CGEventSetIntegerValueField(
event,
Quartz.kCGMouseEventClickState,
self._click)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
if button == self._drag_button:
self._drag_button = None
def __enter__(self):
self._click = 0
return self
def __exit__(self, exc_type, value, traceback):
self._click = None
class Listener(ListenerMixin, _base.Listener):
#: The events that we listen to
_EVENTS = (
Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved) |
Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDragged) |
Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDragged) |
Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDragged) |
Quartz.CGEventMaskBit(Quartz.kCGEventScrollWheel))
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._intercept = self._options.get(
'intercept',
None)
def _handle(self, dummy_proxy, event_type, event, dummy_refcon):
"""The callback registered with *Mac OSX* for mouse events.
This method will call the callbacks registered on initialisation.
"""
try:
(px, py) = Quartz.CGEventGetLocation(event)
except AttributeError:
# This happens during teardown of the virtual machine
return
# Quickly detect the most common event type
if event_type == Quartz.kCGEventMouseMoved:
self.on_move(px, py)
elif event_type == Quartz.kCGEventScrollWheel:
dx = Quartz.CGEventGetIntegerValueField(
event,
Quartz.kCGScrollWheelEventDeltaAxis2)
dy = Quartz.CGEventGetIntegerValueField(
event,
Quartz.kCGScrollWheelEventDeltaAxis1)
self.on_scroll(px, py, dx, dy)
else:
for button in Button:
try:
(press, release, drag), _ = button.value
except TypeError:
# Button.unknown cannot be enumerated
continue
# Press and release generate click events, and drag
# generates move events
if event_type in (press, release):
self.on_click(px, py, button, event_type == press)
elif event_type == drag:
self.on_move(px, py)

195
pynput/mouse/_win32.py Normal file
View File

@ -0,0 +1,195 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The mouse implementation for *Windows*.
"""
# pylint: disable=C0111
# The documentation is extracted from the base classes
# pylint: disable=R0903
# We implement stubs
import ctypes
import enum
from ctypes import (
windll,
wintypes)
from pynput._util import NotifierMixin
from pynput._util.win32 import (
INPUT,
INPUT_union,
ListenerMixin,
MOUSEINPUT,
SendInput,
SystemHook)
from . import _base
class Button(enum.Enum):
"""The various buttons.
"""
unknown = None
left = (MOUSEINPUT.LEFTUP, MOUSEINPUT.LEFTDOWN)
middle = (MOUSEINPUT.MIDDLEUP, MOUSEINPUT.MIDDLEDOWN)
right = (MOUSEINPUT.RIGHTUP, MOUSEINPUT.RIGHTDOWN)
class Controller(NotifierMixin, _base.Controller):
__GetCursorPos = windll.user32.GetCursorPos
__SetCursorPos = windll.user32.SetCursorPos
def __init__(self, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
def _position_get(self):
point = wintypes.POINT()
if self.__GetCursorPos(ctypes.byref(point)):
return (point.x, point.y)
else:
return None
def _position_set(self, pos):
pos = int(pos[0]), int(pos[1])
self.__SetCursorPos(*pos)
self._emit('on_move', *pos)
def _scroll(self, dx, dy):
if dy:
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mi=MOUSEINPUT(
dwFlags=MOUSEINPUT.WHEEL,
mouseData=int(dy))))),
ctypes.sizeof(INPUT))
if dx:
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mi=MOUSEINPUT(
dwFlags=MOUSEINPUT.HWHEEL,
mouseData=int(dx))))),
ctypes.sizeof(INPUT))
if dx or dy:
px, py = self._position_get()
self._emit('on_scroll', px, py, dx, dy)
def _press(self, button):
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mi=MOUSEINPUT(
dwFlags=button.value[1])))),
ctypes.sizeof(INPUT))
def _release(self, button):
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mi=MOUSEINPUT(
dwFlags=button.value[0])))),
ctypes.sizeof(INPUT))
@Controller._receiver
class Listener(ListenerMixin, _base.Listener):
#: The Windows hook ID for low level mouse events, ``WH_MOUSE_LL``
_EVENTS = 14
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
_WHEEL_DELTA = 120
#: A mapping from messages to button events
CLICK_BUTTONS = {
WM_LBUTTONDOWN: (Button.left, True),
WM_LBUTTONUP: (Button.left, False),
WM_MBUTTONDOWN: (Button.middle, True),
WM_MBUTTONUP: (Button.middle, False),
WM_RBUTTONDOWN: (Button.right, True),
WM_RBUTTONUP: (Button.right, False)}
#: A mapping from messages to scroll vectors
SCROLL_BUTTONS = {
WM_MOUSEWHEEL: (0, 1),
WM_MOUSEHWHEEL: (1, 0)}
_HANDLED_EXCEPTIONS = (
SystemHook.SuppressException,)
class _MSLLHOOKSTRUCT(ctypes.Structure):
"""Contains information about a mouse event passed to a ``WH_MOUSE_LL``
hook procedure, ``MouseProc``.
"""
_fields_ = [
('pt', wintypes.POINT),
('mouseData', wintypes.DWORD),
('flags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
#: A pointer to a :class:`_MSLLHOOKSTRUCT`
_LPMSLLHOOKSTRUCT = ctypes.POINTER(_MSLLHOOKSTRUCT)
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._event_filter = self._options.get(
'event_filter',
lambda msg, data: True)
def _handle(self, code, msg, lpdata):
if code != SystemHook.HC_ACTION:
return
data = ctypes.cast(lpdata, self._LPMSLLHOOKSTRUCT).contents
# Suppress further propagation of the event if it is filtered
if self._event_filter(msg, data) is False:
return
if msg == self.WM_MOUSEMOVE:
self.on_move(data.pt.x, data.pt.y)
elif msg in self.CLICK_BUTTONS:
button, pressed = self.CLICK_BUTTONS[msg]
self.on_click(data.pt.x, data.pt.y, button, pressed)
elif msg in self.SCROLL_BUTTONS:
mx, my = self.SCROLL_BUTTONS[msg]
dd = wintypes.SHORT(data.mouseData >> 16).value // self._WHEEL_DELTA
self.on_scroll(data.pt.x, data.pt.y, dd * mx, dd * my)

174
pynput/mouse/_xorg.py Normal file
View File

@ -0,0 +1,174 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2018 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The keyboard implementation for *Xorg*.
"""
# pylint: disable=C0111
# The documentation is extracted from the base classes
# pylint: disable=E1101,E1102
# We dynamically generate the Button class
# pylint: disable=R0903
# We implement stubs
import enum
import Xlib.display
import Xlib.ext
import Xlib.ext.xtest
import Xlib.X
import Xlib.protocol
from pynput._util.xorg import (
display_manager,
ListenerMixin)
from . import _base
# pylint: disable=C0103
Button = enum.Enum(
'Button',
module=__name__,
names=[
('unknown', None),
('left', 1),
('middle', 2),
('right', 3),
('scroll_up', 4),
('scroll_down', 5),
('scroll_left', 6),
('scroll_right', 7)] + [
('button%d' % i, i)
for i in range(8, 31)])
# pylint: enable=C0103
class Controller(_base.Controller):
def __init__(self, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
self._display = Xlib.display.Display()
def __del__(self):
if hasattr(self, '_display'):
self._display.close()
def _position_get(self):
with display_manager(self._display) as dm:
qp = dm.screen().root.query_pointer()
return (qp.root_x, qp.root_y)
def _position_set(self, pos):
px, py = self._check_bounds(*pos)
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(dm, Xlib.X.MotionNotify, x=px, y=py)
def _scroll(self, dx, dy):
dx, dy = self._check_bounds(dx, dy)
if dy:
self.click(
button=Button.scroll_up if dy > 0 else Button.scroll_down,
count=abs(dy))
if dx:
self.click(
button=Button.scroll_right if dx > 0 else Button.scroll_left,
count=abs(dx))
def _press(self, button):
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(dm, Xlib.X.ButtonPress, button.value)
def _release(self, button):
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(dm, Xlib.X.ButtonRelease, button.value)
def _check_bounds(self, *args):
"""Checks the arguments and makes sure they are within the bounds of a
short integer.
:param args: The values to verify.
"""
if not all(
(-0x7fff - 1) <= number <= 0x7fff
for number in args):
raise ValueError(args)
else:
return tuple(int(p) for p in args)
class Listener(ListenerMixin, _base.Listener):
#: A mapping from button values to scroll directions
_SCROLL_BUTTONS = {
Button.scroll_up.value: (0, 1),
Button.scroll_down.value: (0, -1),
Button.scroll_right.value: (1, 0),
Button.scroll_left.value: (-1, 0)}
_EVENTS = (
Xlib.X.ButtonPressMask,
Xlib.X.ButtonReleaseMask)
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
def _handle(self, dummy_display, event):
px = event.root_x
py = event.root_y
if event.type == Xlib.X.ButtonPress:
# Scroll events are sent as button presses with the scroll
# button codes
scroll = self._SCROLL_BUTTONS.get(event.detail, None)
if scroll:
self.on_scroll(px, py, *scroll)
else:
self.on_click(px, py, self._button(event.detail), True)
elif event.type == Xlib.X.ButtonRelease:
# Send an event only if this was not a scroll event
if event.detail not in self._SCROLL_BUTTONS:
self.on_click(px, py, self._button(event.detail), False)
else:
self.on_move(px, py)
def _suppress_start(self, display):
display.screen().root.grab_pointer(
True, self._event_mask, Xlib.X.GrabModeAsync, Xlib.X.GrabModeAsync,
0, 0, Xlib.X.CurrentTime)
def _suppress_stop(self, display):
display.ungrab_pointer(Xlib.X.CurrentTime)
# pylint: disable=R0201
def _button(self, detail):
"""Creates a mouse button from an event detail.
If the button is unknown, :attr:`Button.unknown` is returned.
:param detail: The event detail.
:return: a button
"""
try:
return Button(detail)
except ValueError:
return Button.unknown
# pylint: enable=R0201

186
ui_commandeditwnd.py Normal file
View File

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'commandeditwnd.ui'
#
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_CommandEditDialog(object):
def setupUi(self, CommandEditDialog):
CommandEditDialog.setObjectName("CommandEditDialog")
CommandEditDialog.resize(927, 419)
font = QtGui.QFont()
font.setPointSize(10)
CommandEditDialog.setFont(font)
self.gridLayout = QtWidgets.QGridLayout(CommandEditDialog)
self.gridLayout.setContentsMargins(25, 20, 20, 15)
self.gridLayout.setObjectName("gridLayout")
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 4, 0, 1, 2)
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.label_2 = QtWidgets.QLabel(CommandEditDialog)
self.label_2.setObjectName("label_2")
self.horizontalLayout_6.addWidget(self.label_2)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_6.addItem(spacerItem1)
self.gridLayout.addLayout(self.horizontalLayout_6, 2, 0, 1, 2)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem2, 1, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(CommandEditDialog)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.say = QtWidgets.QLineEdit(CommandEditDialog)
self.say.setObjectName("say")
self.horizontalLayout.addWidget(self.say)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem3)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 8)
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.oneExe = QtWidgets.QRadioButton(CommandEditDialog)
self.oneExe.setAutoExclusive(True)
self.oneExe.setObjectName("oneExe")
self.horizontalLayout_9.addWidget(self.oneExe)
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_9.addItem(spacerItem4)
self.gridLayout.addLayout(self.horizontalLayout_9, 6, 0, 1, 1)
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.continueExe = QtWidgets.QRadioButton(CommandEditDialog)
self.continueExe.setAutoExclusive(True)
self.continueExe.setObjectName("continueExe")
self.horizontalLayout_10.addWidget(self.continueExe)
spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_10.addItem(spacerItem5)
self.gridLayout.addLayout(self.horizontalLayout_10, 7, 0, 1, 1)
self.horizontalLayout_11 = QtWidgets.QHBoxLayout()
self.horizontalLayout_11.setObjectName("horizontalLayout_11")
self.repeatExe = QtWidgets.QRadioButton(CommandEditDialog)
self.repeatExe.setAutoExclusive(True)
self.repeatExe.setObjectName("repeatExe")
self.horizontalLayout_11.addWidget(self.repeatExe)
self.repeatCnt = QtWidgets.QSpinBox(CommandEditDialog)
self.repeatCnt.setMinimum(2)
self.repeatCnt.setObjectName("repeatCnt")
self.horizontalLayout_11.addWidget(self.repeatCnt)
self.label_3 = QtWidgets.QLabel(CommandEditDialog)
self.label_3.setObjectName("label_3")
self.horizontalLayout_11.addWidget(self.label_3)
spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_11.addItem(spacerItem6)
self.gridLayout.addLayout(self.horizontalLayout_11, 8, 0, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.verticalLayout_5 = QtWidgets.QVBoxLayout()
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.keyBut = QtWidgets.QPushButton(CommandEditDialog)
self.keyBut.setAutoDefault(False)
self.keyBut.setObjectName("keyBut")
self.verticalLayout_5.addWidget(self.keyBut)
self.mouseBut = QtWidgets.QPushButton(CommandEditDialog)
self.mouseBut.setAutoDefault(False)
self.mouseBut.setObjectName("mouseBut")
self.verticalLayout_5.addWidget(self.mouseBut)
self.pauseBut = QtWidgets.QPushButton(CommandEditDialog)
self.pauseBut.setAutoDefault(False)
self.pauseBut.setObjectName("pauseBut")
self.verticalLayout_5.addWidget(self.pauseBut)
self.otherBut = QtWidgets.QPushButton(CommandEditDialog)
self.otherBut.setAutoDefault(False)
self.otherBut.setObjectName("otherBut")
self.verticalLayout_5.addWidget(self.otherBut)
self.horizontalLayout_7.addLayout(self.verticalLayout_5)
self.actionsListWidget = QtWidgets.QListWidget(CommandEditDialog)
font = QtGui.QFont()
font.setPointSize(11)
self.actionsListWidget.setFont(font)
self.actionsListWidget.setObjectName("actionsListWidget")
self.horizontalLayout_7.addWidget(self.actionsListWidget)
self.verticalLayout_6 = QtWidgets.QVBoxLayout()
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.upBut = QtWidgets.QPushButton(CommandEditDialog)
self.upBut.setAutoDefault(False)
self.upBut.setObjectName("upBut")
self.verticalLayout_6.addWidget(self.upBut)
self.downBut = QtWidgets.QPushButton(CommandEditDialog)
self.downBut.setAutoDefault(False)
self.downBut.setObjectName("downBut")
self.verticalLayout_6.addWidget(self.downBut)
self.editBut = QtWidgets.QPushButton(CommandEditDialog)
self.editBut.setAutoDefault(False)
self.editBut.setObjectName("editBut")
self.verticalLayout_6.addWidget(self.editBut)
self.deleteBut = QtWidgets.QPushButton(CommandEditDialog)
self.deleteBut.setAutoDefault(False)
self.deleteBut.setObjectName("deleteBut")
self.verticalLayout_6.addWidget(self.deleteBut)
self.horizontalLayout_7.addLayout(self.verticalLayout_6)
self.gridLayout.addLayout(self.horizontalLayout_7, 3, 0, 1, 7)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.asyncChk = QtWidgets.QCheckBox(CommandEditDialog)
self.asyncChk.setChecked(True)
self.asyncChk.setObjectName("asyncChk")
self.horizontalLayout_8.addWidget(self.asyncChk)
spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_8.addItem(spacerItem7)
self.gridLayout.addLayout(self.horizontalLayout_8, 5, 0, 1, 3)
self.horizontalLayout_12 = QtWidgets.QHBoxLayout()
self.horizontalLayout_12.setSpacing(20)
self.horizontalLayout_12.setObjectName("horizontalLayout_12")
spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_12.addItem(spacerItem8)
self.ok = QtWidgets.QPushButton(CommandEditDialog)
self.ok.setMinimumSize(QtCore.QSize(130, 0))
self.ok.setAutoDefault(False)
self.ok.setObjectName("ok")
self.horizontalLayout_12.addWidget(self.ok)
self.cancel = QtWidgets.QPushButton(CommandEditDialog)
self.cancel.setMinimumSize(QtCore.QSize(130, 0))
self.cancel.setAutoDefault(False)
self.cancel.setObjectName("cancel")
self.horizontalLayout_12.addWidget(self.cancel)
self.gridLayout.addLayout(self.horizontalLayout_12, 12, 0, 1, 3)
self.retranslateUi(CommandEditDialog)
QtCore.QMetaObject.connectSlotsByName(CommandEditDialog)
def retranslateUi(self, CommandEditDialog):
_translate = QtCore.QCoreApplication.translate
CommandEditDialog.setWindowTitle(_translate("CommandEditDialog", "Command Edit Dialog"))
self.label_2.setText(_translate("CommandEditDialog", "When this command excutes, do the following sequence:"))
self.label.setText(_translate("CommandEditDialog", "When I say :"))
self.oneExe.setText(_translate("CommandEditDialog", "This command executes once"))
self.continueExe.setText(_translate("CommandEditDialog", "This command repeats continuously"))
self.repeatExe.setText(_translate("CommandEditDialog", "This command repeats"))
self.label_3.setText(_translate("CommandEditDialog", "times"))
self.keyBut.setText(_translate("CommandEditDialog", "Key Press"))
self.mouseBut.setText(_translate("CommandEditDialog", "Mouse"))
self.pauseBut.setText(_translate("CommandEditDialog", "Pause"))
self.otherBut.setText(_translate("CommandEditDialog", "Other"))
self.upBut.setText(_translate("CommandEditDialog", "Up"))
self.downBut.setText(_translate("CommandEditDialog", "Down"))
self.editBut.setText(_translate("CommandEditDialog", "Edit"))
self.deleteBut.setText(_translate("CommandEditDialog", "Delete"))
self.asyncChk.setText(_translate("CommandEditDialog", "Allow other commands to execute while this one is running"))
self.ok.setText(_translate("CommandEditDialog", "OK"))
self.cancel.setText(_translate("CommandEditDialog", "Cancel"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
CommandEditDialog = QtWidgets.QDialog()
ui = Ui_CommandEditDialog()
ui.setupUi(CommandEditDialog)
CommandEditDialog.show()
sys.exit(app.exec_())

107
ui_keyactioneditwnd.py Normal file
View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '.\keyactioneditwnd.ui'
#
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_KeyActionEditDialog(object):
def setupUi(self, KeyActionEditDialog):
KeyActionEditDialog.setObjectName("KeyActionEditDialog")
KeyActionEditDialog.resize(471, 164)
self.gridLayout = QtWidgets.QGridLayout(KeyActionEditDialog)
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(KeyActionEditDialog)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 2, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem1, 4, 0, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(20)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.press_releaseKey = QtWidgets.QRadioButton(KeyActionEditDialog)
self.press_releaseKey.setObjectName("press_releaseKey")
self.horizontalLayout_2.addWidget(self.press_releaseKey)
self.pressKey = QtWidgets.QRadioButton(KeyActionEditDialog)
self.pressKey.setObjectName("pressKey")
self.horizontalLayout_2.addWidget(self.pressKey)
self.releaseKey = QtWidgets.QRadioButton(KeyActionEditDialog)
self.releaseKey.setObjectName("releaseKey")
self.horizontalLayout_2.addWidget(self.releaseKey)
self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem2)
self.ok = QtWidgets.QPushButton(KeyActionEditDialog)
self.ok.setAutoDefault(False)
self.ok.setObjectName("ok")
self.horizontalLayout_3.addWidget(self.ok)
self.cancel = QtWidgets.QPushButton(KeyActionEditDialog)
self.cancel.setAutoDefault(False)
self.cancel.setObjectName("cancel")
self.horizontalLayout_3.addWidget(self.cancel)
self.gridLayout.addLayout(self.horizontalLayout_3, 5, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.ctrlBut = QtWidgets.QPushButton(KeyActionEditDialog)
font = QtGui.QFont()
font.setBold(False)
font.setWeight(50)
font.setStrikeOut(False)
self.ctrlBut.setFont(font)
self.ctrlBut.setAutoDefault(False)
self.ctrlBut.setObjectName("ctrlBut")
self.horizontalLayout.addWidget(self.ctrlBut)
self.altBut = QtWidgets.QPushButton(KeyActionEditDialog)
self.altBut.setAutoDefault(False)
self.altBut.setObjectName("altBut")
self.horizontalLayout.addWidget(self.altBut)
self.shiftBut = QtWidgets.QPushButton(KeyActionEditDialog)
self.shiftBut.setAutoDefault(False)
self.shiftBut.setObjectName("shiftBut")
self.horizontalLayout.addWidget(self.shiftBut)
self.winBut = QtWidgets.QPushButton(KeyActionEditDialog)
self.winBut.setAutoDefault(False)
self.winBut.setObjectName("winBut")
self.horizontalLayout.addWidget(self.winBut)
self.keyEdit = QtWidgets.QLineEdit(KeyActionEditDialog)
self.keyEdit.setAlignment(QtCore.Qt.AlignCenter)
self.keyEdit.setObjectName("keyEdit")
self.horizontalLayout.addWidget(self.keyEdit)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
self.retranslateUi(KeyActionEditDialog)
QtCore.QMetaObject.connectSlotsByName(KeyActionEditDialog)
def retranslateUi(self, KeyActionEditDialog):
_translate = QtCore.QCoreApplication.translate
KeyActionEditDialog.setWindowTitle(_translate("KeyActionEditDialog", "Key Action Dialog"))
self.label.setText(_translate("KeyActionEditDialog", "Key (Combination):"))
self.press_releaseKey.setText(_translate("KeyActionEditDialog", "Press and Release Key(s)"))
self.pressKey.setText(_translate("KeyActionEditDialog", "Press Key(s)"))
self.releaseKey.setText(_translate("KeyActionEditDialog", "Release Key(s)"))
self.ok.setText(_translate("KeyActionEditDialog", "OK"))
self.cancel.setText(_translate("KeyActionEditDialog", "Cancel"))
self.ctrlBut.setText(_translate("KeyActionEditDialog", "Ctrl"))
self.altBut.setText(_translate("KeyActionEditDialog", "Alt"))
self.shiftBut.setText(_translate("KeyActionEditDialog", "Shift"))
self.winBut.setText(_translate("KeyActionEditDialog", "Win"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
KeyActionEditDialog = QtWidgets.QDialog()
ui = Ui_KeyActionEditDialog()
ui.setupUi(KeyActionEditDialog)
KeyActionEditDialog.show()
sys.exit(app.exec_())

91
ui_mainwnd.py Normal file
View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwnd.ui'
#
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWidget(object):
def setupUi(self, MainWidget):
MainWidget.setObjectName("MainWidget")
MainWidget.resize(820, 166)
font = QtGui.QFont()
font.setPointSize(10)
MainWidget.setFont(font)
self.gridLayout_2 = QtWidgets.QGridLayout(MainWidget)
self.gridLayout_2.setContentsMargins(-1, 20, -1, -1)
self.gridLayout_2.setVerticalSpacing(20)
self.gridLayout_2.setObjectName("gridLayout_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(20)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.listeningChk = QtWidgets.QCheckBox(MainWidget)
self.listeningChk.setObjectName("listeningChk")
self.horizontalLayout_2.addWidget(self.listeningChk)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.ok = QtWidgets.QPushButton(MainWidget)
self.ok.setMinimumSize(QtCore.QSize(130, 0))
self.ok.setObjectName("ok")
self.horizontalLayout_2.addWidget(self.ok)
self.cancel = QtWidgets.QPushButton(MainWidget)
self.cancel.setMinimumSize(QtCore.QSize(130, 0))
self.cancel.setObjectName("cancel")
self.horizontalLayout_2.addWidget(self.cancel)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(20)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(MainWidget)
self.label.setMinimumSize(QtCore.QSize(60, 0))
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.profileCbx = QtWidgets.QComboBox(MainWidget)
self.profileCbx.setMinimumSize(QtCore.QSize(250, 0))
self.profileCbx.setObjectName("profileCbx")
self.horizontalLayout.addWidget(self.profileCbx)
self.addBut = QtWidgets.QPushButton(MainWidget)
self.addBut.setMinimumSize(QtCore.QSize(130, 0))
self.addBut.setObjectName("addBut")
self.horizontalLayout.addWidget(self.addBut)
self.editBut = QtWidgets.QPushButton(MainWidget)
self.editBut.setMinimumSize(QtCore.QSize(130, 0))
self.editBut.setObjectName("editBut")
self.horizontalLayout.addWidget(self.editBut)
self.removeBut = QtWidgets.QPushButton(MainWidget)
self.removeBut.setMinimumSize(QtCore.QSize(130, 0))
self.removeBut.setObjectName("removeBut")
self.horizontalLayout.addWidget(self.removeBut)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 1)
self.retranslateUi(MainWidget)
QtCore.QMetaObject.connectSlotsByName(MainWidget)
def retranslateUi(self, MainWidget):
_translate = QtCore.QCoreApplication.translate
MainWidget.setWindowTitle(_translate("MainWidget", "LinVAM"))
self.listeningChk.setText(_translate("MainWidget", "Enable Listening"))
self.ok.setText(_translate("MainWidget", "OK"))
self.cancel.setText(_translate("MainWidget", "Cancel"))
self.label.setText(_translate("MainWidget", "Profile:"))
self.addBut.setText(_translate("MainWidget", "Add"))
self.editBut.setText(_translate("MainWidget", "Edit"))
self.removeBut.setText(_translate("MainWidget", "Remove"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWidget = QtWidgets.QWidget()
ui = Ui_MainWidget()
ui.setupUi(MainWidget)
MainWidget.show()
sys.exit(app.exec_())

218
ui_mouseactioneditwnd.py Normal file
View File

@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mouseactioneditwnd.ui'
#
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MouseActionEditDialog(object):
def setupUi(self, MouseActionEditDialog):
MouseActionEditDialog.setObjectName("MouseActionEditDialog")
MouseActionEditDialog.resize(651, 268)
font = QtGui.QFont()
font.setPointSize(10)
MouseActionEditDialog.setFont(font)
self.gridLayout = QtWidgets.QGridLayout(MouseActionEditDialog)
self.gridLayout.setVerticalSpacing(20)
self.gridLayout.setObjectName("gridLayout")
self.mouseActionTabWidget = QtWidgets.QTabWidget(MouseActionEditDialog)
self.mouseActionTabWidget.setObjectName("mouseActionTabWidget")
self.clickActionTab = QtWidgets.QWidget()
self.clickActionTab.setObjectName("clickActionTab")
self.gridLayout_2 = QtWidgets.QGridLayout(self.clickActionTab)
self.gridLayout_2.setContentsMargins(-1, 20, -1, 9)
self.gridLayout_2.setObjectName("gridLayout_2")
self.leftClick = QtWidgets.QRadioButton(self.clickActionTab)
self.leftClick.setObjectName("leftClick")
self.gridLayout_2.addWidget(self.leftClick, 0, 0, 1, 1)
self.rightClick = QtWidgets.QRadioButton(self.clickActionTab)
self.rightClick.setObjectName("rightClick")
self.gridLayout_2.addWidget(self.rightClick, 0, 1, 1, 1)
self.middleClick = QtWidgets.QRadioButton(self.clickActionTab)
self.middleClick.setObjectName("middleClick")
self.gridLayout_2.addWidget(self.middleClick, 0, 2, 1, 1)
self.leftDclick = QtWidgets.QRadioButton(self.clickActionTab)
self.leftDclick.setObjectName("leftDclick")
self.gridLayout_2.addWidget(self.leftDclick, 1, 0, 1, 1)
self.rightDclick = QtWidgets.QRadioButton(self.clickActionTab)
self.rightDclick.setObjectName("rightDclick")
self.gridLayout_2.addWidget(self.rightDclick, 1, 1, 1, 1)
self.middleDclick = QtWidgets.QRadioButton(self.clickActionTab)
self.middleDclick.setObjectName("middleDclick")
self.gridLayout_2.addWidget(self.middleDclick, 1, 2, 1, 1)
self.leftDown = QtWidgets.QRadioButton(self.clickActionTab)
self.leftDown.setObjectName("leftDown")
self.gridLayout_2.addWidget(self.leftDown, 2, 0, 1, 1)
self.rightDown = QtWidgets.QRadioButton(self.clickActionTab)
self.rightDown.setObjectName("rightDown")
self.gridLayout_2.addWidget(self.rightDown, 2, 1, 1, 1)
self.middleDown = QtWidgets.QRadioButton(self.clickActionTab)
self.middleDown.setObjectName("middleDown")
self.gridLayout_2.addWidget(self.middleDown, 2, 2, 1, 1)
self.leftUp = QtWidgets.QRadioButton(self.clickActionTab)
self.leftUp.setObjectName("leftUp")
self.gridLayout_2.addWidget(self.leftUp, 3, 0, 1, 1)
self.rightUp = QtWidgets.QRadioButton(self.clickActionTab)
self.rightUp.setObjectName("rightUp")
self.gridLayout_2.addWidget(self.rightUp, 3, 1, 1, 1)
self.middleUp = QtWidgets.QRadioButton(self.clickActionTab)
self.middleUp.setObjectName("middleUp")
self.gridLayout_2.addWidget(self.middleUp, 3, 2, 1, 1)
self.mouseActionTabWidget.addTab(self.clickActionTab, "")
self.moveActionTab = QtWidgets.QWidget()
self.moveActionTab.setObjectName("moveActionTab")
self.gridLayout_3 = QtWidgets.QGridLayout(self.moveActionTab)
self.gridLayout_3.setContentsMargins(-1, 20, -1, -1)
self.gridLayout_3.setObjectName("gridLayout_3")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.moveTo = QtWidgets.QRadioButton(self.moveActionTab)
self.moveTo.setMinimumSize(QtCore.QSize(100, 0))
self.moveTo.setObjectName("moveTo")
self.horizontalLayout_2.addWidget(self.moveTo)
self.label_2 = QtWidgets.QLabel(self.moveActionTab)
self.label_2.setMinimumSize(QtCore.QSize(25, 0))
self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_2.setObjectName("label_2")
self.horizontalLayout_2.addWidget(self.label_2)
self.xEdit = QtWidgets.QLineEdit(self.moveActionTab)
self.xEdit.setObjectName("xEdit")
self.horizontalLayout_2.addWidget(self.xEdit)
self.label_3 = QtWidgets.QLabel(self.moveActionTab)
self.label_3.setMinimumSize(QtCore.QSize(25, 0))
self.label_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_3.setObjectName("label_3")
self.horizontalLayout_2.addWidget(self.label_3)
self.yEdit = QtWidgets.QLineEdit(self.moveActionTab)
self.yEdit.setObjectName("yEdit")
self.horizontalLayout_2.addWidget(self.yEdit)
self.gridLayout_3.addLayout(self.horizontalLayout_2, 1, 0, 1, 3)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.moveOffset = QtWidgets.QRadioButton(self.moveActionTab)
self.moveOffset.setMinimumSize(QtCore.QSize(100, 0))
self.moveOffset.setAutoExclusive(True)
self.moveOffset.setObjectName("moveOffset")
self.horizontalLayout_3.addWidget(self.moveOffset)
self.label_5 = QtWidgets.QLabel(self.moveActionTab)
self.label_5.setMinimumSize(QtCore.QSize(25, 0))
self.label_5.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_5.setObjectName("label_5")
self.horizontalLayout_3.addWidget(self.label_5)
self.xOffsetEdit = QtWidgets.QLineEdit(self.moveActionTab)
self.xOffsetEdit.setObjectName("xOffsetEdit")
self.horizontalLayout_3.addWidget(self.xOffsetEdit)
self.label_4 = QtWidgets.QLabel(self.moveActionTab)
self.label_4.setMinimumSize(QtCore.QSize(70, 0))
self.label_4.setObjectName("label_4")
self.horizontalLayout_3.addWidget(self.label_4)
self.label_6 = QtWidgets.QLabel(self.moveActionTab)
self.label_6.setMinimumSize(QtCore.QSize(25, 0))
self.label_6.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_6.setObjectName("label_6")
self.horizontalLayout_3.addWidget(self.label_6)
self.yOffsetEdit = QtWidgets.QLineEdit(self.moveActionTab)
self.yOffsetEdit.setObjectName("yOffsetEdit")
self.horizontalLayout_3.addWidget(self.yOffsetEdit)
self.label_7 = QtWidgets.QLabel(self.moveActionTab)
self.label_7.setMinimumSize(QtCore.QSize(70, 0))
self.label_7.setObjectName("label_7")
self.horizontalLayout_3.addWidget(self.label_7)
self.gridLayout_3.addLayout(self.horizontalLayout_3, 2, 0, 1, 6)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.scrollUp = QtWidgets.QRadioButton(self.moveActionTab)
self.scrollUp.setMinimumSize(QtCore.QSize(100, 0))
self.scrollUp.setAutoExclusive(True)
self.scrollUp.setObjectName("scrollUp")
self.horizontalLayout_4.addWidget(self.scrollUp)
self.scrollUpEdit = QtWidgets.QLineEdit(self.moveActionTab)
self.scrollUpEdit.setObjectName("scrollUpEdit")
self.horizontalLayout_4.addWidget(self.scrollUpEdit)
self.label_8 = QtWidgets.QLabel(self.moveActionTab)
self.label_8.setMinimumSize(QtCore.QSize(80, 0))
self.label_8.setObjectName("label_8")
self.horizontalLayout_4.addWidget(self.label_8)
self.scrollDown = QtWidgets.QRadioButton(self.moveActionTab)
self.scrollDown.setMinimumSize(QtCore.QSize(110, 0))
self.scrollDown.setAutoExclusive(True)
self.scrollDown.setObjectName("scrollDown")
self.horizontalLayout_4.addWidget(self.scrollDown)
self.scrollDownEdit = QtWidgets.QLineEdit(self.moveActionTab)
self.scrollDownEdit.setObjectName("scrollDownEdit")
self.horizontalLayout_4.addWidget(self.scrollDownEdit)
self.label_9 = QtWidgets.QLabel(self.moveActionTab)
self.label_9.setMinimumSize(QtCore.QSize(80, 0))
self.label_9.setObjectName("label_9")
self.horizontalLayout_4.addWidget(self.label_9)
self.gridLayout_3.addLayout(self.horizontalLayout_4, 3, 0, 1, 1)
self.mouseActionTabWidget.addTab(self.moveActionTab, "")
self.gridLayout.addWidget(self.mouseActionTabWidget, 0, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(20)
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.ok = QtWidgets.QPushButton(MouseActionEditDialog)
self.ok.setMinimumSize(QtCore.QSize(130, 0))
self.ok.setAutoDefault(False)
self.ok.setObjectName("ok")
self.horizontalLayout.addWidget(self.ok)
self.cancel = QtWidgets.QPushButton(MouseActionEditDialog)
self.cancel.setMinimumSize(QtCore.QSize(130, 0))
self.cancel.setAutoDefault(False)
self.cancel.setObjectName("cancel")
self.horizontalLayout.addWidget(self.cancel)
self.gridLayout.addLayout(self.horizontalLayout, 3, 0, 1, 1)
self.retranslateUi(MouseActionEditDialog)
self.mouseActionTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(MouseActionEditDialog)
def retranslateUi(self, MouseActionEditDialog):
_translate = QtCore.QCoreApplication.translate
MouseActionEditDialog.setWindowTitle(_translate("MouseActionEditDialog", "Dialog"))
self.leftClick.setText(_translate("MouseActionEditDialog", "Click left button"))
self.rightClick.setText(_translate("MouseActionEditDialog", "Click right button"))
self.middleClick.setText(_translate("MouseActionEditDialog", "Click middle button"))
self.leftDclick.setText(_translate("MouseActionEditDialog", "Double-click left button"))
self.rightDclick.setText(_translate("MouseActionEditDialog", "Double-click right button"))
self.middleDclick.setText(_translate("MouseActionEditDialog", "Double-click middle button"))
self.leftDown.setText(_translate("MouseActionEditDialog", "left button down"))
self.rightDown.setText(_translate("MouseActionEditDialog", "right button down"))
self.middleDown.setText(_translate("MouseActionEditDialog", "middle button down"))
self.leftUp.setText(_translate("MouseActionEditDialog", "left button up"))
self.rightUp.setText(_translate("MouseActionEditDialog", "right button up"))
self.middleUp.setText(_translate("MouseActionEditDialog", "middle button up"))
self.mouseActionTabWidget.setTabText(self.mouseActionTabWidget.indexOf(self.clickActionTab), _translate("MouseActionEditDialog", "Click"))
self.moveTo.setText(_translate("MouseActionEditDialog", "Move to:"))
self.label_2.setText(_translate("MouseActionEditDialog", "X"))
self.label_3.setText(_translate("MouseActionEditDialog", "Y"))
self.moveOffset.setText(_translate("MouseActionEditDialog", "Move offset"))
self.label_5.setText(_translate("MouseActionEditDialog", "X"))
self.label_4.setText(_translate("MouseActionEditDialog", "pixels"))
self.label_6.setText(_translate("MouseActionEditDialog", "Y"))
self.label_7.setText(_translate("MouseActionEditDialog", "pixels"))
self.scrollUp.setText(_translate("MouseActionEditDialog", "Scroll up"))
self.label_8.setText(_translate("MouseActionEditDialog", "pixels"))
self.scrollDown.setText(_translate("MouseActionEditDialog", "Scroll down"))
self.label_9.setText(_translate("MouseActionEditDialog", "pixels"))
self.mouseActionTabWidget.setTabText(self.mouseActionTabWidget.indexOf(self.moveActionTab), _translate("MouseActionEditDialog", "Move"))
self.ok.setText(_translate("MouseActionEditDialog", "OK"))
self.cancel.setText(_translate("MouseActionEditDialog", "Cancel"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MouseActionEditDialog = QtWidgets.QDialog()
ui = Ui_MouseActionEditDialog()
ui.setupUi(MouseActionEditDialog)
MouseActionEditDialog.show()
sys.exit(app.exec_())

69
ui_pauseactioneditwnd.py Normal file
View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'pauseactioneditwnd.ui'
#
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_PauseActionEditDialog(object):
def setupUi(self, PauseActionEditDialog):
PauseActionEditDialog.setObjectName("PauseActionEditDialog")
PauseActionEditDialog.resize(271, 133)
font = QtGui.QFont()
font.setPointSize(10)
PauseActionEditDialog.setFont(font)
self.gridLayout = QtWidgets.QGridLayout(PauseActionEditDialog)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(PauseActionEditDialog)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.secondEdit = QtWidgets.QLineEdit(PauseActionEditDialog)
self.secondEdit.setObjectName("secondEdit")
self.horizontalLayout.addWidget(self.secondEdit)
self.label_2 = QtWidgets.QLabel(PauseActionEditDialog)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(20)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.ok = QtWidgets.QPushButton(PauseActionEditDialog)
self.ok.setMinimumSize(QtCore.QSize(100, 0))
self.ok.setObjectName("ok")
self.horizontalLayout_2.addWidget(self.ok)
self.cancel = QtWidgets.QPushButton(PauseActionEditDialog)
self.cancel.setMinimumSize(QtCore.QSize(100, 0))
self.cancel.setObjectName("cancel")
self.horizontalLayout_2.addWidget(self.cancel)
self.gridLayout.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
self.retranslateUi(PauseActionEditDialog)
QtCore.QMetaObject.connectSlotsByName(PauseActionEditDialog)
def retranslateUi(self, PauseActionEditDialog):
_translate = QtCore.QCoreApplication.translate
PauseActionEditDialog.setWindowTitle(_translate("PauseActionEditDialog", "Pause Action Edit Dialog"))
self.label.setText(_translate("PauseActionEditDialog", "Pause for "))
self.label_2.setText(_translate("PauseActionEditDialog", "seconds"))
self.ok.setText(_translate("PauseActionEditDialog", "OK"))
self.cancel.setText(_translate("PauseActionEditDialog", "Cancel"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
PauseActionEditDialog = QtWidgets.QDialog()
ui = Ui_PauseActionEditDialog()
ui.setupUi(PauseActionEditDialog)
PauseActionEditDialog.show()
sys.exit(app.exec_())

109
ui_profileeditwnd.py Normal file
View File

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'profileeditwnd.ui'
#
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ProfileEditDialog(object):
def setupUi(self, ProfileEditDialog):
ProfileEditDialog.setObjectName("ProfileEditDialog")
ProfileEditDialog.resize(1191, 591)
self.gridLayout_2 = QtWidgets.QGridLayout(ProfileEditDialog)
self.gridLayout_2.setObjectName("gridLayout_2")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(ProfileEditDialog)
self.label.setMinimumSize(QtCore.QSize(60, 0))
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.profileNameEdit = QtWidgets.QLineEdit(ProfileEditDialog)
self.profileNameEdit.setObjectName("profileNameEdit")
self.horizontalLayout.addWidget(self.profileNameEdit)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 1)
self.groupBox = QtWidgets.QGroupBox(ProfileEditDialog)
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout.setObjectName("gridLayout")
self.cmdTable = QtWidgets.QTableWidget(self.groupBox)
self.cmdTable.setMinimumSize(QtCore.QSize(0, 200))
font = QtGui.QFont()
font.setPointSize(11)
self.cmdTable.setFont(font)
self.cmdTable.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.cmdTable.setDragDropOverwriteMode(False)
self.cmdTable.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.cmdTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.cmdTable.setColumnCount(2)
self.cmdTable.setObjectName("cmdTable")
self.cmdTable.setRowCount(0)
self.cmdTable.horizontalHeader().setDefaultSectionSize(160)
self.cmdTable.horizontalHeader().setMinimumSectionSize(160)
self.gridLayout.addWidget(self.cmdTable, 0, 0, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(20)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.newCmd = QtWidgets.QPushButton(self.groupBox)
self.newCmd.setMinimumSize(QtCore.QSize(130, 0))
self.newCmd.setObjectName("newCmd")
self.horizontalLayout_2.addWidget(self.newCmd)
self.editCmd = QtWidgets.QPushButton(self.groupBox)
self.editCmd.setMinimumSize(QtCore.QSize(130, 0))
self.editCmd.setObjectName("editCmd")
self.horizontalLayout_2.addWidget(self.editCmd)
self.deleteCmd = QtWidgets.QPushButton(self.groupBox)
self.deleteCmd.setMinimumSize(QtCore.QSize(130, 0))
self.deleteCmd.setObjectName("deleteCmd")
self.horizontalLayout_2.addWidget(self.deleteCmd)
self.gridLayout.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
self.gridLayout_2.addWidget(self.groupBox, 1, 0, 1, 1)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setSpacing(20)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem3)
self.ok = QtWidgets.QPushButton(ProfileEditDialog)
self.ok.setMinimumSize(QtCore.QSize(130, 0))
self.ok.setObjectName("ok")
self.horizontalLayout_3.addWidget(self.ok)
self.cancel = QtWidgets.QPushButton(ProfileEditDialog)
self.cancel.setMinimumSize(QtCore.QSize(130, 0))
self.cancel.setObjectName("cancel")
self.horizontalLayout_3.addWidget(self.cancel)
self.gridLayout_2.addLayout(self.horizontalLayout_3, 2, 0, 1, 1)
self.retranslateUi(ProfileEditDialog)
QtCore.QMetaObject.connectSlotsByName(ProfileEditDialog)
def retranslateUi(self, ProfileEditDialog):
_translate = QtCore.QCoreApplication.translate
ProfileEditDialog.setWindowTitle(_translate("ProfileEditDialog", "Dialog"))
self.label.setText(_translate("ProfileEditDialog", "Profile:"))
self.groupBox.setTitle(_translate("ProfileEditDialog", "Commands"))
self.newCmd.setText(_translate("ProfileEditDialog", "New"))
self.editCmd.setText(_translate("ProfileEditDialog", "Edit"))
self.deleteCmd.setText(_translate("ProfileEditDialog", "Delete"))
self.ok.setText(_translate("ProfileEditDialog", "OK"))
self.cancel.setText(_translate("ProfileEditDialog", "Cancel"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ProfileEditDialog = QtWidgets.QDialog()
ui = Ui_ProfileEditDialog()
ui.setupUi(ProfileEditDialog)
ProfileEditDialog.show()
sys.exit(app.exec_())