Added emoji support, refactored, internalized pyautogui
This commit is contained in:
		| @@ -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): | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								src/core/widgets/emoji_popover.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/core/widgets/emoji_popover.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||||
|         typwriter.type(key) |         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): |     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) | ||||||
|   | |||||||
							
								
								
									
										2163
									
								
								src/utils/pyautogui/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2163
									
								
								src/utils/pyautogui/__init__.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								src/utils/pyautogui/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/utils/pyautogui/__main__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | from . import displayMousePosition | ||||||
|  | displayMousePosition() | ||||||
							
								
								
									
										0
									
								
								src/utils/pyautogui/_pyautogui_java.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/utils/pyautogui/_pyautogui_java.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										434
									
								
								src/utils/pyautogui/_pyautogui_osx.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								src/utils/pyautogui/_pyautogui_osx.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||||
							
								
								
									
										568
									
								
								src/utils/pyautogui/_pyautogui_win.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										568
									
								
								src/utils/pyautogui/_pyautogui_win.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  |  | ||||||
							
								
								
									
										301
									
								
								src/utils/pyautogui/_pyautogui_x11.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								src/utils/pyautogui/_pyautogui_x11.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								user_config/bin/mouse-keyboard
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								user_config/bin/mouse-keyboard
									
									
									
									
									
										Executable file
									
								
							| @@ -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 "$@"; | ||||||
							
								
								
									
										11
									
								
								user_config/usr/applications/mouse-keyboard.desktop
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								user_config/usr/applications/mouse-keyboard.desktop
									
									
									
									
									
										Executable file
									
								
							| @@ -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
											
										
									
								
							
							
								
								
									
										23215
									
								
								user_config/usr/share/mouse-keyboard/emoji.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23215
									
								
								user_config/usr/share/mouse-keyboard/emoji.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB | 
		Reference in New Issue
	
	Block a user