From 71a341ca1c2d41f309da52a3f05240fee411bf24 Mon Sep 17 00:00:00 2001 From: Charles Milette Date: Sat, 6 Jan 2018 19:51:42 -0500 Subject: [PATCH] Implement automatic discovery of terminal adapters --- README.md | 2 +- pokemonterminal/adapter/__init__.py | 24 ---------- pokemonterminal/adapter/base.py | 22 --------- .../adapter/implementations/ITerm.py | 33 ------------- .../adapter/implementations/NullAdapter.py | 15 ------ .../adapter/implementations/Terminology.py | 15 ------ .../adapter/implementations/__init__.py | 0 pokemonterminal/scripter.py | 46 +++++++++++++++---- pokemonterminal/terminal/__init__.py | 34 ++++++++++++++ pokemonterminal/terminal/adapters/ITerm.py | 31 +++++++++++++ .../terminal/adapters/Terminology.py | 14 ++++++ .../adapters}/Tilix.py | 11 ++--- pokemonterminal/terminal/adapters/__init__.py | 27 +++++++++++ pokemonterminal/wallpaper/__init__.py | 2 +- 14 files changed, 150 insertions(+), 126 deletions(-) delete mode 100644 pokemonterminal/adapter/__init__.py delete mode 100644 pokemonterminal/adapter/base.py delete mode 100644 pokemonterminal/adapter/implementations/ITerm.py delete mode 100644 pokemonterminal/adapter/implementations/NullAdapter.py delete mode 100644 pokemonterminal/adapter/implementations/Terminology.py delete mode 100644 pokemonterminal/adapter/implementations/__init__.py create mode 100644 pokemonterminal/terminal/__init__.py create mode 100644 pokemonterminal/terminal/adapters/ITerm.py create mode 100644 pokemonterminal/terminal/adapters/Terminology.py rename pokemonterminal/{adapter/implementations => terminal/adapters}/Tilix.py (64%) create mode 100644 pokemonterminal/terminal/adapters/__init__.py diff --git a/README.md b/README.md index 4da39ea..874bcf2 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ Alternatively, you can delete images from this folder and it will not break the $ sudo apt-get update $ sudo apt install terminology ``` -* If you get the error `39:46: syntax error: Expected end of line but found identifier. (-2741)`: Locate the file `ITerm.py` in `pokemonterminal/adapter/implementations` and on line 7, change `iTerm` to `iTerm2`. If you still experience the error, try changing it to `iTerm 2`. +* If you get the error `39:46: syntax error: Expected end of line but found identifier. (-2741)`: Locate the file `ITerm.py` in `pokemonterminal/terminal/adapters` and on line 9, change `iTerm` to `iTerm2`. If you still experience the error, try changing it to `iTerm 2`. ## Saving diff --git a/pokemonterminal/adapter/__init__.py b/pokemonterminal/adapter/__init__.py deleted file mode 100644 index 8296af6..0000000 --- a/pokemonterminal/adapter/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -from pokemonterminal.adapter.implementations.ITerm import ITerm -from pokemonterminal.adapter.implementations.NullAdapter import NullAdapter -from pokemonterminal.adapter.implementations.Terminology import Terminology -from pokemonterminal.adapter.implementations.Tilix import Tilix - - -available_terminals = [ - Terminology, - Tilix, - ITerm -] - - -def identify(): - """ - Identify the terminal we are using based on env vars. - :return: A terminal adapter interface or a NullAdapter. - :rtype: TerminalAdapterInterface - """ - for terminal in available_terminals: - if terminal.is_available(): - return terminal() - - return NullAdapter() diff --git a/pokemonterminal/adapter/base.py b/pokemonterminal/adapter/base.py deleted file mode 100644 index 0bfe9c4..0000000 --- a/pokemonterminal/adapter/base.py +++ /dev/null @@ -1,22 +0,0 @@ -class TerminalAdapterInterface(object): - @staticmethod - def is_available(): - """ - :return: True if the environment implies we are using this terminal. - :rtype bool - """ - raise NotImplementedError() - - def set_image_file_path(self, image_file_path): - """ - Set the background image of the terminal. - :param image_file_path: Path to an image file. - :rtype str - """ - raise NotImplementedError() - - def clear(self): - """ - Clear the terminal's background image. - """ - raise NotImplementedError() diff --git a/pokemonterminal/adapter/implementations/ITerm.py b/pokemonterminal/adapter/implementations/ITerm.py deleted file mode 100644 index 5b41e92..0000000 --- a/pokemonterminal/adapter/implementations/ITerm.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import subprocess - -from pokemonterminal.adapter.base import TerminalAdapterInterface - - -# OSA script that will change the terminal background image -osa_script_fmt = """tell application "iTerm" -\ttell current session of current window -\t\tset background image to "{}" -\tend tell -end tell""" - - -class ITerm(TerminalAdapterInterface): - @staticmethod - def is_available(): - return os.environ.get("ITERM_PROFILE") - - def __run_osascript(self, stream): - p = subprocess.Popen(['osascript'], stdout=subprocess.PIPE, - stdin=subprocess.PIPE) - p.stdin.write(stream) - p.communicate() - p.stdin.close() - - def set_image_file_path(self, image_file_path): - stdin = osa_script_fmt.format(image_file_path) - self.__run_osascript(str.encode(stdin)) - - def clear(self): - stdin = osa_script_fmt.format("") - self.__run_osascript(str.encode(stdin)) diff --git a/pokemonterminal/adapter/implementations/NullAdapter.py b/pokemonterminal/adapter/implementations/NullAdapter.py deleted file mode 100644 index 2414fc7..0000000 --- a/pokemonterminal/adapter/implementations/NullAdapter.py +++ /dev/null @@ -1,15 +0,0 @@ -from pokemonterminal.adapter.base import TerminalAdapterInterface - - -class NullAdapter(TerminalAdapterInterface): - err = "This terminal emulator is not supported." - - @staticmethod - def is_available(): - return True - - def set_image_file_path(self, image_file_path): - print(self.err) - - def clear(self): - print(self.err) diff --git a/pokemonterminal/adapter/implementations/Terminology.py b/pokemonterminal/adapter/implementations/Terminology.py deleted file mode 100644 index d1052e0..0000000 --- a/pokemonterminal/adapter/implementations/Terminology.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - -from pokemonterminal.adapter.base import TerminalAdapterInterface - - -class Terminology(TerminalAdapterInterface): - @staticmethod - def is_available(): - return os.environ.get("TERMINOLOGY") == '1' - - def set_image_file_path(self, image_file_path): - os.system('tybg "{}"'.format(image_file_path)) - - def clear(self): - os.system("tybg") diff --git a/pokemonterminal/adapter/implementations/__init__.py b/pokemonterminal/adapter/implementations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pokemonterminal/scripter.py b/pokemonterminal/scripter.py index 2ed95f6..2de7f83 100644 --- a/pokemonterminal/scripter.py +++ b/pokemonterminal/scripter.py @@ -1,17 +1,47 @@ # Used for creating, running and analyzing applescript and bash scripts. import sys -from pokemonterminal.adapter import identify -from .wallpaper import get_current_adapters +from .terminal import get_current_terminal_adapters +from .wallpaper import get_current_wallpaper_adapters +TERMINAL_PROVIDER = None WALLPAPER_PROVIDER = None +def __init_terminal_provider(): + global TERMINAL_PROVIDER + if TERMINAL_PROVIDER is not None: + return + providers = get_current_terminal_adapters() + if len(providers) > 1: + # All this if is really not supposed to happen at all whatsoever + # really what kind of person has 2 simultaneous D.E??? + print("Multiple providers found select the appropriate one:") + [print(str(x)) for x in providers] + print("If some of these make no sense or are irrelevant please file" + + "an issue in https://github.com/LazoCoder/Pokemon-Terminal") + print("=> ", end='') + inp = None + while inp is None: + try: + inp = int(input()) + if inp >= len(providers): + raise ValueError() + except ValueError as _: + print("Invalid number, try again!") + TERMINAL_PROVIDER = providers[inp] + elif len(providers) <= 0: + print("Your terminal emulator isn't supported at this time.") + sys.exit() + else: + TERMINAL_PROVIDER = providers[0] + + def __init_wallpaper_provider(): global WALLPAPER_PROVIDER if WALLPAPER_PROVIDER is not None: return - providers = get_current_adapters() + providers = get_current_wallpaper_adapters() if len(providers) > 1: # All this if is really not supposed to happen at all whatsoever # really what kind of person has 2 simultaneous D.E??? @@ -37,18 +67,16 @@ def __init_wallpaper_provider(): def clear_terminal(): - adapter = identify() - adapter.clear() + __init_terminal_provider() + TERMINAL_PROVIDER.clear() def change_terminal(image_file_path): if not isinstance(image_file_path, str): print("A image path must be passed to the change terminal function.") return - adapter = identify() - if adapter is None: - print("Terminal not supported") - adapter.set_image_file_path(image_file_path) + __init_terminal_provider() + TERMINAL_PROVIDER.change_terminal(image_file_path) def change_wallpaper(image_file_path): diff --git a/pokemonterminal/terminal/__init__.py b/pokemonterminal/terminal/__init__.py new file mode 100644 index 0000000..1b6c41a --- /dev/null +++ b/pokemonterminal/terminal/__init__.py @@ -0,0 +1,34 @@ +import os +import importlib +import inspect +from .adapters import TerminalProvider + + +def _is_adapter(member) -> bool: + return (inspect.isclass(member) + and issubclass(member, TerminalProvider) + and member != TerminalProvider) + + +def _get_adapter_classes() -> [TerminalProvider]: + """ + This methods reads all the modules in the adapters folder searching for + all the implementing wallpaper adapter classes + thanks for/adapted from https://github.com/cclauss/adapter_pattern/ + """ + dirname = os.path.join(os.path.dirname( + os.path.abspath(__file__)), 'adapters') + adapter_classes = [] + for file_name in sorted(os.listdir(dirname)): + root, ext = os.path.splitext(file_name) + if ext.lower() == '.py' and not root.startswith('__'): + module = importlib.import_module( + '.' + root, 'pokemonterminal.terminal.adapters') + for _, c in inspect.getmembers(module, _is_adapter): + adapter_classes.append(c) + return adapter_classes + + +def get_current_terminal_adapters() -> [TerminalProvider]: + arr = _get_adapter_classes() + return [x for x in arr if x.is_compatible()] diff --git a/pokemonterminal/terminal/adapters/ITerm.py b/pokemonterminal/terminal/adapters/ITerm.py new file mode 100644 index 0000000..b15b4f0 --- /dev/null +++ b/pokemonterminal/terminal/adapters/ITerm.py @@ -0,0 +1,31 @@ +import os +import subprocess + +from . import TerminalProvider as _TProv + + +class ITerm(_TProv): + # OSA script that will change the terminal background image + osa_script_fmt = """tell application "iTerm" + \ttell current session of current window + \t\tset background image to "{}" + \tend tell + end tell""" + + def is_compatible() -> bool: + return os.environ.get("ITERM_PROFILE") + + def __run_osascript(stream): + p = subprocess.Popen(['osascript'], stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + p.stdin.write(stream) + p.communicate() + p.stdin.close() + + def change_terminal(self, path: str): + stdin = self.osa_script_fmt.format(path) + self.__run_osascript(str.encode(stdin)) + + def clear(self): + stdin = self.osa_script_fmt.format("") + self.__run_osascript(str.encode(stdin)) diff --git a/pokemonterminal/terminal/adapters/Terminology.py b/pokemonterminal/terminal/adapters/Terminology.py new file mode 100644 index 0000000..66b172e --- /dev/null +++ b/pokemonterminal/terminal/adapters/Terminology.py @@ -0,0 +1,14 @@ +import os + +from . import TerminalProvider as _TProv + + +class Terminology(_TProv): + def is_compatible() -> bool: + return os.environ.get("TERMINOLOGY") == '1' + + def change_terminal(path: str): + os.system('tybg "{}"'.format(path)) + + def clear(): + os.system("tybg") diff --git a/pokemonterminal/adapter/implementations/Tilix.py b/pokemonterminal/terminal/adapters/Tilix.py similarity index 64% rename from pokemonterminal/adapter/implementations/Tilix.py rename to pokemonterminal/terminal/adapters/Tilix.py index 6de1de2..c070169 100644 --- a/pokemonterminal/adapter/implementations/Tilix.py +++ b/pokemonterminal/terminal/adapters/Tilix.py @@ -1,21 +1,20 @@ import os -from pokemonterminal.adapter.base import TerminalAdapterInterface +from . import TerminalProvider as _TProv -class Tilix(TerminalAdapterInterface): +class Tilix(_TProv): setting_key = "com.gexperts.Tilix.Settings" setting_field = "background-image" - @staticmethod - def is_available(): + def is_compatible() -> bool: return "TILIX_ID" in os.environ - def set_image_file_path(self, image_file_path): + def change_terminal(self, path: str): command = 'gsettings set {} {} "{}"' os.system(command.format(self.setting_key, self.setting_field, - image_file_path)) + path)) def clear(self): command = 'gsettings set {} {}' diff --git a/pokemonterminal/terminal/adapters/__init__.py b/pokemonterminal/terminal/adapters/__init__.py new file mode 100644 index 0000000..1d14d6f --- /dev/null +++ b/pokemonterminal/terminal/adapters/__init__.py @@ -0,0 +1,27 @@ +class TerminalProvider: + """ + Interface representing all the different terminal emulators supported + by pokemon-terminal if you want to implement a TE, create a module in this + folder that implements this interface, reflection will do the rest. + """ + + def change_terminal(path: str): + """ + This sets the wallpaper of the corresponding TE of this adapter. + :param path The full path of the required pokemon image + """ + raise NotImplementedError() + + def is_compatible() -> bool: + """ + checks for compatibility + :return a boolean saying whether or not the current adaptor is + compatible with the running TE + """ + raise NotImplementedError() + + def clear(self): + """ + Clear the terminal's background image. + """ + raise NotImplementedError() diff --git a/pokemonterminal/wallpaper/__init__.py b/pokemonterminal/wallpaper/__init__.py index 34bbf87..17da7fc 100644 --- a/pokemonterminal/wallpaper/__init__.py +++ b/pokemonterminal/wallpaper/__init__.py @@ -29,6 +29,6 @@ def _get_adapter_classes() -> [WallpaperProvider]: return adapter_classes -def get_current_adapters() -> [WallpaperProvider]: +def get_current_wallpaper_adapters() -> [WallpaperProvider]: arr = _get_adapter_classes() return [x for x in arr if x.is_compatible()]