├── .gitignore ├── Makefile ├── README.md ├── main.py ├── modules ├── image_handler.py ├── settings_loader.py └── tkinter_manager.py ├── requirements.txt └── usage.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | 164 | test.py -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON = python3 2 | PYTHON_VENV = venv 3 | PYINSTALLER = $(PYTHON_VENV)/bin/pyinstaller 4 | SRC_DIR = . 5 | DIST_DIR = dist 6 | CONFIG_DIR = $${HOME}/.config/pinboard 7 | REQUIREMENTS_FILE = requirements.txt 8 | 9 | EXECUTABLE_NAME = pinboard 10 | 11 | all: create_venv install_dependencies create_config_dir build_executable 12 | 13 | create_venv: 14 | $(PYTHON) -m venv $(PYTHON_VENV) 15 | @echo "Venv created" 16 | 17 | install_dependencies: 18 | $(PYTHON_VENV)/bin/pip install -r $(REQUIREMENTS_FILE) 19 | @echo "Dependencies installed." 20 | 21 | create_config_dir: 22 | mkdir -p $(CONFIG_DIR) 23 | @echo ".config/pinboard created" 24 | 25 | build_executable: 26 | $(PYINSTALLER) --onefile --hidden-import=PIL._tkinter_finder $(SRC_DIR)/main.py --distpath $(DIST_DIR) --name $(EXECUTABLE_NAME) 27 | @echo "Building done." 28 | 29 | clean: 30 | rm -rf $(DIST_DIR) 31 | rm -rf build 32 | rm -rf $(EXECUTABLE_NAME).spec 33 | rm -rf $(PYTHON_VENV) 34 | @echo "Cleaned up." 35 | 36 | uninstall: clean 37 | rm -rf $(CONFIG_DIR) 38 | @echo "Uninstalled" 39 | 40 | .PHONY: create_venv install_dependencies create_config_dir build_executable clean uninstall -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pinboard 2 | Pinboard is a simple Python Tkinter app that can pin your images to your desktop. 3 | 4 | ### Features 5 | - Pin images to desktop 6 | - Pin from clipboard to desktop 7 | - Open image in image viewer from clipboard 8 | - Change border / background color 9 | - Drag images across your screen 10 | - Zoom into images 11 | - Pan images inside the window 12 | - Resize images 13 | - Show/hide shortcut buttons 14 | 15 | ### TODO: 16 | - [ ] Add rotation 17 | - [ ] Fix zooming on windows 18 | - [ ] Add tablet/touch support 19 | - [ ] Fix args 20 | - [ ] Add rotate buttons 21 | 22 | --- 23 | ### Installation 24 | You need to install the `tkinter` system package **and** `xclip` for X11 or `wl-clipboard` for Wayland before building:\ 25 | To see what XDG session you are running: `echo $XDG_SESSION_TYPE` 26 | 27 | #### X11: 28 | Ubuntu/Debian: 29 | 30 | sudo apt-get install python3-tk xclip 31 | 32 | Fedora: 33 | 34 | sudo dnf install python3-tkinter xclip 35 | 36 | Arch: 37 | 38 | sudo pacman -S tk xclip 39 | 40 | #### Wayland: 41 | Ubuntu/Debian: 42 | 43 | sudo apt-get install python3-tk wl-clipboard 44 | 45 | Fedora: 46 | 47 | sudo dnf install python3-tkinter wl-clipboard 48 | 49 | Arch: 50 | 51 | sudo pacman -S tk wl-clipboard 52 | 53 | #### Building: 54 | git clone https://github.com/itslu666/Pinboard.git 55 | cd Pinboard 56 | make 57 | \ 58 | The executable will be in `dist` you can use it in there or move it to somewhere in your PATH. e.g.: 59 | 60 | sudo mv dist/pinboard /usr/bin/ 61 | \ 62 | Optional cleaning (removes `dist` too): 63 | 64 | make clean 65 | 66 | --- 67 | ### Uninstalling 68 | make uninstall 69 | (Don't forget to remove the executable in /usr/bin/pinboard if you moved it) 70 | 71 | --- 72 | ### Usage: 73 | pinboard [option] 74 | Option | Effect 75 | -- | -- 76 | -h, --help | Display help 77 | -s, --standard | Open clipboard image in default image viewer 78 | -p, --pin | Pin image to desktop (like if you don't pass arg) 79 | -f, --file | Select a file to display (need to specify -p or -s) 80 | --create-config | Make config file (Warning: Overwrites current file if existing) 81 | 82 | For peak experience make a keyboard shortcut to execute `pinboard` 83 | 84 | Drag the image on your display\ 85 | Pan the image with `mouswheel click`\ 86 | Zoom the image with `scroll`\ 87 | Resize the image with `right click and drag` 88 | 89 | Press `+` to add another image from clipboard\ 90 | Press `q` to close the selected image\ 91 | Press `,` to proportionally decrease image size\ 92 | Press `.` to proportionally increase image size\ 93 | Press `b` to reset the image size 94 | 95 | --- 96 | ### Settings: 97 | Option | Value | Effect | Example | Default 98 | -- | -- | -- | -- | --- 99 | border_color | any color | Changes the color of the pin border | red/#ff0000 | black 100 | background_color | any color | Changes the background color when image panned out of bounds | red/#ff0000 | white 101 | always_on_top | True/False | Wether the pin should be always on top or not | true | true 102 | close_key | any key symbol | Defines the closing key | w | q 103 | open_key | any key symbol | Defines the opening key | p | KeyPress-plus 104 | reset_size_key | any key symbol | Defines the reset size key | Control-z | b 105 | buttons | True/False | Shows shortcut buttons | true | false 106 | 107 | Settings file is in `~/.config/pinboard/settings.json` after first execution\ 108 | For the keybinds, you can find a list of keysyms here: http://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm 109 | 110 | --- 111 | Tested on:\ 112 | Windows\ 113 | Arch Linux X11/Wayland -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from PIL import ImageGrab, Image 2 | import sys 3 | from modules import tkinter_manager, image_handler 4 | import os 5 | import json 6 | from tkinter import filedialog 7 | 8 | def main(): 9 | # setting file checks 10 | settings_file = os.path.expanduser("~") + "/.config/pinboard/settings.json" 11 | if not os.path.isfile(settings_file): 12 | settings = { 13 | "border_color": "black", 14 | "background_color": "white", 15 | "always_on_top": True, 16 | "close_key": "q", 17 | "open_key": "KeyPress-plus", 18 | "reset_size_key": "b", 19 | "buttons": False 20 | } 21 | with open(settings_file, 'w') as file: 22 | json.dump(settings, file, indent=4) 23 | 24 | if "-f" in sys.argv or "--file" in sys.argv: 25 | path = filedialog.askopenfilename( 26 | title="Choose an image", 27 | filetypes=[("Images", "*.jpg *.jpeg *.png *.gif *.bmp")] 28 | ) 29 | if path: 30 | img = Image.open(path) 31 | 32 | if "-s" in sys.argv: 33 | img.show() 34 | 35 | if "-p" in sys.argv: 36 | tkinter_manager.make_window([0], img.size[0], img.size[1], img) 37 | 38 | 39 | # handle args 40 | if ("-s" in sys.argv or "--standard" in sys.argv) and ("-f" not in sys.argv and "--file" not in sys.argv): 41 | # get img and show in default app 42 | _, _, img = image_handler.get_image() 43 | img.show() 44 | 45 | if len(sys.argv) == 1 or "-p" in sys.argv or "--pin" in sys.argv: 46 | open_windows = [0] 47 | wid, hgt, img = image_handler.get_image() 48 | tkinter_manager.make_window(open_windows, wid, hgt, img) 49 | 50 | 51 | 52 | # OTHER 53 | if "--create-config" in sys.argv: 54 | settings = { 55 | "border_color": "black", 56 | "background_color": "white", 57 | "always_on_top": True, 58 | "close_key": "q", 59 | "open_key": "KeyPress-plus", 60 | "reset_size_key": "b", 61 | "buttons": False 62 | } 63 | 64 | with open(settings_file, 'w') as file: 65 | json.dump(settings, file, indent=4) 66 | print(f"Made new config file in {settings_file}") 67 | 68 | if "-h" in sys.argv or "--help" in sys.argv: 69 | with open("usage.txt", 'r') as file: 70 | print(file.read()) 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /modules/image_handler.py: -------------------------------------------------------------------------------- 1 | from PIL import ImageGrab, Image 2 | import sys 3 | 4 | def get_image(): 5 | img = ImageGrab.grabclipboard() 6 | if isinstance(img, Image.Image): 7 | return img.size[0], img.size[1], img 8 | else: 9 | print("No image in clipboard") 10 | sys.exit() -------------------------------------------------------------------------------- /modules/settings_loader.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | def load_settings(): 5 | with open(os.path.expanduser("~") + "/.config/pinboard/settings.json") as file: 6 | settings = json.load(file) 7 | 8 | return settings 9 | 10 | def change_window(canvas, root): 11 | settings = load_settings() 12 | 13 | canvas.config(highlightbackground=settings['border_color']) 14 | canvas.config(bg=settings['background_color']) 15 | 16 | root.attributes("-topmost", settings['always_on_top']) 17 | root.configure(background=settings['background_color']) 18 | -------------------------------------------------------------------------------- /modules/tkinter_manager.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import customtkinter as ctk 3 | from PIL import ImageTk, Image 4 | from modules import settings_loader, image_handler 5 | import sys 6 | import platform 7 | 8 | 9 | def make_window(open_windows, wid, hgt, img, win=None): 10 | win = win or tk.Tk() 11 | if win.winfo_name() == "tk": 12 | win.withdraw() 13 | 14 | root = tk.Toplevel() 15 | open_windows[0] += 1 16 | # check if windows or linux 17 | if platform.system() == "Linux": 18 | root.wm_attributes("-type", "splash") 19 | 20 | root.attributes("-topmost", True) 21 | 22 | x, y = root.winfo_screenwidth() - wid - 30, 30 23 | root.geometry(f"{wid}x{hgt}+{x}+{y}") 24 | 25 | root.initial_width = wid 26 | root.initial_height = hgt 27 | 28 | img_tk = ImageTk.PhotoImage(img) 29 | canvas = tk.Canvas(root, width=wid, height=hgt, highlightbackground="black") 30 | canvas.pack(fill=tk.BOTH, expand=True) 31 | canvas.create_image(0, 0, anchor=tk.NW, image=img_tk) 32 | 33 | # Store references to avoid garbage collection 34 | root.img_tk = canvas.img_tk = img_tk 35 | canvas.img, canvas.img_x, canvas.img_y = img, 0, 0 36 | canvas.configure(scrollregion=canvas.bbox("all")) 37 | 38 | # Bind events 39 | # moving 40 | root.bind("", lambda e: get_start(e, root)) 41 | root.bind("", lambda e: move(root, e)) 42 | 43 | # close/open 44 | settings = settings_loader.load_settings() 45 | root.bind( 46 | f"<{settings['close_key']}>", lambda e: on_close_window(root, open_windows) 47 | ) 48 | root.bind( 49 | f"<{settings['open_key']}>", 50 | lambda e: make_window(open_windows, *image_handler.get_image(), win), 51 | ) 52 | 53 | # zoom/panning 54 | canvas.bind("<4>", lambda e: zoom_in(e, canvas, img, img.size)) 55 | canvas.bind("<5>", lambda e: zoom_out(e, canvas, img, img.size)) 56 | canvas.bind("", lambda e: start_move(e, canvas)) 57 | canvas.bind("", lambda e: move_image(e, canvas)) 58 | 59 | # resizing 60 | root.bind("", lambda e: start_resize(e, root)) 61 | root.bind("", lambda e: perform_resize(e, root, canvas, img, False)) 62 | root.bind( 63 | f"<{settings['reset_size_key']}>", 64 | lambda e: reset_size(e, root, canvas, img, img.size), 65 | ) 66 | root.bind("", lambda e: perform_resize(e, root, canvas, img, ",")) 67 | root.bind("", lambda e: perform_resize(e, root, canvas, img, ".")) 68 | 69 | # rotating 70 | root.bind("", lambda e: rotate(e, canvas, root)) 71 | 72 | # load settings 73 | settings_loader.change_window(canvas, root) 74 | 75 | # make buttons if enabled 76 | if settings["buttons"]: 77 | add_button = ctk.CTkButton( 78 | canvas, 79 | text=" ", 80 | font=("", 20), 81 | width=50, 82 | command=lambda: root.event_generate(f"<{settings['open_key']}>"), 83 | ) 84 | close_button = ctk.CTkButton( 85 | canvas, 86 | text=" ", 87 | font=("", 20), 88 | width=50, 89 | command=lambda: root.event_generate(f"<{settings['close_key']}>"), 90 | ) 91 | increase_size_button = ctk.CTkButton( 92 | canvas, 93 | text="+", 94 | font=("", 20), 95 | width=50, 96 | command=lambda: root.event_generate(""), 97 | ) 98 | decrease_size_button = ctk.CTkButton( 99 | canvas, 100 | text="-", 101 | font=("", 20), 102 | width=50, 103 | command=lambda: root.event_generate(""), 104 | ) 105 | reset_size_button = ctk.CTkButton( 106 | canvas, 107 | text=" ", 108 | font=("", 20), 109 | width=50, 110 | command=lambda: root.event_generate(f"{settings['reset_size_key']}"), 111 | ) 112 | rotate_left_button = ctk.CTkButton( 113 | canvas, 114 | text=" ", 115 | font=("", 20), 116 | width=50, 117 | command=lambda: root.event_generate(""), 118 | ) 119 | rotate_right_button = ... 120 | 121 | add_button.pack(anchor="ne", pady=(0, 5)) 122 | close_button.pack(anchor="ne", pady=5) 123 | increase_size_button.pack(anchor="ne", pady=5) 124 | decrease_size_button.pack(anchor="ne", pady=5) 125 | reset_size_button.pack(anchor="ne", pady=5) 126 | rotate_left_button.pack(anchor="ne", pady=(5, 0)) 127 | 128 | if win.winfo_name() == "tk": 129 | win.mainloop() 130 | 131 | 132 | def start_resize(e, root): 133 | root.start_x, root.start_y = e.x_root, e.y_root 134 | root.start_width, root.start_height = root.winfo_width(), root.winfo_height() 135 | 136 | 137 | def perform_resize(e, root, canvas, img, pressed_key): 138 | if pressed_key: 139 | root.start_width, root.start_height = root.winfo_width(), root.winfo_height() 140 | if pressed_key == ",": 141 | new_width = int(root.start_width * 0.8) 142 | new_height = int(root.start_height * 0.8) 143 | elif pressed_key == ".": 144 | new_width = int(root.start_width * 1.2) 145 | new_height = int(root.start_height * 1.2) 146 | else: 147 | new_width = root.start_width + (e.x_root - root.start_x) 148 | new_height = root.start_height + (e.y_root - root.start_y) 149 | 150 | if new_width > 100 and new_height > 100: 151 | root.geometry(f"{new_width}x{new_height}") 152 | canvas.config(width=new_width, height=new_height) 153 | 154 | # image resizing 155 | canvas.img = img.resize((int(new_width), int(new_height)), Image.LANCZOS) 156 | canvas.img_tk = ImageTk.PhotoImage(canvas.img) 157 | canvas.delete("all") 158 | canvas.create_image( 159 | canvas.img_x, canvas.img_y, anchor=tk.NW, image=canvas.img_tk 160 | ) 161 | 162 | 163 | def reset_size(e, root, canvas, img, og_size): 164 | canvas.img = img.resize((og_size[0], og_size[1]), Image.LANCZOS) 165 | 166 | canvas.img_tk = ImageTk.PhotoImage(canvas.img) 167 | canvas.delete("all") 168 | canvas.create_image(canvas.img_x, canvas.img_y, anchor=tk.NW, image=canvas.img_tk) 169 | root.geometry(f"{root.initial_width}x{root.initial_height}") 170 | 171 | 172 | def on_close_window(window, open_windows): 173 | window.destroy() 174 | open_windows[0] -= 1 175 | if open_windows[0] == 0: 176 | sys.exit() 177 | 178 | 179 | def move(root, e): 180 | x = root.winfo_pointerx() - root.start_x 181 | y = root.winfo_pointery() - root.start_y 182 | root.geometry(f"+{x}+{y}") 183 | 184 | 185 | def get_start(e, root): 186 | root.start_x, root.start_y = e.x_root - root.winfo_x(), e.y_root - root.winfo_y() 187 | 188 | 189 | def zoom_in(e, canvas, img, og_size): 190 | current_width, current_height = canvas.img.size 191 | 192 | # Restrict resizing to 3 times the size 193 | if (current_width < 3 * og_size[0]) and (current_height < 3 * og_size[1]): 194 | canvas.img = img.resize( 195 | (int(current_width * 1.2), int(current_height * 1.2)), Image.LANCZOS 196 | ) 197 | canvas.img_x -= (e.x - canvas.canvasx(0)) * 0.2 198 | canvas.img_y -= (e.y - canvas.canvasy(0)) * 0.2 199 | 200 | canvas.img_tk = ImageTk.PhotoImage(canvas.img) 201 | canvas.delete("all") 202 | canvas.create_image( 203 | canvas.img_x, canvas.img_y, anchor=tk.NW, image=canvas.img_tk 204 | ) 205 | canvas.configure(scrollregion=canvas.bbox("all")) 206 | else: 207 | print("Zoom limit reached") 208 | 209 | 210 | def zoom_out(e, canvas, img, og_size): 211 | current_width, current_height = canvas.img.size 212 | 213 | if (current_width > og_size[0]) and (current_height > og_size[1]): 214 | new_width = max(int(current_width * 0.8), og_size[0]) 215 | new_height = max(int(current_height * 0.8), og_size[1]) 216 | 217 | canvas.img = img.resize((new_width, new_height), Image.LANCZOS) 218 | canvas.img_x += (e.x - canvas.canvasx(0)) * 0.2 219 | canvas.img_y += (e.y - canvas.canvasy(0)) * 0.2 220 | 221 | canvas.img_tk = ImageTk.PhotoImage(canvas.img) 222 | canvas.delete("all") 223 | canvas.create_image( 224 | canvas.img_x, canvas.img_y, anchor=tk.NW, image=canvas.img_tk 225 | ) 226 | canvas.configure(scrollregion=canvas.bbox("all")) 227 | else: 228 | print("Original size reached") 229 | 230 | 231 | def move_image(e, canvas): 232 | dx, dy = e.x - canvas.last_x, e.y - canvas.last_y 233 | canvas.img_x += dx 234 | canvas.img_y += dy 235 | 236 | canvas.delete("all") 237 | canvas.create_image(canvas.img_x, canvas.img_y, anchor=tk.NW, image=canvas.img_tk) 238 | canvas.last_x, canvas.last_y = e.x, e.y 239 | 240 | 241 | def start_move(e, canvas): 242 | canvas.last_x, canvas.last_y = e.x, e.y 243 | 244 | 245 | def rotate(e, canvas, root): 246 | canvas.img = canvas.img.rotate(90, expand=True) 247 | canvas.img_tk = ImageTk.PhotoImage(canvas.img) 248 | canvas.create_image(canvas.img_x, canvas.img_y, anchor=tk.NW, image=canvas.img_tk) 249 | 250 | # set new dimensions 251 | width = root.winfo_width() 252 | height = root.winfo_height() 253 | root.geometry(f"{height}x{width}") 254 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyinstaller 2 | pillow 3 | customtkinter -------------------------------------------------------------------------------- /usage.txt: -------------------------------------------------------------------------------- 1 | pinboard [option] 2 | options: 3 | -h, --help Display help 4 | -s, --standard Open clipboard image in default image viewer 5 | -p, --pin | Pin image to desktop (like if you don't pass arg) 6 | -f, --file | Select a file to display (need to specify -p or -s) 7 | 8 | --create-config Make config file (Warning: Overwrites current file if existing) 9 | 10 | Drag the image on your display 11 | Pan the image with mouswheel click 12 | Zoom the image with scroll 13 | Resize the image with right click and drag 14 | 15 | default keybinds: 16 | Press + to add another image from clipboard 17 | Press q to close the selected image 18 | Press , to proportionally decrease image size 19 | Press . to proportionally increase image size 20 | Press b to reset the image size --------------------------------------------------------------------------------