diff --git a/README.md b/README.md index e35fd2a..eb75fb6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# Mouse_Keyboard - # Mouse_Keyboard An onscreen keyboard for the mouse. + +# TODO +
  • Get save and execute of custom commands working.
  • +
  • Get button case toggle working.
  • + +# Images +![1 GUI of the keyboard. ](images/pic1.png) diff --git a/images/pic1.png b/images/pic1.png new file mode 100644 index 0000000..f737500 Binary files /dev/null and b/images/pic1.png differ diff --git a/src/__builtins__.py b/src/__builtins__.py new file mode 100644 index 0000000..ece2dd4 --- /dev/null +++ b/src/__builtins__.py @@ -0,0 +1,7 @@ +import builtins + +class Builtins: + def __init__(self): + pass + +builtins.app_name = "Mouse_Keyboard" diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..3f5db84 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,34 @@ +# Python imports +import inspect + + +# Gtk imports + + +# Application imports +from utils.settings import Settings +from signal_classes.signals import Signals +from __builtins__ import Builtins + + +class Main(Builtins): + def __init__(self, args): + settings = Settings() + builder = settings.returnBuilder() + + # Gets the methods from the classes and sets to handler. + # Then, builder connects to any signals it needs. + classes = [Signals(settings)] + + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + pass + + builder.connect_signals(handlers) + window = settings.createWindow() + window.show() diff --git a/src/__main__.py b/src/__main__.py new file mode 100644 index 0000000..1065eb3 --- /dev/null +++ b/src/__main__.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + + +# Python imports +import argparse +from setproctitle import setproctitle + +# Gtk imports +import gi, faulthandler, signal +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GLib + +# Application imports +from __init__ import Main + + +if __name__ == "__main__": + try: + setproctitle('Mouse Keyboard') + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit) + faulthandler.enable() # For better debug info + parser = argparse.ArgumentParser() + # Add long and short arguments + parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.") + + # Read arguments (If any...) + args = parser.parse_args() + main = Main(args) + Gtk.main() + except Exception as e: + print( repr(e) ) diff --git a/src/resources/Main_Window.glade b/src/resources/Main_Window.glade new file mode 100644 index 0000000..7931fad --- /dev/null +++ b/src/resources/Main_Window.glade @@ -0,0 +1,436 @@ + + + + + + + + + + + + False + Mouse Board + center + 260 + icon.png + toolbar + True + True + False + center + + + True + False + vertical + + + True + False + + + True + False + + + True + True + 0 + + + + + 520 + True + False + + + True + True + gtk-go-forward + False + False + Autotype Field... + + + True + True + 0 + + + + + Type + True + True + True + + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + + + True + True + 2 + + + + + False + True + 0 + + + + + True + False + + + True + False + vertical + + + True + False + 10 + 10 + Special Characters + + + + + + False + True + 0 + + + + + True + False + in + + + True + False + + + True + False + vertical + 10 + start + + + + + + + + + + True + True + 1 + + + + + False + True + 0 + + + + + popoutkeyboard + True + False + 5 + vertical + True + + + + + + + + + + + + + + + + + + + + + False + True + 1 + + + + + True + False + 10 + 10 + vertical + + + True + False + start + + + Del + True + True + True + + + + True + True + 0 + + + + + Ctrl + True + True + True + + + + True + True + 1 + + + + + Shift + True + True + True + + + + True + True + 2 + + + + + Alt + True + True + True + + + + True + True + 3 + + + + + PrtSc + True + True + False + + + + True + True + 4 + + + + + gtk-add + True + True + True + True + + + True + True + 5 + + + + + False + True + 0 + + + + + True + True + in + + + True + False + + + True + True + commands + + + + + + Commands + + + + 0 + + + + + + + + + + + True + True + 1 + + + + + + True + False + True + + + Up + True + True + True + + + + 1 + 0 + + + + + Down + True + True + True + + + + 1 + 2 + + + + + Left + True + True + True + + + + 0 + 1 + + + + + Right + True + True + True + + + + 2 + 1 + + + + + + + + + + + + + + + + + + + + False + True + 2 + + + + + False + True + 2 + + + + + True + True + 1 + + + + + + diff --git a/src/resources/icon.png b/src/resources/icon.png new file mode 100644 index 0000000..bcf986e Binary files /dev/null and b/src/resources/icon.png differ diff --git a/src/resources/stylesheet.css b/src/resources/stylesheet.css new file mode 100644 index 0000000..f219141 --- /dev/null +++ b/src/resources/stylesheet.css @@ -0,0 +1,8 @@ +/* * { + background: rgba(0, 0, 0, 0.14); + color: rgba(255, 255, 255, 1); +} + +#popoutkeyboard { + background-color: rgba(0, 65, 125, 1); +} */ diff --git a/src/signal_classes/__init__.py b/src/signal_classes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/signal_classes/mixins/__init__.py b/src/signal_classes/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/signal_classes/mixins/keyboardmixin.py b/src/signal_classes/mixins/keyboardmixin.py new file mode 100644 index 0000000..9a96478 --- /dev/null +++ b/src/signal_classes/mixins/keyboardmixin.py @@ -0,0 +1,94 @@ +# Python imports +import pyautogui + +# Gtk imports + +# Application imports + + +# Let piautogui make updates as quick as it can... +pyautogui.FAILSAFE = False # If we hit corner, that's ok +pyautogui.MINIMUM_DURATION = 0 +pyautogui.PAUSE = 0 + + +class KeyboardMixin: + + def typeString(self, widget = None, data = None): + text = self.autoTypeField.get_text() + for char in text: + self.do_insert(char) + + def insert(self, widget = None, data = None, key = None): + if not key: + key = widget.get_label().strip() + + if self.is_keypress_type(key): + return + + if self.isCapsLockOn: + key = key.upper() + + self.do_insert(key) + + + def do_insert(self, key): + if self.isCtrlOn or self.isShiftOn or self.isAltOn: + self.set_hotkeys() + + pyautogui.typewrite(key) + + if self.isCtrlOn or self.isShiftOn or self.isAltOn: + self.unset_hotkeys() + + + def is_keypress_type(self, key): + if key in ["Esc", "Tab", "Space", "Del", "Up", "Down", "Left", "Right", "PrtSc"]: + pyautogui.press(key.lower()) + return True + + for i in range(1, 13): + fkey = 'F' + str(i) + if key == fkey: + pyautogui.press(key.lower()) + return True + + return False + + + def set_hotkeys(self): + if self.isCtrlOn: + pyautogui.keyDown('ctrl') + if self.isShiftOn: + pyautogui.keyDown('shiftleft') + pyautogui.keyDown('shiftright') + if self.isAltOn: + pyautogui.keyDown('alt') + + + def unset_hotkeys(self): + pyautogui.keyUp('ctrl') + pyautogui.keyUp('shiftleft') + pyautogui.keyUp('shiftright') + pyautogui.keyUp('alt') + + + def toggleCaps(self, widget, data=None): + self.isCapsLockOn = False if self.isCapsLockOn else True + + def tgglCtrl(self, widget, data=None): + self.isCtrlOn = False if self.isCtrlOn else True + + def tgglShift(self, widget, data=None): + self.isShiftOn = False if self.isShiftOn else True + + def tgglAlt(self, widget, data=None): + self.isAltOn = False if self.isAltOn else True + + + def enter(self, widget, data=None): + pyautogui.press("enter") + + + def backspace(self, widget, data=None): + pyautogui.press("backspace") diff --git a/src/signal_classes/signals.py b/src/signal_classes/signals.py new file mode 100644 index 0000000..0751a68 --- /dev/null +++ b/src/signal_classes/signals.py @@ -0,0 +1,107 @@ +# Python imports +import threading, subprocess, os + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from .mixins.keyboardmixin import KeyboardMixin + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + return wrapper + + +class Signals(KeyboardMixin): + def __init__(self, settings): + self.settings = settings + self.builder = self.settings.returnBuilder() + self.autoTypeField = self.builder.get_object("autoTypeField") + self.commandsStore = self.builder.get_object("commands") + self.specialsStore = self.builder.get_object("specials") + self.specialsTree = self.builder.get_object("specialsTree") + main_keys = self.builder.get_object("main_keys") + + self.isCapsLockOn = False + self.isCtrlOn = False + self.isShiftOn = False + self.isAltOn = False + + special_characters = "<>()[]{}/\!?#$%&@*:^|'\"-_=+~`" + self.generate_keys(special_characters, self.specialsStore) + self.specialsStore.show_all() + main_keys.show_all() + + row1_characters = "1234567890" + row1 = Gtk.Box() + self.generate_keys(["Esc",], row1) + self.generate_keys(row1_characters, row1) + self.generate_keys(["Backspace",], row1) + row1.set_homogeneous(True) + row1.show_all() + main_keys.add(row1) + + row2_characters = "qwertyuiop" + row2 = Gtk.Box() + self.generate_keys(["Tab",], row2) + self.generate_keys(row2_characters, row2) + row2.set_homogeneous(True) + row2.show_all() + main_keys.add(row2) + + row3_characters = "asdfghjkl" + row3 = Gtk.Box() + self.generate_keys(["Caps Lock",], row3) + self.generate_keys(row3_characters, row3) + self.generate_keys(["Enter",], row3) + row3.set_homogeneous(True) + row3.show_all() + main_keys.add(row3) + + row4_characters = "zxcvbnm,.:" + row4 = Gtk.Box() + self.generate_keys(row4_characters, row4) + row4.set_homogeneous(True) + row4.show_all() + main_keys.add(row4) + + row5_characters = "Space" + row5 = Gtk.Box() + self.generate_keys([row5_characters,], row5) + row5.set_homogeneous(True) + row5.show_all() + main_keys.add(row5) + + + def generate_keys(self, labels, target): + for label in labels: + button = Gtk.Button.new_with_label(label) + if label not in ["Enter", "Backspace", "Caps Lock"]: + button.connect("clicked", self.insert) + else: + if label == "Enter": + button.connect("clicked", self.enter) + if label == "Backspace": + button.connect("clicked", self.backspace) + if label == "Caps Lock": + button = Gtk.ToggleButton.new_with_label(label) + button.connect("toggled", self.toggleCaps) + + target.add(button) + + + def getClipboardData(self): + proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE) + retcode = proc.wait() + data = proc.stdout.read() + return data.decode("utf-8").strip() + + def setClipboardData(self, data): + proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.close() + retcode = proc.wait() diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/logger.py b/src/utils/logger.py new file mode 100644 index 0000000..c7f294e --- /dev/null +++ b/src/utils/logger.py @@ -0,0 +1,56 @@ +# Python imports +import os, logging + +# Application imports + + +class Logger: + def __init__(self): + pass + + def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True): + """ + Create a new logging object and return it. + :note: + NOSET # Don't know the actual log level of this... (defaulting or literally none?) + Log Levels (From least to most) + Type Value + CRITICAL 50 + ERROR 40 + WARNING 30 + INFO 20 + DEBUG 10 + :param loggerName: Sets the name of the logger object. (Used in log lines) + :param createFile: Whether we create a log file or just pump to terminal + + :return: the logging object we created + """ + + globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels + chLogLevel = logging.CRITICAL # Prety musch the only one we change ever + fhLogLevel = logging.DEBUG + log = logging.getLogger(loggerName) + log.setLevel(globalLogLvl) + + # Set our log output styles + fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S') + cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s') + + ch = logging.StreamHandler() + ch.setLevel(level=chLogLevel) + ch.setFormatter(cFormatter) + log.addHandler(ch) + + if createFile: + folder = "logs" + file = folder + "/application.log" + + if not os.path.exists(folder): + os.mkdir(folder) + + fh = logging.FileHandler(file) + fh.setLevel(level=fhLogLevel) + fh.setFormatter(fFormatter) + log.addHandler(fh) + + return log diff --git a/src/utils/settings.py b/src/utils/settings.py new file mode 100644 index 0000000..081eaed --- /dev/null +++ b/src/utils/settings.py @@ -0,0 +1,97 @@ +# Python imports +import os + +# Gtk imports +import gi, cairo +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') + +from gi.repository import Gtk +from gi.repository import Gdk + + +# Application imports + + +class Settings: + def __init__(self): + self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._USER_HOME = os.path.expanduser('~') + self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}" + self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade" + self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css" + self._USR_PATH = f"/usr/share/{app_name.lower()}" + + if not os.path.exists(self._CONFIG_PATH): + os.mkdir(self._CONFIG_PATH) + if not os.path.exists(self._GLADE_FILE): + self._GLADE_FILE = f"{self._USR_PATH}/Main_Window.glade" + if not os.path.exists(self._CSS_FILE): + self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css" + + # 'Filters' + self.office = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', + '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf') + self.vids = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', + '.mpeg', '.mp4', '.webm') + self.txt = ('.txt', '.text', '.sh', '.cfg', '.conf') + self.music = ('.psf', '.mp3', '.ogg' , '.flac') + self.images = ('.png', '.jpg', '.jpeg', '.gif') + self.pdf = ('.pdf') + + self.builder = Gtk.Builder() + self.builder.add_from_file(self._GLADE_FILE) + + + def createWindow(self): + # Get window and connect signals + window = self.builder.get_object("Main_Window") + window.connect("delete-event", Gtk.main_quit) + self.setWindowData(window, False) + return window + + def setWindowData(self, window, paintable): + screen = window.get_screen() + visual = screen.get_rgba_visual() + + if visual != None and screen.is_composited(): + window.set_visual(visual) + + # bind css file + cssProvider = Gtk.CssProvider() + cssProvider.load_from_path(self._CSS_FILE) + screen = Gdk.Screen.get_default() + styleContext = Gtk.StyleContext() + styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) + + window.set_app_paintable(paintable) + if paintable: + window.connect("draw", self.area_draw) + + def getMonitorData(self): + screen = self.builder.get_object("Main_Window").get_screen() + monitors = [] + for m in range(screen.get_n_monitors()): + monitors.append(screen.get_monitor_geometry(m)) + + for monitor in monitors: + print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) + + return monitors + + def area_draw(self, widget, cr): + cr.set_source_rgba(0, 0, 0, 0.54) + cr.set_operator(cairo.OPERATOR_SOURCE) + cr.paint() + cr.set_operator(cairo.OPERATOR_OVER) + + + def returnBuilder(self): return self.builder + + # Filter returns + def returnOfficeFilter(self): return self.office + def returnVidsFilter(self): return self.vids + def returnTextFilter(self): return self.txt + def returnMusicFilter(self): return self.music + def returnImagesFilter(self): return self.images + def returnPdfFilter(self): return self.pdf diff --git a/user_config/usr/share/mouse_keyboard/Main_Window.glade b/user_config/usr/share/mouse_keyboard/Main_Window.glade new file mode 100644 index 0000000..7931fad --- /dev/null +++ b/user_config/usr/share/mouse_keyboard/Main_Window.glade @@ -0,0 +1,436 @@ + + + + + + + + + + + + False + Mouse Board + center + 260 + icon.png + toolbar + True + True + False + center + + + True + False + vertical + + + True + False + + + True + False + + + True + True + 0 + + + + + 520 + True + False + + + True + True + gtk-go-forward + False + False + Autotype Field... + + + True + True + 0 + + + + + Type + True + True + True + + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + + + True + True + 2 + + + + + False + True + 0 + + + + + True + False + + + True + False + vertical + + + True + False + 10 + 10 + Special Characters + + + + + + False + True + 0 + + + + + True + False + in + + + True + False + + + True + False + vertical + 10 + start + + + + + + + + + + True + True + 1 + + + + + False + True + 0 + + + + + popoutkeyboard + True + False + 5 + vertical + True + + + + + + + + + + + + + + + + + + + + + False + True + 1 + + + + + True + False + 10 + 10 + vertical + + + True + False + start + + + Del + True + True + True + + + + True + True + 0 + + + + + Ctrl + True + True + True + + + + True + True + 1 + + + + + Shift + True + True + True + + + + True + True + 2 + + + + + Alt + True + True + True + + + + True + True + 3 + + + + + PrtSc + True + True + False + + + + True + True + 4 + + + + + gtk-add + True + True + True + True + + + True + True + 5 + + + + + False + True + 0 + + + + + True + True + in + + + True + False + + + True + True + commands + + + + + + Commands + + + + 0 + + + + + + + + + + + True + True + 1 + + + + + + True + False + True + + + Up + True + True + True + + + + 1 + 0 + + + + + Down + True + True + True + + + + 1 + 2 + + + + + Left + True + True + True + + + + 0 + 1 + + + + + Right + True + True + True + + + + 2 + 1 + + + + + + + + + + + + + + + + + + + + False + True + 2 + + + + + False + True + 2 + + + + + True + True + 1 + + + + + + diff --git a/user_config/usr/share/mouse_keyboard/mouse-keyboard.png b/user_config/usr/share/mouse_keyboard/mouse-keyboard.png new file mode 100644 index 0000000..bcf986e Binary files /dev/null and b/user_config/usr/share/mouse_keyboard/mouse-keyboard.png differ diff --git a/user_config/usr/share/mouse_keyboard/stylesheet.css b/user_config/usr/share/mouse_keyboard/stylesheet.css new file mode 100644 index 0000000..f219141 --- /dev/null +++ b/user_config/usr/share/mouse_keyboard/stylesheet.css @@ -0,0 +1,8 @@ +/* * { + background: rgba(0, 0, 0, 0.14); + color: rgba(255, 255, 255, 1); +} + +#popoutkeyboard { + background-color: rgba(0, 65, 125, 1); +} */