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.
|
An onscreen keyboard for the mouse.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
* PyGObject
|
||||||
|
* python-xlib
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
<li>Get save and execute of custom commands working.</li>
|
<li>Get save and execute of custom commands working.</li>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import builtins, threading
|
import os
|
||||||
|
import builtins
|
||||||
|
import threading
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
|
@ -24,6 +26,9 @@ def daemon_threaded_wrapper(fn):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MissingConfigError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Pyautogui_Controller(ControlMixin):
|
class Pyautogui_Controller(ControlMixin):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -38,39 +43,69 @@ keys_json = {
|
||||||
"keys": {
|
"keys": {
|
||||||
"row1": {
|
"row1": {
|
||||||
"pKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
"pKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
||||||
"sKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
"sKeys": ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
|
||||||
"eKeys": ['🤩', '\U0001F600', '', '', '', '', '', '', '', '']
|
|
||||||
},
|
},
|
||||||
"row2": {
|
"row2": {
|
||||||
"pKeys": ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
|
"pKeys": ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
|
||||||
"sKeys": ['\\', '^', '#', '$', '%', '&', '-', '_', '"', '*'],
|
"sKeys": ['\\', '^', '#', '$', '%', '&', '-', '_', '"', '*']
|
||||||
"eKeys": ['', '', '', '', '', '', '', '', '', '']
|
|
||||||
},
|
},
|
||||||
"row3": {
|
"row3": {
|
||||||
"pKeys": ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', "'"],
|
"pKeys": ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', "'"],
|
||||||
"sKeys": ['/', '|', ':', '=', '+', '', '', '', ';', '!'],
|
"sKeys": ['/', '|', ':', '=', '+', '', '', '', ';', '!']
|
||||||
"eKeys": ['', '', '', '', '', '', '', '', '', '']
|
|
||||||
},
|
},
|
||||||
"row4": {
|
"row4": {
|
||||||
"pKeys": ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?'],
|
"pKeys": ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?'],
|
||||||
"sKeys": ['', '', '<', '>', '[', ']', '(', ')', '{', '}'],
|
"sKeys": ['', '', '<', '>', '[', ']', '(', ')', '{', '}']
|
||||||
"eKeys": ['', '', '', '', '', '', '', '', '', '']
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||||
# __builtins__.update({"event_system": Builtins()})
|
# __builtins__.update({"event_system": Builtins()})
|
||||||
builtins.app_name = "Mouse Keyboard"
|
builtins.app_name = "Mouse-Keyboard"
|
||||||
builtins.threaded = threaded_wrapper
|
builtins.threaded = threaded_wrapper
|
||||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||||
builtins.keys_set = keys_json
|
builtins.keys_set = keys_json
|
||||||
builtins.trace_debug = False
|
builtins.trace_debug = False
|
||||||
builtins.debug = False
|
builtins.debug = False
|
||||||
builtins.app_settings = None
|
builtins.app_settings = None
|
||||||
|
builtins.get_clipboard = ['xclip','-selection', 'clipboard', '-o']
|
||||||
|
builtins.set_clipboard = ['xclip','-selection','clipboard']
|
||||||
builtins.endpoint_registry = EndpointRegistry()
|
builtins.endpoint_registry = EndpointRegistry()
|
||||||
builtins.event_system = EventSystem()
|
builtins.event_system = EventSystem()
|
||||||
builtins.typwriter = Pyautogui_Controller()
|
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
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# 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 Del_Key
|
||||||
from ..widgets.defined_keys import Ctrl_Key
|
from ..widgets.defined_keys import Ctrl_Key
|
||||||
from ..widgets.defined_keys import Shift_Key
|
from ..widgets.defined_keys import Shift_Key
|
||||||
|
@ -26,7 +26,7 @@ class Button_Box(Gtk.ButtonBox):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Button_Box, self).__init__()
|
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)
|
self.add(key)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,17 +38,15 @@ class Keys_Column(Gtk.Box):
|
||||||
for child in children:
|
for child in children:
|
||||||
pKeys = keys[child]["pKeys"]
|
pKeys = keys[child]["pKeys"]
|
||||||
sKeys = keys[child]["sKeys"]
|
sKeys = keys[child]["sKeys"]
|
||||||
eKeys = keys[child]["eKeys"]
|
|
||||||
|
|
||||||
row_box = self.add_row()
|
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):
|
for i in range(10):
|
||||||
pkey = pKeys[i]
|
pkey = pKeys[i]
|
||||||
sKey = sKeys[i]
|
sKey = sKeys[i]
|
||||||
eKey = eKeys[i]
|
row_box.add(Key(pkey, sKey))
|
||||||
row_box.add(Key(pkey, sKey, eKey))
|
|
||||||
else:
|
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())
|
self.add(Bottom_Key_Row())
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from ..widgets.defined_keys import Esc_Key
|
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
|
from ..widgets.defined_keys import CAPS_Key
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class Left_Column(Gtk.Box):
|
||||||
|
|
||||||
self.setup_styling()
|
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.add(key)
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
|
@ -6,12 +6,14 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# 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 Backspace_Key
|
||||||
from ..widgets.defined_keys import Enter_Key
|
from ..widgets.defined_keys import Enter_Key
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Right_Column(Gtk.Box):
|
class Right_Column(Gtk.Box):
|
||||||
"""docstring for Right_Column."""
|
"""docstring for Right_Column."""
|
||||||
|
|
||||||
|
@ -20,7 +22,14 @@ class Right_Column(Gtk.Box):
|
||||||
|
|
||||||
self.setup_styling()
|
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.add(key)
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
|
@ -63,21 +63,25 @@ class Backspace_Key(Key):
|
||||||
def _clicked(self, widget = None):
|
def _clicked(self, widget = None):
|
||||||
typwriter.press_special_keys(self.get_label())
|
typwriter.press_special_keys(self.get_label())
|
||||||
|
|
||||||
class Emoji_Keys(Key):
|
class Emoji_Key(Key):
|
||||||
def __init__(self):
|
def __init__(self, emoji_popover):
|
||||||
super(Emoji_Keys, self).__init__("Emoji", "Emoji", iscontrol=True)
|
super(Emoji_Key, self).__init__("Emoji", "Emoji", iscontrol=True)
|
||||||
|
|
||||||
|
self._ctx = self.get_style_context()
|
||||||
|
self._emoji_popover = emoji_popover
|
||||||
|
|
||||||
def setup_signals(self):
|
def setup_signals(self):
|
||||||
self.connect("released", self._clicked)
|
self.connect("released", self._clicked)
|
||||||
|
|
||||||
def _clicked(self, widget = None):
|
def _clicked(self, widget = None):
|
||||||
ctx = widget.get_style_context()
|
self._ctx.add_class("toggled_bttn")
|
||||||
ctx.remove_class("toggled_bttn") if ctx.has_class("toggled_bttn") else 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):
|
class Enter_Key(Key):
|
||||||
def __init__(self):
|
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):
|
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__()
|
super(Key, self).__init__()
|
||||||
|
|
||||||
self.iscontrol = iscontrol
|
self.iscontrol = iscontrol
|
||||||
self._primary_symbol = primary
|
self._primary_symbol = primary
|
||||||
self._secondary_symbol = secondary
|
self._secondary_symbol = secondary
|
||||||
self._emoji_symbol = emoji
|
|
||||||
self._is_upper = False
|
self._is_upper = False
|
||||||
self._is_symbol = False
|
self._is_symbol = False
|
||||||
self._is_emoji = False
|
self._is_emoji = False
|
||||||
|
@ -30,13 +29,17 @@ class Key(Gtk.Button or Gtk.ToggleButton):
|
||||||
|
|
||||||
def setup_signals(self):
|
def setup_signals(self):
|
||||||
self.connect("released", self._do_type)
|
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)
|
self.connect("toggle-emoji-keys", self.toggle_emoji_keys)
|
||||||
|
|
||||||
def _do_type(self, widget = None):
|
def _do_type(self, widget = None):
|
||||||
key = self.get_label().strip()
|
key = self.get_label().strip()
|
||||||
|
if not self._is_emoji:
|
||||||
typwriter.type(key)
|
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):
|
def _do_press_special_key(self, widget = None):
|
||||||
key = self.get_label()
|
key = self.get_label()
|
||||||
|
@ -51,18 +54,13 @@ class Key(Gtk.Button or Gtk.ToggleButton):
|
||||||
self._is_symbol = not self._is_symbol
|
self._is_symbol = not self._is_symbol
|
||||||
if self._is_symbol:
|
if self._is_symbol:
|
||||||
self.set_label(self._secondary_symbol)
|
self.set_label(self._secondary_symbol)
|
||||||
elif self._is_emoji:
|
|
||||||
self.set_label(self._emoji_symbol)
|
|
||||||
else:
|
else:
|
||||||
self.set_label(self._primary_symbol.upper()) if self._is_upper else self.set_label(self._primary_symbol.lower())
|
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.
|
# NOTE: Might use name attrib on widgets and de-duplicate this and the above logic.
|
||||||
def toggle_emoji_keys(self, widget = None, eve = None):
|
def toggle_emoji_keys(self, widget = None, eve = None):
|
||||||
if not self.iscontrol:
|
if not self.iscontrol:
|
||||||
self._is_emoji = not self._is_emoji
|
if self._is_symbol:
|
||||||
if self._is_emoji:
|
|
||||||
self.set_label(self._emoji_symbol)
|
|
||||||
elif self._is_symbol:
|
|
||||||
self.set_label(self._secondary_symbol)
|
self.set_label(self._secondary_symbol)
|
||||||
else:
|
else:
|
||||||
self.set_label(self._primary_symbol.upper()) if self._is_upper else self.set_label(self._primary_symbol.lower())
|
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):
|
class Window(SignalsMixin, Gtk.ApplicationWindow):
|
||||||
"""docstring for Window."""
|
"""docstring for Window."""
|
||||||
|
@ -26,22 +23,6 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
super(Window, self).__init__()
|
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_win_settings()
|
||||||
self.setup_styling()
|
self.setup_styling()
|
||||||
self.setup_signals()
|
self.setup_signals()
|
||||||
|
@ -50,12 +31,11 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
|
|
||||||
def setup_signals(self):
|
def setup_signals(self):
|
||||||
self.connect("delete-event", Gtk.main_quit)
|
self.connect("delete-event", Gtk.main_quit)
|
||||||
|
|
||||||
def setup_win_settings(self):
|
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_title(app_name)
|
||||||
self.set_default_size(800, 200)
|
self.set_default_size(800, 200)
|
||||||
self.set_keep_above(True)
|
self.set_keep_above(True)
|
||||||
|
@ -77,7 +57,7 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
||||||
self.connect("draw", self._area_draw)
|
self.connect("draw", self._area_draw)
|
||||||
|
|
||||||
css_provider = Gtk.CssProvider()
|
css_provider = Gtk.CssProvider()
|
||||||
css_provider.load_from_path(self._CSS_FILE)
|
css_provider.load_from_path(CSS_FILE)
|
||||||
screen = Gdk.Screen.get_default()
|
screen = Gdk.Screen.get_default()
|
||||||
style_context = Gtk.StyleContext()
|
style_context = Gtk.StyleContext()
|
||||||
style_context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
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
|
# 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
|
# Application imports
|
||||||
|
|
||||||
|
@ -14,6 +23,18 @@ pyautogui.PAUSE = 0
|
||||||
|
|
||||||
class ControlMixin:
|
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):
|
def type(self, key):
|
||||||
if self.isCtrlOn or self.isShiftOn or self.isAltOn:
|
if self.isCtrlOn or self.isShiftOn or self.isAltOn:
|
||||||
self.set_hotkeys()
|
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