max / alloy
| 1 | #!/bin/sh |
| 2 | # alloy-shot — take a screenshot and say that it worked. |
| 3 | # |
| 4 | # The four Print binds in the sway config used to be four inline grim calls. |
| 5 | # They worked, and they were reported as broken anyway, on 2026-07-29, by the |
| 6 | # person who wrote them: pressing Print produced a correct capture and no |
| 7 | # notification, no OSD, no shutter, no window. Nothing at all. The only way to |
| 8 | # learn it had worked was to go and look in the directory, which is |
| 9 | # indistinguishable from a dead keybind. So this exists for the notification |
| 10 | # more than for the capture. |
| 11 | # |
| 12 | # Three things moved in here that were wrong or fragile inline, and are the |
| 13 | # reason this is a script rather than `&& notify-send` appended four times: |
| 14 | # |
| 15 | # 1. The timestamp is taken ONCE. The config's `set $shot` held a literal |
| 16 | # `$(date ...)`, expanded by the shell sway's `exec` runs it through. An |
| 17 | # appended notification naming `$shot` would have expanded it a second time, |
| 18 | # so any capture that straddled a second boundary would have announced a |
| 19 | # filename that does not exist. A notification that lies about the path is |
| 20 | # worse than no notification, and it would have been intermittent. |
| 21 | # 2. Cancelling a region select is not a failure. `slurp` exits non-zero when |
| 22 | # Escape is pressed, which inline made `grim -g ""` run and fail; with a |
| 23 | # notification appended it would have reported an error for a deliberate |
| 24 | # cancel. Here it exits quietly. |
| 25 | # 3. One notification, one wording, one place to change it. |
| 26 | # |
| 27 | # Modes rather than four scripts because the notification and the timestamp are |
| 28 | # the shared part, which is exactly what was missing. |
| 29 | |
| 30 | |
| 31 | |
| 32 | # XDG_PICTURES_DIR is what xdg-user-dirs writes and what a localized install |
| 33 | # actually uses; ~/Pictures is the fallback for a session that has no user-dirs |
| 34 | # file yet. The sway config also `mkdir -p`s this at startup, and it is repeated |
| 35 | # here because this script is reachable without that having run: from a bind on |
| 36 | # a hand-edited config, from a shell, or on a first login where the exec order |
| 37 | # is not something to depend on. |
| 38 | dir="/Screenshots" |
| 39 | |
| 40 | |
| 41 | shot="/screenshot-.png" |
| 42 | |
| 43 | # Told, not guessed: a bind that names a mode this does not know is a typo in |
| 44 | # the config, and it should say so rather than silently capture the whole |
| 45 | # screen. |
| 46 | mode="" |
| 47 | case "" in |
| 48 | output) |
| 49 | |
| 50 | ;; |
| 51 | region) |
| 52 | # `|| exit 0` covers the Escape case above. It also swallows a real |
| 53 | # slurp failure, which is the accepted cost: from here the two are the |
| 54 | # same non-zero exit, and treating a cancel as an error is the louder |
| 55 | # mistake. |
| 56 | geometry="" || |
| 57 | |
| 58 | ;; |
| 59 | window) |
| 60 | # The focused node's rect, in grim's `x,y WxH` geometry. jq is a stated |
| 61 | # dependency of the image for exactly this line; see the Containerfile's |
| 62 | # package list. |
| 63 | geometry=" |
| 64 | | )" |
| 65 | |
| 66 | ;; |
| 67 | annotate) |
| 68 | # Annotate the most recent capture rather than taking a new one, which |
| 69 | # is the bind's whole point: shoot first, mark it up after. Nothing to |
| 70 | # notify about, because satty opens a window and is its own feedback. |
| 71 | # |
| 72 | # `ls -t` on a glob that matches nothing exits non-zero under `set -e`, |
| 73 | # so an empty directory ends here instead of running satty on an empty |
| 74 | # filename. |
| 75 | latest="" || |
| 76 | [ || |
| 77 | |
| 78 | ;; |
| 79 | *) |
| 80 | |
| 81 | |
| 82 | ;; |
| 83 | esac |
| 84 | |
| 85 | # Past here the capture is on disk. The notification is the point of this |
| 86 | # script, so a notification daemon that is not running is worth one line on |
| 87 | # stderr rather than a failed exit: the screenshot was taken either way, and |
| 88 | # `set -e` would otherwise turn a missing daemon into what looks like a failed |
| 89 | # capture. |
| 90 | |
| 91 | || |
| 92 |