pax_global_header00006660000000000000000000000064147065567770014542gustar00rootroot0000000000000052 comment=78d45e5b5093711e0e692f8ecf9af207417eb791 nwg-clipman-0.2.4/000077500000000000000000000000001470655677700137615ustar00rootroot00000000000000nwg-clipman-0.2.4/.github/000077500000000000000000000000001470655677700153215ustar00rootroot00000000000000nwg-clipman-0.2.4/.github/FUNDING.yml000066400000000000000000000000411470655677700171310ustar00rootroot00000000000000github: nwg-piotr liberapay: nwg nwg-clipman-0.2.4/.gitignore000066400000000000000000000001241470655677700157460ustar00rootroot00000000000000/.idea /venv /nwg_clipman.egg-info/ /build/ /dist/ /nwg_clipman/__pycache__ /result nwg-clipman-0.2.4/LICENSE000066400000000000000000000020551470655677700147700ustar00rootroot00000000000000MIT License Copyright (c) 2024 Piotr Miller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nwg-clipman-0.2.4/README.md000066400000000000000000000047561470655677700152540ustar00rootroot00000000000000nwg-shell logo

nwg-clipman


This program is a part of the [nwg-shell](https://nwg-piotr.github.io/nwg-shell) project. **Nwg-clipman** is a GTK3-based GUI for Senan Kelly's [cliphist](https://github.com/sentriz/cliphist). It provides access to previously copied items, as well as management of the clipboard history from a window opened on gtk-layer-shell. The program is intended for use with sway, Hyprland and other wlroots-based Wayland compositors. screenshot
## Features - image & text history item preview; - searching clipboard history by a textual phrase; - deleting selected history item; - clearing clipboard history. ## Dependencies - python >= 3.6; - python-gobject; - gtk3; - gtk-layer-shell; - wl-clipboard; - cliphist; - xdg-utils. ## Installation [![Packaging status](https://repology.org/badge/vertical-allrepos/nwg-clipman.svg)](https://repology.org/project/nwg-clipman/versions) The program may be installed by cloning this repository and executing the `install.sh` script (make sure you installed dependencies first). Then you need to edit your compositor config file, to enable storing clipboard history to cliphist. Example for sway: ```text exec wl-paste --type text --watch cliphist store exec wl-paste --type image --watch cliphist store ``` Example for Hyprland: ```text exec-once = wl-paste --type text --watch cliphist store exec-once = wl-paste --type image --watch cliphist store ``` You may omit the second line if you don't want images to be remembered. Then create a key binding to launch nwg-clipman: Example for sway: ```text bindsym Mod1+C exec nwg-clipman ``` Example for Hyprland: ```text bind = ALT, C, exec, nwg-clipman ``` ## Options ```text ❯ nwg-clipman -h usage: nwg-clipman [-h] [-v] [-n] [-w] options: -h, --help show this help message and exit -v, --version display Version information -n, --numbers show item Numbers in the list -w, --window run in regular Window, w/o layer shell ``` ## Hints - To see numbers in the cliboard history use the `nwg-clipman -n` command. - If you'd like the window to open normally, not on the layer shell, use the `nwg-clipman -w` command. - You may clear the search entry / close the program window with the `Esc` key. nwg-clipman-0.2.4/install.sh000077500000000000000000000016561470655677700157760ustar00rootroot00000000000000#!/usr/bin/env bash # Before running this script, make sure you have python-build, python-installer, # python-wheel and python-setuptools installed. PROGRAM_NAME="nwg-clipman" MODULE_NAME="nwg_clipman" SITE_PACKAGES="$(python3 -c "import sysconfig; print(sysconfig.get_paths()['purelib'])")" PATTERN="$SITE_PACKAGES/$MODULE_NAME*" # Remove from site_packages for path in $PATTERN; do if [ -e "$path" ]; then echo "Removing $path" rm -r "$path" fi done # Remove launcher script if [ -f "/usr/bin/$PROGRAM_NAME" ]; then echo "Removing /usr/bin/$PROGRAM_NAME" rm "/usr/bin/nwg-clipman" fi python -m build --wheel --no-isolation python -m installer dist/*.whl install -Dm 644 -t "/usr/share/pixmaps" "$PROGRAM_NAME.svg" install -Dm 644 -t "/usr/share/applications" "$PROGRAM_NAME.desktop" install -Dm 644 -t "/usr/share/licenses/$PROGRAM_NAME" LICENSE install -Dm 644 -t "/usr/share/doc/$PROGRAM_NAME" README.md nwg-clipman-0.2.4/nwg-clipman.desktop000066400000000000000000000005541470655677700175740ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Clipboard manager Name[pl]=Menedżer schowka GenericName=nwg-shell clipboard manager GenericName[pl]=Menedżer schowka nwg-shell Comment=Clipboard history search and management tool Comment[pl]=Narzędzie do wyszukiwania i zarządzania historią schowka Exec=nwg-clipman Icon=nwg-clipman Terminal=false Categories=Utility;nwg-clipman-0.2.4/nwg-clipman.svg000066400000000000000000000115361470655677700167240ustar00rootroot00000000000000 nwg-clipman-0.2.4/nwg_clipman/000077500000000000000000000000001470655677700162575ustar00rootroot00000000000000nwg-clipman-0.2.4/nwg_clipman/__about__.py000066400000000000000000000003141470655677700205350ustar00rootroot00000000000000try: from importlib import metadata except ImportError: import importlib_metadata as metadata try: __version__ = metadata.version("nwg-clipman") except Exception: __version__ = "unknown" nwg-clipman-0.2.4/nwg_clipman/__init__.py000066400000000000000000000000001470655677700203560ustar00rootroot00000000000000nwg-clipman-0.2.4/nwg_clipman/langs/000077500000000000000000000000001470655677700173635ustar00rootroot00000000000000nwg-clipman-0.2.4/nwg_clipman/langs/de_AT.json000066400000000000000000000003271470655677700212340ustar00rootroot00000000000000{ "clear": "Klar", "clear-clipboard-history": "Verlauf der Zwischenablage löschen", "close": "Schließen", "copy": "Kopieren", "preview": "Vorschau", "preview-unavailable": "Vorschau nicht verfügbar" }nwg-clipman-0.2.4/nwg_clipman/langs/de_DE.json000066400000000000000000000003271470655677700212200ustar00rootroot00000000000000{ "clear": "Klar", "clear-clipboard-history": "Verlauf der Zwischenablage löschen", "close": "Schließen", "copy": "Kopieren", "preview": "Vorschau", "preview-unavailable": "Vorschau nicht verfügbar" }nwg-clipman-0.2.4/nwg_clipman/langs/en_US.json000066400000000000000000000002741470655677700212720ustar00rootroot00000000000000{ "clear": "Clear", "clear-clipboard-history": "Clear clipboard history", "close": "Close", "copy": "Copy", "preview": "Preview", "preview-unavailable": "Preview unavailable" }nwg-clipman-0.2.4/nwg_clipman/langs/es_AR.json000066400000000000000000000003161470655677700212470ustar00rootroot00000000000000{ "clear": "Claro", "clear-clipboard-history": "Borrar historial del portapapeles", "close": "Cerca", "copy": "Copiar", "preview": "Avance", "preview-unavailable": "Vista previa no disponible" }nwg-clipman-0.2.4/nwg_clipman/langs/es_ES.json000066400000000000000000000003161470655677700212540ustar00rootroot00000000000000{ "clear": "Claro", "clear-clipboard-history": "Borrar historial del portapapeles", "close": "Cerca", "copy": "Copiar", "preview": "Avance", "preview-unavailable": "Vista previa no disponible" }nwg-clipman-0.2.4/nwg_clipman/langs/fr_FR.json000066400000000000000000000003161470655677700212540ustar00rootroot00000000000000{ "clear": "Clair", "clear-clipboard-history": "Effacer l'historique du presse-papiers", "close": "Fermer", "copy": "Copie", "preview": "Aperçu", "preview-unavailable": "Aperçu indisponible" }nwg-clipman-0.2.4/nwg_clipman/langs/it_IT.json000066400000000000000000000003241470655677700212650ustar00rootroot00000000000000{ "clear": "Chiaro", "clear-clipboard-history": "Cancella la cronologia degli appunti", "close": "Vicino", "copy": "Copia", "preview": "Anteprima", "preview-unavailable": "Anteprima non disponibile" }nwg-clipman-0.2.4/nwg_clipman/langs/pl_PL.json000066400000000000000000000003151470655677700212630ustar00rootroot00000000000000{ "clear": "Wyczyść", "clear-clipboard-history": "Wyczyścić historię schowka", "close": "Zamknij", "copy": "Kopiuj", "preview": "Podgląd", "preview-unavailable": "Podgląd niedostępny" }nwg-clipman-0.2.4/nwg_clipman/langs/pt_BR.json000066400000000000000000000003301470655677700212600ustar00rootroot00000000000000{ "clear": "Limpar", "clear-clipboard-history": "Limpar histórico da área de transferência", "close": "Fechar", "copy": "Copiar", "preview": "Prévia", "preview-unavailable": "Prévia indisponível" } nwg-clipman-0.2.4/nwg_clipman/langs/pt_PT.json000066400000000000000000000003441470655677700213050ustar00rootroot00000000000000{ "clear": "Claro", "clear-clipboard-history": "Limpar histórico da área de transferência", "close": "Fechar", "copy": "Cópia", "preview": "Visualização", "preview-unavailable": "Visualização indisponível" }nwg-clipman-0.2.4/nwg_clipman/langs/ru_RU.json000066400000000000000000000005431470655677700213140ustar00rootroot00000000000000{ "clear": "Прозрачный", "clear-clipboard-history": "Очистить историю буфера обмена", "close": "Закрывать", "copy": "Копировать", "preview": "Предварительный просмотр", "preview-unavailable": "Предварительный просмотр недоступен" }nwg-clipman-0.2.4/nwg_clipman/main.py000066400000000000000000000340171470655677700175620ustar00rootroot00000000000000#!/usr/bin/env python """ nwg-shell clipboard manager (GUI for cliphist by Senan Kelly) Copyright (c) 2024 Piotr Miller e-mail: nwg.piotr@gmail.com Project: https://github.com/nwg-piotr/nwg-clipman License: MIT """ import argparse import os.path import signal import sys import gi gi.require_version('Gtk', '3.0') try: gi.require_version('GtkLayerShell', '0.1') except ValueError: raise RuntimeError('\n\n' + 'If you haven\'t installed GTK Layer Shell, you need to point Python to the\n' + 'library by setting GI_TYPELIB_PATH and LD_LIBRARY_PATH to /src/.\n' + 'For example you might need to run:\n\n' + 'GI_TYPELIB_PATH=build/src LD_LIBRARY_PATH=build/src python3 ' + ' '.join(sys.argv)) from nwg_clipman.tools import * from nwg_clipman.__about__ import __version__ from gi.repository import Gtk, Gdk, GtkLayerShell, GdkPixbuf, Pango dir_name = os.path.dirname(__file__) pid = os.getpid() args = None voc = {} tmp_file = os.path.join(temp_dir(), "clipman.dump") window = None search_entry = None flowbox_wrapper = None flowbox = None preview_frame = None btn_copy = None selected_item = None if not is_command("cliphist") or not is_command("wl-copy"): # die if dependencies check failed eprint("Dependencies (cliphist, wl-clipboard) check failed, terminating") sys.exit(1) def list_cliphist(): # query cliphist try: output = subprocess.check_output("cliphist list", shell=True).decode('utf-8', errors="ignore").splitlines() return output except subprocess.CalledProcessError: return [] def signal_handler(sig, frame): # gentle handle termination desc = {2: "SIGINT", 15: "SIGTERM"} if sig == 2 or sig == 15: eprint("Terminated with {}".format(desc[sig])) Gtk.main_quit() def terminate_old_instance(): pid_file = os.path.join(temp_dir(), "nwg-clipman-pid") if os.path.isfile(pid_file): try: old_pid = int(load_text_file(pid_file)) if old_pid != pid: print(f"Attempting to kill the old instance in case it's still running, pid: {old_pid}") os.kill(old_pid, signal.SIGINT) except: pass # save new pid save_string(str(pid), pid_file) def handle_keyboard(win, event): # on Esc key first search entry if not empty, else terminate global search_entry if event.type == Gdk.EventType.KEY_RELEASE and event.keyval == Gdk.KEY_Escape: if search_entry.get_text(): search_entry.set_text("") else: Gtk.main_quit() def load_vocabulary(): # translate UI global voc # basic vocabulary (en_US) voc = load_json(os.path.join(dir_name, "langs", "en_US.json")) if not voc: eprint("Failed loading vocabulary, terminating") sys.exit(1) # check "interface-locale" forced in nwg-shell data file - if forced, and the file exists shell_data = load_shell_data() lang = os.getenv("LANG") if lang is None: lang = "en_US" else: lang = lang.split(".")[0] if not shell_data["interface-locale"] else shell_data["interface-locale"] # translate if translation available if lang != "en_US": loc_file = os.path.join(dir_name, "langs", "{}.json".format(lang)) if os.path.isfile(loc_file): # localized vocabulary loc = load_json(loc_file) if not loc: eprint(f"Failed loading translation '{loc_file}'") else: print(f"Loaded translation file: '{loc_file}'") for key in loc: voc[key] = loc[key] else: eprint(f"Translation into {lang} not found") def on_enter_notify_event(widget, event): # highlight item widget.set_state_flags(Gtk.StateFlags.DROP_ACTIVE, clear=False) widget.set_state_flags(Gtk.StateFlags.SELECTED, clear=False) def on_leave_notify_event(widget, event): # clear highlight widget.unset_state_flags(Gtk.StateFlags.DROP_ACTIVE) widget.unset_state_flags(Gtk.StateFlags.SELECTED) def flowbox_filter(_search_entry): # filter flowbox visibility by search_entry content def filter_func(fb_child, _text): if _text in fb_child.get_name(): return True else: return False text = _search_entry.get_text() flowbox.set_filter_func(filter_func, text) def on_child_activated(fb, child): # on flowbox item clicked global selected_item selected_item = child.get_name() name = bytes(child.get_name(), 'utf-8') subprocess.run(f"cliphist decode > {tmp_file}", shell=True, input=name) preview() def preview(): # create preview frame content pixbuf = None try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(tmp_file, 256, 256) except Exception as e: pass for child in preview_frame.get_children(): child.destroy() preview_frame.set_label(voc["preview"]) if pixbuf: scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolled.set_min_content_height(256) preview_frame.add(scrolled) image = Gtk.Image.new_from_pixbuf(pixbuf) scrolled.add(image) else: scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolled.set_min_content_height(256) preview_frame.add(scrolled) text = load_text_file(tmp_file) if not text: text = voc["preview-unavailable"] label = Gtk.Label.new(text) label.set_max_width_chars(80) label.set_line_wrap(True) label.set_line_wrap_mode(Pango.WrapMode.CHAR) scrolled.add(label) scrolled.set_property("name", "preview-window") btn_copy.set_sensitive(True) preview_frame.show_all() def on_del_button(btn, name): # delete entry from cliphist eprint(f"Delete '{name}'") name = bytes(name, 'utf-8') subprocess.run("cliphist delete", shell=True, input=name) search_entry.set_text("") build_flowbox() def on_copy_button(btn): eprint(f"Copying: '{selected_item}'") name = bytes(selected_item, 'utf-8') subprocess.run("cliphist decode | wl-copy", shell=True, input=name) Gtk.main_quit() def on_wipe_button(btn): win = ConfirmationWindow() class ConfirmationWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) self.set_modal(True) self.set_destroy_with_parent(True) self.connect("key-release-event", self.handle_keyboard) GtkLayerShell.init_for_window(self) GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) vbox.set_property("name", "warning") self.add(vbox) lbl = Gtk.Label.new(f'{voc["clear-clipboard-history"]}?') vbox.pack_start(lbl, False, False, 6) hbox = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) vbox.pack_start(hbox, False, False, 6) btn = Gtk.Button.new_with_label(voc["clear"]) btn.connect("clicked", self.clear_history) hbox.pack_start(btn, False, False, 0) btn = Gtk.Button.new_with_label(voc["close"]) btn.connect("clicked", self.quit) hbox.pack_start(btn, False, False, 0) self.show_all() def quit(self, btn): self.destroy() def handle_keyboard(self, win, event): if event.type == Gdk.EventType.KEY_RELEASE and event.keyval == Gdk.KEY_Escape: self.destroy() def clear_history(self, btn): eprint("Wipe cliphist") subprocess.run("cliphist wipe", shell=True) Gtk.main_quit() class FlowboxItem(Gtk.Box): def __init__(self, parts): Gtk.EventBox.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=6) if args.numbers: label = Gtk.Label.new(parts[0]) self.add(label) eb = Gtk.EventBox() self.pack_start(eb, True, True, 0) name = parts[1] label = Gtk.Label.new(name) label.set_property("halign", Gtk.Align.START) eb.add(label) button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) button.set_property("name", "del-btn") button.set_property("margin-right", 9) button.connect("clicked", on_del_button, "\t".join(parts)) self.pack_end(button, False, False, 0) eb.connect('enter-notify-event', on_enter_notify_event) eb.connect('leave-notify-event', on_leave_notify_event) def build_flowbox(): global flowbox_wrapper global flowbox # destroy flowbox wrapper content, if any for item in flowbox_wrapper.get_children(): item.destroy() # build from scratch scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolled.set_min_content_height(300) scrolled.set_propagate_natural_height(True) flowbox_wrapper.add(scrolled) flowbox = Gtk.FlowBox() flowbox.set_selection_mode(Gtk.SelectionMode.NONE) flowbox.connect("child-activated", on_child_activated) flowbox.set_valign(Gtk.Align.START) flowbox.set_max_children_per_line(1) scrolled.add(flowbox) # query cliphist clip_hist = list_cliphist() for line in clip_hist: try: parts = line.split("\t") _name = parts[1] item = FlowboxItem(parts) child = Gtk.FlowBoxChild() # we will be filtering by _name child.set_name(line) child.add(item) flowbox.add(child) except IndexError: eprint(f"Error parsing line: {line}") flowbox_wrapper.show_all() def main(): # handle signals catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP} for sig in catchable_sigs: signal.signal(sig, signal_handler) # arguments parser = argparse.ArgumentParser() parser.add_argument("-v", "--version", action="version", version="%(prog)s version {}".format(__version__), help="display Version information") parser.add_argument("-n", "--numbers", action="store_true", help="show item Numbers in the list") parser.add_argument("-w", "--window", action="store_true", help="run in regular Window, w/o layer shell") global args args = parser.parse_args() # kill running instance, if any terminate_old_instance() global search_entry # UI strings localization load_vocabulary() global window window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL) if not args.window: # attach to gtk-layer-shell GtkLayerShell.init_for_window(window) GtkLayerShell.set_layer(window, GtkLayerShell.Layer.TOP) GtkLayerShell.set_exclusive_zone(window, 0) GtkLayerShell.set_keyboard_mode(window, GtkLayerShell.KeyboardMode.ON_DEMAND) window.connect('destroy', Gtk.main_quit) window.connect("key-release-event", handle_keyboard) vbox = Gtk.Box.new(orientation=Gtk.Orientation.VERTICAL, spacing=6) vbox.set_property("name", "main-wrapper") window.add(vbox) hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) vbox.pack_start(hbox, False, False, 0) # search entry search_entry = Gtk.SearchEntry() search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic") search_entry.set_property("hexpand", True) search_entry.set_property("margin", 12) search_entry.set_size_request(700, 0) search_entry.connect('search_changed', flowbox_filter) hbox.pack_start(search_entry, False, True, 0) # "Clear" button next to search entry btn = Gtk.Button.new_with_label(voc["clear"]) btn.set_property("valign", Gtk.Align.CENTER) btn.connect("clicked", on_wipe_button) hbox.pack_start(btn, False, False, 6) # wrapper for the flowbox global flowbox_wrapper flowbox_wrapper = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) flowbox_wrapper.set_property("margin-left", 12) flowbox_wrapper.set_property("margin-right", 12) vbox.pack_start(flowbox_wrapper, False, False, 0) # clear flowbox wrapper, build content build_flowbox() hbox = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) hbox.set_property("margin", 12) vbox.pack_end(hbox, False, False, 0) global preview_frame preview_frame = Gtk.Frame.new(voc["preview"]) hbox.pack_start(preview_frame, True, True, 0) # temporary placeholder for future content preview placeholder_box = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) preview_frame.add(placeholder_box) placeholder_box.set_size_request(0, 256) preview_frame.show_all() # "Clear" and "Close" buttons ibox = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) ibox.set_homogeneous(True) hbox.pack_end(ibox, False, False, 0) # "Copy" button global btn_copy btn_copy = Gtk.Button.new_with_label(voc["copy"]) btn_copy.set_property("valign", Gtk.Align.END) btn_copy.connect("clicked", on_copy_button) btn_copy.set_sensitive(False) ibox.pack_start(btn_copy, True, True, 0) # "Close" button btn = Gtk.Button.new_with_label(voc["close"]) btn.set_property("valign", Gtk.Align.END) btn.connect("clicked", Gtk.main_quit) ibox.pack_start(btn, True, True, 0) window.show_all() # customize styling screen = Gdk.Screen.get_default() provider = Gtk.CssProvider() style_context = Gtk.StyleContext() style_context.add_provider_for_screen(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) css = b""" #main-wrapper { background-color: rgba(0, 0, 0, 0.1) } #preview-window { padding: 0 6px 0 6px } #del-btn { background: none; border: none; margin: 0; padding: 0 } #del-btn:hover { background-color: rgba(255, 255, 255, 0.1) } #warning { border: solid 1px; padding: 24px; margin: 6px} """ provider.load_from_data(css) Gtk.main() if __name__ == "__main__": sys.exit(main()) nwg-clipman-0.2.4/nwg_clipman/tools.py000066400000000000000000000035451470655677700200000ustar00rootroot00000000000000import json import os import subprocess import sys def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) def is_command(cmd): cmd = cmd.split()[0] cmd = "command -v {}".format(cmd) try: is_cmd = subprocess.check_output( cmd, shell=True).decode("utf-8").strip() if is_cmd: return True except subprocess.CalledProcessError: return False def temp_dir(): if os.getenv("TMPDIR"): return os.getenv("TMPDIR") elif os.getenv("TEMP"): return os.getenv("TEMP") elif os.getenv("TMP"): return os.getenv("TMP") return "/tmp" def load_text_file(path): try: with open(path, 'r') as file: data = file.read() return data except Exception as e: return None def save_string(string, file): try: file = open(file, "wt") file.write(string) file.close() except Exception as e: print(f"Error writing '{file}': {e}") def load_json(path): try: with open(path, 'r') as f: return json.load(f) except Exception as e: print("Error loading json: {}".format(e)) return None def get_shell_data_dir(): data_dir = "" home = os.getenv("HOME") xdg_data_home = os.getenv("XDG_DATA_HOME") if xdg_data_home: data_dir = os.path.join(xdg_data_home, "nwg-shell/") else: if home: data_dir = os.path.join(home, ".local/share/nwg-shell/") return data_dir def load_shell_data(): shell_data_file = os.path.join(get_shell_data_dir(), "data") shell_data = load_json(shell_data_file) if os.path.isfile(shell_data_file) else {} defaults = { "interface-locale": "" } for key in defaults: if key not in shell_data: shell_data[key] = defaults[key] return shell_data nwg-clipman-0.2.4/setup.py000066400000000000000000000012311470655677700154700ustar00rootroot00000000000000import os from setuptools import setup, find_packages def read(f_name): return open(os.path.join(os.path.dirname(__file__), f_name)).read() setup( name='nwg-clipman', version='0.2.4', description='nwg-shell clipboard manager', packages=find_packages(), include_package_data=True, package_data={ "": ["langs/*"] }, url='https://github.com/nwg-piotr/nwg-clipman', license='MIT', author='Piotr Miller', author_email='nwg.piotr@gmail.com', python_requires='>=3.6.0', install_requires=[], entry_points={ 'gui_scripts': [ 'nwg-clipman = nwg_clipman.main:main' ] } ) nwg-clipman-0.2.4/uninstall.sh000077500000000000000000000013451470655677700163340ustar00rootroot00000000000000#!/usr/bin/env bash PROGRAM_NAME="nwg-clipman" MODULE_NAME="nwg_clipman" SITE_PACKAGES="$(python3 -c "import sysconfig; print(sysconfig.get_paths()['purelib'])")" PATTERN="$SITE_PACKAGES/$MODULE_NAME*" # Remove from site_packages for path in $PATTERN; do if [ -e "$path" ]; then echo "Removing $path" rm -r "$path" fi done # Remove launcher script if [ -f "/usr/bin/$PROGRAM_NAME" ]; then echo "Removing /usr/bin/$PROGRAM_NAME" rm "/usr/bin/nwg-clipman" fi echo "Removing icon, .desktop file, license and readme" rm -f "/usr/share/pixmaps/$PROGRAM_NAME.svg" rm -f "/usr/share/applications/$PROGRAM_NAME.desktop" rm -f "/usr/share/licenses/$PROGRAM_NAME/LICENSE" rm -f "/usr/share/doc/$PROGRAM_NAME/README.md"