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/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 diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 5cd41cd..3cbd861 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,55 @@ class SystemClipboard(Module): def init(self): self.init_logging(__name__) - if self.has_xsel_support(): - self.clipboard_type = "xsel" - elif self.has_pb_support(): - self.clipboard_type = "pb" - elif self.has_xclip_support(): - self.clipboard_type = "xclip" + if ( + system() == 'Windows' and + self.which("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 = { + "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 = { + "get": ["wl-paste", "-n"], + "set": ["wl-copy"] + } + elif self.which("xsel"): + self.clipboard = { + "get": ["xsel", "-b"], + "set": ["xsel", "-i", "-b"] + } + elif self.which("pbcopy"): + self.clipboard = { + "get": ["pbpaste", "-Prefer", "txt"], + "set": ["pbcopy"] + } + elif self.which("xclip"): + self.clipboard = { + "get": ["xclip", "-selection", "clipboard", "-out"], + "set": ["xclip", "-selection", "clipboard", "-in"] + } + elif self.which("termux-clipboard-get"): + 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.") + "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,54 +78,39 @@ def insert(self, event): def get_clipboard(self): try: - if 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"] - 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 == "xsel": - command = ["xsel", "-i", "-b"] - elif self.clipboard_type == "pb": - command = ["pbcopy"] - elif self.clipboard_type == "xclip": - command = ["xclip", "-selection", "clipboard", "-in"] - 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: 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):