From 6a3a8d6246ccfbf903ccabf48f9209309fea2905 Mon Sep 17 00:00:00 2001 From: Thore Bödecker Date: Sun, 26 Jun 2022 22:33:26 +0200 Subject: add support for clipboard_target config option Default behaviour of `xclip` is to copy into the `primary` selection. Default behaviour of `wl-copy` is to copy into the `clipboard`. This commit introduces a new config option called `clipboard_target` that: - allows disabling the copy to any clipboard entirely - enforce copy to `primary` (i.e. for `wl-copy`) - enforce copy to `clipboard` (i.e. for `xclip`) --- fb.1 | 19 ++++++++++++++++++- fb.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/fb.1 b/fb.1 index c777c5d..87af4b2 100644 --- a/fb.1 +++ b/fb.1 @@ -128,7 +128,24 @@ The following option names are supported: .It pastebin The URL of the pastebin you want to use .It clipboard_cmd -The command used to copy URLs of uploaded files to the clipboard. This defaults to pbcopy on Darwin and xclip otherwise. +The command used to copy URLs of uploaded files to the clipboard. +This defaults to +.Xr xclip 1 on X11, +.Xr wl-copy 1 on Wayland and +.Xr pbcopy 1 on Mac OS / Darwin. +.It clipboard_target +Configure which clipboard to use. Allowed settings: +.Bl -tag -width "none / off" +.It default +Use the default behavior, i.e. "primary" for X11/xclip and "clipboard" for Wayland/wl-copy. +This is implicitly the default if not specified. +.It none / off +Disable copying the upload URL into the clipboard. +.It primary +Enforce copy into the "primary" selection (for Wayland/wl-copy). +.It clipboard +Enforce copy into the "clipboard" selection (for X11/xclip). +.El .It apikey_file The file that contains the API key. This defaults to "$XDG_CONFIG_HOME/fb-client/apikey" .El diff --git a/fb.py b/fb.py index 15d92d2..cda7b32 100755 --- a/fb.py +++ b/fb.py @@ -21,10 +21,17 @@ import sys import tarfile import tempfile import time +import typing import xdg.BaseDirectory from io import BytesIO + +X11_CLIPBOARD_CMD = 'xclip' +WAYLAND_CLIPBOARD_CMD = 'wl-copy' +DARWIN_CLIPBOARD_CMD = 'pbcopy' + + class ApikeyNotFoundException(Exception): pass @@ -394,21 +401,55 @@ class Compressor: f_out.writelines(f_in) return dst + +class ConfigConstraint(): + def __init__(self, cvar: str, match: str, pattern: typing.Any, enforce: bool = False): + self.cvar: str = cvar + self.match: str = match.lower() + self.pattern: typing.Any = pattern + self.enforce: bool = enforce + + def validate(self, input: str) -> bool: + result = False + if self.match == 'enum' and (isinstance(self.pattern, tuple) or + isinstance(self.pattern, list)): + result = input in self.pattern + if self.enforce: + print(f"Invalid config setting for {self.cvar}: '{input}', " \ + "allowed: '{self.pattern}' ({self.match})", file=sys.stderr) + raise ValueError(f'Invalid {self.cvar} config setting: {input}') + return result + + class ConfigParser: + + MATCHER = re.compile('^(?P[^=]+)=(?P"?)(?P.+)(?P=quotechar)$') + + CONSTRAINTS = { + 'clipboard_target': ConfigConstraint('clipboard_target', 'enum', ('none', 'off', 'default', 'primary', 'clipboard')) + } + def __init__(self, file, ignoreMissing=False): self.config = {} self.config["pastebin"] = "https://paste.xinu.at" - self.config["clipboard_cmd"] = "xclip" + self.config["clipboard_cmd"] = X11_CLIPBOARD_CMD if os.uname()[0] == "Darwin": - self.config["clipboard_cmd"] = "pbcopy" + self.config["clipboard_cmd"] = DARWIN_CLIPBOARD_CMD elif os.environ.get('XDG_SESSION_TYPE') == 'wayland': - self.config["clipboard_cmd"] = "wl-copy" + self.config["clipboard_cmd"] = WAYLAND_CLIPBOARD_CMD self.config["apikey_file"] = os.path.join(xdg.BaseDirectory.xdg_config_home, "fb-client/apikey") self._parse(file, ignoreMissing=ignoreMissing) + self._validate() self.config["apikey_file"] = os.path.expandvars(self.config["apikey_file"]) + def _validate(self): + for cvar, constraint in self.CONSTRAINTS.items(): + if not constraint.validate(self.config[cvar]): + print(f"WARN: ignoring invalid config setting: '{cvar}'", file=sys.stderr) + del self.config[cvar] + def _parse(self, file, ignoreMissing=False): try: fh = open(file) @@ -420,7 +461,7 @@ class ConfigParser: with fh: for line in fh: - matches = re.match('^(?P[^=]+)=(?P"?)(?P.+)(?P=quotechar)$', line) + matches = self.MATCHER.match(line) if matches != None: self.config[matches.group('key')] = matches.group('value') @@ -645,9 +686,20 @@ class FBClient: self.setClipboard(' '.join(urls)) def setClipboard(self, content): + cmd = self.config['clipboard_cmd'] + args = [] + target = self.config.get('clipboard_target') + if target in ('none', 'off'): + return + elif target == 'primary': + if cmd == WAYLAND_CLIPBOARD_CMD: + args.extend(['--primary']) + elif target == 'clipboard': + if cmd == X11_CLIPBOARD_CMD: + args.extend(['-selection', 'clipboard']) try: with open('/dev/null', 'w') as devnull: - p = subprocess.Popen([self.config['clipboard_cmd']], stdin=subprocess.PIPE, stdout=devnull, stderr=devnull) + p = subprocess.Popen([cmd, *args], stdin=subprocess.PIPE, stdout=devnull, stderr=devnull) p.communicate(input=content.encode('utf-8')) except OSError as e: if e.errno == errno.ENOENT: -- cgit v1.2.3-24-g4f1b