From 4e89252ef9d73a5d3097ac97593867c9581ecb57 Mon Sep 17 00:00:00 2001 From: joshcangit Date: Mon, 1 Nov 2021 17:19:02 +0800 Subject: [PATCH 1/3] Support more platforms Add support for Windows, Termux and Wayland on Linux. The which command is deprecated in debianutils (5.0-1). Use a python function instead for POSIX compatibility. --- setup.py | 3 +- suplemon/key_mappings.py | 4 +- suplemon/modules/system_clipboard.py | 80 ++++++++++++++++++++-------- suplemon/ui.py | 13 +++-- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/setup.py b/setup.py index ef9cba1..1a1edb7 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ include_package_data=True, install_requires=[ "pygments", - "wcwidth" + "wcwidth", + "windows-curses; platform_system=='Windows'" ], entry_points={ "console_scripts": ["suplemon=suplemon.cli:main"] diff --git a/suplemon/key_mappings.py b/suplemon/key_mappings.py index caa79c8..991311e 100644 --- a/suplemon/key_mappings.py +++ b/suplemon/key_mappings.py @@ -47,11 +47,13 @@ "KEY_ENTER": "enter", "\n": "enter", "^J": "enter", + "^M": "enter", 343: "shift+enter", curses.KEY_BACKSPACE: "backspace", "KEY_BACKSPACE": "backspace", "^?": "backspace", + "^H": "backspace", # on Windows curses.KEY_DC: "delete", curses.KEY_HOME: "home", @@ -80,7 +82,7 @@ "^E": "ctrl+e", "^F": "ctrl+f", "^G": "ctrl+g", - "^H": "ctrl+h", + # "^H": "ctrl+h", # Conflicts with 'backspace' on Windows # "^I": "ctrl+i", # Conflicts with 'tab' # "^J": "ctrl+j", # Conflicts with 'enter' "^K": "ctrl+k", diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 5cd41cd..2468c00 100644 --- a/suplemon/modules/system_clipboard.py +++ b/suplemon/modules/system_clipboard.py @@ -1,6 +1,8 @@ # -*- encoding: utf-8 +import os import subprocess +from platform import system from suplemon.suplemon_module import Module @@ -10,15 +12,34 @@ class SystemClipboard(Module): def init(self): self.init_logging(__name__) - if self.has_xsel_support(): + if ( + system() == 'Windows' and + self.which("powershell") + ): + self.clipboard_type = "powershell" + elif ( + system() == 'Linux' and + self.which("powershell") and + os.path.isfile('/proc/version') + ): + if "microsoft" in open('/proc/version', 'r').read().lower(): + self.clipboard_type = "powershell" + elif ( + os.environ.get("WAYLAND_DISPLAY") and + self.which("wl-copy") + ): + self.clipboard_type = "wl" + elif self.which("xsel"): self.clipboard_type = "xsel" - elif self.has_pb_support(): + elif self.which("pbcopy"): self.clipboard_type = "pb" - elif self.has_xclip_support(): + elif self.which("xclip"): self.clipboard_type = "xclip" + elif self.which("termux-clipboard-get"): + self.clipboard_type = "termux" else: self.logger.warning( - "Can't use system clipboard. Install 'xsel' or 'pbcopy' or 'xclip' for system clipboard support.") + "Can't use system clipboard. Install 'xsel' or 'pbcopy' or 'xclip' for system clipboard support.\nOn Termux, install 'termux-api' for system clipboard support.") return False self.bind_event_before("insert", self.insert) self.bind_event_after("copy", self.copy) @@ -36,12 +57,18 @@ def insert(self, event): def get_clipboard(self): try: - if self.clipboard_type == "xsel": + if self.clipboard_type == "powershell": + command = ["powershell.exe", "-noprofile", "-command", "Get-Clipboard"] + elif self.clipboard_type == "wl": + command = ["wl-paste", "-n"] + elif self.clipboard_type == "xsel": command = ["xsel", "-b"] elif self.clipboard_type == "pb": command = ["pbpaste", "-Prefer", "txt"] elif self.clipboard_type == "xclip": command = ["xclip", "-selection", "clipboard", "-out"] + elif self.clipboard_type == "termux": + command = ["termux-clipboard-get"] else: return False data = subprocess.check_output(command, universal_newlines=True) @@ -51,12 +78,18 @@ def get_clipboard(self): def set_clipboard(self, data): try: - if self.clipboard_type == "xsel": + if self.clipboard_type == "powershell": + command = ["powershell.exe", "-noprofile", "-command", "Set-Clipboard"] + elif self.clipboard_type == "wl": + command = ["wl-copy"] + elif self.clipboard_type == "xsel": command = ["xsel", "-i", "-b"] elif self.clipboard_type == "pb": command = ["pbcopy"] elif self.clipboard_type == "xclip": command = ["xclip", "-selection", "clipboard", "-in"] + elif self.clipboard_type == "termux": + command = ["termux-clipboard-set"] else: return False p = subprocess.Popen(command, stdin=subprocess.PIPE) @@ -65,25 +98,26 @@ def set_clipboard(self, data): except: return False - def has_pb_support(self): - output = self.get_output(["which", "pbcopy"]) - return output + def which(self, program): # https://stackoverflow.com/a/379535 + def is_exe(fpath): + return os.path.exists(fpath) and os.access(fpath, os.X_OK) and os.path.isfile(fpath) - def has_xsel_support(self): - output = self.get_output(["xsel", "--version"]) - return output + def ext_candidates(fpath): + yield fpath + for ext in os.environ.get("PATHEXT", "").split(os.pathsep): + yield fpath + ext - def has_xclip_support(self): - output = self.get_output(["which", "xclip"]) # xclip -version outputs to stderr - return output - - def get_output(self, cmd): - try: - process = subprocess.Popen(cmd, stdout=subprocess.PIPE) - except (OSError, EnvironmentError): # can't use FileNotFoundError in Python 2 - return False - out, err = process.communicate() - return out + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + for candidate in ext_candidates(exe_file): + if is_exe(candidate): + return candidate + return False module = { diff --git a/suplemon/ui.py b/suplemon/ui.py index 5b3305b..36452cf 100644 --- a/suplemon/ui.py +++ b/suplemon/ui.py @@ -6,6 +6,7 @@ import os import sys import logging +from platform import system from wcwidth import wcswidth from .prompt import Prompt, PromptBool, PromptFiltered, PromptFile, PromptAutocmp @@ -120,7 +121,7 @@ def init(self): global curses # Set ESC detection time os.environ["ESCDELAY"] = str(self.app.config["app"]["escdelay"]) - termenv = os.environ["TERM"] + termenv = os.environ.get("TERM", "") if termenv.endswith("-256color") and self.app.config["app"].get("imitate_256color"): # Curses doesn't recognize 'screen-256color' or 'tmux-256color' as 256-color terminals. # These terminals all seem to be identical to xterm-256color, which is recognized. @@ -199,7 +200,9 @@ def setup_colors(self): # curses.init_pair(10, -1, -1) # Default (white on black) # Colors for xterm (not xterm-256color) # Dark Colors - curses.init_pair(0, curses.COLOR_BLACK, bg) # 0 Black + if system() != 'Windows': + # it raises an exception on windows, cf https://github.com/zephyrproject-rtos/windows-curses/issues/10 + curses.init_pair(0, curses.COLOR_BLACK, bg) # 0 Black curses.init_pair(1, curses.COLOR_RED, bg) # 1 Red curses.init_pair(2, curses.COLOR_GREEN, bg) # 2 Green curses.init_pair(3, curses.COLOR_YELLOW, bg) # 3 Yellow @@ -230,7 +233,9 @@ def setup_colors(self): # TODO: Define RGB for these to avoid getting # different results in different terminals # xterm-256color chart http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html - curses.init_pair(0, 242, bg) # 0 Black + if system() != 'Windows': + # it raises an exception on windows, cf https://github.com/zephyrproject-rtos/windows-curses/issues/10 + curses.init_pair(0, 242, bg) # 0 Black curses.init_pair(1, 204, bg) # 1 Red curses.init_pair(2, 119, bg) # 2 Green curses.init_pair(3, 221, bg) # 3 Yellow @@ -320,7 +325,7 @@ def resize(self, yx=None): if yx is None: yx = self.screen.getmaxyx() self.screen.erase() - curses.resizeterm(yx[0], yx[1]) + curses.resize_term(yx[0], yx[1]) self.setup_windows() def check_resize(self): From a859970577859d6742acae38b5edd34ca2b9f531 Mon Sep 17 00:00:00 2001 From: joshcangit Date: Mon, 1 Nov 2021 17:35:22 +0800 Subject: [PATCH 2/3] Try to refactor code to using dictionaries. --- suplemon/modules/system_clipboard.py | 67 +++++++++++++--------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 2468c00..3cbd861 100644 --- a/suplemon/modules/system_clipboard.py +++ b/suplemon/modules/system_clipboard.py @@ -16,27 +16,48 @@ def init(self): system() == 'Windows' and self.which("powershell") ): - self.clipboard_type = "powershell" + self.clipboard = { + "get": ["powershell.exe", "-noprofile", "-command", "Get-Clipboard"], + "set": ["powershell.exe", "-noprofile", "-command", "Set-Clipboard"] + } elif ( system() == 'Linux' and self.which("powershell") and os.path.isfile('/proc/version') ): if "microsoft" in open('/proc/version', 'r').read().lower(): - self.clipboard_type = "powershell" + self.clipboard = { + "get": ["powershell.exe", "-noprofile", "-command", "Get-Clipboard"], + "set": ["powershell.exe", "-noprofile", "-command", "Set-Clipboard"] + } elif ( os.environ.get("WAYLAND_DISPLAY") and self.which("wl-copy") ): - self.clipboard_type = "wl" + self.clipboard = { + "get": ["wl-paste", "-n"], + "set": ["wl-copy"] + } elif self.which("xsel"): - self.clipboard_type = "xsel" + self.clipboard = { + "get": ["xsel", "-b"], + "set": ["xsel", "-i", "-b"] + } elif self.which("pbcopy"): - self.clipboard_type = "pb" + self.clipboard = { + "get": ["pbpaste", "-Prefer", "txt"], + "set": ["pbcopy"] + } elif self.which("xclip"): - self.clipboard_type = "xclip" + self.clipboard = { + "get": ["xclip", "-selection", "clipboard", "-out"], + "set": ["xclip", "-selection", "clipboard", "-in"] + } elif self.which("termux-clipboard-get"): - self.clipboard_type = "termux" + self.clipboard = { + "get": ["termux-clipboard-get"], + "set": ["termux-clipboard-set"] + } else: self.logger.warning( "Can't use system clipboard. Install 'xsel' or 'pbcopy' or 'xclip' for system clipboard support.\nOn Termux, install 'termux-api' for system clipboard support.") @@ -57,42 +78,14 @@ def insert(self, event): def get_clipboard(self): try: - if self.clipboard_type == "powershell": - command = ["powershell.exe", "-noprofile", "-command", "Get-Clipboard"] - elif self.clipboard_type == "wl": - command = ["wl-paste", "-n"] - elif self.clipboard_type == "xsel": - command = ["xsel", "-b"] - elif self.clipboard_type == "pb": - command = ["pbpaste", "-Prefer", "txt"] - elif self.clipboard_type == "xclip": - command = ["xclip", "-selection", "clipboard", "-out"] - elif self.clipboard_type == "termux": - command = ["termux-clipboard-get"] - else: - return False - data = subprocess.check_output(command, universal_newlines=True) + data = subprocess.check_output(self.clipboard["get"], universal_newlines=True) return data except: return False def set_clipboard(self, data): try: - if self.clipboard_type == "powershell": - command = ["powershell.exe", "-noprofile", "-command", "Set-Clipboard"] - elif self.clipboard_type == "wl": - command = ["wl-copy"] - elif self.clipboard_type == "xsel": - command = ["xsel", "-i", "-b"] - elif self.clipboard_type == "pb": - command = ["pbcopy"] - elif self.clipboard_type == "xclip": - command = ["xclip", "-selection", "clipboard", "-in"] - elif self.clipboard_type == "termux": - command = ["termux-clipboard-set"] - else: - return False - p = subprocess.Popen(command, stdin=subprocess.PIPE) + p = subprocess.Popen(self.clipboard["set"], stdin=subprocess.PIPE) out, err = p.communicate(input=bytes(data, "utf-8")) return out except: From 84f76b2bb0d7cc908f9f77cb48017ea5c971a16b Mon Sep 17 00:00:00 2001 From: joshcangit Date: Tue, 12 Nov 2024 19:39:20 +0800 Subject: [PATCH 3/3] Replace imp with importlib The imp module has been deprecated in 3.4 and remove in 3.12 Function for load_source is taken from the importlib documentation Importing a source file directly That function renamed from import_from_path --- suplemon/module_loader.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/suplemon/module_loader.py b/suplemon/module_loader.py index 2441df7..c74b8d6 100644 --- a/suplemon/module_loader.py +++ b/suplemon/module_loader.py @@ -3,9 +3,16 @@ Addon module loader. """ import os -import imp +import importlib.util +import sys import logging +def load_source(module_name, file_path): + spec = importlib.util.spec_from_file_location(module_name, file_path) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module class ModuleLoader: def __init__(self, app=None): @@ -63,7 +70,7 @@ def load_single(self, name): """Load single module file.""" path = os.path.join(self.module_path, name+".py") try: - mod = imp.load_source(name, path) + mod = load_source(name, path) except: self.logger.error("Failed loading module: {0}".format(name), exc_info=True) return False