nandi/frqpublic Fork 0
563ada3
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Click a picture to see it full size

A screen rather than a layer over the chat: the tree backend paints in one
pass with no z-order, so there is nothing to put an overlay on top of. It
takes the whole window, names the URL it came from, and closes on the same
gesture that opened it — clicking the picture — or on Back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-08-30T11:02:39-07:00 Browse files
563ada3 parent: 56cdac3
modified README.md +1 -1
@@ -104,7 +104,7 @@ surface — that surface does not work on Android either, while the syscalls do.
104104 * Join channels, channel buffers with unread counts, send and receive `PRIVMSG`
105105 * Backlog on join, and `CHATHISTORY` for the channels freeq restores instead
106106 * Inline previews for PNG links, fetched once and cached under
107- `$XDG_CACHE_HOME/frq/media`
107+ `$XDG_CACHE_HOME/frq/media`; click one to see it full size
108108 * Join/part notices, DMs bucketed under the sender's nick
109109 * Discover list, search over buffers, disconnect
110110 * Conversations listed most recently opened first
@@ -104,7 +104,7 @@ surface — that surface does not work on Android either, while the syscalls do.
104 * Join channels, channel buffers with unread counts, send and receive `PRIVMSG`104 * Join channels, channel buffers with unread counts, send and receive `PRIVMSG`
105 * Backlog on join, and `CHATHISTORY` for the channels freeq restores instead105 * Backlog on join, and `CHATHISTORY` for the channels freeq restores instead
106 * Inline previews for PNG links, fetched once and cached under106 * Inline previews for PNG links, fetched once and cached under
107- `$XDG_CACHE_HOME/frq/media`107+ `$XDG_CACHE_HOME/frq/media`; click one to see it full size
108 * Join/part notices, DMs bucketed under the sender's nick108 * Join/part notices, DMs bucketed under the sender's nick
109 * Discover list, search over buffers, disconnect109 * Discover list, search over buffers, disconnect
110 * Conversations listed most recently opened first110 * Conversations listed most recently opened first
modified src/frq/app.jolt +26 -7
@@ -200,12 +200,29 @@
200200 (let [_ @s/media-tick]
201201 (for [url (:images m)]
202202 (when-let [path (media/path-when-ready url)]
203- [:image {:key url :src path :max-height 260}]))))]]))
203+ [:image {:key url
204+ :src path
205+ :max-height 260
206+ :on-click #(reset! s/lightbox {:path path :url url})}]))))]]))
204207
205208 (defn- message-rows [messages]
206209 (map-indexed (fn [i m] [message-row i (when (pos? i) (nth messages (dec i))) m])
207210 messages))
208211
212+(defn lightbox-screen
213+ "One picture, as big as the window allows.
214+
215+ A screen rather than an overlay: the tree backend paints in one layer and
216+ has no z-order to put something on top of everything else with."
217+ []
218+ (let [{:keys [path url]} @s/lightbox]
219+ [:vbox {:spacing 8 :margin 12}
220+ [:hbox {:spacing 8}
221+ [:button {:label "← Back" :on-click #(reset! s/lightbox nil)}]
222+ [:dim-label {:label url}]]
223+ ;; Clicking the picture closes it too — the same gesture that opened it.
224+ [:image {:src path :max-height 2000 :on-click #(reset! s/lightbox nil)}]]))
225+
209226 (defn chat-screen []
210227 (let [name @s/current
211228 buffer (get @s/channels name)]
@@ -289,12 +306,14 @@
289306 ;; ---------------------------------------------------------------- shell
290307
291308 (defn app []
292- (case @s/screen
293- :connect [connect-screen]
294- :chat [chat-screen]
295- :discover [discover-screen]
296- :settings [settings-screen]
297- [chats-screen]))
309+ (if @s/lightbox
310+ [lightbox-screen]
311+ (case @s/screen
312+ :connect [connect-screen]
313+ :chat [chat-screen]
314+ :discover [discover-screen]
315+ :settings [settings-screen]
316+ [chats-screen])))
298317
299318 (defn -main [& _]
300319 ;; Before the window: a saved sign-in decides which mode the connect screen
@@ -200,12 +200,29 @@
200 (let [_ @s/media-tick]200 (let [_ @s/media-tick]
201 (for [url (:images m)]201 (for [url (:images m)]
202 (when-let [path (media/path-when-ready url)]202 (when-let [path (media/path-when-ready url)]
203- [:image {:key url :src path :max-height 260}]))))]]))203+ [:image {:key url
204+ :src path
205+ :max-height 260
206+ :on-click #(reset! s/lightbox {:path path :url url})}]))))]]))
204 207
205 (defn- message-rows [messages]208 (defn- message-rows [messages]
206 (map-indexed (fn [i m] [message-row i (when (pos? i) (nth messages (dec i))) m])209 (map-indexed (fn [i m] [message-row i (when (pos? i) (nth messages (dec i))) m])
207 messages))210 messages))
208 211
212+(defn lightbox-screen
213+ "One picture, as big as the window allows.
214+
215+ A screen rather than an overlay: the tree backend paints in one layer and
216+ has no z-order to put something on top of everything else with."
217+ []
218+ (let [{:keys [path url]} @s/lightbox]
219+ [:vbox {:spacing 8 :margin 12}
220+ [:hbox {:spacing 8}
221+ [:button {:label "← Back" :on-click #(reset! s/lightbox nil)}]
222+ [:dim-label {:label url}]]
223+ ;; Clicking the picture closes it too — the same gesture that opened it.
224+ [:image {:src path :max-height 2000 :on-click #(reset! s/lightbox nil)}]]))
225+
209 (defn chat-screen []226 (defn chat-screen []
210 (let [name @s/current227 (let [name @s/current
211 buffer (get @s/channels name)]228 buffer (get @s/channels name)]
@@ -289,12 +306,14 @@
289 ;; ---------------------------------------------------------------- shell306 ;; ---------------------------------------------------------------- shell
290 307
291 (defn app []308 (defn app []
292- (case @s/screen309+ (if @s/lightbox
293- :connect [connect-screen]310+ [lightbox-screen]
294- :chat [chat-screen]311+ (case @s/screen
295- :discover [discover-screen]312+ :connect [connect-screen]
296- :settings [settings-screen]313+ :chat [chat-screen]
297- [chats-screen]))314+ :discover [discover-screen]
315+ :settings [settings-screen]
316+ [chats-screen])))
298 317
299 (defn -main [& _]318 (defn -main [& _]
300 ;; Before the window: a saved sign-in decides which mode the connect screen319 ;; Before the window: a saved sign-in decides which mode the connect screen
modified src/frq/oauth.jolt +2 -3
@@ -12,6 +12,7 @@
1212 from the broker token on every later connection."
1313 (:require [clojure.string :as str]
1414 [frq.atproto :as atproto]
15+ [frq.platform :as platform]
1516 [jolt.ffi :as ffi]
1617 [jolt.host :as host]
1718 [jolt.socket :as socket]))
@@ -46,9 +47,7 @@
4647 "Hand the URL to the desktop. A failure here is not fatal — the caller shows
4748 the URL so it can be opened by hand."
4849 [url]
49- (try
50- (zero? (host/sh (str "xdg-open '" (str/replace url "'" "%27") "' >/dev/null 2>&1 &")))
51- (catch Exception _ false)))
50+ (platform/open-url! url))
5251
5352 ;; ------------------------------------------------------------------ capture
5453
@@ -12,6 +12,7 @@
12 from the broker token on every later connection."12 from the broker token on every later connection."
13 (:require [clojure.string :as str]13 (:require [clojure.string :as str]
14 [frq.atproto :as atproto]14 [frq.atproto :as atproto]
15+ [frq.platform :as platform]
15 [jolt.ffi :as ffi]16 [jolt.ffi :as ffi]
16 [jolt.host :as host]17 [jolt.host :as host]
17 [jolt.socket :as socket]))18 [jolt.socket :as socket]))
@@ -46,9 +47,7 @@
46 "Hand the URL to the desktop. A failure here is not fatal — the caller shows47 "Hand the URL to the desktop. A failure here is not fatal — the caller shows
47 the URL so it can be opened by hand."48 the URL so it can be opened by hand."
48 [url]49 [url]
49- (try50+ (platform/open-url! url))
50- (zero? (host/sh (str "xdg-open '" (str/replace url "'" "%27") "' >/dev/null 2>&1 &")))
51- (catch Exception _ false)))
52 51
53 ;; ------------------------------------------------------------------ capture52 ;; ------------------------------------------------------------------ capture
54 53
added src/frq/platform.jolt +12 -0
new file mode 100644
@@ -0,0 +1,12 @@
1+(ns frq.platform
2+ "The few things that are the desktop's job rather than the app's."
3+ (:require [clojure.string :as str]
4+ [jolt.host :as host]))
5+
6+(defn open-url!
7+ "Hand a URL to the desktop. Android has no xdg-open, so this is a no-op
8+ there — the same reason sign-in is desktop-only."
9+ [url]
10+ (try
11+ (zero? (host/sh (str "xdg-open '" (str/replace (or url "") "'" "%27") "' >/dev/null 2>&1 &")))
12+ (catch Exception _ false)))
new file mode 100644
@@ -0,0 +1,12 @@
1+(ns frq.platform
2+ "The few things that are the desktop's job rather than the app's."
3+ (:require [clojure.string :as str]
4+ [jolt.host :as host]))
5+
6+(defn open-url!
7+ "Hand a URL to the desktop. Android has no xdg-open, so this is a no-op
8+ there — the same reason sign-in is desktop-only."
9+ [url]
10+ (try
11+ (zero? (host/sh (str "xdg-open '" (str/replace (or url "") "'" "%27") "' >/dev/null 2>&1 &")))
12+ (catch Exception _ false)))
modified src/frq/state.jolt +4 -0
@@ -59,6 +59,10 @@
5959 (defonce channels (atom {}))
6060 (defonce current (atom nil))
6161 (defonce draft (atom ""))
62+
63+;; The picture being looked at full size, or nil. Vidya's tree has no overlay,
64+;; so this is a screen of its own rather than a layer over the chat.
65+(defonce lightbox (atom nil)) ; {:path :url}
6266 (defonce join-input (atom ""))
6367 ;; A counter rather than a clock: the list only needs their order, and a
6468 ;; monotonic tick cannot be surprised by the system time moving.
@@ -59,6 +59,10 @@
59 (defonce channels (atom {}))59 (defonce channels (atom {}))
60 (defonce current (atom nil))60 (defonce current (atom nil))
61 (defonce draft (atom ""))61 (defonce draft (atom ""))
62+
63+;; The picture being looked at full size, or nil. Vidya's tree has no overlay,
64+;; so this is a screen of its own rather than a layer over the chat.
65+(defonce lightbox (atom nil)) ; {:path :url}
62 (defonce join-input (atom ""))66 (defonce join-input (atom ""))
63 ;; A counter rather than a clock: the list only needs their order, and a67 ;; A counter rather than a clock: the list only needs their order, and a
64 ;; monotonic tick cannot be surprised by the system time moving.68 ;; monotonic tick cannot be surprised by the system time moving.