Added emoji support, refactored, internalized pyautogui
This commit is contained in:
parent
9eea74f841
commit
a3496263b9
|
@ -1,6 +1,10 @@
|
|||
# Mouse_Keyboard
|
||||
# Mouse-Keyboard
|
||||
An onscreen keyboard for the mouse.
|
||||
|
||||
### Requirements
|
||||
* PyGObject
|
||||
* python-xlib
|
||||
|
||||
# TODO
|
||||
<li>Get save and execute of custom commands working.</li>
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Python imports
|
||||
import builtins, threading
|
||||
import os
|
||||
import builtins
|
||||
import threading
|
||||
|
||||
# Lib imports
|
||||
|
||||
|
@ -24,6 +26,9 @@ def daemon_threaded_wrapper(fn):
|
|||
|
||||
|
||||
|
||||
class MissingConfigError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Pyautogui_Controller(ControlMixin):
|
||||
def __init__(self):
|
||||
|
@ -38,39 +43,69 @@ keys_json = {
|
|||
"keys": {
|
||||
"row1": {
|
||||
"pKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
||||
"sKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
||||
"eKeys": ['🤩', '\U0001F600', '', '', '', '', '', '', '', '']
|
||||
"sKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
|
||||
},
|
||||
"row2": {
|
||||
"pKeys": ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
|
||||
"sKeys": ['\\', '^', '#', '$', '%', '&', '-', '_', '"', '*'],
|
||||
"eKeys": ['', '', '', '', '', '', '', '', '', '']
|
||||
"sKeys": ['\\', '^', '#', '$', '%', '&', '-', '_', '"', '*']
|
||||
},
|
||||
"row3": {
|
||||
"pKeys": ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', "'"],
|
||||
"sKeys": ['/', '|', ':', '=', '+', '', '', '', ';', '!'],
|
||||
"eKeys": ['', '', '', '', '', '', '', '', '', '']
|
||||
"sKeys": ['/', '|', ':', '=', '+', '', '', '', ';', '!']
|
||||
},
|
||||
"row4": {
|
||||
"pKeys": ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?'],
|
||||
"sKeys": ['', '', '<', '>', '[', ']', '(', ')', '{', '}'],
|
||||
"eKeys": ['', '', '', '', '', '', '', '', '', '']
|
||||
"sKeys": ['', '', '<', '>', '[', ']', '(', ')', '{', '}']
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||
# __builtins__.update({"event_system": Builtins()})
|
||||
builtins.app_name = "Mouse Keyboard"
|
||||
builtins.app_name = "Mouse-Keyboard"
|
||||
builtins.threaded = threaded_wrapper
|
||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||
builtins.keys_set = keys_json
|
||||
builtins.trace_debug = False
|
||||
builtins.debug = False
|
||||
builtins.app_settings = None
|
||||
builtins.get_clipboard = ['xclip','-selection', 'clipboard', '-o']
|
||||
builtins.set_clipboard = ['xclip','-selection','clipboard']
|
||||
builtins.endpoint_registry = EndpointRegistry()
|
||||
builtins.event_system = EventSystem()
|
||||
builtins.typwriter = Pyautogui_Controller()
|
||||
|
||||
|
||||
|
||||
_USER_HOME = os.path.expanduser('~')
|
||||
_USR_PATH = f"/usr/share/{app_name.lower()}"
|
||||
_CONFIG_PATH = f"{_USER_HOME}/.config/{app_name.lower()}"
|
||||
_ICON_FILE = f"{_CONFIG_PATH}/icons/{app_name.lower()}.png"
|
||||
_CSS_FILE = f"{_CONFIG_PATH}/stylesheet.css"
|
||||
_EMOJI_FILE = f"{_CONFIG_PATH}/emoji.json"
|
||||
|
||||
|
||||
if not os.path.exists(_ICON_FILE):
|
||||
_ICON_FILE = f"{_USR_PATH}/icons/{app_name.lower()}.png"
|
||||
if not os.path.exists(_ICON_FILE):
|
||||
print(_ICON_FILE)
|
||||
raise MissingConfigError("Unable to find the application icon.")
|
||||
|
||||
if not os.path.exists(_CSS_FILE):
|
||||
_CSS_FILE = f"{_USR_PATH}/stylesheet.css"
|
||||
if not os.path.exists(_CSS_FILE):
|
||||
raise MissingConfigError("Unable to find the stylesheet.")
|
||||
|
||||
if not os.path.exists(_EMOJI_FILE):
|
||||
_EMOJI_FILE = f"{_USR_PATH}/emoji.json"
|
||||
if not os.path.exists(_EMOJI_FILE):
|
||||
raise MissingConfigError("Unable to find the stylesheet.")
|
||||
|
||||
|
||||
|
||||
builtins.CONFIG_PATH = _CONFIG_PATH
|
||||
builtins.ICON_FILE = _ICON_FILE
|
||||
builtins.CSS_FILE = _CSS_FILE
|
||||
builtins.EMOJI_FILE = _EMOJI_FILE
|
||||
|
|
|
@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.defined_keys import Tab_Key
|
||||
from ..widgets.defined_keys import Symbols_Key
|
||||
from ..widgets.defined_keys import Del_Key
|
||||
from ..widgets.defined_keys import Ctrl_Key
|
||||
from ..widgets.defined_keys import Shift_Key
|
||||
|
@ -26,7 +26,7 @@ class Button_Box(Gtk.ButtonBox):
|
|||
def __init__(self):
|
||||
super(Button_Box, self).__init__()
|
||||
|
||||
for key in [Tab_Key(), Del_Key(), Ctrl_Key(), Shift_Key(), Alt_Key(), PrtSc_Key()]:
|
||||
for key in [Symbols_Key(), Del_Key(), Ctrl_Key(), Shift_Key(), Alt_Key(), PrtSc_Key()]:
|
||||
self.add(key)
|
||||
|
||||
|
||||
|
|
|
@ -38,17 +38,15 @@ class Keys_Column(Gtk.Box):
|
|||
for child in children:
|
||||
pKeys = keys[child]["pKeys"]
|
||||
sKeys = keys[child]["sKeys"]
|
||||
eKeys = keys[child]["eKeys"]
|
||||
|
||||
row_box = self.add_row()
|
||||
if len(pKeys) == len(sKeys) and len(pKeys) == len(eKeys):
|
||||
if len(pKeys) == len(sKeys):
|
||||
for i in range(10):
|
||||
pkey = pKeys[i]
|
||||
sKey = sKeys[i]
|
||||
eKey = eKeys[i]
|
||||
row_box.add(Key(pkey, sKey, eKey))
|
||||
row_box.add(Key(pkey, sKey))
|
||||
else:
|
||||
raise KeyboardRowMatchError("A row in keys_json has missmatched pKeys, sKeys, or eKeys lengths.")
|
||||
raise KeyboardRowMatchError("A row in keys_json has missmatched pKeys to sKeys lengths.")
|
||||
|
||||
self.add(Bottom_Key_Row())
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from gi.repository import Gtk
|
|||
|
||||
# Application imports
|
||||
from ..widgets.defined_keys import Esc_Key
|
||||
from ..widgets.defined_keys import Symbols_Key
|
||||
from ..widgets.defined_keys import Tab_Key
|
||||
from ..widgets.defined_keys import CAPS_Key
|
||||
|
||||
|
||||
|
@ -21,7 +21,7 @@ class Left_Column(Gtk.Box):
|
|||
|
||||
self.setup_styling()
|
||||
|
||||
for key in [Symbols_Key(), Esc_Key(), CAPS_Key()]:
|
||||
for key in [Tab_Key(), Esc_Key(), CAPS_Key()]:
|
||||
self.add(key)
|
||||
|
||||
self.show_all()
|
||||
|
|
|
@ -6,12 +6,14 @@ gi.require_version('Gtk', '3.0')
|
|||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.defined_keys import Emoji_Keys
|
||||
from ..widgets.emoji_popover import Emoji_Popover
|
||||
from ..widgets.defined_keys import Emoji_Key
|
||||
from ..widgets.defined_keys import Backspace_Key
|
||||
from ..widgets.defined_keys import Enter_Key
|
||||
|
||||
|
||||
|
||||
|
||||
class Right_Column(Gtk.Box):
|
||||
"""docstring for Right_Column."""
|
||||
|
||||
|
@ -20,7 +22,14 @@ class Right_Column(Gtk.Box):
|
|||
|
||||
self.setup_styling()
|
||||
|
||||
for key in [Emoji_Keys(), Backspace_Key(), Enter_Key()]:
|
||||
emoji_popover = Emoji_Popover()
|
||||
emoji_key = Emoji_Key(emoji_popover)
|
||||
|
||||
emoji_popover.set_parent_key(emoji_key)
|
||||
emoji_popover.set_relative_to(emoji_key)
|
||||
emoji_popover.set_constrain_to(0) # LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3
|
||||
|
||||
for key in [emoji_key, Backspace_Key(), Enter_Key()]:
|
||||
self.add(key)
|
||||
|
||||
self.show_all()
|
||||
|
|
|
@ -63,21 +63,25 @@ class Backspace_Key(Key):
|
|||
def _clicked(self, widget = None):
|
||||
typwriter.press_special_keys(self.get_label())
|
||||
|
||||
class Emoji_Keys(Key):
|
||||
def __init__(self):
|
||||
super(Emoji_Keys, self).__init__("Emoji", "Emoji", iscontrol=True)
|
||||
class Emoji_Key(Key):
|
||||
def __init__(self, emoji_popover):
|
||||
super(Emoji_Key, self).__init__("Emoji", "Emoji", iscontrol=True)
|
||||
|
||||
self._ctx = self.get_style_context()
|
||||
self._emoji_popover = emoji_popover
|
||||
|
||||
def setup_signals(self):
|
||||
self.connect("released", self._clicked)
|
||||
|
||||
def _clicked(self, widget = None):
|
||||
ctx = widget.get_style_context()
|
||||
ctx.remove_class("toggled_bttn") if ctx.has_class("toggled_bttn") else ctx.add_class("toggled_bttn")
|
||||
self._ctx.add_class("toggled_bttn")
|
||||
self._emoji_popover.popup()
|
||||
|
||||
def unset_selected(self, widget = None):
|
||||
self._ctx.remove_class("toggled_bttn")
|
||||
|
||||
|
||||
|
||||
key_columns = self.get_parent().get_parent().get_children()[1]
|
||||
for row in key_columns.get_children():
|
||||
for key in row:
|
||||
key.emit("toggle-emoji-keys", ())
|
||||
|
||||
class Enter_Key(Key):
|
||||
def __init__(self):
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
# Python imports
|
||||
from collections import defaultdict
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from .key import Key
|
||||
|
||||
|
||||
|
||||
|
||||
class Emoji_Notebook(Gtk.Notebook):
|
||||
"""docstring for Emoji_Notebook."""
|
||||
|
||||
def __init__(self):
|
||||
super(Emoji_Notebook, self).__init__()
|
||||
|
||||
self.load_ui( self.get_data(EMOJI_FILE) )
|
||||
|
||||
self.setup_styling()
|
||||
self.show_all()
|
||||
|
||||
|
||||
def setup_styling(self):
|
||||
self.set_current_page(0)
|
||||
self.set_scrollable(True)
|
||||
|
||||
def get_data(self, file):
|
||||
emoji_grouping = defaultdict(list)
|
||||
|
||||
with open(file, 'r') as f:
|
||||
emoji_data = json.load(f)
|
||||
for emoji in emoji_data:
|
||||
category = emoji['category']
|
||||
del emoji['category']
|
||||
del emoji['unicode_version']
|
||||
del emoji['ios_version']
|
||||
emoji_grouping[category].append(emoji)
|
||||
|
||||
return emoji_grouping
|
||||
|
||||
def load_ui(self, emoji_grouping):
|
||||
width = 1
|
||||
height = 1
|
||||
for group in emoji_grouping:
|
||||
tab_widget = Gtk.Label(label=group)
|
||||
scroll, grid = self.create_scroll_and_grid()
|
||||
|
||||
top = 0
|
||||
left = 0
|
||||
for emoji in emoji_grouping[group]:
|
||||
key = Key(emoji["emoji"], emoji["emoji"])
|
||||
key._is_emoji = True
|
||||
grid.attach(key, left, top, width, height)
|
||||
|
||||
left += 1
|
||||
if left > 8:
|
||||
left = 0
|
||||
top += 1
|
||||
|
||||
self.append_page(scroll, tab_widget)
|
||||
self.set_tab_reorderable(scroll, False)
|
||||
self.set_tab_detachable(scroll, False)
|
||||
|
||||
def create_scroll_and_grid(self):
|
||||
scroll = Gtk.ScrolledWindow()
|
||||
grid = Gtk.Grid()
|
||||
scroll.add(grid)
|
||||
|
||||
return scroll, grid
|
||||
|
||||
|
||||
|
||||
class Emoji_Popover(Gtk.Popover):
|
||||
"""docstring for Emoji_Popover."""
|
||||
|
||||
def __init__(self):
|
||||
super(Emoji_Popover, self).__init__()
|
||||
|
||||
emoji_notebook = Emoji_Notebook()
|
||||
self.add(emoji_notebook)
|
||||
self.set_default_widget(emoji_notebook)
|
||||
self.setup_styling()
|
||||
|
||||
self._emoji_key = None
|
||||
|
||||
|
||||
def setup_styling(self):
|
||||
self.set_vexpand(True)
|
||||
self.set_size_request(480, 280)
|
||||
|
||||
def setup_signals(self):
|
||||
self.connect("closed", self._emoji_key.unset_selected)
|
||||
|
||||
def set_parent_key(self, emoji_key):
|
||||
self._emoji_key = emoji_key
|
||||
self.setup_signals()
|
|
@ -9,13 +9,12 @@ from gi.repository import Gtk
|
|||
|
||||
|
||||
class Key(Gtk.Button or Gtk.ToggleButton):
|
||||
def __init__(self, primary = "NULL", secondary = "NULL", emoji = "NULL", iscontrol=False):
|
||||
def __init__(self, primary = "NULL", secondary = "NULL", iscontrol=False):
|
||||
super(Key, self).__init__()
|
||||
|
||||
self.iscontrol = iscontrol
|
||||
self._primary_symbol = primary
|
||||
self._secondary_symbol = secondary
|
||||
self._emoji_symbol = emoji
|
||||
self._is_upper = False
|
||||
self._is_symbol = False
|
||||
self._is_emoji = False
|
||||
|
@ -30,13 +29,17 @@ class Key(Gtk.Button or Gtk.ToggleButton):
|
|||
|
||||
def setup_signals(self):
|
||||
self.connect("released", self._do_type)
|
||||
# self.connect("toggle-caps", self.toggle_caps)
|
||||
# self.connect("toggle-symbol-keys", self.toggle_symbol_keys)
|
||||
self.connect("toggle-emoji-keys", self.toggle_emoji_keys)
|
||||
|
||||
def _do_type(self, widget = None):
|
||||
key = self.get_label().strip()
|
||||
if not self._is_emoji:
|
||||
typwriter.type(key)
|
||||
else:
|
||||
typwriter.set_clipboard_data(key, "utf-16")
|
||||
typwriter.isCtrlOn = True
|
||||
typwriter.type('v')
|
||||
typwriter.isCtrlOn = False
|
||||
|
||||
def _do_press_special_key(self, widget = None):
|
||||
key = self.get_label()
|
||||
|
@ -51,18 +54,13 @@ class Key(Gtk.Button or Gtk.ToggleButton):
|
|||
self._is_symbol = not self._is_symbol
|
||||
if self._is_symbol:
|
||||
self.set_label(self._secondary_symbol)
|
||||
elif self._is_emoji:
|
||||
self.set_label(self._emoji_symbol)
|
||||
else:
|
||||
self.set_label(self._primary_symbol.upper()) if self._is_upper else self.set_label(self._primary_symbol.lower())
|
||||
|
||||
# NOTE: Might use name attrib on widgets and de-duplicate this and the above logic.
|
||||
def toggle_emoji_keys(self, widget = None, eve = None):
|
||||
if not self.iscontrol:
|
||||
self._is_emoji = not self._is_emoji
|
||||
if self._is_emoji:
|
||||
self.set_label(self._emoji_symbol)
|
||||
elif self._is_symbol:
|
||||
if self._is_symbol:
|
||||
self.set_label(self._secondary_symbol)
|
||||
else:
|
||||
self.set_label(self._primary_symbol.upper()) if self._is_upper else self.set_label(self._primary_symbol.lower())
|
||||
|
|
|
@ -16,9 +16,6 @@ from .container import Container
|
|||
|
||||
|
||||
|
||||
class MissingConfigError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Window(SignalsMixin, Gtk.ApplicationWindow):
|
||||
"""docstring for Window."""
|
||||
|
@ -26,22 +23,6 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
|||
def __init__(self, args, unknownargs):
|
||||
super(Window, self).__init__()
|
||||
|
||||
self._USER_HOME = os.path.expanduser('~')
|
||||
self._USR_PATH = f"/usr/share/{app_name.lower()}"
|
||||
self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
|
||||
self._ICON_FILE = f"{self._CONFIG_PATH}/icon.png"
|
||||
self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css"
|
||||
|
||||
if not os.path.exists(self._ICON_FILE):
|
||||
self._ICON_FILE = f"{self._USR_PATH}/icon.png"
|
||||
if not os.path.exists(self._ICON_FILE):
|
||||
raise MissingConfigError("Unable to find the application icon.")
|
||||
|
||||
if not os.path.exists(self._ICON_FILE):
|
||||
self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css"
|
||||
if not os.path.exists(self._ICON_FILE):
|
||||
raise MissingConfigError("Unable to find the stylesheet.")
|
||||
|
||||
self.setup_win_settings()
|
||||
self.setup_styling()
|
||||
self.setup_signals()
|
||||
|
@ -50,12 +31,11 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
|||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def setup_signals(self):
|
||||
self.connect("delete-event", Gtk.main_quit)
|
||||
|
||||
def setup_win_settings(self):
|
||||
self.set_icon_from_file(self._ICON_FILE)
|
||||
self.set_icon_from_file(ICON_FILE)
|
||||
self.set_title(app_name)
|
||||
self.set_default_size(800, 200)
|
||||
self.set_keep_above(True)
|
||||
|
@ -77,7 +57,7 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
|||
self.connect("draw", self._area_draw)
|
||||
|
||||
css_provider = Gtk.CssProvider()
|
||||
css_provider.load_from_path(self._CSS_FILE)
|
||||
css_provider.load_from_path(CSS_FILE)
|
||||
screen = Gdk.Screen.get_default()
|
||||
style_context = Gtk.StyleContext()
|
||||
style_context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
from . import displayMousePosition
|
||||
displayMousePosition()
|
|
@ -0,0 +1,434 @@
|
|||
import time
|
||||
import sys
|
||||
|
||||
try:
|
||||
import Quartz
|
||||
except:
|
||||
assert False, "You must first install pyobjc-core and pyobjc: https://pyautogui.readthedocs.io/en/latest/install.html"
|
||||
import AppKit
|
||||
|
||||
import pyautogui
|
||||
from pyautogui import LEFT, MIDDLE, RIGHT
|
||||
|
||||
if sys.platform != 'darwin':
|
||||
raise Exception('The pyautogui_osx module should only be loaded on an OS X system.')
|
||||
|
||||
|
||||
|
||||
""" Taken from events.h
|
||||
/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
|
||||
|
||||
The *KB dictionaries in pyautogui map a string that can be passed to keyDown(),
|
||||
keyUp(), or press() into the code used for the OS-specific keyboard function.
|
||||
|
||||
They should always be lowercase, and the same keys should be used across all OSes."""
|
||||
keyboardMapping = dict([(key, None) for key in pyautogui.KEY_NAMES])
|
||||
keyboardMapping.update({
|
||||
'a': 0x00, # kVK_ANSI_A
|
||||
's': 0x01, # kVK_ANSI_S
|
||||
'd': 0x02, # kVK_ANSI_D
|
||||
'f': 0x03, # kVK_ANSI_F
|
||||
'h': 0x04, # kVK_ANSI_H
|
||||
'g': 0x05, # kVK_ANSI_G
|
||||
'z': 0x06, # kVK_ANSI_Z
|
||||
'x': 0x07, # kVK_ANSI_X
|
||||
'c': 0x08, # kVK_ANSI_C
|
||||
'v': 0x09, # kVK_ANSI_V
|
||||
'b': 0x0b, # kVK_ANSI_B
|
||||
'q': 0x0c, # kVK_ANSI_Q
|
||||
'w': 0x0d, # kVK_ANSI_W
|
||||
'e': 0x0e, # kVK_ANSI_E
|
||||
'r': 0x0f, # kVK_ANSI_R
|
||||
'y': 0x10, # kVK_ANSI_Y
|
||||
't': 0x11, # kVK_ANSI_T
|
||||
'1': 0x12, # kVK_ANSI_1
|
||||
'!': 0x12, # kVK_ANSI_1
|
||||
'2': 0x13, # kVK_ANSI_2
|
||||
'@': 0x13, # kVK_ANSI_2
|
||||
'3': 0x14, # kVK_ANSI_3
|
||||
'#': 0x14, # kVK_ANSI_3
|
||||
'4': 0x15, # kVK_ANSI_4
|
||||
'$': 0x15, # kVK_ANSI_4
|
||||
'6': 0x16, # kVK_ANSI_6
|
||||
'^': 0x16, # kVK_ANSI_6
|
||||
'5': 0x17, # kVK_ANSI_5
|
||||
'%': 0x17, # kVK_ANSI_5
|
||||
'=': 0x18, # kVK_ANSI_Equal
|
||||
'+': 0x18, # kVK_ANSI_Equal
|
||||
'9': 0x19, # kVK_ANSI_9
|
||||
'(': 0x19, # kVK_ANSI_9
|
||||
'7': 0x1a, # kVK_ANSI_7
|
||||
'&': 0x1a, # kVK_ANSI_7
|
||||
'-': 0x1b, # kVK_ANSI_Minus
|
||||
'_': 0x1b, # kVK_ANSI_Minus
|
||||
'8': 0x1c, # kVK_ANSI_8
|
||||
'*': 0x1c, # kVK_ANSI_8
|
||||
'0': 0x1d, # kVK_ANSI_0
|
||||
')': 0x1d, # kVK_ANSI_0
|
||||
']': 0x1e, # kVK_ANSI_RightBracket
|
||||
'}': 0x1e, # kVK_ANSI_RightBracket
|
||||
'o': 0x1f, # kVK_ANSI_O
|
||||
'u': 0x20, # kVK_ANSI_U
|
||||
'[': 0x21, # kVK_ANSI_LeftBracket
|
||||
'{': 0x21, # kVK_ANSI_LeftBracket
|
||||
'i': 0x22, # kVK_ANSI_I
|
||||
'p': 0x23, # kVK_ANSI_P
|
||||
'l': 0x25, # kVK_ANSI_L
|
||||
'j': 0x26, # kVK_ANSI_J
|
||||
"'": 0x27, # kVK_ANSI_Quote
|
||||
'"': 0x27, # kVK_ANSI_Quote
|
||||
'k': 0x28, # kVK_ANSI_K
|
||||
';': 0x29, # kVK_ANSI_Semicolon
|
||||
':': 0x29, # kVK_ANSI_Semicolon
|
||||
'\\': 0x2a, # kVK_ANSI_Backslash
|
||||
'|': 0x2a, # kVK_ANSI_Backslash
|
||||
',': 0x2b, # kVK_ANSI_Comma
|
||||
'<': 0x2b, # kVK_ANSI_Comma
|
||||
'/': 0x2c, # kVK_ANSI_Slash
|
||||
'?': 0x2c, # kVK_ANSI_Slash
|
||||
'n': 0x2d, # kVK_ANSI_N
|
||||
'm': 0x2e, # kVK_ANSI_M
|
||||
'.': 0x2f, # kVK_ANSI_Period
|
||||
'>': 0x2f, # kVK_ANSI_Period
|
||||
'`': 0x32, # kVK_ANSI_Grave
|
||||
'~': 0x32, # kVK_ANSI_Grave
|
||||
' ': 0x31, # kVK_Space
|
||||
'space': 0x31,
|
||||
'\r': 0x24, # kVK_Return
|
||||
'\n': 0x24, # kVK_Return
|
||||
'enter': 0x24, # kVK_Return
|
||||
'return': 0x24, # kVK_Return
|
||||
'\t': 0x30, # kVK_Tab
|
||||
'tab': 0x30, # kVK_Tab
|
||||
'backspace': 0x33, # kVK_Delete, which is "Backspace" on OS X.
|
||||
'\b': 0x33, # kVK_Delete, which is "Backspace" on OS X.
|
||||
'esc': 0x35, # kVK_Escape
|
||||
'escape': 0x35, # kVK_Escape
|
||||
'command': 0x37, # kVK_Command
|
||||
'shift': 0x38, # kVK_Shift
|
||||
'shiftleft': 0x38, # kVK_Shift
|
||||
'capslock': 0x39, # kVK_CapsLock
|
||||
'option': 0x3a, # kVK_Option
|
||||
'optionleft': 0x3a, # kVK_Option
|
||||
'alt': 0x3a, # kVK_Option
|
||||
'altleft': 0x3a, # kVK_Option
|
||||
'ctrl': 0x3b, # kVK_Control
|
||||
'ctrlleft': 0x3b, # kVK_Control
|
||||
'shiftright': 0x3c, # kVK_RightShift
|
||||
'optionright': 0x3d, # kVK_RightOption
|
||||
'ctrlright': 0x3e, # kVK_RightControl
|
||||
'fn': 0x3f, # kVK_Function
|
||||
'f17': 0x40, # kVK_F17
|
||||
'volumeup': 0x48, # kVK_VolumeUp
|
||||
'volumedown': 0x49, # kVK_VolumeDown
|
||||
'volumemute': 0x4a, # kVK_Mute
|
||||
'f18': 0x4f, # kVK_F18
|
||||
'f19': 0x50, # kVK_F19
|
||||
'f20': 0x5a, # kVK_F20
|
||||
'f5': 0x60, # kVK_F5
|
||||
'f6': 0x61, # kVK_F6
|
||||
'f7': 0x62, # kVK_F7
|
||||
'f3': 0x63, # kVK_F3
|
||||
'f8': 0x64, # kVK_F8
|
||||
'f9': 0x65, # kVK_F9
|
||||
'f11': 0x67, # kVK_F11
|
||||
'f13': 0x69, # kVK_F13
|
||||
'f16': 0x6a, # kVK_F16
|
||||
'f14': 0x6b, # kVK_F14
|
||||
'f10': 0x6d, # kVK_F10
|
||||
'f12': 0x6f, # kVK_F12
|
||||
'f15': 0x71, # kVK_F15
|
||||
'help': 0x72, # kVK_Help
|
||||
'home': 0x73, # kVK_Home
|
||||
'pageup': 0x74, # kVK_PageUp
|
||||
'pgup': 0x74, # kVK_PageUp
|
||||
'del': 0x75, # kVK_ForwardDelete
|
||||
'delete': 0x75, # kVK_ForwardDelete
|
||||
'f4': 0x76, # kVK_F4
|
||||
'end': 0x77, # kVK_End
|
||||
'f2': 0x78, # kVK_F2
|
||||
'pagedown': 0x79, # kVK_PageDown
|
||||
'pgdn': 0x79, # kVK_PageDown
|
||||
'f1': 0x7a, # kVK_F1
|
||||
'left': 0x7b, # kVK_LeftArrow
|
||||
'right': 0x7c, # kVK_RightArrow
|
||||
'down': 0x7d, # kVK_DownArrow
|
||||
'up': 0x7e, # kVK_UpArrow
|
||||
'yen': 0x5d, # kVK_JIS_Yen
|
||||
#'underscore' : 0x5e, # kVK_JIS_Underscore (only applies to Japanese keyboards)
|
||||
#'comma': 0x5f, # kVK_JIS_KeypadComma (only applies to Japanese keyboards)
|
||||
'eisu': 0x66, # kVK_JIS_Eisu
|
||||
'kana': 0x68, # kVK_JIS_Kana
|
||||
})
|
||||
|
||||
"""
|
||||
# TODO - additional key codes to add
|
||||
kVK_ANSI_KeypadDecimal = 0x41,
|
||||
kVK_ANSI_KeypadMultiply = 0x43,
|
||||
kVK_ANSI_KeypadPlus = 0x45,
|
||||
kVK_ANSI_KeypadClear = 0x47,
|
||||
kVK_ANSI_KeypadDivide = 0x4B,
|
||||
kVK_ANSI_KeypadEnter = 0x4C,
|
||||
kVK_ANSI_KeypadMinus = 0x4E,
|
||||
kVK_ANSI_KeypadEquals = 0x51,
|
||||
kVK_ANSI_Keypad0 = 0x52,
|
||||
kVK_ANSI_Keypad1 = 0x53,
|
||||
kVK_ANSI_Keypad2 = 0x54,
|
||||
kVK_ANSI_Keypad3 = 0x55,
|
||||
kVK_ANSI_Keypad4 = 0x56,
|
||||
kVK_ANSI_Keypad5 = 0x57,
|
||||
kVK_ANSI_Keypad6 = 0x58,
|
||||
kVK_ANSI_Keypad7 = 0x59,
|
||||
kVK_ANSI_Keypad8 = 0x5B,
|
||||
kVK_ANSI_Keypad9 = 0x5C,
|
||||
"""
|
||||
|
||||
# add mappings for uppercase letters
|
||||
for c in 'abcdefghijklmnopqrstuvwxyz':
|
||||
keyboardMapping[c.upper()] = keyboardMapping[c]
|
||||
|
||||
# Taken from ev_keymap.h
|
||||
# http://www.opensource.apple.com/source/IOHIDFamily/IOHIDFamily-86.1/IOHIDSystem/IOKit/hidsystem/ev_keymap.h
|
||||
special_key_translate_table = {
|
||||
'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 _keyDown(key):
|
||||
if key not in keyboardMapping or keyboardMapping[key] is None:
|
||||
return
|
||||
|
||||
if key in special_key_translate_table:
|
||||
_specialKeyEvent(key, 'down')
|
||||
else:
|
||||
_normalKeyEvent(key, 'down')
|
||||
|
||||
def _keyUp(key):
|
||||
if key not in keyboardMapping or keyboardMapping[key] is None:
|
||||
return
|
||||
|
||||
if key in special_key_translate_table:
|
||||
_specialKeyEvent(key, 'up')
|
||||
else:
|
||||
_normalKeyEvent(key, 'up')
|
||||
|
||||
|
||||
def _normalKeyEvent(key, upDown):
|
||||
assert upDown in ('up', 'down'), "upDown argument must be 'up' or 'down'"
|
||||
|
||||
try:
|
||||
if pyautogui.isShiftCharacter(key):
|
||||
key_code = keyboardMapping[key.lower()]
|
||||
|
||||
event = Quartz.CGEventCreateKeyboardEvent(None,
|
||||
keyboardMapping['shift'], upDown == 'down')
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
|
||||
# Tiny sleep to let OS X catch up on us pressing shift
|
||||
time.sleep(pyautogui.DARWIN_CATCH_UP_TIME)
|
||||
|
||||
else:
|
||||
key_code = keyboardMapping[key]
|
||||
|
||||
event = Quartz.CGEventCreateKeyboardEvent(None, key_code, upDown == 'down')
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
|
||||
time.sleep(pyautogui.DARWIN_CATCH_UP_TIME)
|
||||
|
||||
# TODO - wait, is the shift key's keyup not done?
|
||||
# TODO - get rid of this try-except.
|
||||
|
||||
except KeyError:
|
||||
raise RuntimeError("Key %s not implemented." % (key))
|
||||
|
||||
def _specialKeyEvent(key, upDown):
|
||||
""" Helper method for special keys.
|
||||
|
||||
Source: http://stackoverflow.com/questions/11045814/emulate-media-key-press-on-mac
|
||||
"""
|
||||
assert upDown in ('up', 'down'), "upDown argument must be 'up' or 'down'"
|
||||
|
||||
key_code = special_key_translate_table[key]
|
||||
|
||||
ev = AppKit.NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
|
||||
Quartz.NSSystemDefined, # type
|
||||
(0,0), # location
|
||||
0xa00 if upDown == 'down' else 0xb00, # flags
|
||||
0, # timestamp
|
||||
0, # window
|
||||
0, # ctx
|
||||
8, # subtype
|
||||
(key_code << 16) | ((0xa if upDown == 'down' else 0xb) << 8), # data1
|
||||
-1 # data2
|
||||
)
|
||||
|
||||
Quartz.CGEventPost(0, ev.CGEvent())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _position():
|
||||
loc = AppKit.NSEvent.mouseLocation()
|
||||
return int(loc.x), int(Quartz.CGDisplayPixelsHigh(0) - loc.y)
|
||||
|
||||
|
||||
def _size():
|
||||
return Quartz.CGDisplayPixelsWide(Quartz.CGMainDisplayID()), Quartz.CGDisplayPixelsHigh(Quartz.CGMainDisplayID())
|
||||
|
||||
|
||||
|
||||
def _scroll(clicks, x=None, y=None):
|
||||
_vscroll(clicks, x, y)
|
||||
|
||||
|
||||
"""
|
||||
According to https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html#//apple_ref/c/func/Quartz.CGEventCreateScrollWheelEvent
|
||||
"Scrolling movement is generally represented by small signed integer values, typically in a range from -10 to +10. Large values may have unexpected results, depending on the application that processes the event."
|
||||
The scrolling functions will create multiple events that scroll 10 each, and then scroll the remainder.
|
||||
"""
|
||||
|
||||
def _vscroll(clicks, x=None, y=None):
|
||||
_moveTo(x, y)
|
||||
clicks = int(clicks)
|
||||
for _ in range(abs(clicks) // 10):
|
||||
scrollWheelEvent = Quartz.CGEventCreateScrollWheelEvent(
|
||||
None, # no source
|
||||
Quartz.kCGScrollEventUnitLine, # units
|
||||
1, # wheelCount (number of dimensions)
|
||||
10 if clicks >= 0 else -10) # vertical movement
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, scrollWheelEvent)
|
||||
|
||||
scrollWheelEvent = Quartz.CGEventCreateScrollWheelEvent(
|
||||
None, # no source
|
||||
Quartz.kCGScrollEventUnitLine, # units
|
||||
1, # wheelCount (number of dimensions)
|
||||
clicks % 10 if clicks >= 0 else -1 * (-clicks % 10)) # vertical movement
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, scrollWheelEvent)
|
||||
|
||||
|
||||
def _hscroll(clicks, x=None, y=None):
|
||||
_moveTo(x, y)
|
||||
clicks = int(clicks)
|
||||
for _ in range(abs(clicks) // 10):
|
||||
scrollWheelEvent = Quartz.CGEventCreateScrollWheelEvent(
|
||||
None, # no source
|
||||
Quartz.kCGScrollEventUnitLine, # units
|
||||
2, # wheelCount (number of dimensions)
|
||||
0, # vertical movement
|
||||
10 if clicks >= 0 else -10) # horizontal movement
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, scrollWheelEvent)
|
||||
|
||||
scrollWheelEvent = Quartz.CGEventCreateScrollWheelEvent(
|
||||
None, # no source
|
||||
Quartz.kCGScrollEventUnitLine, # units
|
||||
2, # wheelCount (number of dimensions)
|
||||
0, # vertical movement
|
||||
(clicks % 10) if clicks >= 0 else (-1 * clicks % 10)) # horizontal movement
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, scrollWheelEvent)
|
||||
|
||||
|
||||
def _mouseDown(x, y, button):
|
||||
if button == LEFT:
|
||||
_sendMouseEvent(Quartz.kCGEventLeftMouseDown, x, y, Quartz.kCGMouseButtonLeft)
|
||||
elif button == MIDDLE:
|
||||
_sendMouseEvent(Quartz.kCGEventOtherMouseDown, x, y, Quartz.kCGMouseButtonCenter)
|
||||
elif button == RIGHT:
|
||||
_sendMouseEvent(Quartz.kCGEventRightMouseDown, x, y, Quartz.kCGMouseButtonRight)
|
||||
else:
|
||||
assert False, "button argument not in ('left', 'middle', 'right')"
|
||||
|
||||
|
||||
def _mouseUp(x, y, button):
|
||||
if button == LEFT:
|
||||
_sendMouseEvent(Quartz.kCGEventLeftMouseUp, x, y, Quartz.kCGMouseButtonLeft)
|
||||
elif button == MIDDLE:
|
||||
_sendMouseEvent(Quartz.kCGEventOtherMouseUp, x, y, Quartz.kCGMouseButtonCenter)
|
||||
elif button == RIGHT:
|
||||
_sendMouseEvent(Quartz.kCGEventRightMouseUp, x, y, Quartz.kCGMouseButtonRight)
|
||||
else:
|
||||
assert False, "button argument not in ('left', 'middle', 'right')"
|
||||
|
||||
|
||||
def _click(x, y, button):
|
||||
if button == LEFT:
|
||||
_sendMouseEvent(Quartz.kCGEventLeftMouseDown, x, y, Quartz.kCGMouseButtonLeft)
|
||||
_sendMouseEvent(Quartz.kCGEventLeftMouseUp, x, y, Quartz.kCGMouseButtonLeft)
|
||||
elif button == MIDDLE:
|
||||
_sendMouseEvent(Quartz.kCGEventOtherMouseDown, x, y, Quartz.kCGMouseButtonCenter)
|
||||
_sendMouseEvent(Quartz.kCGEventOtherMouseUp, x, y, Quartz.kCGMouseButtonCenter)
|
||||
elif button == RIGHT:
|
||||
_sendMouseEvent(Quartz.kCGEventRightMouseDown, x, y, Quartz.kCGMouseButtonRight)
|
||||
_sendMouseEvent(Quartz.kCGEventRightMouseUp, x, y, Quartz.kCGMouseButtonRight)
|
||||
else:
|
||||
assert False, "button argument not in ('left', 'middle', 'right')"
|
||||
|
||||
def _multiClick(x, y, button, num, interval=0.0):
|
||||
btn = None
|
||||
down = None
|
||||
up = None
|
||||
|
||||
if button == LEFT:
|
||||
btn = Quartz.kCGMouseButtonLeft
|
||||
down = Quartz.kCGEventLeftMouseDown
|
||||
up = Quartz.kCGEventLeftMouseUp
|
||||
elif button == MIDDLE:
|
||||
btn = Quartz.kCGMouseButtonCenter
|
||||
down = Quartz.kCGEventOtherMouseDown
|
||||
up = Quartz.kCGEventOtherMouseUp
|
||||
elif button == RIGHT:
|
||||
btn = Quartz.kCGMouseButtonRight
|
||||
down = Quartz.kCGEventRightMouseDown
|
||||
up = Quartz.kCGEventRightMouseUp
|
||||
else:
|
||||
assert False, "button argument not in ('left', 'middle', 'right')"
|
||||
return
|
||||
|
||||
for i in range(num):
|
||||
_click(x, y, button)
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
def _sendMouseEvent(ev, x, y, button):
|
||||
mouseEvent = Quartz.CGEventCreateMouseEvent(None, ev, (x, y), button)
|
||||
Quartz.CGEventPost(Quartz.kCGHIDEventTap, mouseEvent)
|
||||
|
||||
|
||||
def _dragTo(x, y, button):
|
||||
if button == LEFT:
|
||||
_sendMouseEvent(Quartz.kCGEventLeftMouseDragged , x, y, Quartz.kCGMouseButtonLeft)
|
||||
elif button == MIDDLE:
|
||||
_sendMouseEvent(Quartz.kCGEventOtherMouseDragged , x, y, Quartz.kCGMouseButtonCenter)
|
||||
elif button == RIGHT:
|
||||
_sendMouseEvent(Quartz.kCGEventRightMouseDragged , x, y, Quartz.kCGMouseButtonRight)
|
||||
else:
|
||||
assert False, "button argument not in ('left', 'middle', 'right')"
|
||||
time.sleep(pyautogui.DARWIN_CATCH_UP_TIME) # needed to allow OS time to catch up.
|
||||
|
||||
def _moveTo(x, y):
|
||||
_sendMouseEvent(Quartz.kCGEventMouseMoved, x, y, 0)
|
||||
time.sleep(pyautogui.DARWIN_CATCH_UP_TIME) # needed to allow OS time to catch up.
|
|
@ -0,0 +1,568 @@
|
|||
# Windows implementation of PyAutoGUI functions.
|
||||
# BSD license
|
||||
# Al Sweigart al@inventwithpython.com
|
||||
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
import pyautogui
|
||||
from pyautogui import LEFT, MIDDLE, RIGHT
|
||||
|
||||
import sys
|
||||
if sys.platform != 'win32':
|
||||
raise Exception('The pyautogui_win module should only be loaded on a Windows system.')
|
||||
|
||||
|
||||
# Fixes the scaling issues where PyAutoGUI was reporting the wrong resolution:
|
||||
try:
|
||||
ctypes.windll.user32.SetProcessDPIAware()
|
||||
except AttributeError:
|
||||
pass # Windows XP doesn't support this, so just do nothing.
|
||||
|
||||
|
||||
"""
|
||||
A lot of this code is probably repeated from win32 extensions module, but I didn't want to have that dependency.
|
||||
|
||||
Note: According to http://msdn.microsoft.com/en-us/library/windows/desktop/ms646260(v=vs.85).aspx
|
||||
the ctypes.windll.user32.mouse_event() function has been superseded by SendInput.
|
||||
|
||||
SendInput() is documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx
|
||||
|
||||
UPDATE: SendInput() doesn't seem to be working for me. I've switched back to mouse_event()."""
|
||||
|
||||
|
||||
# Event codes to be passed to the mouse_event() win32 function.
|
||||
# Documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646273(v=vs.85).aspx
|
||||
MOUSEEVENTF_MOVE = 0x0001
|
||||
MOUSEEVENTF_LEFTDOWN = 0x0002
|
||||
MOUSEEVENTF_LEFTUP = 0x0004
|
||||
MOUSEEVENTF_LEFTCLICK = MOUSEEVENTF_LEFTDOWN + MOUSEEVENTF_LEFTUP
|
||||
MOUSEEVENTF_RIGHTDOWN = 0x0008
|
||||
MOUSEEVENTF_RIGHTUP = 0x0010
|
||||
MOUSEEVENTF_RIGHTCLICK = MOUSEEVENTF_RIGHTDOWN + MOUSEEVENTF_RIGHTUP
|
||||
MOUSEEVENTF_MIDDLEDOWN = 0x0020
|
||||
MOUSEEVENTF_MIDDLEUP = 0x0040
|
||||
MOUSEEVENTF_MIDDLECLICK = MOUSEEVENTF_MIDDLEDOWN + MOUSEEVENTF_MIDDLEUP
|
||||
|
||||
MOUSEEVENTF_ABSOLUTE = 0x8000
|
||||
MOUSEEVENTF_WHEEL = 0x0800
|
||||
MOUSEEVENTF_HWHEEL = 0x01000
|
||||
|
||||
# Documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646304(v=vs.85).aspx
|
||||
KEYEVENTF_KEYDOWN = 0x0000 # Technically this constant doesn't exist in the MS documentation. It's the lack of KEYEVENTF_KEYUP that means pressing the key down.
|
||||
KEYEVENTF_KEYUP = 0x0002
|
||||
|
||||
# Documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx
|
||||
INPUT_MOUSE = 0
|
||||
INPUT_KEYBOARD = 1
|
||||
|
||||
|
||||
# These ctypes structures are for Win32 INPUT, MOUSEINPUT, KEYBDINPUT, and HARDWAREINPUT structures,
|
||||
# used by SendInput and documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx
|
||||
# Thanks to BSH for this StackOverflow answer: https://stackoverflow.com/questions/18566289/how-would-you-recreate-this-windows-api-structure-with-ctypes
|
||||
class MOUSEINPUT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dx', ctypes.wintypes.LONG),
|
||||
('dy', ctypes.wintypes.LONG),
|
||||
('mouseData', ctypes.wintypes.DWORD),
|
||||
('dwFlags', ctypes.wintypes.DWORD),
|
||||
('time', ctypes.wintypes.DWORD),
|
||||
('dwExtraInfo', ctypes.POINTER(ctypes.wintypes.ULONG)),
|
||||
]
|
||||
|
||||
class KEYBDINPUT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('wVk', ctypes.wintypes.WORD),
|
||||
('wScan', ctypes.wintypes.WORD),
|
||||
('dwFlags', ctypes.wintypes.DWORD),
|
||||
('time', ctypes.wintypes.DWORD),
|
||||
('dwExtraInfo', ctypes.POINTER(ctypes.wintypes.ULONG)),
|
||||
]
|
||||
|
||||
class HARDWAREINPUT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('uMsg', ctypes.wintypes.DWORD),
|
||||
('wParamL', ctypes.wintypes.WORD),
|
||||
('wParamH', ctypes.wintypes.DWORD)
|
||||
]
|
||||
|
||||
class INPUT(ctypes.Structure):
|
||||
class _I(ctypes.Union):
|
||||
_fields_ = [
|
||||
('mi', MOUSEINPUT),
|
||||
('ki', KEYBDINPUT),
|
||||
('hi', HARDWAREINPUT),
|
||||
]
|
||||
|
||||
_anonymous_ = ('i', )
|
||||
_fields_ = [
|
||||
('type', ctypes.wintypes.DWORD),
|
||||
('i', _I),
|
||||
]
|
||||
# End of the SendInput win32 data structures.
|
||||
|
||||
|
||||
|
||||
""" Keyboard key mapping for pyautogui:
|
||||
Documented at http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
|
||||
The *KB dictionaries in pyautogui map a string that can be passed to keyDown(),
|
||||
keyUp(), or press() into the code used for the OS-specific keyboard function.
|
||||
|
||||
They should always be lowercase, and the same keys should be used across all OSes."""
|
||||
keyboardMapping = dict([(key, None) for key in pyautogui.KEY_NAMES])
|
||||
keyboardMapping.update({
|
||||
'backspace': 0x08, # VK_BACK
|
||||
'\b': 0x08, # VK_BACK
|
||||
'super': 0x5B, #VK_LWIN
|
||||
'tab': 0x09, # VK_TAB
|
||||
'\t': 0x09, # VK_TAB
|
||||
'clear': 0x0c, # VK_CLEAR
|
||||
'enter': 0x0d, # VK_RETURN
|
||||
'\n': 0x0d, # VK_RETURN
|
||||
'return': 0x0d, # VK_RETURN
|
||||
'shift': 0x10, # VK_SHIFT
|
||||
'ctrl': 0x11, # VK_CONTROL
|
||||
'alt': 0x12, # VK_MENU
|
||||
'pause': 0x13, # VK_PAUSE
|
||||
'capslock': 0x14, # VK_CAPITAL
|
||||
'kana': 0x15, # VK_KANA
|
||||
'hanguel': 0x15, # VK_HANGUEL
|
||||
'hangul': 0x15, # VK_HANGUL
|
||||
'junja': 0x17, # VK_JUNJA
|
||||
'final': 0x18, # VK_FINAL
|
||||
'hanja': 0x19, # VK_HANJA
|
||||
'kanji': 0x19, # VK_KANJI
|
||||
'esc': 0x1b, # VK_ESCAPE
|
||||
'escape': 0x1b, # VK_ESCAPE
|
||||
'convert': 0x1c, # VK_CONVERT
|
||||
'nonconvert': 0x1d, # VK_NONCONVERT
|
||||
'accept': 0x1e, # VK_ACCEPT
|
||||
'modechange': 0x1f, # VK_MODECHANGE
|
||||
' ': 0x20, # VK_SPACE
|
||||
'space': 0x20, # VK_SPACE
|
||||
'pgup': 0x21, # VK_PRIOR
|
||||
'pgdn': 0x22, # VK_NEXT
|
||||
'pageup': 0x21, # VK_PRIOR
|
||||
'pagedown': 0x22, # VK_NEXT
|
||||
'end': 0x23, # VK_END
|
||||
'home': 0x24, # VK_HOME
|
||||
'left': 0x25, # VK_LEFT
|
||||
'up': 0x26, # VK_UP
|
||||
'right': 0x27, # VK_RIGHT
|
||||
'down': 0x28, # VK_DOWN
|
||||
'select': 0x29, # VK_SELECT
|
||||
'print': 0x2a, # VK_PRINT
|
||||
'execute': 0x2b, # VK_EXECUTE
|
||||
'prtsc': 0x2c, # VK_SNAPSHOT
|
||||
'prtscr': 0x2c, # VK_SNAPSHOT
|
||||
'prntscrn': 0x2c, # VK_SNAPSHOT
|
||||
'printscreen': 0x2c, # VK_SNAPSHOT
|
||||
'insert': 0x2d, # VK_INSERT
|
||||
'del': 0x2e, # VK_DELETE
|
||||
'delete': 0x2e, # VK_DELETE
|
||||
'help': 0x2f, # VK_HELP
|
||||
'win': 0x5b, # VK_LWIN
|
||||
'winleft': 0x5b, # VK_LWIN
|
||||
'winright': 0x5c, # VK_RWIN
|
||||
'apps': 0x5d, # VK_APPS
|
||||
'sleep': 0x5f, # VK_SLEEP
|
||||
'num0': 0x60, # VK_NUMPAD0
|
||||
'num1': 0x61, # VK_NUMPAD1
|
||||
'num2': 0x62, # VK_NUMPAD2
|
||||
'num3': 0x63, # VK_NUMPAD3
|
||||
'num4': 0x64, # VK_NUMPAD4
|
||||
'num5': 0x65, # VK_NUMPAD5
|
||||
'num6': 0x66, # VK_NUMPAD6
|
||||
'num7': 0x67, # VK_NUMPAD7
|
||||
'num8': 0x68, # VK_NUMPAD8
|
||||
'num9': 0x69, # VK_NUMPAD9
|
||||
'multiply': 0x6a, # VK_MULTIPLY ??? Is this the numpad *?
|
||||
'add': 0x6b, # VK_ADD ??? Is this the numpad +?
|
||||
'separator': 0x6c, # VK_SEPARATOR ??? Is this the numpad enter?
|
||||
'subtract': 0x6d, # VK_SUBTRACT ??? Is this the numpad -?
|
||||
'decimal': 0x6e, # VK_DECIMAL
|
||||
'divide': 0x6f, # VK_DIVIDE
|
||||
'f1': 0x70, # VK_F1
|
||||
'f2': 0x71, # VK_F2
|
||||
'f3': 0x72, # VK_F3
|
||||
'f4': 0x73, # VK_F4
|
||||
'f5': 0x74, # VK_F5
|
||||
'f6': 0x75, # VK_F6
|
||||
'f7': 0x76, # VK_F7
|
||||
'f8': 0x77, # VK_F8
|
||||
'f9': 0x78, # VK_F9
|
||||
'f10': 0x79, # VK_F10
|
||||
'f11': 0x7a, # VK_F11
|
||||
'f12': 0x7b, # VK_F12
|
||||
'f13': 0x7c, # VK_F13
|
||||
'f14': 0x7d, # VK_F14
|
||||
'f15': 0x7e, # VK_F15
|
||||
'f16': 0x7f, # VK_F16
|
||||
'f17': 0x80, # VK_F17
|
||||
'f18': 0x81, # VK_F18
|
||||
'f19': 0x82, # VK_F19
|
||||
'f20': 0x83, # VK_F20
|
||||
'f21': 0x84, # VK_F21
|
||||
'f22': 0x85, # VK_F22
|
||||
'f23': 0x86, # VK_F23
|
||||
'f24': 0x87, # VK_F24
|
||||
'numlock': 0x90, # VK_NUMLOCK
|
||||
'scrolllock': 0x91, # VK_SCROLL
|
||||
'shiftleft': 0xa0, # VK_LSHIFT
|
||||
'shiftright': 0xa1, # VK_RSHIFT
|
||||
'ctrlleft': 0xa2, # VK_LCONTROL
|
||||
'ctrlright': 0xa3, # VK_RCONTROL
|
||||
'altleft': 0xa4, # VK_LMENU
|
||||
'altright': 0xa5, # VK_RMENU
|
||||
'browserback': 0xa6, # VK_BROWSER_BACK
|
||||
'browserforward': 0xa7, # VK_BROWSER_FORWARD
|
||||
'browserrefresh': 0xa8, # VK_BROWSER_REFRESH
|
||||
'browserstop': 0xa9, # VK_BROWSER_STOP
|
||||
'browsersearch': 0xaa, # VK_BROWSER_SEARCH
|
||||
'browserfavorites': 0xab, # VK_BROWSER_FAVORITES
|
||||
'browserhome': 0xac, # VK_BROWSER_HOME
|
||||
'volumemute': 0xad, # VK_VOLUME_MUTE
|
||||
'volumedown': 0xae, # VK_VOLUME_DOWN
|
||||
'volumeup': 0xaf, # VK_VOLUME_UP
|
||||
'nexttrack': 0xb0, # VK_MEDIA_NEXT_TRACK
|
||||
'prevtrack': 0xb1, # VK_MEDIA_PREV_TRACK
|
||||
'stop': 0xb2, # VK_MEDIA_STOP
|
||||
'playpause': 0xb3, # VK_MEDIA_PLAY_PAUSE
|
||||
'launchmail': 0xb4, # VK_LAUNCH_MAIL
|
||||
'launchmediaselect': 0xb5, # VK_LAUNCH_MEDIA_SELECT
|
||||
'launchapp1': 0xb6, # VK_LAUNCH_APP1
|
||||
'launchapp2': 0xb7, # VK_LAUNCH_APP2
|
||||
})
|
||||
|
||||
# There are other virtual key constants that are not used here because the printable ascii keys are
|
||||
# handled in the following `for` loop.
|
||||
# The virtual key constants that aren't used are:
|
||||
# VK_OEM_1, VK_OEM_PLUS, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_2, VK_OEM_3, VK_OEM_4,
|
||||
# VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_PACKET, VK_ATTN, VK_CRSEL, VK_EXSEL, VK_EREOF,
|
||||
# VK_PLAY, VK_ZOOM, VK_NONAME, VK_PA1, VK_OEM_CLEAR
|
||||
|
||||
# Populate the basic printable ascii characters.
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscana
|
||||
for c in range(32, 128):
|
||||
keyboardMapping[chr(c)] = ctypes.windll.user32.VkKeyScanA(ctypes.wintypes.WCHAR(chr(c)))
|
||||
|
||||
|
||||
def _keyDown(key):
|
||||
"""Performs a keyboard key press without the release. This will put that
|
||||
key in a held down state.
|
||||
|
||||
NOTE: For some reason, this does not seem to cause key repeats like would
|
||||
happen if a keyboard key was held down on a text field.
|
||||
|
||||
Args:
|
||||
key (str): The key to be pressed down. The valid names are listed in
|
||||
pyautogui.KEY_NAMES.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if key not in keyboardMapping or keyboardMapping[key] is None:
|
||||
return
|
||||
|
||||
needsShift = pyautogui.isShiftCharacter(key)
|
||||
|
||||
"""
|
||||
# OLD CODE: The new code relies on having all keys be loaded in keyboardMapping from the start.
|
||||
if key in keyboardMapping.keys():
|
||||
vkCode = keyboardMapping[key]
|
||||
elif len(key) == 1:
|
||||
# note: I could use this case to update keyboardMapping to cache the VkKeyScan results, but I've decided not to just to make any possible bugs easier to reproduce.
|
||||
vkCode = ctypes.windll.user32.VkKeyScanW(ctypes.wintypes.WCHAR(key))
|
||||
if vkCode == -1:
|
||||
raise ValueError('There is no VK code for key "%s"' % (key))
|
||||
if vkCode > 0x100: # the vk code will be > 0x100 if it needs shift
|
||||
vkCode -= 0x100
|
||||
needsShift = True
|
||||
"""
|
||||
mods, vkCode = divmod(keyboardMapping[key], 0x100)
|
||||
|
||||
for apply_mod, vk_mod in [(mods & 4, 0x12), (mods & 2, 0x11),
|
||||
(mods & 1 or needsShift, 0x10)]: #HANKAKU not supported! mods & 8
|
||||
if apply_mod:
|
||||
ctypes.windll.user32.keybd_event(vk_mod, 0, KEYEVENTF_KEYDOWN, 0) #
|
||||
ctypes.windll.user32.keybd_event(vkCode, 0, KEYEVENTF_KEYDOWN, 0)
|
||||
for apply_mod, vk_mod in [(mods & 1 or needsShift, 0x10), (mods & 2, 0x11),
|
||||
(mods & 4, 0x12)]: #HANKAKU not supported! mods & 8
|
||||
if apply_mod:
|
||||
ctypes.windll.user32.keybd_event(vk_mod, 0, KEYEVENTF_KEYUP, 0) #
|
||||
|
||||
|
||||
def _keyUp(key):
|
||||
"""Performs a keyboard key release (without the press down beforehand).
|
||||
|
||||
Args:
|
||||
key (str): The key to be released up. The valid names are listed in
|
||||
pyautogui.KEY_NAMES.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if key not in keyboardMapping or keyboardMapping[key] is None:
|
||||
return
|
||||
|
||||
needsShift = pyautogui.isShiftCharacter(key)
|
||||
"""
|
||||
# OLD CODE: The new code relies on having all keys be loaded in keyboardMapping from the start.
|
||||
if key in keyboardMapping.keys():
|
||||
vkCode = keyboardMapping[key]
|
||||
elif len(key) == 1:
|
||||
# note: I could use this case to update keyboardMapping to cache the VkKeyScan results, but I've decided not to just to make any possible bugs easier to reproduce.
|
||||
vkCode = ctypes.windll.user32.VkKeyScanW(ctypes.wintypes.WCHAR(key))
|
||||
if vkCode == -1:
|
||||
raise ValueError('There is no VK code for key "%s"' % (key))
|
||||
if vkCode > 0x100: # the vk code will be > 0x100 if it needs shift
|
||||
vkCode -= 0x100
|
||||
needsShift = True
|
||||
"""
|
||||
mods, vkCode = divmod(keyboardMapping[key], 0x100)
|
||||
|
||||
for apply_mod, vk_mod in [(mods & 4, 0x12), (mods & 2, 0x11),
|
||||
(mods & 1 or needsShift, 0x10)]: #HANKAKU not supported! mods & 8
|
||||
if apply_mod:
|
||||
ctypes.windll.user32.keybd_event(vk_mod, 0, 0, 0) #
|
||||
ctypes.windll.user32.keybd_event(vkCode, 0, KEYEVENTF_KEYUP, 0)
|
||||
for apply_mod, vk_mod in [(mods & 1 or needsShift, 0x10), (mods & 2, 0x11),
|
||||
(mods & 4, 0x12)]: #HANKAKU not supported! mods & 8
|
||||
if apply_mod:
|
||||
ctypes.windll.user32.keybd_event(vk_mod, 0, KEYEVENTF_KEYUP, 0) #
|
||||
|
||||
|
||||
def _position():
|
||||
"""Returns the current xy coordinates of the mouse cursor as a two-integer
|
||||
tuple by calling the GetCursorPos() win32 function.
|
||||
|
||||
Returns:
|
||||
(x, y) tuple of the current xy coordinates of the mouse cursor.
|
||||
"""
|
||||
|
||||
cursor = ctypes.wintypes.POINT()
|
||||
ctypes.windll.user32.GetCursorPos(ctypes.byref(cursor))
|
||||
return (cursor.x, cursor.y)
|
||||
|
||||
|
||||
def _size():
|
||||
"""Returns the width and height of the screen as a two-integer tuple.
|
||||
|
||||
Returns:
|
||||
(width, height) tuple of the screen size, in pixels.
|
||||
"""
|
||||
return (ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1))
|
||||
|
||||
|
||||
def _moveTo(x, y):
|
||||
"""Send the mouse move event to Windows by calling SetCursorPos() win32
|
||||
function.
|
||||
|
||||
Args:
|
||||
button (str): The mouse button, either 'left', 'middle', or 'right'
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
ctypes.windll.user32.SetCursorPos(x, y)
|
||||
# This was a possible solution to issue #314 https://github.com/asweigart/pyautogui/issues/314
|
||||
# but I'd like to hang on to SetCursorPos because mouse_event() has been superseded.
|
||||
#_sendMouseEvent(MOUSEEVENTF_MOVE + MOUSEEVENTF_ABSOLUTE, x, y)
|
||||
|
||||
|
||||
def _mouseDown(x, y, button):
|
||||
"""Send the mouse down event to Windows by calling the mouse_event() win32
|
||||
function.
|
||||
|
||||
Args:
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
button (str): The mouse button, either 'left', 'middle', or 'right'
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if button not in (LEFT, MIDDLE, RIGHT):
|
||||
raise ValueError('button arg to _click() must be one of "left", "middle", or "right", not %s' % button)
|
||||
|
||||
if button == LEFT:
|
||||
EV = MOUSEEVENTF_LEFTDOWN
|
||||
elif button == MIDDLE:
|
||||
EV = MOUSEEVENTF_MIDDLEDOWN
|
||||
elif button == RIGHT:
|
||||
EV = MOUSEEVENTF_RIGHTDOWN
|
||||
|
||||
try:
|
||||
_sendMouseEvent(EV, x, y)
|
||||
except (PermissionError, OSError):
|
||||
# TODO: We need to figure out how to prevent these errors, see https://github.com/asweigart/pyautogui/issues/60
|
||||
pass
|
||||
|
||||
|
||||
def _mouseUp(x, y, button):
|
||||
"""Send the mouse up event to Windows by calling the mouse_event() win32
|
||||
function.
|
||||
|
||||
Args:
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
button (str): The mouse button, either 'left', 'middle', or 'right'
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if button not in (LEFT, MIDDLE, RIGHT):
|
||||
raise ValueError('button arg to _click() must be one of "left", "middle", or "right", not %s' % button)
|
||||
|
||||
if button == LEFT:
|
||||
EV = MOUSEEVENTF_LEFTUP
|
||||
elif button == MIDDLE:
|
||||
EV = MOUSEEVENTF_MIDDLEUP
|
||||
elif button == RIGHT:
|
||||
EV = MOUSEEVENTF_RIGHTUP
|
||||
|
||||
try:
|
||||
_sendMouseEvent(EV, x, y)
|
||||
except (PermissionError, OSError): # TODO: We need to figure out how to prevent these errors, see https://github.com/asweigart/pyautogui/issues/60
|
||||
pass
|
||||
|
||||
|
||||
def _click(x, y, button):
|
||||
"""Send the mouse click event to Windows by calling the mouse_event() win32
|
||||
function.
|
||||
|
||||
Args:
|
||||
button (str): The mouse button, either 'left', 'middle', or 'right'
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if button not in (LEFT, MIDDLE, RIGHT):
|
||||
raise ValueError('button arg to _click() must be one of "left", "middle", or "right", not %s' % button)
|
||||
|
||||
if button == LEFT:
|
||||
EV = MOUSEEVENTF_LEFTCLICK
|
||||
elif button == MIDDLE:
|
||||
EV = MOUSEEVENTF_MIDDLECLICK
|
||||
elif button ==RIGHT:
|
||||
EV = MOUSEEVENTF_RIGHTCLICK
|
||||
|
||||
try:
|
||||
_sendMouseEvent(EV, x, y)
|
||||
except (PermissionError, OSError):
|
||||
# TODO: We need to figure out how to prevent these errors, see https://github.com/asweigart/pyautogui/issues/60
|
||||
pass
|
||||
|
||||
|
||||
def _sendMouseEvent(ev, x, y, dwData=0):
|
||||
"""The helper function that actually makes the call to the mouse_event()
|
||||
win32 function.
|
||||
|
||||
Args:
|
||||
ev (int): The win32 code for the mouse event. Use one of the MOUSEEVENTF_*
|
||||
constants for this argument.
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
dwData (int): The argument for mouse_event()'s dwData parameter. So far
|
||||
this is only used by mouse scrolling.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
assert x != None and y != None, 'x and y cannot be set to None'
|
||||
# TODO: ARG! For some reason, SendInput isn't working for mouse events. I'm switching to using the older mouse_event win32 function.
|
||||
#mouseStruct = MOUSEINPUT()
|
||||
#mouseStruct.dx = x
|
||||
#mouseStruct.dy = y
|
||||
#mouseStruct.mouseData = ev
|
||||
#mouseStruct.time = 0
|
||||
#mouseStruct.dwExtraInfo = ctypes.pointer(ctypes.c_ulong(0)) # according to https://stackoverflow.com/questions/13564851/generate-keyboard-events I can just set this. I don't really care about this value.
|
||||
#inputStruct = INPUT()
|
||||
#inputStruct.mi = mouseStruct
|
||||
#inputStruct.type = INPUT_MOUSE
|
||||
#ctypes.windll.user32.SendInput(1, ctypes.pointer(inputStruct), ctypes.sizeof(inputStruct))
|
||||
|
||||
# TODO Note: We need to handle additional buttons, which I believe is documented here:
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-mouse_event
|
||||
|
||||
width, height = _size()
|
||||
convertedX = 65536 * x // width + 1
|
||||
convertedY = 65536 * y // height + 1
|
||||
ctypes.windll.user32.mouse_event(ev, ctypes.c_long(convertedX), ctypes.c_long(convertedY), dwData, 0)
|
||||
|
||||
# TODO: Too many false positives with this code: See: https://github.com/asweigart/pyautogui/issues/108
|
||||
#if ctypes.windll.kernel32.GetLastError() != 0:
|
||||
# raise ctypes.WinError()
|
||||
|
||||
|
||||
def _scroll(clicks, x=None, y=None):
|
||||
"""Send the mouse vertical scroll event to Windows by calling the
|
||||
mouse_event() win32 function.
|
||||
|
||||
Args:
|
||||
clicks (int): The amount of scrolling to do. A positive value is the mouse
|
||||
wheel moving forward (scrolling up), a negative value is backwards (down).
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
startx, starty = _position()
|
||||
width, height = _size()
|
||||
|
||||
if x is None:
|
||||
x = startx
|
||||
else:
|
||||
if x < 0:
|
||||
x = 0
|
||||
elif x >= width:
|
||||
x = width - 1
|
||||
if y is None:
|
||||
y = starty
|
||||
else:
|
||||
if y < 0:
|
||||
y = 0
|
||||
elif y >= height:
|
||||
y = height - 1
|
||||
|
||||
try:
|
||||
_sendMouseEvent(MOUSEEVENTF_WHEEL, x, y, dwData=clicks)
|
||||
except (PermissionError, OSError): # TODO: We need to figure out how to prevent these errors, see https://github.com/asweigart/pyautogui/issues/60
|
||||
pass
|
||||
|
||||
|
||||
def _hscroll(clicks, x, y):
|
||||
"""Send the mouse horizontal scroll event to Windows by calling the
|
||||
mouse_event() win32 function.
|
||||
|
||||
Args:
|
||||
clicks (int): The amount of scrolling to do. A positive value is the mouse
|
||||
wheel moving right, a negative value is moving left.
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
return _scroll(clicks, x, y)
|
||||
|
||||
|
||||
def _vscroll(clicks, x, y):
|
||||
"""A wrapper for _scroll(), which does vertical scrolling.
|
||||
|
||||
Args:
|
||||
clicks (int): The amount of scrolling to do. A positive value is the mouse
|
||||
wheel moving forward (scrolling up), a negative value is backwards (down).
|
||||
x (int): The x position of the mouse event.
|
||||
y (int): The y position of the mouse event.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
return _scroll(clicks, x, y)
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
# NOTE - It is a known issue that the keyboard-related functions don't work on Ubuntu VMs in Virtualbox.
|
||||
|
||||
# import pyautogui
|
||||
import sys
|
||||
import os
|
||||
from . import LEFT, MIDDLE, RIGHT, KEY_NAMES, isShiftCharacter
|
||||
# from pyautogui import LEFT, MIDDLE, RIGHT
|
||||
|
||||
from Xlib.display import Display
|
||||
from Xlib import X
|
||||
from Xlib.ext.xtest import fake_input
|
||||
import Xlib.XK
|
||||
|
||||
BUTTON_NAME_MAPPING = {LEFT: 1, MIDDLE: 2, RIGHT: 3, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7}
|
||||
|
||||
|
||||
if sys.platform in ('java', 'darwin', 'win32'):
|
||||
raise Exception('The pyautogui_x11 module should only be loaded on a Unix system that supports X11.')
|
||||
|
||||
#from pyautogui import *
|
||||
|
||||
"""
|
||||
Much of this code is based on information gleaned from Paul Barton's PyKeyboard in PyUserInput from 2013, itself derived from Akkana Peck's pykey in 2008 ( http://www.shallowsky.com/software/crikey/pykey-0.1 ), itself derived from her "Crikey" lib.
|
||||
"""
|
||||
|
||||
def _position():
|
||||
"""Returns the current xy coordinates of the mouse cursor as a two-integer
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
(x, y) tuple of the current xy coordinates of the mouse cursor.
|
||||
"""
|
||||
coord = _display.screen().root.query_pointer()._data
|
||||
return coord["root_x"], coord["root_y"]
|
||||
|
||||
|
||||
def _size():
|
||||
return _display.screen().width_in_pixels, _display.screen().height_in_pixels
|
||||
|
||||
|
||||
|
||||
def _vscroll(clicks, x=None, y=None):
|
||||
clicks = int(clicks)
|
||||
if clicks == 0:
|
||||
return
|
||||
elif clicks > 0:
|
||||
button = 4 # scroll up
|
||||
else:
|
||||
button = 5 # scroll down
|
||||
|
||||
for i in range(abs(clicks)):
|
||||
_click(x, y, button=button)
|
||||
|
||||
|
||||
def _hscroll(clicks, x=None, y=None):
|
||||
clicks = int(clicks)
|
||||
if clicks == 0:
|
||||
return
|
||||
elif clicks > 0:
|
||||
button = 7 # scroll right
|
||||
else:
|
||||
button = 6 # scroll left
|
||||
|
||||
for i in range(abs(clicks)):
|
||||
_click(x, y, button=button)
|
||||
|
||||
|
||||
def _scroll(clicks, x=None, y=None):
|
||||
return _vscroll(clicks, x, y)
|
||||
|
||||
|
||||
def _click(x, y, button):
|
||||
assert button in BUTTON_NAME_MAPPING.keys(), "button argument not in ('left', 'middle', 'right', 4, 5, 6, 7)"
|
||||
button = BUTTON_NAME_MAPPING[button]
|
||||
|
||||
_mouseDown(x, y, button)
|
||||
_mouseUp(x, y, button)
|
||||
|
||||
|
||||
def _moveTo(x, y):
|
||||
fake_input(_display, X.MotionNotify, x=x, y=y)
|
||||
_display.sync()
|
||||
|
||||
|
||||
def _mouseDown(x, y, button):
|
||||
_moveTo(x, y)
|
||||
assert button in BUTTON_NAME_MAPPING.keys(), "button argument not in ('left', 'middle', 'right', 4, 5, 6, 7)"
|
||||
button = BUTTON_NAME_MAPPING[button]
|
||||
fake_input(_display, X.ButtonPress, button)
|
||||
_display.sync()
|
||||
|
||||
|
||||
def _mouseUp(x, y, button):
|
||||
_moveTo(x, y)
|
||||
assert button in BUTTON_NAME_MAPPING.keys(), "button argument not in ('left', 'middle', 'right', 4, 5, 6, 7)"
|
||||
button = BUTTON_NAME_MAPPING[button]
|
||||
fake_input(_display, X.ButtonRelease, button)
|
||||
_display.sync()
|
||||
|
||||
|
||||
def _keyDown(key):
|
||||
"""Performs a keyboard key press without the release. This will put that
|
||||
key in a held down state.
|
||||
|
||||
NOTE: For some reason, this does not seem to cause key repeats like would
|
||||
happen if a keyboard key was held down on a text field.
|
||||
|
||||
Args:
|
||||
key (str): The key to be pressed down. The valid names are listed in
|
||||
KEY_NAMES.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if key not in keyboardMapping or keyboardMapping[key] is None:
|
||||
return
|
||||
|
||||
if type(key) == int:
|
||||
fake_input(_display, X.KeyPress, key)
|
||||
_display.sync()
|
||||
return
|
||||
|
||||
needsShift = isShiftCharacter(key)
|
||||
if needsShift:
|
||||
fake_input(_display, X.KeyPress, keyboardMapping['shift'])
|
||||
|
||||
fake_input(_display, X.KeyPress, keyboardMapping[key])
|
||||
|
||||
if needsShift:
|
||||
fake_input(_display, X.KeyRelease, keyboardMapping['shift'])
|
||||
_display.sync()
|
||||
|
||||
|
||||
def _keyUp(key):
|
||||
"""Performs a keyboard key release (without the press down beforehand).
|
||||
|
||||
Args:
|
||||
key (str): The key to be released up. The valid names are listed in
|
||||
KEY_NAMES.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
"""
|
||||
Release a given character key. Also works with character keycodes as
|
||||
integers, but not keysyms.
|
||||
"""
|
||||
if key not in keyboardMapping or keyboardMapping[key] is None:
|
||||
return
|
||||
|
||||
if type(key) == int:
|
||||
keycode = key
|
||||
else:
|
||||
keycode = keyboardMapping[key]
|
||||
|
||||
fake_input(_display, X.KeyRelease, keycode)
|
||||
_display.sync()
|
||||
|
||||
|
||||
# Taken from PyKeyboard's ctor function.
|
||||
_display = Display(os.environ['DISPLAY'])
|
||||
|
||||
|
||||
""" Information for keyboardMapping derived from PyKeyboard's special_key_assignment() function.
|
||||
|
||||
The *KB dictionaries in pyautogui map a string that can be passed to keyDown(),
|
||||
keyUp(), or press() into the code used for the OS-specific keyboard function.
|
||||
|
||||
They should always be lowercase, and the same keys should be used across all OSes."""
|
||||
keyboardMapping = dict([(key, None) for key in KEY_NAMES])
|
||||
keyboardMapping.update({
|
||||
'backspace': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('BackSpace')),
|
||||
'\b': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('BackSpace')),
|
||||
'tab': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Tab')),
|
||||
'enter': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Return')),
|
||||
'return': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Return')),
|
||||
'shift': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Shift_L')),
|
||||
'ctrl': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Control_L')),
|
||||
'alt': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Alt_L')),
|
||||
'pause': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Pause')),
|
||||
'capslock': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Caps_Lock')),
|
||||
'esc': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Escape')),
|
||||
'escape': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Escape')),
|
||||
'pgup': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Page_Up')),
|
||||
'pgdn': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Page_Down')),
|
||||
'pageup': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Page_Up')),
|
||||
'pagedown': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Page_Down')),
|
||||
'end': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('End')),
|
||||
'home': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Home')),
|
||||
'left': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Left')),
|
||||
'up': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Up')),
|
||||
'right': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Right')),
|
||||
'down': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Down')),
|
||||
'select': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Select')),
|
||||
'print': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Print')),
|
||||
'execute': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Execute')),
|
||||
'prtsc': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Print')),
|
||||
'prtscr': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Print')),
|
||||
'prntscrn': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Print')),
|
||||
'printscreen': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Print')),
|
||||
'insert': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Insert')),
|
||||
'del': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Delete')),
|
||||
'delete': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Delete')),
|
||||
'help': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Help')),
|
||||
'win': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Super_L')),
|
||||
'winleft': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Super_L')),
|
||||
'winright': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Super_R')),
|
||||
'apps': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Menu')),
|
||||
'num0': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_0')),
|
||||
'num1': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_1')),
|
||||
'num2': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_2')),
|
||||
'num3': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_3')),
|
||||
'num4': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_4')),
|
||||
'num5': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_5')),
|
||||
'num6': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_6')),
|
||||
'num7': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_7')),
|
||||
'num8': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_8')),
|
||||
'num9': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_9')),
|
||||
'multiply': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_Multiply')),
|
||||
'add': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_Add')),
|
||||
'separator': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_Separator')),
|
||||
'subtract': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_Subtract')),
|
||||
'decimal': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_Decimal')),
|
||||
'divide': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('KP_Divide')),
|
||||
'f1': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F1')),
|
||||
'f2': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F2')),
|
||||
'f3': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F3')),
|
||||
'f4': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F4')),
|
||||
'f5': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F5')),
|
||||
'f6': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F6')),
|
||||
'f7': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F7')),
|
||||
'f8': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F8')),
|
||||
'f9': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F9')),
|
||||
'f10': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F10')),
|
||||
'f11': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F11')),
|
||||
'f12': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F12')),
|
||||
'f13': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F13')),
|
||||
'f14': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F14')),
|
||||
'f15': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F15')),
|
||||
'f16': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F16')),
|
||||
'f17': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F17')),
|
||||
'f18': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F18')),
|
||||
'f19': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F19')),
|
||||
'f20': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F20')),
|
||||
'f21': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F21')),
|
||||
'f22': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F22')),
|
||||
'f23': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F23')),
|
||||
'f24': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('F24')),
|
||||
'numlock': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Num_Lock')),
|
||||
'scrolllock': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Scroll_Lock')),
|
||||
'shiftleft': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Shift_L')),
|
||||
'shiftright': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Shift_R')),
|
||||
'ctrlleft': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Control_L')),
|
||||
'ctrlright': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Control_R')),
|
||||
'altleft': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Alt_L')),
|
||||
'altright': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Alt_R')),
|
||||
# These are added because unlike a-zA-Z0-9, the single characters do not have a
|
||||
' ': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('space')),
|
||||
'space': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('space')),
|
||||
'\t': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Tab')),
|
||||
'\n': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Return')), # for some reason this needs to be cr, not lf
|
||||
'\r': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Return')),
|
||||
'\e': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('Escape')),
|
||||
'!': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('exclam')),
|
||||
'#': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('numbersign')),
|
||||
'%': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('percent')),
|
||||
'$': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('dollar')),
|
||||
'&': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('ampersand')),
|
||||
'"': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('quotedbl')),
|
||||
"'": _display.keysym_to_keycode(Xlib.XK.string_to_keysym('apostrophe')),
|
||||
'(': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('parenleft')),
|
||||
')': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('parenright')),
|
||||
'*': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('asterisk')),
|
||||
'=': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('equal')),
|
||||
'+': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('plus')),
|
||||
',': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('comma')),
|
||||
'-': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('minus')),
|
||||
'.': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('period')),
|
||||
'/': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('slash')),
|
||||
':': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('colon')),
|
||||
';': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('semicolon')),
|
||||
'<': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('less')),
|
||||
'>': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('greater')),
|
||||
'?': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('question')),
|
||||
'@': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('at')),
|
||||
'[': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('bracketleft')),
|
||||
']': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('bracketright')),
|
||||
'\\': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('backslash')),
|
||||
'^': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('asciicircum')),
|
||||
'_': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('underscore')),
|
||||
'`': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('grave')),
|
||||
'{': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('braceleft')),
|
||||
'|': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('bar')),
|
||||
'}': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('braceright')),
|
||||
'~': _display.keysym_to_keycode(Xlib.XK.string_to_keysym('asciitilde')),
|
||||
})
|
||||
|
||||
# Trading memory for time" populate winKB so we don't have to call VkKeyScanA each time.
|
||||
for c in """abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890""":
|
||||
keyboardMapping[c] = _display.keysym_to_keycode(Xlib.XK.string_to_keysym(c))
|
|
@ -1,7 +1,16 @@
|
|||
# Python imports
|
||||
import pyautogui
|
||||
import subprocess
|
||||
|
||||
# Gtk imports
|
||||
# Lib imports
|
||||
# NOTE: Source: https://github.com/asweigart/pyautogui
|
||||
# Gunna try importing an env pyautogui; but, If none exist we will use the internal one
|
||||
# modified to import itself properly for Linux systems.
|
||||
try:
|
||||
import pyautogui
|
||||
print("Found system/env pyautogui instance...")
|
||||
except Exception as e:
|
||||
print("Defering to internal pyautogui instance...")
|
||||
from . import pyautogui
|
||||
|
||||
# Application imports
|
||||
|
||||
|
@ -14,6 +23,18 @@ pyautogui.PAUSE = 0
|
|||
|
||||
class ControlMixin:
|
||||
|
||||
def get_clipboard_data(self, encoding="utf-8") -> str:
|
||||
proc = subprocess.Popen(get_clipboard, stdout=subprocess.PIPE)
|
||||
retcode = proc.wait()
|
||||
data = proc.stdout.read()
|
||||
return data.decode(encoding).strip()
|
||||
|
||||
def set_clipboard_data(self, data: type, encoding="utf-8") -> None:
|
||||
proc = subprocess.Popen(set_clipboard, stdin=subprocess.PIPE)
|
||||
proc.stdin.write(data.encode(encoding))
|
||||
proc.stdin.close()
|
||||
retcode = proc.wait()
|
||||
|
||||
def type(self, key):
|
||||
if self.isCtrlOn or self.isShiftOn or self.isAltOn:
|
||||
self.set_hotkeys()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
# . CONFIG.sh
|
||||
|
||||
# set -o xtrace ## To debug scripts
|
||||
# set -o errexit ## To exit on error
|
||||
# set -o errunset ## To exit if a variable is referenced but not set
|
||||
|
||||
|
||||
function main() {
|
||||
call_path=`pwd`
|
||||
path=""
|
||||
|
||||
if [[ ! "${1::1}" == /* ]]; then
|
||||
path="${call_path}/${1}"
|
||||
else
|
||||
path="${1}"
|
||||
fi
|
||||
|
||||
cd "/opt/"
|
||||
python /opt/mouse-keyboard.zip "$@"
|
||||
}
|
||||
main "$@";
|
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Name=Mouse-Board
|
||||
GenericName=Mouse Keyboard
|
||||
Comment=A Python and Gtk+ mouse based keyboard.
|
||||
Exec=/bin/mouse-keyboard %F
|
||||
Icon=/usr/share/mouse-keyboard/icons/mouse-keyboard.png
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
Categories=System;FileTools;Utility;Core
|
||||
MimeType=
|
||||
Terminal=false
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Loading…
Reference in New Issue