diff --git a/README.md b/README.md index c7c91c4..f723f09 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -# Mouse-Keyboard -An onscreen keyboard for the mouse. - -### Requirements -* PyGObject -* python-xlib - -# TODO -
  • Get save and execute of custom commands working.
  • - -# Images -![1 GUI of the alphabet. ](images/pic1.png) -![2 GUI of the symbols. ](images/pic2.png) +# Mouse-Keyboard +An onscreen keyboard for the mouse. + +### Requirements +* PyGObject +* python-xlib +* pyautogui + +# TODO +
  • Get save and execute of custom commands working.
  • + +# Images +![1 image of the alphabet. ](images/pic1.png) +![2 Image of the symbols. ](images/pic2.png) +![3 Image of the emoji. ](images/pic3.png) \ No newline at end of file diff --git a/debugger.sh b/debugger.sh deleted file mode 100755 index 42162e7..0000000 --- a/debugger.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/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() { - SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )" - cd "${SCRIPTPATH}" - echo "Working Dir: " $(pwd) - - source '/home/abaddon/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/activate' - python -m pudb $(pwd)/src/__main__.py; bash -} -main "$@"; diff --git a/images/pic1.png b/images/pic1.png index 30f945c..e75bce1 100644 Binary files a/images/pic1.png and b/images/pic1.png differ diff --git a/images/pic2.png b/images/pic2.png index d921d40..11ea5f3 100644 Binary files a/images/pic2.png and b/images/pic2.png differ diff --git a/images/pic3.png b/images/pic3.png new file mode 100644 index 0000000..a6ee757 Binary files /dev/null and b/images/pic3.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..92aa60d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +PyGObject +pyautogui +python-xlib +setproctitle \ No newline at end of file diff --git a/src/__builtins__.py b/src/__builtins__.py index 728a2a6..86c482d 100644 --- a/src/__builtins__.py +++ b/src/__builtins__.py @@ -6,9 +6,9 @@ import threading # Lib imports # Application imports -from utils.pyautogui_control import ControlMixin -from utils.endpoint_registry import EndpointRegistry -from utils.event_system import EventSystem +from libs.pyautogui_control import ControlMixin +from libs.endpoint_registry import EndpointRegistry +from libs.event_system import EventSystem diff --git a/src/__main__.py b/src/__main__.py index 6188fed..7d9554f 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,6 +1,5 @@ #!/usr/bin/python3 - # Python imports import argparse import faulthandler @@ -10,36 +9,38 @@ from setproctitle import setproctitle import tracemalloc tracemalloc.start() - # Lib imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk # Application imports +from __builtins__ import * from app import Application +def main(args, unknownargs): + setproctitle(f'{app_name}') + Application(args, unknownargs) + + + if __name__ == "__main__": - """ Set process title, get arguments, and create GTK main thread. """ + ''' Set process title, get arguments, and create GTK main thread. ''' + + parser = argparse.ArgumentParser() + # Add long and short arguments + parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.") + parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.") + parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.") + + parser.add_argument("--new-tab", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.") + parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.") + + # Read arguments (If any...) + args, unknownargs = parser.parse_known_args() try: - # import web_pdb - # web_pdb.set_trace() - - setproctitle('Mouse Keyboard') faulthandler.enable() # For better debug info - parser = argparse.ArgumentParser() - # Add long and short arguments - parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.") - parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.") - - # Read arguments (If any...) - args, unknownargs = parser.parse_known_args() - - Application(args, unknownargs) - Gtk.main() + main(args, unknownargs) except Exception as e: traceback.print_exc() - quit() + quit() \ No newline at end of file diff --git a/src/app.py b/src/app.py index d80a172..6bcc0bb 100644 --- a/src/app.py +++ b/src/app.py @@ -1,20 +1,37 @@ # Python imports -import inspect - - -# Gtk imports +import signal +import os +# Lib imports # Application imports -from __builtins__ import * +from libs.debugging import debug_signal_handler from core.window import Window +class AppLaunchException(Exception): + ... + + + +class Application: + """ docstring for Application. """ -class Application(object): def __init__(self, args, unknownargs): + super(Application, self).__init__() + + self.setup_debug_hook() + Window(args, unknownargs).main() + + + def setup_debug_hook(self): try: - Window(args, unknownargs) - except Exception as e: - raise + # kill -SIGUSR2 from Linux/Unix or SIGBREAK signal from Windows + signal.signal( + vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR1"), + debug_signal_handler + ) + except ValueError: + # Typically: ValueError: signal only works in main thread + ... \ No newline at end of file diff --git a/src/core/columns/keys_column.py b/src/core/columns/keys_column.py index fd67536..5f7f482 100644 --- a/src/core/columns/keys_column.py +++ b/src/core/columns/keys_column.py @@ -21,6 +21,8 @@ class Keys_Column(Gtk.Box): super(Keys_Column, self).__init__() self.setup_styling() + self.setup_signals() + self.setup_custom_signals() self.setup_key_buttons() self.show_all() @@ -31,6 +33,16 @@ class Keys_Column(Gtk.Box): self.set_property("homogeneous", True) self.set_hexpand(True) + def setup_signals(self): + self.connect("button-release-event", self._on_button_release_event) + + def setup_custom_signals(self): + event_system.subscribe("itterate_mode", self.itterate_mode) + + def _on_button_release_event(self, widget = None, eve = None): + if eve.button == 3: # NOTE: right-click + event_system.emit_and_await("itterate_mode") + def setup_key_buttons(self): keys = keys_set["keys"] children = keys.keys() @@ -57,3 +69,15 @@ class Keys_Column(Gtk.Box): self.add(row_box) return row_box + + def itterate_mode(self): + emoji_view_shown = event_system.emit_and_await("is_emoji_view_shown") + is_symbols_enabled = event_system.emit_and_await("is_symbols_enabled") + + if not is_symbols_enabled and not emoji_view_shown: + event_system.emit("toggle_symbol_keys") + elif is_symbols_enabled and not emoji_view_shown: + event_system.emit("show_emoji_view") + elif is_symbols_enabled and emoji_view_shown: + event_system.emit("hide_emoji_view") + event_system.emit("toggle_symbol_keys") diff --git a/src/core/widgets/defined_keys.py b/src/core/widgets/defined_keys.py index 4689c5e..6a8b688 100644 --- a/src/core/widgets/defined_keys.py +++ b/src/core/widgets/defined_keys.py @@ -15,26 +15,14 @@ from .key import Key class Esc_Key(Key): def __init__(self): - super(Esc_Key, self).__init__("Esc", "Esc", iscontrol=True) + super(Esc_Key, self).__init__("Esc", "Esc", iscontrol = True) def setup_signals(self): self.connect("released", self._do_press_special_key) -class Symbols_Key(Key): - def __init__(self): - super(Symbols_Key, self).__init__("Symbols", "Symbols", iscontrol=True) - - def setup_signals(self): - self.connect("released", self._clicked) - - def _clicked(self, widget = None): - ctx = widget.get_style_context() - ctx.remove_class("toggled_bttn") if ctx.has_class("toggled_bttn") else ctx.add_class("toggled_bttn") - event_system.emit("toggle_symbol_keys") - class CAPS_Key(Key): def __init__(self): - super(CAPS_Key, self).__init__("Caps", "Caps", iscontrol=True) + super(CAPS_Key, self).__init__("Caps", "Caps", iscontrol = True) self.setup_styling() self.show_all() @@ -80,8 +68,26 @@ class Emoji_Key(Key): def unset_selected(self, widget = None): self._ctx.remove_class("toggled_bttn") +class Symbols_Key(Key): + def __init__(self): + super(Symbols_Key, self).__init__("Symbols", "Symbols", iscontrol = True) + self.setup_custom_signals() + def setup_signals(self): + self.connect("released", self._clicked) + def setup_custom_signals(self): + event_system.subscribe("is_symbols_enabled", self.is_symbols_enabled) + event_system.subscribe("toggle_symbol_keys", self.toggle_symbol_keys) + + def _clicked(self, widget = None): + ctx = widget.get_style_context() + ctx.remove_class("toggled_bttn") if ctx.has_class("toggled_bttn") else ctx.add_class("toggled_bttn") + event_system.emit("toggle_symbol_keys") + + def is_symbols_enabled(self): + ctx = self.get_style_context() + return True if ctx.has_class("toggled_bttn") else False class Enter_Key(Key): def __init__(self): diff --git a/src/core/widgets/emoji_popover.py b/src/core/widgets/emoji_popover.py index 89caa57..69e2251 100644 --- a/src/core/widgets/emoji_popover.py +++ b/src/core/widgets/emoji_popover.py @@ -49,12 +49,16 @@ class Emoji_Notebook(Gtk.Notebook): for group in emoji_grouping: tab_widget = Gtk.Label(label=group) scroll, grid = self.create_scroll_and_grid() + self.append_page(scroll, tab_widget) + self.set_tab_reorderable(scroll, False) + self.set_tab_detachable(scroll, False) top = 0 left = 0 for emoji in emoji_grouping[group]: key = Key(emoji["emoji"], emoji["emoji"]) key._is_emoji = True + key.show() grid.attach(key, left, top, width, height) left += 1 @@ -62,9 +66,6 @@ class Emoji_Notebook(Gtk.Notebook): 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() @@ -83,8 +84,9 @@ class Emoji_Popover(Gtk.Popover): emoji_notebook = Emoji_Notebook() self.add(emoji_notebook) - self.set_default_widget(emoji_notebook) self.setup_styling() + self.setup_signals() + self.setup_custom_signals() self._emoji_key = None @@ -94,8 +96,22 @@ class Emoji_Popover(Gtk.Popover): self.set_size_request(480, 280) def setup_signals(self): - self.connect("closed", self._emoji_key.unset_selected) + ... + + def setup_custom_signals(self): + event_system.subscribe("is_emoji_view_shown", self.is_emoji_view_shown) + event_system.subscribe("show_emoji_view", self.show_emoji_view) + event_system.subscribe("hide_emoji_view", self.hide_emoji_view) def set_parent_key(self, emoji_key): self._emoji_key = emoji_key self.setup_signals() + + def is_emoji_view_shown(self): + return self.is_visible() + + def show_emoji_view(self): + self.popup() + + def hide_emoji_view(self): + self.popdown() diff --git a/src/core/window.py b/src/core/window.py index b3d6111..72bc7d5 100644 --- a/src/core/window.py +++ b/src/core/window.py @@ -67,3 +67,6 @@ class Window(SignalsMixin, Gtk.ApplicationWindow): cr.set_operator(cairo.OPERATOR_SOURCE) cr.paint() cr.set_operator(cairo.OPERATOR_OVER) + + def main(self): + Gtk.main() \ No newline at end of file diff --git a/src/utils/__init__.py b/src/libs/__init__.py similarity index 100% rename from src/utils/__init__.py rename to src/libs/__init__.py diff --git a/src/libs/debugging.py b/src/libs/debugging.py new file mode 100644 index 0000000..b84193a --- /dev/null +++ b/src/libs/debugging.py @@ -0,0 +1,52 @@ +# Python imports + +# Lib imports + +# Application imports + + + +# Break into a Python console upon SIGUSR1 (Linux) or SIGBREAK (Windows: +# CTRL+Pause/Break). To be included in all production code, just in case. +def debug_signal_handler(signal, frame): + del signal + del frame + + try: + import rpdb2 + logger.debug("\n\nStarting embedded RPDB2 debugger. Password is 'foobar'\n\n") + rpdb2.start_embedded_debugger("foobar", True, True) + rpdb2.setbreak(depth=1) + return + except StandardError: + ... + + try: + from rfoo.utils import rconsole + logger.debug("\n\nStarting embedded rconsole debugger...\n\n") + rconsole.spawn_server() + return + except StandardError as ex: + ... + + try: + from pudb import set_trace + logger.debug("\n\nStarting PuDB debugger...\n\n") + set_trace(paused = True) + return + except StandardError as ex: + ... + + try: + import pdb + logger.debug("\n\nStarting embedded PDB debugger...\n\n") + pdb.Pdb(skip=['gi.*']).set_trace() + return + except StandardError as ex: + ... + + try: + import code + code.interact() + except StandardError as ex: + logger.debug(f"{ex}, returning to normal program flow...") diff --git a/src/utils/endpoint_registry.py b/src/libs/endpoint_registry.py similarity index 100% rename from src/utils/endpoint_registry.py rename to src/libs/endpoint_registry.py diff --git a/src/libs/event_system.py b/src/libs/event_system.py new file mode 100644 index 0000000..cd6975f --- /dev/null +++ b/src/libs/event_system.py @@ -0,0 +1,73 @@ +# Python imports +from collections import defaultdict + +# Lib imports + +# Application imports +from .singleton import Singleton + + + +class EventSystem(Singleton): + """ Create event system. """ + + def __init__(self): + self.subscribers = defaultdict(list) + self._is_paused = False + + self._subscribe_to_events() + + + def _subscribe_to_events(self): + self.subscribe("pause_event_processing", self._pause_processing_events) + self.subscribe("resume_event_processing", self._resume_processing_events) + + def _pause_processing_events(self): + self._is_paused = True + + def _resume_processing_events(self): + self._is_paused = False + + def subscribe(self, event_type, fn): + self.subscribers[event_type].append(fn) + + def unsubscribe(self, event_type, fn): + self.subscribers[event_type].remove(fn) + + def unsubscribe_all(self, event_type): + self.subscribers.pop(event_type, None) + + def emit(self, event_type, data = None): + if self._is_paused and event_type != "resume_event_processing": + return + + if event_type in self.subscribers: + for fn in self.subscribers[event_type]: + if data: + if hasattr(data, '__iter__') and not type(data) is str: + fn(*data) + else: + fn(data) + else: + fn() + + def emit_and_await(self, event_type, data = None): + if self._is_paused and event_type != "resume_event_processing": + return + + """ NOTE: Should be used when signal has only one listener and vis-a-vis """ + if event_type in self.subscribers: + response = None + for fn in self.subscribers[event_type]: + if data: + if hasattr(data, '__iter__') and not type(data) is str: + response = fn(*data) + else: + response = fn(data) + else: + response = fn() + + if not response in (None, ''): + break + + return response diff --git a/src/utils/logger.py b/src/libs/logger.py similarity index 100% rename from src/utils/logger.py rename to src/libs/logger.py diff --git a/src/utils/pyautogui/__init__.py b/src/libs/pyautogui/__init__.py similarity index 100% rename from src/utils/pyautogui/__init__.py rename to src/libs/pyautogui/__init__.py diff --git a/src/utils/pyautogui/__main__.py b/src/libs/pyautogui/__main__.py similarity index 100% rename from src/utils/pyautogui/__main__.py rename to src/libs/pyautogui/__main__.py diff --git a/src/utils/pyautogui/_pyautogui_java.py b/src/libs/pyautogui/_pyautogui_java.py similarity index 100% rename from src/utils/pyautogui/_pyautogui_java.py rename to src/libs/pyautogui/_pyautogui_java.py diff --git a/src/utils/pyautogui/_pyautogui_osx.py b/src/libs/pyautogui/_pyautogui_osx.py similarity index 100% rename from src/utils/pyautogui/_pyautogui_osx.py rename to src/libs/pyautogui/_pyautogui_osx.py diff --git a/src/utils/pyautogui/_pyautogui_win.py b/src/libs/pyautogui/_pyautogui_win.py similarity index 100% rename from src/utils/pyautogui/_pyautogui_win.py rename to src/libs/pyautogui/_pyautogui_win.py diff --git a/src/utils/pyautogui/_pyautogui_x11.py b/src/libs/pyautogui/_pyautogui_x11.py similarity index 100% rename from src/utils/pyautogui/_pyautogui_x11.py rename to src/libs/pyautogui/_pyautogui_x11.py diff --git a/src/utils/pyautogui_control.py b/src/libs/pyautogui_control.py similarity index 98% rename from src/utils/pyautogui_control.py rename to src/libs/pyautogui_control.py index 26538ad..1ad933c 100644 --- a/src/utils/pyautogui_control.py +++ b/src/libs/pyautogui_control.py @@ -48,7 +48,7 @@ class ControlMixin: def enter(self, widget = None, data = None): pyautogui.press("enter") - def backspace(self, widget = None, data=None): + def backspace(self, widget = None, data = None): pyautogui.press("backspace") def press_special_keys(self, key): diff --git a/src/libs/singleton.py b/src/libs/singleton.py new file mode 100644 index 0000000..23b7191 --- /dev/null +++ b/src/libs/singleton.py @@ -0,0 +1,24 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class SingletonError(Exception): + pass + + + +class Singleton: + ccount = 0 + + def __new__(cls, *args, **kwargs): + obj = super(Singleton, cls).__new__(cls) + cls.ccount += 1 + + if cls.ccount == 2: + raise SingletonError(f"Exceeded {cls.__name__} instantiation limit...") + + return obj diff --git a/src/utils/event_system.py b/src/utils/event_system.py deleted file mode 100644 index 25c96fc..0000000 --- a/src/utils/event_system.py +++ /dev/null @@ -1,30 +0,0 @@ -# Python imports -from collections import defaultdict - -# Lib imports - -# Application imports - - - - -class EventSystem: - """ Create event system. """ - - def __init__(self): - self.subscribers = defaultdict(list) - - - def subscribe(self, event_type, fn): - self.subscribers[event_type].append(fn) - - def emit(self, event_type, data = None): - if event_type in self.subscribers: - for fn in self.subscribers[event_type]: - if data: - if hasattr(data, '__iter__') and not type(data) is str: - fn(*data) - else: - fn(data) - else: - fn()