waypaste 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- waypaste-0.1.0/LICENSE +21 -0
- waypaste-0.1.0/PKG-INFO +72 -0
- waypaste-0.1.0/README.md +60 -0
- waypaste-0.1.0/pyproject.toml +23 -0
- waypaste-0.1.0/setup.cfg +4 -0
- waypaste-0.1.0/waypaste.egg-info/PKG-INFO +72 -0
- waypaste-0.1.0/waypaste.egg-info/SOURCES.txt +9 -0
- waypaste-0.1.0/waypaste.egg-info/dependency_links.txt +1 -0
- waypaste-0.1.0/waypaste.egg-info/entry_points.txt +2 -0
- waypaste-0.1.0/waypaste.egg-info/top_level.txt +1 -0
- waypaste-0.1.0/waypaste.py +177 -0
waypaste-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
waypaste-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waypaste
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Clipboard image saver for Wayland/Sway
|
|
5
|
+
Author-email: Ivan <ivan@primaguna.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/primaguna/waypaste
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# WayPaste
|
|
14
|
+
|
|
15
|
+
Save clipboard images to disk from anywhere on Sway/Wayland.
|
|
16
|
+
|
|
17
|
+
Press **Ctrl+Alt+V** → native save dialog → file saved → notification.
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
1. Checks clipboard for `image/png` (e.g. after Figma "Copy as PNG")
|
|
22
|
+
2. Opens a GTK file-save dialog pre-filled with a date-based filename and your last-used directory
|
|
23
|
+
3. Saves the file and sends a `notify-send` confirmation
|
|
24
|
+
4. If clipboard has no image, sends an error notification instead
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
**Arch / CachyOS (recommended):**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
yay -S python-waypaste
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**pip (system Python):**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install waypaste
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires system Python with `python-gobject` already installed. Do not use `uvx` or an isolated venv.
|
|
41
|
+
|
|
42
|
+
**From source:**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bash install.sh
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then add to `~/.config/sway/config`:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
bindsym Ctrl+Alt+v exec ~/.local/bin/waypaste
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Reload Sway: **Super+Shift+C**
|
|
55
|
+
|
|
56
|
+
## Dependencies
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
sudo pacman -S wl-clipboard libnotify python-gobject
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Package | Provides |
|
|
63
|
+
|---------|----------|
|
|
64
|
+
| `wl-clipboard` | `wl-paste` — reads Wayland clipboard |
|
|
65
|
+
| `libnotify` | `notify-send` — desktop notifications |
|
|
66
|
+
| `python-gobject` | GTK3 file-save dialog |
|
|
67
|
+
|
|
68
|
+
## Default filename
|
|
69
|
+
|
|
70
|
+
`YYYY-MM-DD-HHmmss.png` — e.g. `2026-04-03-143200.png`
|
|
71
|
+
|
|
72
|
+
Last-used directory is remembered in `~/.local/share/waypaste/last-dir`.
|
waypaste-0.1.0/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# WayPaste
|
|
2
|
+
|
|
3
|
+
Save clipboard images to disk from anywhere on Sway/Wayland.
|
|
4
|
+
|
|
5
|
+
Press **Ctrl+Alt+V** → native save dialog → file saved → notification.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
1. Checks clipboard for `image/png` (e.g. after Figma "Copy as PNG")
|
|
10
|
+
2. Opens a GTK file-save dialog pre-filled with a date-based filename and your last-used directory
|
|
11
|
+
3. Saves the file and sends a `notify-send` confirmation
|
|
12
|
+
4. If clipboard has no image, sends an error notification instead
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
**Arch / CachyOS (recommended):**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
yay -S python-waypaste
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**pip (system Python):**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install waypaste
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires system Python with `python-gobject` already installed. Do not use `uvx` or an isolated venv.
|
|
29
|
+
|
|
30
|
+
**From source:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bash install.sh
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then add to `~/.config/sway/config`:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
bindsym Ctrl+Alt+v exec ~/.local/bin/waypaste
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Reload Sway: **Super+Shift+C**
|
|
43
|
+
|
|
44
|
+
## Dependencies
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
sudo pacman -S wl-clipboard libnotify python-gobject
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Package | Provides |
|
|
51
|
+
|---------|----------|
|
|
52
|
+
| `wl-clipboard` | `wl-paste` — reads Wayland clipboard |
|
|
53
|
+
| `libnotify` | `notify-send` — desktop notifications |
|
|
54
|
+
| `python-gobject` | GTK3 file-save dialog |
|
|
55
|
+
|
|
56
|
+
## Default filename
|
|
57
|
+
|
|
58
|
+
`YYYY-MM-DD-HHmmss.png` — e.g. `2026-04-03-143200.png`
|
|
59
|
+
|
|
60
|
+
Last-used directory is remembered in `~/.local/share/waypaste/last-dir`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "waypaste"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Clipboard image saver for Wayland/Sway"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ivan", email = "ivan@primaguna.com" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
waypaste = "waypaste:main"
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/primaguna/waypaste"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools]
|
|
23
|
+
py-modules = ["waypaste"]
|
waypaste-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waypaste
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Clipboard image saver for Wayland/Sway
|
|
5
|
+
Author-email: Ivan <ivan@primaguna.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/primaguna/waypaste
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# WayPaste
|
|
14
|
+
|
|
15
|
+
Save clipboard images to disk from anywhere on Sway/Wayland.
|
|
16
|
+
|
|
17
|
+
Press **Ctrl+Alt+V** → native save dialog → file saved → notification.
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
1. Checks clipboard for `image/png` (e.g. after Figma "Copy as PNG")
|
|
22
|
+
2. Opens a GTK file-save dialog pre-filled with a date-based filename and your last-used directory
|
|
23
|
+
3. Saves the file and sends a `notify-send` confirmation
|
|
24
|
+
4. If clipboard has no image, sends an error notification instead
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
**Arch / CachyOS (recommended):**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
yay -S python-waypaste
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**pip (system Python):**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install waypaste
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires system Python with `python-gobject` already installed. Do not use `uvx` or an isolated venv.
|
|
41
|
+
|
|
42
|
+
**From source:**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bash install.sh
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then add to `~/.config/sway/config`:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
bindsym Ctrl+Alt+v exec ~/.local/bin/waypaste
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Reload Sway: **Super+Shift+C**
|
|
55
|
+
|
|
56
|
+
## Dependencies
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
sudo pacman -S wl-clipboard libnotify python-gobject
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Package | Provides |
|
|
63
|
+
|---------|----------|
|
|
64
|
+
| `wl-clipboard` | `wl-paste` — reads Wayland clipboard |
|
|
65
|
+
| `libnotify` | `notify-send` — desktop notifications |
|
|
66
|
+
| `python-gobject` | GTK3 file-save dialog |
|
|
67
|
+
|
|
68
|
+
## Default filename
|
|
69
|
+
|
|
70
|
+
`YYYY-MM-DD-HHmmss.png` — e.g. `2026-04-03-143200.png`
|
|
71
|
+
|
|
72
|
+
Last-used directory is remembered in `~/.local/share/waypaste/last-dir`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
waypaste
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
waypaste — clipboard image saver for Wayland/Sway
|
|
4
|
+
|
|
5
|
+
Usage: waypaste
|
|
6
|
+
Bind to Ctrl+Alt+V in ~/.config/sway/config:
|
|
7
|
+
bindsym Ctrl+Alt+v exec ~/.local/bin/waypaste
|
|
8
|
+
"""
|
|
9
|
+
# /// script
|
|
10
|
+
# requires-python = ">=3.10"
|
|
11
|
+
# ///
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
LAST_DIR_FILE = Path.home() / ".local/share/waypaste/last-dir"
|
|
19
|
+
DEFAULT_DIR = Path.home() / "Pictures"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _check_deps() -> None:
|
|
23
|
+
if not shutil.which("wl-paste"):
|
|
24
|
+
print("Error: wl-paste not found. Install with: sudo pacman -S wl-clipboard", file=sys.stderr)
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
try:
|
|
27
|
+
import gi # noqa: F401
|
|
28
|
+
except ImportError:
|
|
29
|
+
print("Error: python-gobject not found. Install with: sudo pacman -S python-gobject", file=sys.stderr)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
if not shutil.which("notify-send"):
|
|
32
|
+
print("Warning: notify-send not found. Notifications disabled. Install with: sudo pacman -S libnotify", file=sys.stderr)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_clipboard_types() -> list[str]:
|
|
36
|
+
try:
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
["wl-paste", "--list-types"],
|
|
39
|
+
capture_output=True, text=True, timeout=5
|
|
40
|
+
)
|
|
41
|
+
if result.returncode != 0:
|
|
42
|
+
return []
|
|
43
|
+
return [t.strip() for t in result.stdout.strip().split("\n") if t.strip()]
|
|
44
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_image_bytes() -> bytes | None:
|
|
49
|
+
try:
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
["wl-paste", "--type", "image/png"],
|
|
52
|
+
capture_output=True, timeout=10
|
|
53
|
+
)
|
|
54
|
+
return result.stdout if result.returncode == 0 and result.stdout else None
|
|
55
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def notify(summary: str, body: str = "", urgency: str = "normal") -> None:
|
|
60
|
+
subprocess.run(
|
|
61
|
+
["notify-send", "-u", urgency, summary, body],
|
|
62
|
+
check=False
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_last_dir() -> str:
|
|
67
|
+
if LAST_DIR_FILE.exists():
|
|
68
|
+
path = LAST_DIR_FILE.read_text().strip()
|
|
69
|
+
if path and Path(path).is_dir():
|
|
70
|
+
return path
|
|
71
|
+
return str(DEFAULT_DIR)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def save_last_dir(directory: str) -> None:
|
|
75
|
+
LAST_DIR_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
LAST_DIR_FILE.write_text(directory.rstrip("/"))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def default_filename() -> str:
|
|
80
|
+
return datetime.now().strftime("%Y-%m-%d-%H%M%S.png")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _make_preview_widget(image_bytes: bytes):
|
|
84
|
+
import gi
|
|
85
|
+
gi.require_version("Gtk", "3.0")
|
|
86
|
+
gi.require_version("GdkPixbuf", "2.0")
|
|
87
|
+
from gi.repository import Gtk, GdkPixbuf
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
loader = GdkPixbuf.PixbufLoader.new_with_type("png")
|
|
91
|
+
loader.write(image_bytes)
|
|
92
|
+
loader.close()
|
|
93
|
+
pixbuf = loader.get_pixbuf()
|
|
94
|
+
|
|
95
|
+
MAX_SIZE = 200
|
|
96
|
+
w, h = pixbuf.get_width(), pixbuf.get_height()
|
|
97
|
+
scale = min(MAX_SIZE / w, MAX_SIZE / h, 1.0)
|
|
98
|
+
if scale < 1.0:
|
|
99
|
+
pixbuf = pixbuf.scale_simple(
|
|
100
|
+
int(w * scale), int(h * scale),
|
|
101
|
+
GdkPixbuf.InterpType.BILINEAR,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
|
105
|
+
box.pack_start(Gtk.Label(label="Clipboard image"), False, False, 0)
|
|
106
|
+
box.pack_start(Gtk.Image.new_from_pixbuf(pixbuf), False, False, 0)
|
|
107
|
+
box.show_all()
|
|
108
|
+
return box
|
|
109
|
+
except Exception:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def show_save_dialog(default_dir: str, default_name: str, image_bytes: bytes) -> str | None:
|
|
114
|
+
import gi
|
|
115
|
+
gi.require_version("Gtk", "3.0")
|
|
116
|
+
from gi.repository import Gtk
|
|
117
|
+
|
|
118
|
+
dialog = Gtk.FileChooserDialog(
|
|
119
|
+
title="Save Clipboard Image",
|
|
120
|
+
action=Gtk.FileChooserAction.SAVE,
|
|
121
|
+
)
|
|
122
|
+
dialog.add_buttons(
|
|
123
|
+
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
124
|
+
Gtk.STOCK_SAVE, Gtk.ResponseType.OK,
|
|
125
|
+
)
|
|
126
|
+
dialog.set_do_overwrite_confirmation(True)
|
|
127
|
+
dialog.set_current_folder(default_dir)
|
|
128
|
+
dialog.set_current_name(default_name)
|
|
129
|
+
|
|
130
|
+
png_filter = Gtk.FileFilter()
|
|
131
|
+
png_filter.set_name("PNG Images")
|
|
132
|
+
png_filter.add_mime_type("image/png")
|
|
133
|
+
dialog.add_filter(png_filter)
|
|
134
|
+
|
|
135
|
+
preview = _make_preview_widget(image_bytes)
|
|
136
|
+
if preview:
|
|
137
|
+
dialog.set_preview_widget(preview)
|
|
138
|
+
dialog.set_preview_widget_active(True)
|
|
139
|
+
|
|
140
|
+
response = dialog.run()
|
|
141
|
+
path = dialog.get_filename() if response == Gtk.ResponseType.OK else None
|
|
142
|
+
dialog.destroy()
|
|
143
|
+
|
|
144
|
+
# Flush GTK events so the window closes before we proceed
|
|
145
|
+
while Gtk.events_pending():
|
|
146
|
+
Gtk.main_iteration()
|
|
147
|
+
|
|
148
|
+
return path
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main() -> None:
|
|
152
|
+
_check_deps()
|
|
153
|
+
types = get_clipboard_types()
|
|
154
|
+
if "image/png" not in types:
|
|
155
|
+
notify("waypaste", "No image on clipboard")
|
|
156
|
+
sys.exit(0)
|
|
157
|
+
|
|
158
|
+
image_bytes = get_image_bytes()
|
|
159
|
+
if not image_bytes:
|
|
160
|
+
notify("waypaste", "Failed to read image from clipboard", urgency="critical")
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
save_path = show_save_dialog(get_last_dir(), default_filename(), image_bytes)
|
|
164
|
+
if save_path is None:
|
|
165
|
+
sys.exit(0)
|
|
166
|
+
|
|
167
|
+
if not save_path.lower().endswith(".png"):
|
|
168
|
+
save_path += ".png"
|
|
169
|
+
|
|
170
|
+
dest = Path(save_path)
|
|
171
|
+
dest.write_bytes(image_bytes)
|
|
172
|
+
save_last_dir(str(dest.parent))
|
|
173
|
+
notify("waypaste", f"Saved: {dest.name}")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
main()
|