Browse Source

initial commit

master
rose-jinyang 2 years ago
parent
commit
821cee0db2
  1. 168
      commandeditwnd.py
  2. 390
      commandeditwnd.ui
  3. 152
      keyactioneditwnd.py
  4. 177
      keyactioneditwnd.ui
  5. 1155
      keyboard/__init__.py
  6. 13
      keyboard/__main__.py
  7. BIN
      keyboard/__pycache__/__init__.cpython-36.pyc
  8. BIN
      keyboard/__pycache__/_canonical_names.cpython-36.pyc
  9. BIN
      keyboard/__pycache__/_generic.cpython-36.pyc
  10. BIN
      keyboard/__pycache__/_keyboard_event.cpython-36.pyc
  11. BIN
      keyboard/__pycache__/_nixcommon.cpython-36.pyc
  12. BIN
      keyboard/__pycache__/_nixkeyboard.cpython-36.pyc
  13. BIN
      keyboard/__pycache__/_winkeyboard.cpython-36.pyc
  14. 1246
      keyboard/_canonical_names.py
  15. 442
      keyboard/_darwinkeyboard.py
  16. 173
      keyboard/_darwinmouse.py
  17. 73
      keyboard/_generic.py
  18. 53
      keyboard/_keyboard_event.py
  19. 827
      keyboard/_keyboard_tests.py
  20. 20
      keyboard/_mouse_event.py
  21. 271
      keyboard/_mouse_tests.py
  22. 174
      keyboard/_nixcommon.py
  23. 183
      keyboard/_nixkeyboard.py
  24. 130
      keyboard/_nixmouse.py
  25. 620
      keyboard/_winkeyboard.py
  26. 201
      keyboard/_winmouse.py
  27. 232
      keyboard/mouse.py
  28. 231
      main.py
  29. 167
      mainwnd.ui
  30. 117
      mouseactioneditwnd.py
  31. 402
      mouseactioneditwnd.ui
  32. 27
      pauseactioneditwnd.py
  33. 93
      pauseactioneditwnd.ui
  34. 221
      profileeditwnd.py
  35. 219
      profileeditwnd.ui
  36. 318
      profileexecutor.py
  37. BIN
      profiles.dat
  38. 41
      pynput/__init__.py
  39. BIN
      pynput/__pycache__/__init__.cpython-36.pyc
  40. 19
      pynput/_info.py
  41. 296
      pynput/_util/__init__.py
  42. BIN
      pynput/_util/__pycache__/__init__.cpython-36.pyc
  43. BIN
      pynput/_util/__pycache__/win32.cpython-36.pyc
  44. BIN
      pynput/_util/__pycache__/win32_vks.cpython-36.pyc
  45. BIN
      pynput/_util/__pycache__/xorg.cpython-36.pyc
  46. BIN
      pynput/_util/__pycache__/xorg_keysyms.cpython-36.pyc
  47. 273
      pynput/_util/darwin.py
  48. 641
      pynput/_util/win32.py
  49. 179
      pynput/_util/win32_vks.py
  50. 480
      pynput/_util/xorg.py
  51. 1715
      pynput/_util/xorg_keysyms.py
  52. 56
      pynput/mouse/__init__.py
  53. BIN
      pynput/mouse/__pycache__/__init__.cpython-36.pyc
  54. BIN
      pynput/mouse/__pycache__/_base.cpython-36.pyc
  55. BIN
      pynput/mouse/__pycache__/_win32.cpython-36.pyc
  56. BIN
      pynput/mouse/__pycache__/_xorg.cpython-36.pyc
  57. 263
      pynput/mouse/_base.py
  58. 215
      pynput/mouse/_darwin.py
  59. 195
      pynput/mouse/_win32.py
  60. 174
      pynput/mouse/_xorg.py
  61. 186
      ui_commandeditwnd.py
  62. 107
      ui_keyactioneditwnd.py
  63. 91
      ui_mainwnd.py
  64. 218
      ui_mouseactioneditwnd.py
  65. 69
      ui_pauseactioneditwnd.py
  66. 109
      ui_profileeditwnd.py

168
commandeditwnd.py

@ -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

@ -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

@ -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

@ -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

File diff suppressed because it is too large

13
keyboard/__main__.py

@ -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())

BIN
keyboard/__pycache__/__init__.cpython-36.pyc

diff.bin_not_shown

BIN
keyboard/__pycache__/_canonical_names.cpython-36.pyc

diff.bin_not_shown

BIN
keyboard/__pycache__/_generic.cpython-36.pyc

diff.bin_not_shown

BIN
keyboard/__pycache__/_keyboard_event.cpython-36.pyc

diff.bin_not_shown

BIN
keyboard/__pycache__/_nixcommon.cpython-36.pyc

diff.bin_not_shown

BIN
keyboard/__pycache__/_nixkeyboard.cpython-36.pyc

diff.bin_not_shown

BIN
keyboard/__pycache__/_winkeyboard.cpython-36.pyc

diff.bin_not_shown

1246
keyboard/_canonical_names.py

File diff suppressed because it is too large

442
keyboard/_darwinkeyboard.py

@ -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

@ -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

@ -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.lo