From 6c0a8f86822635f5e6e9eb9d654883a9cb7e8ba4 Mon Sep 17 00:00:00 2001 From: Scott Hansen Date: Fri, 25 Aug 2023 09:48:55 -0700 Subject: [PATCH] Clipboard support. Closes #110 --- README.md | 11 ++++++----- docs/usage.md | 8 +++++--- keepmenu.1 | 7 +++++-- keepmenu.1.md | 4 +++- keepmenu/__init__.py | 6 ++++++ keepmenu/__main__.py | 10 ++++++++++ keepmenu/keepmenu.py | 8 ++++++++ keepmenu/type.py | 22 +++++++++++++++++++++- 8 files changed, 64 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 10a71c7..40f3de2 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,15 @@ For full installation documention see the [installation docs][docs/install.md]. 1. Python 3.7+ 2. [Pykeepass][1] >= 4.0.0 and [pynput][5] 3. Dmenu, Rofi, Wofi, Yofi or Bemenu -4. (optional) Pinentry -5. (optional) xdotool (for X), [ydotool][10] or [wtype][11](for Wayland), [dotool][12] (X or Wayland). +4. xsel or wl-copy +5. (optional) Pinentry +6. (optional) xdotool (for X), [ydotool][10] or [wtype][11](for Wayland), [dotool][12] (X or Wayland). ## Features - Supports .kdbx databases, not .kdb. -- Auto-type username and/or password on selection. No clipboard copy/paste - involved. +- Auto-type username and/or password on selection. Select to clipboard if + desired (clears clipboard after 30s). - Background process allows selectable time-out for locking the database. - Multiple databases can be unlocked and switched on the fly. - Use a custom [Keepass 2.x style auto-type sequence][6]. @@ -58,7 +59,7 @@ For full installation documention see the [installation docs][docs/install.md]. ## Usage -`keepmenu [-h] [-a AUTOTYPE] [-c CONF_FILE] [-d DATABASE] [-k KEY_FILE] [-t]` +`keepmenu [-h] [-a AUTOTYPE] [-c CONF_FILE] [-C] [-d DATABASE] [-k KEY_FILE] [-t]` - Run `keepmenu` or bind to keystroke combination. - Enter database path on first run. diff --git a/docs/usage.md b/docs/usage.md index 59d0a14..2ee6f35 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -12,7 +12,7 @@ ## CLI Options -`keepmenu [-h] [-a AUTOTYPE] [-c CONF_FILE] [-d DATABASE] [-k KEY_FILE]` +`keepmenu [-h] [-a AUTOTYPE] [-c CONF_FILE] [-C] [-d DATABASE] [-k KEY_FILE]` --help, -h Output a usage message and exit. @@ -20,6 +20,8 @@ -c CONF_FILE, --config CONF_FILE File path to a config file +-C --clipboard, type to clipboard + -d DATABASE, --database DATABASE File path to a database to open, skipping the database selection menu -k KEY_FILE, --keyfile KEY_FILE File path of the keyfile needed to open the database specified by --database/-d @@ -35,8 +37,8 @@ - Add, edit and type TOTP codes. RFC 6238, Steam and custom settings are supported. Supports TOTP attributes generated by [KeePass2][3], as well as [KeeOtp][4] and [TrayTOTP][5] plugins' [formats][6]. - *Type entries* - - Auto-type username and/or password on selection. No clipboard copy/paste - involved. + - Auto-type username and/or password on selection. Select to clipboard if + desired (clears clipboard after 30s). - Use a custom [Keepass 2.x style auto-type sequence][1] if you have one defined (except for character repetition and the 'special commands'). Set it per entry or set a global default. Disable autotype for an entry, if desired. diff --git a/keepmenu.1 b/keepmenu.1 index 8e0426f..d2f29c8 100644 --- a/keepmenu.1 +++ b/keepmenu.1 @@ -24,8 +24,9 @@ of Keepass databases. .SH SYNOPSIS .PP \f[B]keepmenu\f[R] [\f[B]\[en]autotype\f[R] pattern] -[\f[B]\[en]config\f[R] file] [\f[B]\[en]database\f[R] file] -[\f[B]\[en]keyfile\f[R] file] [\f[B]\[en]totp\f[R]] +[\f[B]\[en]config\f[R] file] [\f[B]\[en]clipboard\f[R]] +[\f[B]\[en]database\f[R] file] [\f[B]\[en]keyfile\f[R] file] +[\f[B]\[en]totp\f[R]] .SH DESCRIPTION .PP \f[B]Keepmenu\f[R] is a fast and minimal application to facilitate @@ -40,6 +41,8 @@ Overrides global default from config.ini for current database. .PP \f[B]-c\f[R], \f[B]\[en]config\f[R] Path to config file .PP +\f[B]-C\f[R], \f[B]\[en]clipboard\f[R] Select to clipboard +.PP \f[B]-d\f[R], \f[B]\[en]database\f[R] Path to Keepass database .PP \f[B]-k\f[R], \f[B]\[en]keyfile\f[R] Path to keyfile diff --git a/keepmenu.1.md b/keepmenu.1.md index 39b1451..3d00a0e 100644 --- a/keepmenu.1.md +++ b/keepmenu.1.md @@ -12,7 +12,7 @@ keepmenu - Fully featured Dmenu/Rofi frontend for autotype and managing of Keepa # SYNOPSIS -**keepmenu** [**--autotype** pattern] [**--config** file] [**--database** file] [**--keyfile** file] [**--totp**] +**keepmenu** [**--autotype** pattern] [**--config** file] [**--clipboard**] [**--database** file] [**--keyfile** file] [**--totp**] # DESCRIPTION @@ -26,6 +26,8 @@ Passhole, but is more dmenu and less command line focused. **-c**, **--config** Path to config file +**-C**, **--clipboard** Select to clipboard + **-d**, **--database** Path to Keepass database **-k**, **--keyfile** Path to keyfile diff --git a/keepmenu/__init__.py b/keepmenu/__init__.py index cd33435..23dfe49 100644 --- a/keepmenu/__init__.py +++ b/keepmenu/__init__.py @@ -32,6 +32,7 @@ SEQUENCE = "{USERNAME}{TAB}{PASSWORD}{ENTER}" MAX_LEN = 24 CONF = configparser.ConfigParser() +CLIPBOARD = False def reload_config(conf_file = None): # pylint: disable=too-many-statements,too-many-branches @@ -43,6 +44,7 @@ def reload_config(conf_file = None): # pylint: disable=too-many-statements,too- # pragma pylint: disable=global-statement,global-variable-not-assigned global CACHE_PERIOD_MIN, \ CACHE_PERIOD_DEFAULT_MIN, \ + CLIPBOARD_CMD, \ CONF, \ MAX_LEN, \ ENV, \ @@ -95,5 +97,9 @@ def reload_config(conf_file = None): # pylint: disable=too-many-statements,too- dmenu_err(f"{typ} not installed.\n" "Please install or remove that option from config.ini") sys.exit() + if os.environ.get('WAYLAND_DISPLAY'): + CLIPBOARD_CMD = 'wl-copy' + else: + CLIPBOARD_CMD = 'xsel' # vim: set et ts=4 sw=4 : diff --git a/keepmenu/__main__.py b/keepmenu/__main__.py index 554a954..f533640 100644 --- a/keepmenu/__main__.py +++ b/keepmenu/__main__.py @@ -174,6 +174,15 @@ def main(): help="File path to a config file", ) + parser.add_argument( + "-C", + "--clipboard", + action="store_true", + default=False, + required=False, + help="Copy values to clipboard instead of typing.", + ) + parser.add_argument( "-d", "--database", @@ -199,6 +208,7 @@ def main(): ) args = vars(parser.parse_args()) + keepmenu.CLIPBOARD = args.get('clipboard', False) port, auth = get_auth() if port_in_use(port) is False: diff --git a/keepmenu/keepmenu.py b/keepmenu/keepmenu.py index 96c96a2..6c4308c 100644 --- a/keepmenu/keepmenu.py +++ b/keepmenu/keepmenu.py @@ -378,6 +378,7 @@ def dmenu_run(self, totp_mode=False): i for i in self.database.kpo.entries if not any(j in "/".join(i.path[:-1]) for j in hid_groups) ] + clip = "Clipboard off" if keepmenu.CLIPBOARD is True else "Clipboard" options = { 'View/Type Individual entries': functools.partial(self.menu_view_type_individual_entries, hid_groups), @@ -389,6 +390,7 @@ def dmenu_run(self, totp_mode=False): 'Manage groups': self.menu_manage_groups, 'Reload database': self.menu_reload_database, 'Open/create another database': self.menu_open_another_database, + clip: self.menu_clipboard, 'Kill Keepmenu daemon': self.menu_kill_daemon, } if self.prev_entry is None: @@ -505,6 +507,12 @@ def menu_open_another_database(self, **kwargs): self.expiring = get_expiring_entries(self.database.kpo.entries) self.dmenu_run(self.database.totp) + def menu_clipboard(self): + """Process menu entry - Toggle clipboard entry + + """ + keepmenu.CLIPBOARD = not keepmenu.CLIPBOARD + def menu_kill_daemon(self): """Process menu entry - Kill keepmenu daemon diff --git a/keepmenu/type.py b/keepmenu/type.py index abee8d8..ece5ea3 100644 --- a/keepmenu/type.py +++ b/keepmenu/type.py @@ -5,6 +5,7 @@ # pylint: disable=import-outside-toplevel import re from subprocess import call, PIPE, run +from threading import Timer import time import keepmenu @@ -98,7 +99,12 @@ def type_entry(entry, db_autotype=None): """ sequence = keepmenu.SEQUENCE - + if keepmenu.CLIPBOARD is True: + if entry.password: + type_clipboard(entry.password) + else: + dmenu_err("Clipboard is active. 'View/Type Individual entries' and select field to copy") + return if hasattr(entry, 'autotype_enabled') and entry.autotype_enabled is False: dmenu_err("Autotype disabled for this entry") return @@ -338,6 +344,9 @@ def type_text(data): """Type the given text data """ + if keepmenu.CLIPBOARD is True: + type_clipboard(data) + return library = 'pynput' if keepmenu.CONF.has_option('database', 'type_library'): library = keepmenu.CONF.get('database', 'type_library') @@ -360,3 +369,14 @@ def type_text(data): except kbd.InvalidCharacterException: dmenu_err("Unable to type string...bad character.\n" "Try setting `type_library = xdotool` in config.ini") + + +def type_clipboard(text): + """Copy text to clipboard and clear clipboard after 30 seconds + + Args: text - str + + """ + run([keepmenu.CLIPBOARD_CMD], input=text.encode(keepmenu.ENC)) + clear = Timer(30, lambda: run([keepmenu.CLIPBOARD_CMD, "--clear"])) + clear.start()