Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
4 changes: 3 additions & 1 deletion suplemon/key_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
11 changes: 9 additions & 2 deletions suplemon/module_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
111 changes: 69 additions & 42 deletions suplemon/modules/system_clipboard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- encoding: utf-8

import os
import subprocess
from platform import system

from suplemon.suplemon_module import Module

Expand All @@ -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)
Expand All @@ -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 = {
Expand Down
13 changes: 9 additions & 4 deletions suplemon/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down