max / alloy
| 1 | #!/bin/sh |
| 2 | # alloy-open — open a link, or say why it cannot. |
| 3 | # |
| 4 | # Alloy may ship without a browser. `BROWSER=none` is a supported build value |
| 5 | # and under [[alloy-byo-principle]] it may become the default, so "no browser |
| 6 | # installed" is a state the system has to handle rather than an error state. |
| 7 | # Measured on 2026-08-17, before this existed, that state looked like: |
| 8 | # |
| 9 | # $ xdg-open https://example.com ; echo $? |
| 10 | # /usr/sbin/xdg-open: line 1045: www-browser: command not found |
| 11 | # ...five more of those... |
| 12 | # xdg-open: no method available for opening 'https://example.com' |
| 13 | # 3 |
| 14 | # |
| 15 | # All of it on stderr. Invoked from yazi's Open bind, from a mako action, or |
| 16 | # from a click in shop, the person sees NOTHING. That is the silent-failure |
| 17 | # class this repo hunts, and it is the reason this script exists: for the |
| 18 | # message far more than for the launching, the same way alloy-shot exists for |
| 19 | # the notification more than for the capture. |
| 20 | # |
| 21 | # Registered as the system default for http, https and text/html in |
| 22 | # /etc/xdg/mimeapps.list. That is the sysadmin level, which sits ABOVE the |
| 23 | # vendor file and BELOW the user's own, so `xdg-settings set |
| 24 | # default-web-browser` still wins and a person who has chosen a browser never |
| 25 | # comes through here again. |
| 26 | # |
| 27 | # Testing this in a container will make it look broken, and it is not. xdg-open |
| 28 | # consults the scheme-handler registration only inside `if has_display` |
| 29 | # (open_generic, /usr/sbin/xdg-open), so with no WAYLAND_DISPLAY or DISPLAY it |
| 30 | # skips straight to its hardcoded list of text browsers and prints the wall of |
| 31 | # "command not found" this script exists to prevent. Set WAYLAND_DISPLAY to |
| 32 | # test it, or read the wrong conclusion. |
| 33 | # |
| 34 | # It also fixes a smaller thing that worked by luck. The vendor mimeapps.list |
| 35 | # ships from shared-mime-info and names `org.mozilla.firefox.desktop`, which |
| 36 | # this image does not contain under any BROWSER value: Fedora's Firefox rpm is |
| 37 | # firefox.desktop, while that id belongs to the Flatpak. It resolved correctly |
| 38 | # anyway, by falling through to mimeinfo.cache where exactly one candidate |
| 39 | # declared the scheme. One browser is not a thing to rely on: a `BROWSER=none` |
| 40 | # image has none, and a user's own Flatpak is a second. |
| 41 | |
| 42 | |
| 43 | |
| 44 | self=alloy-open.desktop |
| 45 | |
| 46 | # The same directory list alloy-menu uses, and for the same reason: a browser |
| 47 | # the user installed as a Flatpak exports its desktop entry into the flatpak |
| 48 | # tree, not into /usr/share/applications. Looking only at the latter would |
| 49 | # make BYO-via-Flatpak invisible to the thing whose whole job is to find it. |
| 50 | dirs="/usr/share/applications |
| 51 | /applications |
| 52 | /var/lib/flatpak/exports/share/applications |
| 53 | /flatpak/exports/share/applications" |
| 54 | |
| 55 | # Find a desktop entry that claims https, skipping this one. |
| 56 | # |
| 57 | # Excluding self is not defensive tidiness: without it this script is the |
| 58 | # handler that finds itself and execs itself, which is an unkillable loop |
| 59 | # behind a keybind. Matched on the filename rather than on Exec= because the |
| 60 | # filename is what the mimeapps entry names. |
| 61 | |
| 62 | | while ; do |
| 63 | [ || continue |
| 64 | for; do |
| 65 | [ || continue |
| 66 | [ && continue |
| 67 | || continue |
| 68 | && continue |
| 69 | |
| 70 | |
| 71 | done |
| 72 | done | |
| 73 | } |
| 74 | |
| 75 | # Exec= carries field codes (%u %U %f %F and friends) that are placeholders |
| 76 | # rather than arguments. Strip them all and append the URL ourselves, which is |
| 77 | # what a launcher is supposed to do and what alloy-menu already does for the |
| 78 | # same reason. |
| 79 | # |
| 80 | # Flatpak wraps its own markers around them, and they have to go too. A real |
| 81 | # exported entry reads: |
| 82 | # |
| 83 | # Exec=/usr/bin/flatpak run --branch=stable org.mozilla.firefox @@u %U @@ |
| 84 | # |
| 85 | # `@@u` and the closing `@@` are flatpak's way of saying "the file or URI |
| 86 | # arguments belong here". Strip only the field code and the browser is |
| 87 | # launched with two literal arguments of `@@u` and `@@`, which it treats as |
| 88 | # URLs to open. Caught by testing against an exported entry rather than a |
| 89 | # hand-written one, which is the only reason it was visible. |
| 90 | |
| 91 | | |
| 92 | | |
| 93 | } |
| 94 | |
| 95 | |
| 96 | # Both channels on purpose. A link opened from the file manager or a |
| 97 | # notification has no terminal to read, and a link opened from a shell has |
| 98 | # no reason to raise a desktop notification. |
| 99 | |
| 100 | if ; then |
| 101 | || |
| 102 | fi |
| 103 | } |
| 104 | |
| 105 | url="" |
| 106 | if [; then |
| 107 | |
| 108 | |
| 109 | fi |
| 110 | |
| 111 | entry="" |
| 112 | |
| 113 | if [; then |
| 114 | cmd="" |
| 115 | [ || { ; ; } |
| 116 | # shellcheck disable=SC2086 |
| 117 | |
| 118 | fi |
| 119 | |
| 120 | # No browser. This is the branch the script is for. |
| 121 | # |
| 122 | # Both routes named are ones that work today. `alloy browser` is the intended |
| 123 | # front door and does not exist yet, so it is deliberately NOT mentioned: |
| 124 | # telling someone to run a command that is not there is the same silent |
| 125 | # failure this script was written to remove, one level up. When that verb |
| 126 | # ships it replaces the flatpak line here. |
| 127 | # |
| 128 | # The layering route is named because it works: Firefox is in Fedora's own |
| 129 | # repos, and those stay enabled after install. That is the whole reason it is |
| 130 | # Alloy's pick (wiki `alloy-byo-principle`), so a machine that chose `none` is |
| 131 | # one command from the browser the image would have carried. |
| 132 | |
| 133 | |
| 134 | Or, sandboxed: flatpak install org.mozilla.firefox, from a remote you have added |
| 135 | Or rebuild the image with BROWSER=firefox to bake one in." |
| 136 | |
| 137 |