snipux 0.6.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.
Files changed (103) hide show
  1. snipux-0.6.0/LICENSE +21 -0
  2. snipux-0.6.0/PKG-INFO +632 -0
  3. snipux-0.6.0/README.md +599 -0
  4. snipux-0.6.0/pyproject.toml +111 -0
  5. snipux-0.6.0/setup.cfg +4 -0
  6. snipux-0.6.0/snipux/__init__.py +34 -0
  7. snipux-0.6.0/snipux/__main__.py +12 -0
  8. snipux-0.6.0/snipux/app.py +2866 -0
  9. snipux-0.6.0/snipux/capture.py +1760 -0
  10. snipux-0.6.0/snipux/chooser.py +1554 -0
  11. snipux-0.6.0/snipux/design/__init__.py +233 -0
  12. snipux-0.6.0/snipux/design/icons/arrow.svg +4 -0
  13. snipux-0.6.0/snipux/design/icons/blur.svg +4 -0
  14. snipux-0.6.0/snipux/design/icons/camera.svg +4 -0
  15. snipux-0.6.0/snipux/design/icons/check.svg +3 -0
  16. snipux-0.6.0/snipux/design/icons/chevron.svg +3 -0
  17. snipux-0.6.0/snipux/design/icons/close.svg +4 -0
  18. snipux-0.6.0/snipux/design/icons/cog.svg +4 -0
  19. snipux-0.6.0/snipux/design/icons/copy.svg +4 -0
  20. snipux-0.6.0/snipux/design/icons/crop.svg +4 -0
  21. snipux-0.6.0/snipux/design/icons/ellipse.svg +3 -0
  22. snipux-0.6.0/snipux/design/icons/eraser.svg +4 -0
  23. snipux-0.6.0/snipux/design/icons/expand.svg +6 -0
  24. snipux-0.6.0/snipux/design/icons/eye.svg +4 -0
  25. snipux-0.6.0/snipux/design/icons/eyedropper.svg +4 -0
  26. snipux-0.6.0/snipux/design/icons/highlighter.svg +13 -0
  27. snipux-0.6.0/snipux/design/icons/image.svg +5 -0
  28. snipux-0.6.0/snipux/design/icons/line.svg +3 -0
  29. snipux-0.6.0/snipux/design/icons/mic.svg +6 -0
  30. snipux-0.6.0/snipux/design/icons/minimize.svg +3 -0
  31. snipux-0.6.0/snipux/design/icons/minus.svg +3 -0
  32. snipux-0.6.0/snipux/design/icons/monitor.svg +5 -0
  33. snipux-0.6.0/snipux/design/icons/mute.svg +5 -0
  34. snipux-0.6.0/snipux/design/icons/panel.svg +4 -0
  35. snipux-0.6.0/snipux/design/icons/pause.svg +4 -0
  36. snipux-0.6.0/snipux/design/icons/pen.svg +4 -0
  37. snipux-0.6.0/snipux/design/icons/pin.svg +4 -0
  38. snipux-0.6.0/snipux/design/icons/play.svg +3 -0
  39. snipux-0.6.0/snipux/design/icons/plus.svg +4 -0
  40. snipux-0.6.0/snipux/design/icons/record.svg +3 -0
  41. snipux-0.6.0/snipux/design/icons/rect.svg +3 -0
  42. snipux-0.6.0/snipux/design/icons/redo.svg +4 -0
  43. snipux-0.6.0/snipux/design/icons/save.svg +5 -0
  44. snipux-0.6.0/snipux/design/icons/select.svg +3 -0
  45. snipux-0.6.0/snipux/design/icons/speaker.svg +5 -0
  46. snipux-0.6.0/snipux/design/icons/step.svg +5 -0
  47. snipux-0.6.0/snipux/design/icons/stop.svg +3 -0
  48. snipux-0.6.0/snipux/design/icons/text.svg +5 -0
  49. snipux-0.6.0/snipux/design/icons/timer.svg +4 -0
  50. snipux-0.6.0/snipux/design/icons/trash.svg +7 -0
  51. snipux-0.6.0/snipux/design/icons/undo.svg +4 -0
  52. snipux-0.6.0/snipux/design/icons/window.svg +5 -0
  53. snipux-0.6.0/snipux/design/logo/generate_small_icons.py +167 -0
  54. snipux-0.6.0/snipux/design/logo/snipux-128.png +0 -0
  55. snipux-0.6.0/snipux/design/logo/snipux-16.png +0 -0
  56. snipux-0.6.0/snipux/design/logo/snipux-24.png +0 -0
  57. snipux-0.6.0/snipux/design/logo/snipux-256.png +0 -0
  58. snipux-0.6.0/snipux/design/logo/snipux-32.png +0 -0
  59. snipux-0.6.0/snipux/design/logo/snipux-48.png +0 -0
  60. snipux-0.6.0/snipux/design/logo/snipux-512.png +0 -0
  61. snipux-0.6.0/snipux/design/logo/snipux-64.png +0 -0
  62. snipux-0.6.0/snipux/design/logo/snipux.png +0 -0
  63. snipux-0.6.0/snipux/design/tokens.py +1271 -0
  64. snipux-0.6.0/snipux/flowbars.py +1163 -0
  65. snipux-0.6.0/snipux/marks.py +457 -0
  66. snipux-0.6.0/snipux/overlay.py +7123 -0
  67. snipux-0.6.0/snipux/platform/__init__.py +297 -0
  68. snipux-0.6.0/snipux/platform/darwin.py +68 -0
  69. snipux-0.6.0/snipux/platform/linux.py +169 -0
  70. snipux-0.6.0/snipux/platform/windows.py +1035 -0
  71. snipux-0.6.0/snipux/platform/windows_ocr.py +214 -0
  72. snipux-0.6.0/snipux/player.py +2518 -0
  73. snipux-0.6.0/snipux/recording.py +1271 -0
  74. snipux-0.6.0/snipux/review.py +930 -0
  75. snipux-0.6.0/snipux/sensitive.py +954 -0
  76. snipux-0.6.0/snipux/settings.py +1137 -0
  77. snipux-0.6.0/snipux/setup_desktop.py +1509 -0
  78. snipux-0.6.0/snipux/shapes.py +1033 -0
  79. snipux-0.6.0/snipux/snipux.desktop +8 -0
  80. snipux-0.6.0/snipux/winchrome.py +598 -0
  81. snipux-0.6.0/snipux.egg-info/PKG-INFO +632 -0
  82. snipux-0.6.0/snipux.egg-info/SOURCES.txt +101 -0
  83. snipux-0.6.0/snipux.egg-info/dependency_links.txt +1 -0
  84. snipux-0.6.0/snipux.egg-info/entry_points.txt +2 -0
  85. snipux-0.6.0/snipux.egg-info/requires.txt +5 -0
  86. snipux-0.6.0/snipux.egg-info/top_level.txt +1 -0
  87. snipux-0.6.0/tests/test_app.py +4866 -0
  88. snipux-0.6.0/tests/test_capture.py +2067 -0
  89. snipux-0.6.0/tests/test_chooser.py +958 -0
  90. snipux-0.6.0/tests/test_design.py +478 -0
  91. snipux-0.6.0/tests/test_flowbars.py +382 -0
  92. snipux-0.6.0/tests/test_marks.py +272 -0
  93. snipux-0.6.0/tests/test_overlay.py +9540 -0
  94. snipux-0.6.0/tests/test_platform.py +1560 -0
  95. snipux-0.6.0/tests/test_player.py +560 -0
  96. snipux-0.6.0/tests/test_recording.py +1792 -0
  97. snipux-0.6.0/tests/test_review.py +1064 -0
  98. snipux-0.6.0/tests/test_sensitive.py +731 -0
  99. snipux-0.6.0/tests/test_settings.py +809 -0
  100. snipux-0.6.0/tests/test_setup_desktop.py +1565 -0
  101. snipux-0.6.0/tests/test_shapes.py +1156 -0
  102. snipux-0.6.0/tests/test_winchrome.py +376 -0
  103. snipux-0.6.0/tests/test_windows_ocr.py +256 -0
snipux-0.6.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cody (CydoEntis)
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.
snipux-0.6.0/PKG-INFO ADDED
@@ -0,0 +1,632 @@
1
+ Metadata-Version: 2.4
2
+ Name: snipux
3
+ Version: 0.6.0
4
+ Summary: Snip, annotate and record your screen -- a Snipping Tool workalike for Linux and Windows.
5
+ Author-email: Cody <cydoentis@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/CydoEntis/snipux
8
+ Project-URL: Repository, https://github.com/CydoEntis/snipux
9
+ Project-URL: Issues, https://github.com/CydoEntis/snipux/issues
10
+ Keywords: screenshot,snipping-tool,screen-recorder,wayland,gnome,linux,windows,annotation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: X11 Applications :: Qt
13
+ Classifier: Environment :: Win32 (MS Windows)
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
23
+ Classifier: Topic :: Multimedia :: Video :: Capture
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: PyQt6>=6.8
29
+ Requires-Dist: jeepney>=0.8
30
+ Provides-Extra: build-windows
31
+ Requires-Dist: pyinstaller>=6.5; extra == "build-windows"
32
+ Dynamic: license-file
33
+
34
+ # Snipux
35
+
36
+ **Snip, annotate and record your screen — a Snipping Tool workalike for Linux
37
+ and Windows.**
38
+
39
+ Snip an area, a window, a freehand shape, or the whole screen. Draw on it, blur
40
+ out the parts that shouldn't be shared, and copy or save it. Or record the same
41
+ region to a video file and trim it down. The workflow Windows gives you for
42
+ free, on Linux too — and a slightly better one back on Windows.
43
+
44
+ MIT licensed, and installed with `pip install snipux` — see
45
+ [Install](#install). There is no Windows installer: that is deliberate, and
46
+ explained where it'd be missed.
47
+
48
+ ## Platform support
49
+
50
+ | Platform | State | How it captures |
51
+ |----------|-------|-----------------|
52
+ | **Linux** (Ubuntu 22.04+, GNOME) | Supported | Wayland via `xdg-desktop-portal`; X11 directly. Session type detected at runtime, never assumed. |
53
+ | **Windows** (10 2004+ / 11) | Supported | Qt's `QScreenCapture`, plus Win32 for the hotkey and shortcuts. |
54
+ | **macOS** | Not yet | The platform seam exists; nothing behind it is implemented. Every operation raises `UnimplementedPlatformError`. |
55
+
56
+ Linux is tested against Ubuntu with GNOME. Other desktops are expected to work
57
+ for snipping but are not what the tool is tested against — and **recording on
58
+ Linux is GNOME-only** (see [Recording](#recording)).
59
+
60
+ ## Why
61
+
62
+ Linux has capable screenshot tools, but the Ubuntu/GNOME/Wayland combination is
63
+ where most of them get awkward — Wayland deliberately forbids applications from
64
+ reading the screen whenever they like, so every capture has to go through a
65
+ permission broker, and the tools that predate that constraint fight it. Snipux
66
+ treats it as the primary target rather than an afterthought.
67
+
68
+ ## How it works
69
+
70
+ Capture the entire virtual desktop in a single shot, then run selection against
71
+ that frozen frame in our own overlay. The compositor is involved for exactly one
72
+ instant, which is what lets the same code path behave identically on X11 and
73
+ Wayland — and what made the Windows port a backend swap rather than a rewrite.
74
+ Everything downstream — region select, annotation, export — is ordinary drawing
75
+ on an image already held in memory.
76
+
77
+ Recording rides the same rule: **the frozen frame is still how you choose, and
78
+ recording starts once you have chosen.** The compositor is never involved while
79
+ you are dragging.
80
+
81
+ ## Install
82
+
83
+ ### Linux
84
+
85
+ First, the things Ubuntu may not already have — [pipx](https://pipx.pypa.io/),
86
+ `git` (which pipx shells out to for the install below), and one library Qt needs
87
+ that nothing else on a stock desktop pulls in:
88
+
89
+ ```sh
90
+ sudo apt install pipx git libxcb-cursor0
91
+ pipx ensurepath # only needed once, and only if pipx was just installed
92
+ ```
93
+
94
+ `libxcb-cursor0` is not optional: without it Snipux installs cleanly and then
95
+ crashes on launch, behind four lines of Qt plugin text that name the library
96
+ but not the package. Then:
97
+
98
+ ```sh
99
+ pipx install git+https://github.com/CydoEntis/snipux.git
100
+ snipux --setup
101
+ snipux &
102
+ ```
103
+
104
+ `pipx` gives Snipux and its dependencies their own isolated environment and
105
+ puts a `snipux` launcher on `PATH`. `--setup` writes the pieces `pipx` can't —
106
+ the `.desktop` entry, the autostart entry, and the GNOME shortcut — and is safe
107
+ to re-run.
108
+
109
+ **The third line is not optional the first time.** The shortcut runs
110
+ `snipux --snip`, which needs a resident Snipux to talk to, and `--setup` only
111
+ *writes* the autostart entry — it doesn't start anything. Without it the key
112
+ you just bound does nothing until your next login. (`packaging/install.sh`
113
+ does this step for you, which is why it isn't mentioned there.)
114
+
115
+ That's it — press **Ctrl+Alt+S**.
116
+
117
+ Prefer SSH, or contributing rather than just using it?
118
+
119
+ ```sh
120
+ pipx install git+ssh://git@github.com/CydoEntis/snipux.git
121
+ ```
122
+
123
+ **From a clone instead.** `packaging/install.sh` does the same job without
124
+ pipx — it builds a virtual environment under `~/.local/share/snipux/venv`,
125
+ installs into it, drops a launcher in `~/.local/bin`, runs `--setup`, and
126
+ starts the app. It checks for the `python3-venv` and `libxcb-cursor0`
127
+ prerequisites first and names the package to install if either is missing:
128
+
129
+ ```sh
130
+ git clone https://github.com/CydoEntis/snipux.git
131
+ cd snipux
132
+ ./packaging/install.sh
133
+ ```
134
+
135
+ ### Windows
136
+
137
+ The one prerequisite is **[Python 3.10+](https://www.python.org/downloads/)** —
138
+ tick *"Add python.exe to PATH"* in the installer. Nothing else: no Git, no
139
+ pipx, no file to download by hand.
140
+
141
+ Then, in PowerShell:
142
+
143
+ ```powershell
144
+ py -m pip install snipux
145
+ py -m snipux
146
+ ```
147
+
148
+ The first line takes a few minutes because it pulls down Qt. The second
149
+ starts Snipux.
150
+
151
+ **The first launch sets itself up**: a Start Menu shortcut, a Startup entry so
152
+ it is running after every login, and the **Ctrl+Alt+S** shortcut. After that,
153
+ Ctrl+Alt+S is all anyone needs — no terminal again.
154
+
155
+ `snipux --setup` does the same thing explicitly, but is not a step to hand
156
+ anyone: it is for redoing the integration after a move.
157
+ `AppController.run_first_launch_setup` already runs it the first time the app
158
+ is ever the resident instance.
159
+
160
+ **Snipux has to actually be running for the hotkey to do anything.** Unlike
161
+ GNOME's shortcut on Linux, which the desktop itself owns, Windows' hotkey is a
162
+ registration the Snipux process holds only while it's alive. That's what
163
+ autostart is for: the Startup entry means Snipux is already running by the time
164
+ you'd want to press the shortcut, from the next login onward. The first time,
165
+ start it yourself with the second command above.
166
+
167
+ **Nothing here trips Smart App Control or SmartScreen**, which is the point.
168
+ Both react to unrecognised *executables*; this is Python source installed by
169
+ the `python.exe` the user already trusts. An earlier version shipped an Inno
170
+ Setup installer and Smart App Control blocked it outright — the message read
171
+ like the file was corrupt rather than like a policy decision, and unlike
172
+ SmartScreen there was no "More info → Run anyway" to click through. Full
173
+ reasoning in
174
+ [docs/releasing.md](docs/releasing.md#why-theres-no-installer).
175
+
176
+ ### Updating
177
+
178
+ ```powershell
179
+ snipux --update
180
+ ```
181
+
182
+ If that answers `'snipux' is not recognized`, use:
183
+
184
+ ```powershell
185
+ py -m snipux --update
186
+ ```
187
+
188
+ Then quit Snipux from the tray and press Ctrl+Alt+S to start the new one.
189
+
190
+ **Why two.** Neither form works everywhere, because it depends on how Snipux
191
+ was installed, and the same machine cannot tell you which:
192
+
193
+ | Installed with | `snipux --update` | `py -m snipux --update` |
194
+ |---|---|---|
195
+ | `py -m pip install …` (this README) | only if Python's `Scripts` folder is on `PATH` | ✅ |
196
+ | `pipx install …` (an older README) | ✅ | ❌ `No module named snipux` |
197
+
198
+ `pipx` puts Snipux in an environment of its own and a launcher on `PATH`, so
199
+ the bare command works and `py -m` cannot see it at all — `py` runs the
200
+ *system* Python, which has no Snipux in it. A plain `pip install` is the other
201
+ way round. Try the bare command first, since it is shorter and covers the
202
+ older instructions people may already have followed.
203
+
204
+ **Installed with pipx? There is nothing to undo.** `pipx upgrade` is the
205
+ cleanest route for those installs, and it needs no reinstalling and no
206
+ uninstalling first:
207
+
208
+ ```powershell
209
+ pipx upgrade snipux
210
+ ```
211
+
212
+ The original `pipx install git+https://github.com/CydoEntis/snipux.git`
213
+ recorded that URL, and it tracks the default branch — so upgrading refetches
214
+ `main` and lands the same code the instructions above install. There is no
215
+ reason to switch a working pipx install over to `pip`, and doing so without
216
+ uninstalling first leaves two copies fighting over one Ctrl+Alt+S
217
+ registration and two Startup entries.
218
+
219
+ Either way `--update` runs exactly this on their behalf — it is not a second
220
+ update mechanism, just the same one without a command to keep somewhere
221
+ findable:
222
+
223
+ ```powershell
224
+ py -m pip install --upgrade snipux
225
+ ```
226
+
227
+ Nothing else to run: the shortcut and hotkey point at a location that does not
228
+ change between versions.
229
+
230
+ **Check it worked:** tray → Settings, bottom-left, e.g.
231
+ `Snipux 0.2.0 / Qt 6.11.0 · Windows`.
232
+
233
+ > `--upgrade` compares versions, so **every release needs a new version
234
+ > number** in `pyproject.toml`. Left the same, pip decides the requirement is
235
+ > already satisfied and changes nothing — no error — so a fixed build
236
+ > published under an old number is a silent no-op and the bug gets reported a
237
+ > second time. `.github/workflows/release.yml` refuses to publish a tag whose
238
+ > number disagrees with `pyproject.toml`, which is the half of this a person
239
+ > can get wrong. (`--force-reinstall` overrides the comparison, but it also
240
+ > re-downloads Qt, so it is not what to tell people.)
241
+
242
+ **There is no update check.** Snipux never phones home, so nobody is told a new
243
+ version exists — they update when they choose to. One is buildable now that
244
+ this repository is public:
245
+ `api.github.com/repos/CydoEntis/snipux/releases/latest` answers an
246
+ unauthenticated request, and comparing that tag against
247
+ `importlib.metadata.version("snipux")` is the whole of the logic. What it needs
248
+ first is tagged releases to compare against.
249
+
250
+ ### Installing a specific version, or offline
251
+
252
+ Any published version installs by name:
253
+
254
+ ```powershell
255
+ py -m pip install snipux==0.5.0
256
+ ```
257
+
258
+ An unreleased commit still installs from this repository directly:
259
+
260
+ ```powershell
261
+ py -m pip install https://github.com/CydoEntis/snipux/archive/refs/heads/main.tar.gz
262
+ ```
263
+
264
+ For a machine with no network, or to pin exactly what someone runs, build a
265
+ wheel and hand them the file instead:
266
+
267
+ ```powershell
268
+ python -m build --wheel
269
+ ```
270
+
271
+ That writes `dist/snipux-<version>-py3-none-any.whl`, which installs the same
272
+ way — `py -m pip install snipux-0.2.0-py3-none-any.whl`.
273
+
274
+ ### The shortcut
275
+
276
+ The default is **Ctrl+Alt+S** on both platforms.
277
+
278
+ Not Win+Shift+S, the combination the Windows Snipping Tool uses: Windows won't
279
+ hand that to a second application — `RegisterHotKey` refuses it — so Snipux
280
+ would have needed a different key on Windows regardless. One combination on both
281
+ platforms beats two. See
282
+ [docs/gnome-shortcut.md](docs/gnome-shortcut.md#why-controlalts-and-not-supershifts).
283
+
284
+ #### Using a different shortcut
285
+
286
+ ```sh
287
+ snipux --setup --shortcut 'Super+Shift+X'
288
+ ```
289
+
290
+ Either spelling is accepted — the readable `Super+Shift+X` or gsettings'
291
+ `<Super><Shift>x` — since both normalise to the same thing. `'Print'` and
292
+ `'F9'` work too. Anything that isn't a shortcut is rejected with an explanation
293
+ rather than bound and silently ignored.
294
+
295
+ The choice is remembered in `~/.config/snipux/config.json`, so later `--setup`
296
+ runs — including the one `packaging/install.sh` performs on every install —
297
+ keep it instead of reverting to the default. `snipux --remove` deletes it along
298
+ with everything else `--setup` wrote.
299
+
300
+ Or set it from **tray → Settings…**, which records the combination you press
301
+ rather than making you spell out any syntax, and warns you if GNOME already uses
302
+ it. That warning only sees GNOME's own shortcuts — an application that grabs a
303
+ key directly owns it just as effectively and can't be detected, so "No GNOME
304
+ shortcut uses this" is not a promise the key is free.
305
+
306
+ No tray icon? `snipux --settings` opens the same window.
307
+
308
+ ## Using Snipux
309
+
310
+ Snipux runs resident in the background (with a tray icon, where one is
311
+ available) so the shortcut reaches an already-warm process instead of paying
312
+ startup cost on every snip.
313
+
314
+ 1. **Press Ctrl+Alt+S.** The whole virtual desktop freezes into a full-screen
315
+ overlay. This single frozen frame is what selection and annotation both work
316
+ against, which is why the flow is identical on Wayland and X11.
317
+ 2. **Choose what to capture, and what happens to it.** A row docked at the top
318
+ of the screen carries both: a capture mode, and a destination.
319
+ 3. **Drag.** Or, in Window mode, hover — the window under the cursor is
320
+ outlined and named — and click to accept it.
321
+ 4. **Annotate in place**, directly on the frozen desktop; there's no separate
322
+ editor window. You can keep reframing as you go: drag any edge or corner of
323
+ the selection and the ink you've already drawn stays exactly where it was
324
+ drawn, over the same pixels, instead of moving with the selection or getting
325
+ clipped away.
326
+ 5. **Copy or save** from the bar that appears under the selection.
327
+
328
+ ### Capture modes
329
+
330
+ | Mode | What it captures |
331
+ |------|------------------|
332
+ | **Region** | Any rectangle you drag |
333
+ | **Window** | One application's window |
334
+ | **Full screen** | The whole monitor you are on — not every monitor |
335
+ | **Freeform** | A shape you draw by hand |
336
+
337
+ Freeform is stills-only; video is rectangular.
338
+
339
+ ### What happens after a snip
340
+
341
+ Set per-capture from the chooser row, or as a default in Settings:
342
+
343
+ | Destination | What it does |
344
+ |-------------|--------------|
345
+ | **Capture and finish** | Straight to the clipboard the moment the selection is made — no overlay, no toolbar, nothing to dismiss |
346
+ | **Capture and annotate** | The frozen frame stays up with the tools on it. Copy or save when you're done. *(default)* |
347
+ | **Capture and review** | Opens the review window afterwards, which annotates too |
348
+
349
+ ### The review window
350
+
351
+ Copy and Save both dismiss the overlay immediately, so a snip saved to the wrong
352
+ place — or copied when you meant to save — means taking the capture again. The
353
+ review window is the answer: the image, where it went, and Copy / Save As… /
354
+ Show in Folder.
355
+
356
+ Press **Annotate** and it reveals the overlay's *own* floating bar over the
357
+ image — the same widget, the same tools, the same mark model, so there is no
358
+ second tool set to drift. The only differences are that there's no capture-mode
359
+ chip (nothing left to capture) and the bar's trailing action is `Done`, since
360
+ the footer already owns Copy and Save As.
361
+
362
+ Marks made here live in image coordinates rather than screen coordinates, so
363
+ they survive zooming and export exactly where they looked. Several snips in a
364
+ row leave several windows open.
365
+
366
+ ## Recording
367
+
368
+ Switch the chooser row from snipping to recording and the same selection you'd
369
+ have screenshotted becomes the thing that gets filmed. Region, Window and Full
370
+ screen all record; Freeform does not.
371
+
372
+ Committing a selection **arms** a recording rather than starting one, so you can
373
+ still reframe it with the handles. One pill carries the whole thing and its
374
+ label always names what a click does — "Start recording", "Cancel · 3",
375
+ "Stop · 0:12" — sitting top-centre of the monitor being recorded, moving out of
376
+ the way only when the recording covers that strip. An optional 3s / 5s / 10s
377
+ delay shows as a countdown numeral inside the region.
378
+
379
+ Window mode films **where the window is right now**. It does not follow a window
380
+ that moves mid-recording; there's no window-following in the API to build it on.
381
+
382
+ **What you get, per platform:**
383
+
384
+ | | Linux (GNOME) | Windows |
385
+ |---|---|---|
386
+ | Container | WebM — GNOME Shell picks it, not us | MP4 |
387
+ | Audio | No — `org.gnome.Shell.Screencast` has no audio option to pass | Yes |
388
+ | Frame rate | Up to 30fps | ~30fps ceiling (`QScreenCapture` exposes no rate control) |
389
+
390
+ **Recording on Linux is GNOME-only.** The route is
391
+ `org.gnome.Shell.Screencast` over D-Bus. `QScreenCapture` does not work under
392
+ Wayland at all, so there is no fallback for other desktops. Snipping works
393
+ everywhere; recording does not. `snipux --list-backends` tells you which of the
394
+ two this machine can do.
395
+
396
+ ### Where a recording goes
397
+
398
+ Set the same way as a snip's destination:
399
+
400
+ | Destination | What it does |
401
+ |-------------|--------------|
402
+ | **Copy to the clipboard** | The video goes to the clipboard as a file reference and the file is deleted. Paste it somewhere that accepts a file. *(default)* |
403
+ | **Save to a folder** | Moved into your recordings folder |
404
+ | **Open in the player** | Saved as above, then opened in the trim editor |
405
+
406
+ ## The player
407
+
408
+ The `Open` destination's other half: playback, a rail with a decoded filmstrip
409
+ and a real waveform, in/out handles with a plain-language readout, and export.
410
+
411
+ | Key | What it does |
412
+ |-----|--------------|
413
+ | `Space` | Play / pause |
414
+ | `I` | Set the start at the playhead |
415
+ | `O` | Set the end at the playhead |
416
+ | `←` / `→` | Previous / next frame |
417
+ | `M` | Mute — drops the audio track on export |
418
+ | `L` | Loop the trimmed range |
419
+ | `Esc` | Close an open menu |
420
+
421
+ **Export formats:**
422
+
423
+ | Format | Notes |
424
+ |--------|-------|
425
+ | **WebM** | What was recorded — no re-encode when untrimmed |
426
+ | **MP4 (H.264)** | Plays anywhere. Slack, Teams, browsers. *(default)* |
427
+ | **GIF** | Silent, loops. Big above ~10 seconds. |
428
+ | **Current frame as PNG** | Just the frame under the playhead |
429
+
430
+ Trimming re-encodes; the untrimmed original stays at its own path until you
431
+ overwrite it.
432
+
433
+ ### About ffmpeg
434
+
435
+ Snipux does not depend on `ffmpeg`, does not bundle it, and never installs it.
436
+ But if one is already on your `PATH`, the player uses it — because Qt's bundled
437
+ FFmpeg is an LGPL build with no software x264, so it cannot encode H.264 in
438
+ software at all.
439
+
440
+ **With a system ffmpeg**, all four export formats work. **Without one**, MP4
441
+ degrades to MPEG-4 Part 2 and the row says so, while GIF and trimmed WebM grey
442
+ out with their reason. Every export still works, one codec down. Nothing is
443
+ hidden from you either way.
444
+
445
+ ## Tools and shortcuts
446
+
447
+ | Key | Tool | What it does |
448
+ |-----|------|--------------|
449
+ | `P` | Pen | Drag to draw freehand |
450
+ | `H` | Highlighter | Sweep over the line that matters |
451
+ | `A` | Arrow | Drag from tail to head |
452
+ | `R` | Rectangle | Drag to box something in. Its button opens a popover with three more shapes that share its colour/stroke tray: Ellipse, Line, and Crop |
453
+ | `S` | Step | Click to drop the next numbered marker |
454
+ | `T` | Text | Click, then type into the label |
455
+ | `B` | Blur | Drag over anything private to obscure it |
456
+ | `E` | Eraser | Click a mark to remove it |
457
+ | `Ctrl+Z` / `Ctrl+Shift+Z` | — | Undo / redo |
458
+ | `Enter` | — | Copy the annotated snip to the clipboard and close the overlay |
459
+ | `Esc` | — | First press discards all ink and leaves the overlay open; press again (once there's nothing left to discard) to close without capturing |
460
+ | `?` | — | Toggle the on-screen shortcut hint bar |
461
+
462
+ Colour and stroke width are chosen from the tray that appears once a drawing
463
+ tool is selected — the eraser has none. Tool shortcuts and `Enter` are
464
+ suppressed while a text label or a slider has keyboard focus; `Esc` and
465
+ undo/redo always work regardless.
466
+
467
+ ## Where files go
468
+
469
+ | | Folder | Default filename |
470
+ |---|---|---|
471
+ | Snips | `~/Pictures/snipux` | `Screenshot from YYYY-MM-DD HH-MM-SS.png` |
472
+ | Recordings | `~/Videos/snipux` | `Recording from YYYY-MM-DD HH-MM-SS.webm` |
473
+
474
+ Both directories are created if they don't exist yet, and a toast confirms the
475
+ path each time. The folder and filename pattern are both configurable in
476
+ Settings.
477
+
478
+ ## Command reference
479
+
480
+ | Command | What it does |
481
+ |---------|--------------|
482
+ | `snipux` | Start the resident/tray instance (or forward a snip request to one already running) |
483
+ | `snipux --snip` | Ask the running instance to start a capture, starting one first if needed. This is what the shortcut runs |
484
+ | `snipux --settings` | Open Settings — the way in on a machine with no tray icon |
485
+ | `snipux --setup` | Install desktop integration. Safe to re-run |
486
+ | `snipux --setup --shortcut '…'` | Same, binding a specific accelerator and remembering it |
487
+ | `snipux --remove` | Undo everything `--setup` wrote |
488
+ | `snipux --update` | Fetch and install the newest Snipux from GitHub, then say what to restart. `py -m snipux --update` is the same thing where `PATH` has no `snipux` on it |
489
+ | `snipux --list-backends` | Print every capture *and* recording backend, its availability, and why the unavailable ones aren't |
490
+
491
+ ## Uninstall
492
+
493
+ Run `snipux --remove` **first**, so the desktop entry, the autostart entry,
494
+ the installed icons and the bound shortcut all go with it. Then remove the
495
+ package the way it was installed.
496
+
497
+ Installed with pipx:
498
+
499
+ ```sh
500
+ snipux --remove
501
+ pipx uninstall snipux
502
+ ```
503
+
504
+ Installed with pip:
505
+
506
+ ```powershell
507
+ py -m snipux --remove
508
+ py -m pip uninstall snipux
509
+ ```
510
+
511
+ If you are unsure which you have, `pipx list` and `py -m pip show snipux`
512
+ answer it — and if both do, you have two copies, which is worth fixing:
513
+ they fight over one Ctrl+Alt+S registration, and whichever loses simply
514
+ never answers the shortcut. Remove one of them.
515
+
516
+ Uninstalling only removes the installed package — it has no idea `--setup`
517
+ also wrote files outside it, so skipping `--remove` first leaves an autostart
518
+ entry pointing at a binary that no longer exists, a dead keyboard shortcut, and
519
+ a ghost entry in your application list. `--remove` only ever splices its own
520
+ custom-keybinding slot out of GNOME's list, so any other shortcuts you've set up
521
+ by hand are left alone. Safe to re-run, same as `--setup`.
522
+
523
+ On Windows, `snipux --remove` also removes the copy of itself it relocated into
524
+ `%LocalAppData%\snipux` on first run.
525
+
526
+ ## Troubleshooting
527
+
528
+ **No tray icon.** Stock Ubuntu/GNOME ships no legacy tray icon support at all
529
+ unless the
530
+ [AppIndicator and KStatusNotifierItem Support](https://extensions.gnome.org/extension/615/appindicator-support/)
531
+ GNOME Shell extension is installed and enabled. Without it, Snipux still runs
532
+ and still answers the shortcut — it just prints a notice to stdout on startup
533
+ and has no tray icon or Quit menu item. Use `snipux --settings` to reach
534
+ Settings, and kill the process to quit. Install the extension if you want the
535
+ icon and menu back.
536
+
537
+ **Capture fails, or a permission prompt appears every time.** On Wayland,
538
+ capture goes through `xdg-desktop-portal`, which owns the permission prompt
539
+ itself. If a capture reports the request was cancelled, press the shortcut again
540
+ and approve the prompt when it appears. If it instead reports the request failed
541
+ outright, check that `xdg-desktop-portal` and a desktop-appropriate portal
542
+ backend (e.g. `xdg-desktop-portal-gnome`) are installed and running — Snipux
543
+ can't get pixels if the portal that owns them refuses, or isn't there at all.
544
+
545
+ **Recording is unavailable.** `snipux --list-backends` answers this directly:
546
+ capture and recording are separate registries with separate answers. On Linux,
547
+ recording needs GNOME Shell — see [Recording](#recording).
548
+
549
+ **`No module named snipux` when updating.** `py` runs the *system* Python,
550
+ and Snipux is not installed in it — which is normal if it was installed with
551
+ `pipx`, whose whole job is to keep it in an environment of its own. Use the
552
+ bare command instead:
553
+
554
+ ```powershell
555
+ snipux --update
556
+ ```
557
+
558
+ If neither that nor `py -m snipux --update` works, nothing is installed where
559
+ either can see it. `pipx list` says whether pipx has it; `py -m pip show
560
+ snipux` says whether the system Python does. Install per
561
+ [Windows](#windows) above and both will work.
562
+
563
+ **`'snipux' is not recognized`.** The opposite case: it is installed in the
564
+ system Python, but that Python's `Scripts` folder is not on `PATH`. Use
565
+ `py -m snipux --update`, which does not need `PATH` at all. (Re-running the
566
+ Python installer and ticking *"Add python.exe to PATH"* fixes it properly.)
567
+
568
+ **Nothing happens when I press the shortcut.** Check `snipux --snip` works when
569
+ run by hand from a terminal. On Windows, check Snipux is actually running: the
570
+ hotkey is a registration the process holds, and Windows releases it the moment
571
+ the process exits.
572
+
573
+ ## What hasn't been tested yet
574
+
575
+ Development has happened on Windows and in an Ubuntu VM. These are believed to
576
+ work from the design but haven't been verified on real hardware:
577
+
578
+ - Fractional display scaling on a real machine at 1.25× — the test suite is kept
579
+ green at 1.0× and 1.5×
580
+ - X11 sessions end to end — the D-Bus recording route works, but the overlay and
581
+ capture path under a real X11 session remain less exercised than Wayland
582
+ - Pasting into a file manager or chat app — the clipboard mime data is verified
583
+ correct on the wire, but nobody has watched a paste land
584
+ - macOS, entirely
585
+
586
+ If one of these is where something goes wrong, that's a known gap, not a
587
+ mystery.
588
+
589
+ ## Requirements
590
+
591
+ - **Python 3.10+** (not needed for the Windows `.exe`)
592
+ - **PyQt6 6.8+** and **jeepney** — installed automatically
593
+ - **`libxcb-cursor0`** on Linux (`sudo apt install libxcb-cursor0`). Qt 6.5+
594
+ needs it to load its xcb platform plugin, and nothing else on a stock Ubuntu
595
+ desktop pulls it in. Without it Snipux installs cleanly and then crashes on
596
+ launch; `packaging/install.sh` checks for it before doing anything.
597
+ - **`python3-venv`** on Debian/Ubuntu if you use `packaging/install.sh`
598
+ - **Ubuntu 22.04+** (Wayland or X11), or **Windows 10 2004+ / 11**
599
+ - **`ffmpeg`** — genuinely optional, never installed. See
600
+ [About ffmpeg](#about-ffmpeg).
601
+
602
+ ## Development
603
+
604
+ ```sh
605
+ python -m venv .venv
606
+ source .venv/bin/activate
607
+ pip install -r requirements.txt
608
+
609
+ QT_QPA_PLATFORM=offscreen python -m pytest -q # the verify step
610
+ python -m snipux # run it
611
+ ```
612
+
613
+ Tests must pass headless — a build machine has no display. `QWidget.grab()`
614
+ runs a full `paintEvent` into an offscreen pixmap without showing anything, and
615
+ that's the preferred way to test painting code.
616
+
617
+ [CLAUDE.md](CLAUDE.md) is the working guide to the codebase: the one
618
+ architectural rule, the module layout, and the conventions. `docs/design/`
619
+ holds the locked design handoffs, each with a `divergences.md` recording what
620
+ was built differently and why — read those before "fixing" anything back to a
621
+ handoff.
622
+
623
+ ## Contributing
624
+
625
+ Issues and pull requests are welcome. Please read
626
+ [CONTRIBUTING.md](CONTRIBUTING.md) first — it's short, and it covers the two
627
+ things that matter most here: the architectural rule that isn't negotiable, and
628
+ why a green test suite is weaker evidence in this codebase than you'd expect.
629
+
630
+ ## Licence
631
+
632
+ MIT — see [LICENSE](LICENSE).