mirror of
https://github.com/LazoCoder/Pokemon-Terminal
synced 2025-02-17 05:18:31 +00:00
Merge pull request #138 from LazoCoder/terminal-adapter
Implement automatic discovery of terminal adapters
This commit is contained in:
commit
b304c7f357
16 changed files with 179 additions and 160 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -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))
|
|
@ -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)
|
|
@ -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")
|
|
@ -1,23 +1,55 @@
|
|||
# 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 T.E???
|
||||
print("Multiple providers found select the appropriate one:")
|
||||
for i, x in enumerate(providers):
|
||||
print(f'{i}. {x.__str__()}')
|
||||
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???
|
||||
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" +
|
||||
for i, x in enumerate(providers):
|
||||
print(f'{i}. {x.__str__()}')
|
||||
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
|
||||
|
@ -37,18 +69,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):
|
||||
|
|
34
pokemonterminal/terminal/__init__.py
Normal file
34
pokemonterminal/terminal/__init__.py
Normal file
|
@ -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()]
|
27
pokemonterminal/terminal/adapters/__init__.py
Normal file
27
pokemonterminal/terminal/adapters/__init__.py
Normal file
|
@ -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()
|
34
pokemonterminal/terminal/adapters/iterm.py
Normal file
34
pokemonterminal/terminal/adapters/iterm.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from . import TerminalProvider as _TProv
|
||||
|
||||
|
||||
class ItermProvider(_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))
|
||||
|
||||
def __str__():
|
||||
return "iTerm 2"
|
17
pokemonterminal/terminal/adapters/terminology.py
Normal file
17
pokemonterminal/terminal/adapters/terminology.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
|
||||
from . import TerminalProvider as _TProv
|
||||
|
||||
|
||||
class TerminologyProvider(_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")
|
||||
|
||||
def __str__():
|
||||
return "Terminology"
|
|
@ -1,23 +1,25 @@
|
|||
import os
|
||||
|
||||
from pokemonterminal.adapter.base import TerminalAdapterInterface
|
||||
from . import TerminalProvider as _TProv
|
||||
|
||||
|
||||
class Tilix(TerminalAdapterInterface):
|
||||
class TilixProvider(_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 {} {}'
|
||||
os.system(command.format(self.setting_key,
|
||||
self.setting_field))
|
||||
|
||||
def __str__():
|
||||
return "Tilix"
|
|
@ -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()]
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# To run use python3 -m pytest --capture=sys
|
||||
|
||||
import os
|
||||
|
||||
from pokemonterminal.adapter import available_terminals, base
|
||||
from tests.test_utils import SCRIPT_DIR
|
||||
|
||||
|
||||
def test_available_terminals():
|
||||
assert available_terminals, 'No available_terminals found.'
|
||||
terminal_names = [terminal.__name__ for terminal in available_terminals]
|
||||
non_terminals = ['NullAdapter', '__init__']
|
||||
assert all(terminal not in terminal_names for terminal in non_terminals)
|
||||
terminals_dir = os.path.join(SCRIPT_DIR, 'adapter', 'implementations')
|
||||
assert os.path.isdir(terminals_dir), 'Not found: ' + terminals_dir
|
||||
for filename in os.listdir(terminals_dir):
|
||||
terminal, ext = os.path.splitext(filename)
|
||||
if ext.lower() == '.py':
|
||||
assert terminal in (terminal_names + non_terminals), terminal
|
||||
|
||||
|
||||
def test_adapter_methods():
|
||||
for terminal in available_terminals + [base.TerminalAdapterInterface]:
|
||||
assert callable(terminal.clear)
|
||||
assert callable(terminal.is_available)
|
||||
assert callable(terminal.set_image_file_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_available_terminals()
|
16
tests/test_terminal.py
Normal file
16
tests/test_terminal.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from pokemonterminal.terminal import _get_adapter_classes
|
||||
import os.path as __p
|
||||
import inspect as __inspct
|
||||
|
||||
|
||||
def test_terminal_adapter_classes():
|
||||
all_adapter = _get_adapter_classes()
|
||||
files = {__inspct.getfile(x) for x in all_adapter}
|
||||
print('all adapter classes:\n', files)
|
||||
assert len(all_adapter) >= len(files), \
|
||||
"Some of the files in the adapter module don't define an adapter"
|
||||
module_name = {x.__name__: __p.splitext(__p.basename(
|
||||
__inspct.getfile(x)))[0] for x in all_adapter}
|
||||
print("'class: module' map\n", module_name)
|
||||
assert all(y.lower() in x.lower() for x, y in module_name.items()), \
|
||||
"Some of the adapters are defined in unrelated named modules"
|
Loading…
Add table
Reference in a new issue