Start the terminal client the way a launch starts the window one
frq.tui came up on a client that had restored nothing, was signed into nothing and was connected to nothing: it seeded a demo buffer and called ui/run, so every real room was empty with nobody in it — which reads as a broken screen rather than as a client nobody told to start. The startup was the first half of -main and a second entry point could only copy it, so it moves into frq.app/start! and takes the backend's timers as an argument. A window passes vidya's pair, its title and its size; a terminal passes its own and leaves out the two things it has not got, plus the media plane — a call paints frames into a texture, and there is no texture here. What is left of frq.tui is the terminal: the point-to-cell scale, the size poll, and a headless screenshot. The demo buffer stays behind --demo, which is what it was always for — a picture that is not waiting on a server — and --wait= is how long the client gets before one is taken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4f04b91 parent: ab83b42 modified
README.md +32 -20 | @@ -65,33 +65,45 @@ cargo run --release --bin freeq-server # in the freeq checkout | ||
| 65 | 65 | The screens are hiccup over glimmer's reconciler, and the reconciler does not |
| 66 | 66 | know what is under it — so the same tree paints into a terminal through |
| 67 | 67 | jolt-native's `libjolttui`, which exports libvidya's retained-tree ABI over a |
| 68 | -grid of cells instead of a GPU window. `src/frq/tui.jolt` is the whole of what | |
| 69 | -that costs on this side: it requires `glimmer-tui.core` after `frq.app`, so the | |
| 70 | -backend installed last is the terminal, and seeds a `#tui` buffer so a session | |
| 71 | -started for a look at the layout has a conversation in it. | |
| 68 | +grid of cells instead of a GPU window. | |
| 69 | + | |
| 70 | +It is the client, not a preview of it. `frq.app/start!` is what a launch does — | |
| 71 | +the saved settings, the rooms this client has been in, the sign-in that | |
| 72 | +connects itself — and `src/frq/tui.jolt` hands it the terminal's timers instead | |
| 73 | +of the window's. Nothing in `frq.app` changed. | |
| 72 | 74 | |
| 73 | 75 | ```bash |
| 74 | -just tui # the terminal, until Ctrl-Q | |
| 76 | +nix run .#tui # or: just tui | |
| 75 | 77 | just tui --headless --cols=90 --rows=60 # one screenshot on stdout |
| 78 | +just tui --headless --demo # a buffer of its own, no server | |
| 79 | +just tui --headless --wait=9000 # long enough to have connected | |
| 76 | 80 | ``` |
| 77 | 81 | |
| 78 | 82 | The headless one is `tui_headless` — the same layout and the same painting with |
| 79 | 83 | the writer taken off the end — which is what a screenshot in a bug report or a |
| 80 | -CI check should be. | |
| 81 | - | |
| 82 | -Two things are unpinned here, because the terminal backend is not in a | |
| 83 | -jolt-native release yet: `libjolttui.so` comes out of a jolt-native checkout's | |
| 84 | -target directory (`JOLT_NATIVE=…`, or beside this tree), and `glimmer-tui` is | |
| 85 | -resolved from the same checkout. Both become pins like every other when it | |
| 86 | -ships. | |
| 87 | - | |
| 88 | -What a terminal has not got, frq does without: pictures, avatars, the lightbox | |
| 89 | -and calls draw nothing. And frq's spacing is written in points, for a window — | |
| 90 | -`scripts/tui.bb` hands the backend `:points-per-cell 8` so those numbers land | |
| 91 | -in cells, but `below-messages` in `src/frq/app.jolt` is point *arithmetic* | |
| 92 | -rather than a point *length*, and a scale cannot fix it: it reserves about | |
| 93 | -thirteen rows more than the compose bar needs, so the bottom of the backlog is | |
| 94 | -pushed out of the list. | |
| 84 | +CI check should be. It paints once and prints, so `--wait=` is how long the | |
| 85 | +client is given first: the default is a picture of the connect screen, because | |
| 86 | +that is where a client is a moment after launch, and `--demo` fills a `#tui` | |
| 87 | +buffer for a screenshot that is not waiting on a server at all. | |
| 88 | + | |
| 89 | +Logs go to stderr, which in a terminal session is the screen frq is painting. | |
| 90 | +Send them somewhere: `nix run .#tui 2>/tmp/frq.log`. | |
| 91 | + | |
| 92 | +What a terminal has not got, frq does without: pictures, avatars and the | |
| 93 | +lightbox draw nothing, and calls are off — the media plane paints frames into | |
| 94 | +a texture, and there is no texture here. And frq's spacing is written in | |
| 95 | +points, for a window — the backend is handed `:points-per-cell 8` so those | |
| 96 | +numbers land in cells, but `below-messages` in `src/frq/app.jolt` is point | |
| 97 | +*arithmetic* rather than a point *length*, and a scale cannot fix it: it | |
| 98 | +reserves about thirteen rows more than the compose bar needs, so the bottom of | |
| 99 | +the backlog is pushed out of the list. | |
| 100 | + | |
| 101 | +Two things are unpinned, because the terminal backend is not in a jolt-native | |
| 102 | +release yet: `libjolttui.so` comes out of a jolt-native checkout's target | |
| 103 | +directory (`JOLT_NATIVE=…`, or beside this tree) for `just tui`, and the flake | |
| 104 | +carries a second `jolt-native-tui` input at the rev that has it — its own input | |
| 105 | +rather than a bump, so the window half stays on the release the rest of the | |
| 106 | +tree names. Both become one pin when it ships. | |
| 95 | 107 | |
| 96 | 108 | ## Signing in |
| 97 | 109 | |
| @@ -65,33 +65,45 @@ cargo run --release --bin freeq-server # in the freeq checkout | |||
| 65 | The screens are hiccup over glimmer's reconciler, and the reconciler does not | 65 | The screens are hiccup over glimmer's reconciler, and the reconciler does not |
| 66 | know what is under it — so the same tree paints into a terminal through | 66 | know what is under it — so the same tree paints into a terminal through |
| 67 | jolt-native's `libjolttui`, which exports libvidya's retained-tree ABI over a | 67 | jolt-native's `libjolttui`, which exports libvidya's retained-tree ABI over a |
| 68 | -grid of cells instead of a GPU window. `src/frq/tui.jolt` is the whole of what | 68 | +grid of cells instead of a GPU window. |
| 69 | -that costs on this side: it requires `glimmer-tui.core` after `frq.app`, so the | 69 | + |
| 70 | -backend installed last is the terminal, and seeds a `#tui` buffer so a session | 70 | +It is the client, not a preview of it. `frq.app/start!` is what a launch does — |
| 71 | -started for a look at the layout has a conversation in it. | 71 | +the saved settings, the rooms this client has been in, the sign-in that |
| 72 | +connects itself — and `src/frq/tui.jolt` hands it the terminal's timers instead | ||
| 73 | +of the window's. Nothing in `frq.app` changed. | ||
| 72 | 74 | ||
| 73 | ```bash | 75 | ```bash |
| 74 | -just tui # the terminal, until Ctrl-Q | 76 | +nix run .#tui # or: just tui |
| 75 | just tui --headless --cols=90 --rows=60 # one screenshot on stdout | 77 | just tui --headless --cols=90 --rows=60 # one screenshot on stdout |
| 78 | +just tui --headless --demo # a buffer of its own, no server | ||
| 79 | +just tui --headless --wait=9000 # long enough to have connected | ||
| 76 | ``` | 80 | ``` |
| 77 | 81 | ||
| 78 | The headless one is `tui_headless` — the same layout and the same painting with | 82 | The headless one is `tui_headless` — the same layout and the same painting with |
| 79 | the writer taken off the end — which is what a screenshot in a bug report or a | 83 | the writer taken off the end — which is what a screenshot in a bug report or a |
| 80 | -CI check should be. | 84 | +CI check should be. It paints once and prints, so `--wait=` is how long the |
| 81 | - | 85 | +client is given first: the default is a picture of the connect screen, because |
| 82 | -Two things are unpinned here, because the terminal backend is not in a | 86 | +that is where a client is a moment after launch, and `--demo` fills a `#tui` |
| 83 | -jolt-native release yet: `libjolttui.so` comes out of a jolt-native checkout's | 87 | +buffer for a screenshot that is not waiting on a server at all. |
| 84 | -target directory (`JOLT_NATIVE=…`, or beside this tree), and `glimmer-tui` is | 88 | + |
| 85 | -resolved from the same checkout. Both become pins like every other when it | 89 | +Logs go to stderr, which in a terminal session is the screen frq is painting. |
| 86 | -ships. | 90 | +Send them somewhere: `nix run .#tui 2>/tmp/frq.log`. |
| 87 | - | 91 | + |
| 88 | -What a terminal has not got, frq does without: pictures, avatars, the lightbox | 92 | +What a terminal has not got, frq does without: pictures, avatars and the |
| 89 | -and calls draw nothing. And frq's spacing is written in points, for a window — | 93 | +lightbox draw nothing, and calls are off — the media plane paints frames into |
| 90 | -`scripts/tui.bb` hands the backend `:points-per-cell 8` so those numbers land | 94 | +a texture, and there is no texture here. And frq's spacing is written in |
| 91 | -in cells, but `below-messages` in `src/frq/app.jolt` is point *arithmetic* | 95 | +points, for a window — the backend is handed `:points-per-cell 8` so those |
| 92 | -rather than a point *length*, and a scale cannot fix it: it reserves about | 96 | +numbers land in cells, but `below-messages` in `src/frq/app.jolt` is point |
| 93 | -thirteen rows more than the compose bar needs, so the bottom of the backlog is | 97 | +*arithmetic* rather than a point *length*, and a scale cannot fix it: it |
| 94 | -pushed out of the list. | 98 | +reserves about thirteen rows more than the compose bar needs, so the bottom of |
| 99 | +the backlog is pushed out of the list. | ||
| 100 | + | ||
| 101 | +Two things are unpinned, because the terminal backend is not in a jolt-native | ||
| 102 | +release yet: `libjolttui.so` comes out of a jolt-native checkout's target | ||
| 103 | +directory (`JOLT_NATIVE=…`, or beside this tree) for `just tui`, and the flake | ||
| 104 | +carries a second `jolt-native-tui` input at the rev that has it — its own input | ||
| 105 | +rather than a bump, so the window half stays on the release the rest of the | ||
| 106 | +tree names. Both become one pin when it ships. | ||
| 95 | 107 | ||
| 96 | ## Signing in | 108 | ## Signing in |
| 97 | 109 | ||
modified
src/frq/app.jolt +63 -35 | @@ -1297,8 +1297,25 @@ | ||
| 1297 | 1297 | :settings [:vbox {:key :screen-settings} [settings-screen]] |
| 1298 | 1298 | [:vbox {:key :screen-chats} [chats-screen]]))) |
| 1299 | 1299 | |
| 1300 | -(defn -main [& _] | |
| 1301 | - ;; Before the window: the settings, the rooms this client has been in, and a | |
| 1300 | +(defn start! | |
| 1301 | + "Everything a launch does before the loop starts, for whichever backend is | |
| 1302 | + about to run it. | |
| 1303 | + | |
| 1304 | + This used to be the first half of `-main`, and a second entry point copied it | |
| 1305 | + — which is how frq.tui came up on a client that had restored nothing, was | |
| 1306 | + signed into nothing and was connected to nothing: an empty buffer with nobody | |
| 1307 | + in it, which reads as a broken screen rather than as a client that was never | |
| 1308 | + told to start. | |
| 1309 | + | |
| 1310 | + The timers are handed in because they belong to the backend: `after!` and | |
| 1311 | + `every!` are how anything gets onto the loop thread, and the terminal has its | |
| 1312 | + own pair. So are the two things only a window has — `title!` renames one, and | |
| 1313 | + `measure!` reports its size — and either may be nil where there is none. | |
| 1314 | + | |
| 1315 | + `av?` is the media plane. It wants a window: a call paints frames into a | |
| 1316 | + texture, and there is no texture in a terminal." | |
| 1317 | + [{:keys [after! every! title! measure! av?] :or {av? true}}] | |
| 1318 | + ;; Before the loop: the settings, the rooms this client has been in, and a | |
| 1302 | 1319 | ;; saved sign-in deciding which mode the connect screen opens in and what it |
| 1303 | 1320 | ;; says. |
| 1304 | 1321 | (s/restore-prefs!) |
| @@ -1307,51 +1324,62 @@ | ||
| 1307 | 1324 | ;; And then it connects on its own. A remembered account has already said |
| 1308 | 1325 | ;; what it wants; making it say so again at every launch is a click that |
| 1309 | 1326 | ;; carries no information. It is a timer rather than a call here so the |
| 1310 | - ;; window is up first — the connect screen with its status is what the | |
| 1327 | + ;; screen is up first — the connect screen with its status is what the | |
| 1311 | 1328 | ;; user should be looking at while this happens, and if it fails, the |
| 1312 | 1329 | ;; error lands somewhere visible. |
| 1313 | - (vidya/after! 150 s/connect!)) | |
| 1314 | - ;; Calls arrive rather than being asked for, so the media plane is drained | |
| 1315 | - ;; every frame whether or not one is up — the drain costs a single integer | |
| 1316 | - ;; read when it is not. It has to be a timer: `frame-rgba!` and everything | |
| 1317 | - ;; else that touches a node belongs to the loop thread, and this is glimmer's | |
| 1318 | - ;; way of getting onto it. | |
| 1319 | - (av/init-logging!) | |
| 1320 | - ;; Who to tell when a call ends under us rather than at our asking. Set here | |
| 1321 | - ;; rather than in frq.av because sending a TAGMSG needs the connection, and | |
| 1322 | - ;; that belongs to the state layer. | |
| 1323 | - (reset! av/on-dropped s/announce-leave!) | |
| 1324 | - (vidya/after! 0 av/install-pump!) | |
| 1325 | - ;; The window's width, into a ratom, a few times a second. Polled rather than | |
| 1330 | + (after! 150 s/connect!)) | |
| 1331 | + (when av? | |
| 1332 | + ;; Calls arrive rather than being asked for, so the media plane is drained | |
| 1333 | + ;; every frame whether or not one is up — the drain costs a single integer | |
| 1334 | + ;; read when it is not. It has to be a timer: `frame-rgba!` and everything | |
| 1335 | + ;; else that touches a node belongs to the loop thread, and this is | |
| 1336 | + ;; glimmer's way of getting onto it. | |
| 1337 | + (av/init-logging!) | |
| 1338 | + ;; Who to tell when a call ends under us rather than at our asking. Set | |
| 1339 | + ;; here rather than in frq.av because sending a TAGMSG needs the | |
| 1340 | + ;; connection, and that belongs to the state layer. | |
| 1341 | + (reset! av/on-dropped s/announce-leave!) | |
| 1342 | + (after! 0 av/install-pump!)) | |
| 1343 | + ;; The surface's size, into a ratom, a few times a second. Polled rather than | |
| 1326 | 1344 | ;; delivered: the backend reports a size by writing it onto the window node, |
| 1327 | 1345 | ;; and only what a component derefs re-renders — so the layout follows a drag |
| 1328 | - ;; of the window's edge without every frame touching the tree. | |
| 1329 | - ;; | |
| 1330 | - ;; The height comes off the same tick, from `screen-size` rather than a | |
| 1331 | - ;; second call: it is the window's content size, and the pictures in the | |
| 1332 | - ;; conversation are sized against it. | |
| 1333 | - (vidya/every! 200 #(let [w (vidya/window-width) | |
| 1334 | - h (long (second (vidya/screen-size)))] | |
| 1335 | - (when (not= w @s/window-width) | |
| 1336 | - (reset! s/window-width w)) | |
| 1337 | - (when (not= h @s/window-height) | |
| 1338 | - (reset! s/window-height h)))) | |
| 1346 | + ;; of the edge without every frame touching the tree. | |
| 1347 | + (when measure! | |
| 1348 | + (measure!) | |
| 1349 | + (every! 200 measure!)) | |
| 1339 | 1350 | ;; The nick in the window title, so a second window of this client is told |
| 1340 | 1351 | ;; apart from the first by the one thing that differs — and so the answer to |
| 1341 | 1352 | ;; "who am I here?" is on screen without opening Settings. |
| 1342 | 1353 | ;; |
| 1343 | - ;; Polled, like the width above and for the same reason: the title belongs to | |
| 1354 | + ;; Polled, like the size above and for the same reason: the title belongs to | |
| 1344 | 1355 | ;; the window rather than to the tree, so no render puts it there, and the |
| 1345 | 1356 | ;; nick is only settled once a connection has been made. Twice a second is |
| 1346 | 1357 | ;; far more often than a nick changes, and a string compare is what a tick |
| 1347 | 1358 | ;; costs when it has not. |
| 1348 | 1359 | ;; Seeded with the title `run` opens the window under, so the first tick of |
| 1349 | 1360 | ;; a launch that has nobody signed in yet sets nothing. |
| 1350 | - (let [shown (atom "frq")] | |
| 1351 | - (vidya/every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick)) | |
| 1352 | - (str "frq — " @s/form-nick) | |
| 1353 | - "frq")] | |
| 1354 | - (when (not= title @shown) | |
| 1355 | - (reset! shown title) | |
| 1356 | - (vidya/set-title! title))))) | |
| 1361 | + (when title! | |
| 1362 | + (let [shown (atom "frq")] | |
| 1363 | + (every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick)) | |
| 1364 | + (str "frq — " @s/form-nick) | |
| 1365 | + "frq")] | |
| 1366 | + (when (not= title @shown) | |
| 1367 | + (reset! shown title) | |
| 1368 | + (title! title)))))) | |
| 1369 | + nil) | |
| 1370 | + | |
| 1371 | +(defn -main [& _] | |
| 1372 | + (start! {:after! vidya/after! | |
| 1373 | + :every! vidya/every! | |
| 1374 | + :title! vidya/set-title! | |
| 1375 | + ;; The height comes off the same tick, from `screen-size` rather | |
| 1376 | + ;; than a second call: it is the window's content size, and the | |
| 1377 | + ;; pictures in the conversation are sized against it. | |
| 1378 | + :measure! (fn [] | |
| 1379 | + (let [w (vidya/window-width) | |
| 1380 | + h (long (second (vidya/screen-size)))] | |
| 1381 | + (when (not= w @s/window-width) | |
| 1382 | + (reset! s/window-width w)) | |
| 1383 | + (when (not= h @s/window-height) | |
| 1384 | + (reset! s/window-height h))))}) | |
| 1357 | 1385 | (ui/run app :title "frq" :width 520 :height 860)) |
| @@ -1297,8 +1297,25 @@ | |||
| 1297 | :settings [:vbox {:key :screen-settings} [settings-screen]] | 1297 | :settings [:vbox {:key :screen-settings} [settings-screen]] |
| 1298 | [:vbox {:key :screen-chats} [chats-screen]]))) | 1298 | [:vbox {:key :screen-chats} [chats-screen]]))) |
| 1299 | 1299 | ||
| 1300 | -(defn -main [& _] | 1300 | +(defn start! |
| 1301 | - ;; Before the window: the settings, the rooms this client has been in, and a | 1301 | + "Everything a launch does before the loop starts, for whichever backend is |
| 1302 | + about to run it. | ||
| 1303 | + | ||
| 1304 | + This used to be the first half of `-main`, and a second entry point copied it | ||
| 1305 | + — which is how frq.tui came up on a client that had restored nothing, was | ||
| 1306 | + signed into nothing and was connected to nothing: an empty buffer with nobody | ||
| 1307 | + in it, which reads as a broken screen rather than as a client that was never | ||
| 1308 | + told to start. | ||
| 1309 | + | ||
| 1310 | + The timers are handed in because they belong to the backend: `after!` and | ||
| 1311 | + `every!` are how anything gets onto the loop thread, and the terminal has its | ||
| 1312 | + own pair. So are the two things only a window has — `title!` renames one, and | ||
| 1313 | + `measure!` reports its size — and either may be nil where there is none. | ||
| 1314 | + | ||
| 1315 | + `av?` is the media plane. It wants a window: a call paints frames into a | ||
| 1316 | + texture, and there is no texture in a terminal." | ||
| 1317 | + [{:keys [after! every! title! measure! av?] :or {av? true}}] | ||
| 1318 | + ;; Before the loop: the settings, the rooms this client has been in, and a | ||
| 1302 | ;; saved sign-in deciding which mode the connect screen opens in and what it | 1319 | ;; saved sign-in deciding which mode the connect screen opens in and what it |
| 1303 | ;; says. | 1320 | ;; says. |
| 1304 | (s/restore-prefs!) | 1321 | (s/restore-prefs!) |
| @@ -1307,51 +1324,62 @@ | |||
| 1307 | ;; And then it connects on its own. A remembered account has already said | 1324 | ;; And then it connects on its own. A remembered account has already said |
| 1308 | ;; what it wants; making it say so again at every launch is a click that | 1325 | ;; what it wants; making it say so again at every launch is a click that |
| 1309 | ;; carries no information. It is a timer rather than a call here so the | 1326 | ;; carries no information. It is a timer rather than a call here so the |
| 1310 | - ;; window is up first — the connect screen with its status is what the | 1327 | + ;; screen is up first — the connect screen with its status is what the |
| 1311 | ;; user should be looking at while this happens, and if it fails, the | 1328 | ;; user should be looking at while this happens, and if it fails, the |
| 1312 | ;; error lands somewhere visible. | 1329 | ;; error lands somewhere visible. |
| 1313 | - (vidya/after! 150 s/connect!)) | 1330 | + (after! 150 s/connect!)) |
| 1314 | - ;; Calls arrive rather than being asked for, so the media plane is drained | 1331 | + (when av? |
| 1315 | - ;; every frame whether or not one is up — the drain costs a single integer | 1332 | + ;; Calls arrive rather than being asked for, so the media plane is drained |
| 1316 | - ;; read when it is not. It has to be a timer: `frame-rgba!` and everything | 1333 | + ;; every frame whether or not one is up — the drain costs a single integer |
| 1317 | - ;; else that touches a node belongs to the loop thread, and this is glimmer's | 1334 | + ;; read when it is not. It has to be a timer: `frame-rgba!` and everything |
| 1318 | - ;; way of getting onto it. | 1335 | + ;; else that touches a node belongs to the loop thread, and this is |
| 1319 | - (av/init-logging!) | 1336 | + ;; glimmer's way of getting onto it. |
| 1320 | - ;; Who to tell when a call ends under us rather than at our asking. Set here | 1337 | + (av/init-logging!) |
| 1321 | - ;; rather than in frq.av because sending a TAGMSG needs the connection, and | 1338 | + ;; Who to tell when a call ends under us rather than at our asking. Set |
| 1322 | - ;; that belongs to the state layer. | 1339 | + ;; here rather than in frq.av because sending a TAGMSG needs the |
| 1323 | - (reset! av/on-dropped s/announce-leave!) | 1340 | + ;; connection, and that belongs to the state layer. |
| 1324 | - (vidya/after! 0 av/install-pump!) | 1341 | + (reset! av/on-dropped s/announce-leave!) |
| 1325 | - ;; The window's width, into a ratom, a few times a second. Polled rather than | 1342 | + (after! 0 av/install-pump!)) |
| 1343 | + ;; The surface's size, into a ratom, a few times a second. Polled rather than | ||
| 1326 | ;; delivered: the backend reports a size by writing it onto the window node, | 1344 | ;; delivered: the backend reports a size by writing it onto the window node, |
| 1327 | ;; and only what a component derefs re-renders — so the layout follows a drag | 1345 | ;; and only what a component derefs re-renders — so the layout follows a drag |
| 1328 | - ;; of the window's edge without every frame touching the tree. | 1346 | + ;; of the edge without every frame touching the tree. |
| 1329 | - ;; | 1347 | + (when measure! |
| 1330 | - ;; The height comes off the same tick, from `screen-size` rather than a | 1348 | + (measure!) |
| 1331 | - ;; second call: it is the window's content size, and the pictures in the | 1349 | + (every! 200 measure!)) |
| 1332 | - ;; conversation are sized against it. | ||
| 1333 | - (vidya/every! 200 #(let [w (vidya/window-width) | ||
| 1334 | - h (long (second (vidya/screen-size)))] | ||
| 1335 | - (when (not= w @s/window-width) | ||
| 1336 | - (reset! s/window-width w)) | ||
| 1337 | - (when (not= h @s/window-height) | ||
| 1338 | - (reset! s/window-height h)))) | ||
| 1339 | ;; The nick in the window title, so a second window of this client is told | 1350 | ;; The nick in the window title, so a second window of this client is told |
| 1340 | ;; apart from the first by the one thing that differs — and so the answer to | 1351 | ;; apart from the first by the one thing that differs — and so the answer to |
| 1341 | ;; "who am I here?" is on screen without opening Settings. | 1352 | ;; "who am I here?" is on screen without opening Settings. |
| 1342 | ;; | 1353 | ;; |
| 1343 | - ;; Polled, like the width above and for the same reason: the title belongs to | 1354 | + ;; Polled, like the size above and for the same reason: the title belongs to |
| 1344 | ;; the window rather than to the tree, so no render puts it there, and the | 1355 | ;; the window rather than to the tree, so no render puts it there, and the |
| 1345 | ;; nick is only settled once a connection has been made. Twice a second is | 1356 | ;; nick is only settled once a connection has been made. Twice a second is |
| 1346 | ;; far more often than a nick changes, and a string compare is what a tick | 1357 | ;; far more often than a nick changes, and a string compare is what a tick |
| 1347 | ;; costs when it has not. | 1358 | ;; costs when it has not. |
| 1348 | ;; Seeded with the title `run` opens the window under, so the first tick of | 1359 | ;; Seeded with the title `run` opens the window under, so the first tick of |
| 1349 | ;; a launch that has nobody signed in yet sets nothing. | 1360 | ;; a launch that has nobody signed in yet sets nothing. |
| 1350 | - (let [shown (atom "frq")] | 1361 | + (when title! |
| 1351 | - (vidya/every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick)) | 1362 | + (let [shown (atom "frq")] |
| 1352 | - (str "frq — " @s/form-nick) | 1363 | + (every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick)) |
| 1353 | - "frq")] | 1364 | + (str "frq — " @s/form-nick) |
| 1354 | - (when (not= title @shown) | 1365 | + "frq")] |
| 1355 | - (reset! shown title) | 1366 | + (when (not= title @shown) |
| 1356 | - (vidya/set-title! title))))) | 1367 | + (reset! shown title) |
| 1368 | + (title! title)))))) | ||
| 1369 | + nil) | ||
| 1370 | + | ||
| 1371 | +(defn -main [& _] | ||
| 1372 | + (start! {:after! vidya/after! | ||
| 1373 | + :every! vidya/every! | ||
| 1374 | + :title! vidya/set-title! | ||
| 1375 | + ;; The height comes off the same tick, from `screen-size` rather | ||
| 1376 | + ;; than a second call: it is the window's content size, and the | ||
| 1377 | + ;; pictures in the conversation are sized against it. | ||
| 1378 | + :measure! (fn [] | ||
| 1379 | + (let [w (vidya/window-width) | ||
| 1380 | + h (long (second (vidya/screen-size)))] | ||
| 1381 | + (when (not= w @s/window-width) | ||
| 1382 | + (reset! s/window-width w)) | ||
| 1383 | + (when (not= h @s/window-height) | ||
| 1384 | + (reset! s/window-height h))))}) | ||
| 1357 | (ui/run app :title "frq" :width 520 :height 860)) | 1385 | (ui/run app :title "frq" :width 520 :height 860)) |
modified
src/frq/tui.jolt +59 -29 | @@ -13,6 +13,12 @@ | ||
| 13 | 13 | title, a window width, a picture chooser — and with no window open every one |
| 14 | 14 | of those is inert, which is the behaviour those calls already have to have. |
| 15 | 15 | |
| 16 | + It is the client, not a preview of it: `frq.app/start!` is what a launch | |
| 17 | + does, and this hands it the terminal's timers instead of the window's. The | |
| 18 | + saved sign-in is restored and connects itself, the rooms this client has been | |
| 19 | + in come back, and `#test` is joined on arrival — the same session as the | |
| 20 | + window, painted somewhere else. | |
| 21 | + | |
| 16 | 22 | Two ways to run it: |
| 17 | 23 | |
| 18 | 24 | jolt -M:tui the real terminal, until Ctrl-Q |
| @@ -20,7 +26,10 @@ | ||
| 20 | 26 | |
| 21 | 27 | The headless one is `tui_headless` — the same layout and the same painting |
| 22 | 28 | with the writer taken off the end — and is what a screenshot in a bug report |
| 23 | - or a CI check should be." | |
| 29 | + or a CI check should be. It is also the one that has nothing to show: a | |
| 30 | + screenshot taken a moment after launch is of a client that has not finished | |
| 31 | + connecting, so `--demo` fills a buffer of its own rather than waiting on a | |
| 32 | + server for one." | |
| 24 | 33 | (:require [clojure.string :as str] |
| 25 | 34 | [frq.app :as app] |
| 26 | 35 | [frq.state :as s] |
| @@ -38,13 +47,13 @@ | ||
| 38 | 47 | (def ^:private points-per-cell 8) |
| 39 | 48 | |
| 40 | 49 | (defn- seed! |
| 41 | - "Put a conversation on screen for a client that is not connected to anything. | |
| 50 | + "Put a conversation on screen without a server behind it — `--demo`. | |
| 42 | 51 | |
| 43 | - A terminal session started for a look at the layout has no server behind it, | |
| 44 | - and an empty buffer says nothing about how the buffer looks. These go in | |
| 45 | - through `push-message!` rather than into the atom directly, so what is drawn | |
| 46 | - is a real buffer — timestamps, day headings, the actor lookup, reply chips | |
| 47 | - and reactions all included." | |
| 52 | + A screenshot is taken before a connection has been made, and an empty buffer | |
| 53 | + says nothing about how a buffer looks. These go in through `push-message!` | |
| 54 | + rather than into the atom directly, so what is drawn is a real buffer: | |
| 55 | + timestamps, day headings, the actor lookup, reply chips and reactions all | |
| 56 | + included." | |
| 48 | 57 | [] |
| 49 | 58 | (let [now (System/currentTimeMillis) |
| 50 | 59 | minute 60000] |
| @@ -83,41 +92,62 @@ | ||
| 83 | 92 | (reset! s/window-height (* rows points-per-cell 2))) |
| 84 | 93 | nil) |
| 85 | 94 | |
| 95 | +(defn- start! | |
| 96 | + "The launch, with the terminal's timers in place of the window's. | |
| 97 | + | |
| 98 | + No title and no media plane: a terminal has no title bar to write a nick | |
| 99 | + into, and a call paints frames into a texture there is no texture for here. | |
| 100 | + Everything else — the saved settings, the rooms, the sign-in that connects | |
| 101 | + itself — is the same startup the window runs." | |
| 102 | + [] | |
| 103 | + (app/start! {:after! tui/after! | |
| 104 | + :every! tui/every! | |
| 105 | + :title! nil | |
| 106 | + :av? false | |
| 107 | + :measure! narrow!})) | |
| 108 | + | |
| 86 | 109 | (defn- run-headless! |
| 87 | 110 | "Mount the app in a session of `cols` by `rows` with no terminal, let it |
| 88 | 111 | settle, and print what was painted." |
| 89 | - [cols rows] | |
| 112 | + [cols rows demo? wait] | |
| 90 | 113 | ;; The size has to be known before the first render, or the layout reads the |
| 91 | 114 | ;; zero it was left at. A timer runs on the loop thread, which is where |
| 92 | 115 | ;; screen-size may be asked — and the first one fires before the first paint. |
| 93 | - (tui/after! 0 narrow!) | |
| 94 | - (tui/after! 120 (fn [] | |
| 116 | + (tui/after! 0 (fn [] | |
| 117 | + (narrow!) | |
| 118 | + (start!) | |
| 119 | + (when demo? (seed!)))) | |
| 120 | + ;; Long enough for whatever the screenshot is of. A client that restores a | |
| 121 | + ;; sign-in spends the first seconds connecting, so the default is a picture | |
| 122 | + ;; of the connect screen and `--wait=` is how you ask for one of the room. | |
| 123 | + (tui/after! wait (fn [] | |
| 95 | 124 | (println (tui/screen-str)) |
| 96 | 125 | (tui/quit!))) |
| 97 | 126 | (ui/run app/app :headless [cols rows] :points-per-cell points-per-cell)) |
| 98 | 127 | |
| 99 | -(defn- run-terminal! [] | |
| 100 | - (tui/after! 0 narrow!) | |
| 101 | - ;; The terminal is resized by dragging its edge, and only a poll notices: | |
| 102 | - ;; the backend reports a size, and only what a component derefs re-renders. | |
| 103 | - (tui/every! 200 narrow!) | |
| 128 | +(defn- run-terminal! [demo?] | |
| 129 | + ;; On the loop thread, and before the first paint: `start!` sets timers of | |
| 130 | + ;; its own, and a timer added from anywhere else is a node touched from the | |
| 131 | + ;; wrong thread waiting to happen. | |
| 132 | + (tui/after! 0 (fn [] | |
| 133 | + (narrow!) | |
| 134 | + (start!) | |
| 135 | + (when demo? (seed!)))) | |
| 104 | 136 | (ui/run app/app :mouse true :points-per-cell points-per-cell)) |
| 105 | 137 | |
| 106 | 138 | (defn -main [& args] |
| 107 | 139 | (let [args (set args) |
| 108 | 140 | headless? (contains? args "--headless") |
| 109 | - cols (if-let [n (first (keep #(when (str/starts-with? % "--cols=") | |
| 110 | - (subs % 7)) | |
| 111 | - args))] | |
| 112 | - (Integer/parseInt n) | |
| 113 | - 100) | |
| 114 | - rows (if-let [n (first (keep #(when (str/starts-with? % "--rows=") | |
| 115 | - (subs % 7)) | |
| 116 | - args))] | |
| 117 | - (Integer/parseInt n) | |
| 118 | - 36)] | |
| 119 | - (s/restore-prefs!) | |
| 120 | - (seed!) | |
| 141 | + demo? (contains? args "--demo") | |
| 142 | + num (fn [flag default] | |
| 143 | + (if-let [n (first (keep #(when (str/starts-with? % flag) | |
| 144 | + (subs % (count flag))) | |
| 145 | + args))] | |
| 146 | + (Integer/parseInt n) | |
| 147 | + default)) | |
| 148 | + cols (num "--cols=" 100) | |
| 149 | + rows (num "--rows=" 36) | |
| 150 | + wait (num "--wait=" 400)] | |
| 121 | 151 | (if headless? |
| 122 | - (run-headless! cols rows) | |
| 123 | - (run-terminal!)))) | |
| 152 | + (run-headless! cols rows demo? wait) | |
| 153 | + (run-terminal! demo?)))) | |
| @@ -13,6 +13,12 @@ | |||
| 13 | title, a window width, a picture chooser — and with no window open every one | 13 | title, a window width, a picture chooser — and with no window open every one |
| 14 | of those is inert, which is the behaviour those calls already have to have. | 14 | of those is inert, which is the behaviour those calls already have to have. |
| 15 | 15 | ||
| 16 | + It is the client, not a preview of it: `frq.app/start!` is what a launch | ||
| 17 | + does, and this hands it the terminal's timers instead of the window's. The | ||
| 18 | + saved sign-in is restored and connects itself, the rooms this client has been | ||
| 19 | + in come back, and `#test` is joined on arrival — the same session as the | ||
| 20 | + window, painted somewhere else. | ||
| 21 | + | ||
| 16 | Two ways to run it: | 22 | Two ways to run it: |
| 17 | 23 | ||
| 18 | jolt -M:tui the real terminal, until Ctrl-Q | 24 | jolt -M:tui the real terminal, until Ctrl-Q |
| @@ -20,7 +26,10 @@ | |||
| 20 | 26 | ||
| 21 | The headless one is `tui_headless` — the same layout and the same painting | 27 | The headless one is `tui_headless` — the same layout and the same painting |
| 22 | with the writer taken off the end — and is what a screenshot in a bug report | 28 | with the writer taken off the end — and is what a screenshot in a bug report |
| 23 | - or a CI check should be." | 29 | + or a CI check should be. It is also the one that has nothing to show: a |
| 30 | + screenshot taken a moment after launch is of a client that has not finished | ||
| 31 | + connecting, so `--demo` fills a buffer of its own rather than waiting on a | ||
| 32 | + server for one." | ||
| 24 | (:require [clojure.string :as str] | 33 | (:require [clojure.string :as str] |
| 25 | [frq.app :as app] | 34 | [frq.app :as app] |
| 26 | [frq.state :as s] | 35 | [frq.state :as s] |
| @@ -38,13 +47,13 @@ | |||
| 38 | (def ^:private points-per-cell 8) | 47 | (def ^:private points-per-cell 8) |
| 39 | 48 | ||
| 40 | (defn- seed! | 49 | (defn- seed! |
| 41 | - "Put a conversation on screen for a client that is not connected to anything. | 50 | + "Put a conversation on screen without a server behind it — `--demo`. |
| 42 | 51 | ||
| 43 | - A terminal session started for a look at the layout has no server behind it, | 52 | + A screenshot is taken before a connection has been made, and an empty buffer |
| 44 | - and an empty buffer says nothing about how the buffer looks. These go in | 53 | + says nothing about how a buffer looks. These go in through `push-message!` |
| 45 | - through `push-message!` rather than into the atom directly, so what is drawn | 54 | + rather than into the atom directly, so what is drawn is a real buffer: |
| 46 | - is a real buffer — timestamps, day headings, the actor lookup, reply chips | 55 | + timestamps, day headings, the actor lookup, reply chips and reactions all |
| 47 | - and reactions all included." | 56 | + included." |
| 48 | [] | 57 | [] |
| 49 | (let [now (System/currentTimeMillis) | 58 | (let [now (System/currentTimeMillis) |
| 50 | minute 60000] | 59 | minute 60000] |
| @@ -83,41 +92,62 @@ | |||
| 83 | (reset! s/window-height (* rows points-per-cell 2))) | 92 | (reset! s/window-height (* rows points-per-cell 2))) |
| 84 | nil) | 93 | nil) |
| 85 | 94 | ||
| 95 | +(defn- start! | ||
| 96 | + "The launch, with the terminal's timers in place of the window's. | ||
| 97 | + | ||
| 98 | + No title and no media plane: a terminal has no title bar to write a nick | ||
| 99 | + into, and a call paints frames into a texture there is no texture for here. | ||
| 100 | + Everything else — the saved settings, the rooms, the sign-in that connects | ||
| 101 | + itself — is the same startup the window runs." | ||
| 102 | + [] | ||
| 103 | + (app/start! {:after! tui/after! | ||
| 104 | + :every! tui/every! | ||
| 105 | + :title! nil | ||
| 106 | + :av? false | ||
| 107 | + :measure! narrow!})) | ||
| 108 | + | ||
| 86 | (defn- run-headless! | 109 | (defn- run-headless! |
| 87 | "Mount the app in a session of `cols` by `rows` with no terminal, let it | 110 | "Mount the app in a session of `cols` by `rows` with no terminal, let it |
| 88 | settle, and print what was painted." | 111 | settle, and print what was painted." |
| 89 | - [cols rows] | 112 | + [cols rows demo? wait] |
| 90 | ;; The size has to be known before the first render, or the layout reads the | 113 | ;; The size has to be known before the first render, or the layout reads the |
| 91 | ;; zero it was left at. A timer runs on the loop thread, which is where | 114 | ;; zero it was left at. A timer runs on the loop thread, which is where |
| 92 | ;; screen-size may be asked — and the first one fires before the first paint. | 115 | ;; screen-size may be asked — and the first one fires before the first paint. |
| 93 | - (tui/after! 0 narrow!) | 116 | + (tui/after! 0 (fn [] |
| 94 | - (tui/after! 120 (fn [] | 117 | + (narrow!) |
| 118 | + (start!) | ||
| 119 | + (when demo? (seed!)))) | ||
| 120 | + ;; Long enough for whatever the screenshot is of. A client that restores a | ||
| 121 | + ;; sign-in spends the first seconds connecting, so the default is a picture | ||
| 122 | + ;; of the connect screen and `--wait=` is how you ask for one of the room. | ||
| 123 | + (tui/after! wait (fn [] | ||
| 95 | (println (tui/screen-str)) | 124 | (println (tui/screen-str)) |
| 96 | (tui/quit!))) | 125 | (tui/quit!))) |
| 97 | (ui/run app/app :headless [cols rows] :points-per-cell points-per-cell)) | 126 | (ui/run app/app :headless [cols rows] :points-per-cell points-per-cell)) |
| 98 | 127 | ||
| 99 | -(defn- run-terminal! [] | 128 | +(defn- run-terminal! [demo?] |
| 100 | - (tui/after! 0 narrow!) | 129 | + ;; On the loop thread, and before the first paint: `start!` sets timers of |
| 101 | - ;; The terminal is resized by dragging its edge, and only a poll notices: | 130 | + ;; its own, and a timer added from anywhere else is a node touched from the |
| 102 | - ;; the backend reports a size, and only what a component derefs re-renders. | 131 | + ;; wrong thread waiting to happen. |
| 103 | - (tui/every! 200 narrow!) | 132 | + (tui/after! 0 (fn [] |
| 133 | + (narrow!) | ||
| 134 | + (start!) | ||
| 135 | + (when demo? (seed!)))) | ||
| 104 | (ui/run app/app :mouse true :points-per-cell points-per-cell)) | 136 | (ui/run app/app :mouse true :points-per-cell points-per-cell)) |
| 105 | 137 | ||
| 106 | (defn -main [& args] | 138 | (defn -main [& args] |
| 107 | (let [args (set args) | 139 | (let [args (set args) |
| 108 | headless? (contains? args "--headless") | 140 | headless? (contains? args "--headless") |
| 109 | - cols (if-let [n (first (keep #(when (str/starts-with? % "--cols=") | 141 | + demo? (contains? args "--demo") |
| 110 | - (subs % 7)) | 142 | + num (fn [flag default] |
| 111 | - args))] | 143 | + (if-let [n (first (keep #(when (str/starts-with? % flag) |
| 112 | - (Integer/parseInt n) | 144 | + (subs % (count flag))) |
| 113 | - 100) | 145 | + args))] |
| 114 | - rows (if-let [n (first (keep #(when (str/starts-with? % "--rows=") | 146 | + (Integer/parseInt n) |
| 115 | - (subs % 7)) | 147 | + default)) |
| 116 | - args))] | 148 | + cols (num "--cols=" 100) |
| 117 | - (Integer/parseInt n) | 149 | + rows (num "--rows=" 36) |
| 118 | - 36)] | 150 | + wait (num "--wait=" 400)] |
| 119 | - (s/restore-prefs!) | ||
| 120 | - (seed!) | ||
| 121 | (if headless? | 151 | (if headless? |
| 122 | - (run-headless! cols rows) | 152 | + (run-headless! cols rows demo? wait) |
| 123 | - (run-terminal!)))) | 153 | + (run-terminal! demo?)))) |