nandi/jolt-nativepublic Fork 0
0b92f67
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

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

Answer the rest of what a client asks its window

The other seven glimmer-vidya calls frq makes: the title, the display, the
browser, the clipboard, the picture chooser and asking to close. None of it
is drawing and none of it belongs in a widget, but a client cannot do any of
it either — the window handle lives in the toolkit, so the questions come
here.

jvui.host holds that handle and answers a harmless nothing before a window
exists: no title to set, no display to measure, no clipboard to read.
jvui's own tests run with no window and should not have to guard every
call, and neither should a client during startup.

quit! is a REQUEST, not an exit. The loop owns teardown — textures,
painter, fonts, the window — and tearing any of it down from under a walk
that is still running is how closing turns into crashing.

screen-size is the display's USABLE bounds rather than its raw ones: a
window sized to the whole display is one with its bottom edge under a
taskbar.

pick-image! answers false and picked-image! nil, and those are the correct
answers rather than gaps. The chooser exists so a phone can hand back a
grant for one picture without the app holding a permission over all of
them; a desktop has no such thing, browsing the filesystem is the answer
there, and glimmer-vidya says exactly the same thing on a desktop. An
Android jvui would replace them.

clipboard-image-png! asks for image/png only. A clipboard may hold a bitmap
or a TIFF, and converting those means an encoder in here — a lot of toolkit
for a paste, when every desktop that puts a picture on a clipboard this
decade puts a PNG there too.

Tested against a real window under SDL's dummy video driver, including the
before-a-window answers and that quit! actually stops the loop early. Not
open-url!: succeeding would open a browser on somebody's desktop and
failing would only say this container has none.

That is twelve of twelve. frq's whole glimmer-vidya surface now exists here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-10T05:39:33-07:00 Browse files
0b92f67 parent: 32fae8d
modified glimmer-backends/glimmer-jvui/src/glimmer_jvui/core.clj +22 -1
@@ -40,7 +40,8 @@
4040 [jvui.core :as c]
4141 [jvui.theme :as theme]
4242 [jvui.widgets :as w]
43- [jvui.frames :as frames]))
43+ [jvui.frames :as frames]
44+ [jvui.host :as host]))
4445
4546 ;; --- the retained tree -------------------------------------------------------
4647
@@ -326,3 +327,23 @@
326327 "Every feed with a picture."
327328 []
328329 (frames/keys*))
330+
331+;; --- the platform -----------------------------------------------------------
332+;; The rest of what glimmer-vidya answers, so a client can ask its backend
333+;; about the window it is in without knowing which backend that is. Thin on
334+;; purpose: every one of these is jvui.host, and the indirection exists so
335+;; the client requires one namespace rather than two.
336+
337+(def set-title! host/set-title!)
338+(def window-width host/window-width)
339+(def screen-size host/screen-size)
340+(def quit! host/quit!)
341+(def open-url! host/open-url!)
342+(def clipboard-image-png! host/clipboard-image-png!)
343+
344+;; False and nil on a desktop, which is the right answer rather than a gap:
345+;; the chooser exists so a phone can hand back a grant for one picture, and
346+;; a caller reads the false and offers a file browser instead. glimmer-vidya
347+;; says the same thing here.
348+(def pick-image! host/pick-image!)
349+(def picked-image! host/picked-image!)
@@ -40,7 +40,8 @@
40 [jvui.core :as c]40 [jvui.core :as c]
41 [jvui.theme :as theme]41 [jvui.theme :as theme]
42 [jvui.widgets :as w]42 [jvui.widgets :as w]
43- [jvui.frames :as frames]))43+ [jvui.frames :as frames]
44+ [jvui.host :as host]))
44 45
45 ;; --- the retained tree -------------------------------------------------------46 ;; --- the retained tree -------------------------------------------------------
46 47
@@ -326,3 +327,23 @@
326 "Every feed with a picture."327 "Every feed with a picture."
327 []328 []
328 (frames/keys*))329 (frames/keys*))
330+
331+;; --- the platform -----------------------------------------------------------
332+;; The rest of what glimmer-vidya answers, so a client can ask its backend
333+;; about the window it is in without knowing which backend that is. Thin on
334+;; purpose: every one of these is jvui.host, and the indirection exists so
335+;; the client requires one namespace rather than two.
336+
337+(def set-title! host/set-title!)
338+(def window-width host/window-width)
339+(def screen-size host/screen-size)
340+(def quit! host/quit!)
341+(def open-url! host/open-url!)
342+(def clipboard-image-png! host/clipboard-image-png!)
343+
344+;; False and nil on a desktop, which is the right answer rather than a gap:
345+;; the chooser exists so a phone can hand back a grant for one picture, and
346+;; a caller reads the false and offers a file browser instead. glimmer-vidya
347+;; says the same thing here.
348+(def pick-image! host/pick-image!)
349+(def picked-image! host/picked-image!)
modified jvui/src/jvui/app.clj +8 -0
@@ -8,6 +8,7 @@
88 [jvui.font :as font]
99 [jvui.paint :as paint]
1010 [jvui.frames :as frames]
11+ [jvui.host :as host]
1112 [jvui.theme :as theme]
1213 [jvui.core :as c]))
1314
@@ -73,6 +74,7 @@
7374 ;; into. Anything that arrives before this is dropped rather than
7475 ;; queued — see jvui.frames.
7576 (frames/install-renderer! renderer)
77+ (host/install! window)
7678 (sdl/start-text-input! window)
7779 (try
7880 (loop [n 0]
@@ -97,11 +99,17 @@
9799 (sdl/present! renderer)
98100 (sdl/delay-ms! 16)
99101 (when-not (or (:quit? @ctx)
102+ ;; A client asking to close — see jvui.host/quit!.
103+ ;; Checked here so teardown stays the loop's, which
104+ ;; is the only thing that knows what is still being
105+ ;; walked.
106+ (host/quit-requested?)
100107 (and frames (>= (inc n) frames))
101108 (and deadline (> (System/currentTimeMillis) deadline)))
102109 (recur (inc n)))))
103110 (finally
104111 (frames/clear!)
112+ (host/clear!)
105113 (paint/close! painter)
106114 (font/close! fonts)
107115 (sdl/close! win))))))
@@ -8,6 +8,7 @@
8 [jvui.font :as font]8 [jvui.font :as font]
9 [jvui.paint :as paint]9 [jvui.paint :as paint]
10 [jvui.frames :as frames]10 [jvui.frames :as frames]
11+ [jvui.host :as host]
11 [jvui.theme :as theme]12 [jvui.theme :as theme]
12 [jvui.core :as c]))13 [jvui.core :as c]))
13 14
@@ -73,6 +74,7 @@
73 ;; into. Anything that arrives before this is dropped rather than74 ;; into. Anything that arrives before this is dropped rather than
74 ;; queued — see jvui.frames.75 ;; queued — see jvui.frames.
75 (frames/install-renderer! renderer)76 (frames/install-renderer! renderer)
77+ (host/install! window)
76 (sdl/start-text-input! window)78 (sdl/start-text-input! window)
77 (try79 (try
78 (loop [n 0]80 (loop [n 0]
@@ -97,11 +99,17 @@
97 (sdl/present! renderer)99 (sdl/present! renderer)
98 (sdl/delay-ms! 16)100 (sdl/delay-ms! 16)
99 (when-not (or (:quit? @ctx)101 (when-not (or (:quit? @ctx)
102+ ;; A client asking to close — see jvui.host/quit!.
103+ ;; Checked here so teardown stays the loop's, which
104+ ;; is the only thing that knows what is still being
105+ ;; walked.
106+ (host/quit-requested?)
100 (and frames (>= (inc n) frames))107 (and frames (>= (inc n) frames))
101 (and deadline (> (System/currentTimeMillis) deadline)))108 (and deadline (> (System/currentTimeMillis) deadline)))
102 (recur (inc n)))))109 (recur (inc n)))))
103 (finally110 (finally
104 (frames/clear!)111 (frames/clear!)
112+ (host/clear!)
105 (paint/close! painter)113 (paint/close! painter)
106 (font/close! fonts)114 (font/close! fonts)
107 (sdl/close! win))))))115 (sdl/close! win))))))
added jvui/src/jvui/host.clj +112 -0
new file mode 100644
@@ -0,0 +1,112 @@
1+(ns jvui.host
2+ "The window as a thing in an operating system, rather than a thing to draw in.
3+
4+ Its title, the display it is on, the browser, the clipboard, and asking it
5+ to close. None of this is painting and none of it belongs in a widget, but
6+ a client cannot do any of it either — the window handle lives here, so the
7+ questions come here too.
8+
9+ Everything answers a harmless nothing before a window exists: no title to
10+ set, no display to measure, no clipboard to read. A client that runs
11+ headless — jvui's own tests do — should not have to guard every call."
12+ (:require [jvui.sdl :as sdl]))
13+
14+(defonce ^:private win (atom nil))
15+(defonce ^:private quit? (atom false))
16+
17+(defn install!
18+ "Called by the frame loop when a window opens."
19+ [window]
20+ (reset! win window)
21+ (reset! quit? false))
22+
23+(defn clear! [] (reset! win nil) nil)
24+
25+(defn window
26+ "The open window, or nil."
27+ []
28+ @win)
29+
30+;; --- closing -----------------------------------------------------------------
31+
32+(defn quit!
33+ "Ask the frame loop to stop after the current frame.
34+
35+ A request rather than an exit: the loop owns teardown — textures, painter,
36+ fonts, the window itself — and tearing any of it down from under a walk
37+ that is still running is how a close turns into a crash."
38+ []
39+ (reset! quit? true)
40+ nil)
41+
42+(defn quit-requested? [] @quit?)
43+
44+;; --- the window --------------------------------------------------------------
45+
46+(defn set-title! [s]
47+ (when-let [w @win] (sdl/set-window-title! w (str s)))
48+ nil)
49+
50+(defn window-size
51+ "[w h] in window units, or nil with no window."
52+ []
53+ (when-let [w @win] (sdl/window-size w)))
54+
55+(defn window-width
56+ "Width in window units, or 0 with no window — a caller dividing by it wants
57+ a number, and zero is the honest one."
58+ []
59+ (or (first (window-size)) 0))
60+
61+(defn screen-size
62+ "[w h] of the display this window is on, or nil with no window."
63+ []
64+ (when-let [w @win] (sdl/display-bounds w)))
65+
66+;; --- the world outside -------------------------------------------------------
67+
68+(defn open-url!
69+ "Hand a URL to whatever the desktop opens URLs with."
70+ [url]
71+ (boolean (and (string? url) (seq url) (sdl/open-url! url))))
72+
73+(defn clipboard-image-png!
74+ "Write the clipboard's picture to `path` as PNG; true when there was one.
75+
76+ Only image/png is asked for. A clipboard may also hold a bitmap or a TIFF
77+ and converting those would mean an encoder in here, which is a lot of
78+ toolkit for a paste — and every desktop that puts a picture on a clipboard
79+ this decade puts a PNG there too."
80+ [path]
81+ (boolean
82+ (when (sdl/has-clipboard-data "image/png")
83+ (when-let [[p n] (sdl/clipboard-data "image/png")]
84+ (try
85+ (let [bs (byte-array n)]
86+ (jolt.ffi/read-array p :byte bs)
87+ (java.nio.file.Files/write
88+ (java.nio.file.Path/of (str path) (into-array String []))
89+ bs
90+ (into-array java.nio.file.OpenOption []))
91+ true)
92+ (finally (sdl/sdl-free! p)))))))
93+
94+;; --- the picture chooser ------------------------------------------------------
95+;; There is not one, and that is the same answer glimmer-vidya gives on a
96+;; desktop. The chooser exists so a phone can hand back a grant for one
97+;; picture without the app holding a permission over all of them; a desktop
98+;; has no such thing and browsing the filesystem is the answer there.
99+;;
100+;; So these are not stubs waiting to be filled in on this backend — they are
101+;; the correct answer for it, and a caller reads the false and offers a file
102+;; browser instead. An Android jvui would replace them.
103+
104+(defn pick-image!
105+ "False: this platform has no picture chooser."
106+ []
107+ false)
108+
109+(defn picked-image!
110+ "nil: nothing was ever chosen, because nothing could be."
111+ [_path]
112+ nil)
new file mode 100644
@@ -0,0 +1,112 @@
1+(ns jvui.host
2+ "The window as a thing in an operating system, rather than a thing to draw in.
3+
4+ Its title, the display it is on, the browser, the clipboard, and asking it
5+ to close. None of this is painting and none of it belongs in a widget, but
6+ a client cannot do any of it either — the window handle lives here, so the
7+ questions come here too.
8+
9+ Everything answers a harmless nothing before a window exists: no title to
10+ set, no display to measure, no clipboard to read. A client that runs
11+ headless — jvui's own tests do — should not have to guard every call."
12+ (:require [jvui.sdl :as sdl]))
13+
14+(defonce ^:private win (atom nil))
15+(defonce ^:private quit? (atom false))
16+
17+(defn install!
18+ "Called by the frame loop when a window opens."
19+ [window]
20+ (reset! win window)
21+ (reset! quit? false))
22+
23+(defn clear! [] (reset! win nil) nil)
24+
25+(defn window
26+ "The open window, or nil."
27+ []
28+ @win)
29+
30+;; --- closing -----------------------------------------------------------------
31+
32+(defn quit!
33+ "Ask the frame loop to stop after the current frame.
34+
35+ A request rather than an exit: the loop owns teardown — textures, painter,
36+ fonts, the window itself — and tearing any of it down from under a walk
37+ that is still running is how a close turns into a crash."
38+ []
39+ (reset! quit? true)
40+ nil)
41+
42+(defn quit-requested? [] @quit?)
43+
44+;; --- the window --------------------------------------------------------------
45+
46+(defn set-title! [s]
47+ (when-let [w @win] (sdl/set-window-title! w (str s)))
48+ nil)
49+
50+(defn window-size
51+ "[w h] in window units, or nil with no window."
52+ []
53+ (when-let [w @win] (sdl/window-size w)))
54+
55+(defn window-width
56+ "Width in window units, or 0 with no window — a caller dividing by it wants
57+ a number, and zero is the honest one."
58+ []
59+ (or (first (window-size)) 0))
60+
61+(defn screen-size
62+ "[w h] of the display this window is on, or nil with no window."
63+ []
64+ (when-let [w @win] (sdl/display-bounds w)))
65+
66+;; --- the world outside -------------------------------------------------------
67+
68+(defn open-url!
69+ "Hand a URL to whatever the desktop opens URLs with."
70+ [url]
71+ (boolean (and (string? url) (seq url) (sdl/open-url! url))))
72+
73+(defn clipboard-image-png!
74+ "Write the clipboard's picture to `path` as PNG; true when there was one.
75+
76+ Only image/png is asked for. A clipboard may also hold a bitmap or a TIFF
77+ and converting those would mean an encoder in here, which is a lot of
78+ toolkit for a paste — and every desktop that puts a picture on a clipboard
79+ this decade puts a PNG there too."
80+ [path]
81+ (boolean
82+ (when (sdl/has-clipboard-data "image/png")
83+ (when-let [[p n] (sdl/clipboard-data "image/png")]
84+ (try
85+ (let [bs (byte-array n)]
86+ (jolt.ffi/read-array p :byte bs)
87+ (java.nio.file.Files/write
88+ (java.nio.file.Path/of (str path) (into-array String []))
89+ bs
90+ (into-array java.nio.file.OpenOption []))
91+ true)
92+ (finally (sdl/sdl-free! p)))))))
93+
94+;; --- the picture chooser ------------------------------------------------------
95+;; There is not one, and that is the same answer glimmer-vidya gives on a
96+;; desktop. The chooser exists so a phone can hand back a grant for one
97+;; picture without the app holding a permission over all of them; a desktop
98+;; has no such thing and browsing the filesystem is the answer there.
99+;;
100+;; So these are not stubs waiting to be filled in on this backend — they are
101+;; the correct answer for it, and a caller reads the false and offers a file
102+;; browser instead. An Android jvui would replace them.
103+
104+(defn pick-image!
105+ "False: this platform has no picture chooser."
106+ []
107+ false)
108+
109+(defn picked-image!
110+ "nil: nothing was ever chosen, because nothing could be."
111+ [_path]
112+ nil)
modified jvui/src/jvui/sdl.clj +35 -0
@@ -282,6 +282,41 @@
282282 (ffi/write-array p :int pixels)
283283 (raw-update-texture tex ffi/null p (* 4 w)))))
284284
285+;; --- the platform around the window -----------------------------------------
286+;; Title, display, browser, clipboard. None of it is drawing, and all of it is
287+;; what a client asks the toolkit for because the toolkit is the only thing
288+;; holding a window handle.
289+
290+(ffi/defcfn set-window-title! "SDL_SetWindowTitle" [:pointer :string] :bool)
291+(ffi/defcfn open-url! "SDL_OpenURL" [:string] :bool)
292+(ffi/defcfn display-for-window "SDL_GetDisplayForWindow" [:pointer] :uint)
293+(ffi/defcfn ^:private raw-display-bounds "SDL_GetDisplayUsableBounds"
294+ [:uint :pointer] :bool)
295+(ffi/defcfn has-clipboard-data "SDL_HasClipboardData" [:string] :bool)
296+(ffi/defcfn ^:private raw-clipboard-data "SDL_GetClipboardData"
297+ [:string :pointer] :pointer)
298+(ffi/defcfn sdl-free! "SDL_free" [:pointer] :void)
299+
300+(defn display-bounds
301+ "[w h] of the display `window` is on, minus whatever the desktop reserves
302+ for panels — usable bounds rather than raw, because a window sized to the
303+ whole display is one with its bottom edge under a taskbar."
304+ [window]
305+ (ffi/with-alloc [r 16]
306+ (when (raw-display-bounds (display-for-window window) r)
307+ [(ffi/read (+ r 8) :int) (ffi/read (+ r 12) :int)])))
308+
309+(defn clipboard-data
310+ "The clipboard's contents for `mime`, as [pointer length], or nil.
311+
312+ The pointer is SDL's and the caller must hand it back to `sdl-free!`."
313+ [mime]
314+ (ffi/with-alloc [sz 8]
315+ (let [p (raw-clipboard-data mime sz)
316+ n (ffi/read sz :uint64)]
317+ (when (and p (not (ffi/null? p)) (pos? n))
318+ [p n]))))
319+
285320 (defn update-texture-raw!
286321 "Upload `h` rows of `pitch` bytes from FOREIGN memory into the whole of `tex`.
287322
@@ -282,6 +282,41 @@
282 (ffi/write-array p :int pixels)282 (ffi/write-array p :int pixels)
283 (raw-update-texture tex ffi/null p (* 4 w)))))283 (raw-update-texture tex ffi/null p (* 4 w)))))
284 284
285+;; --- the platform around the window -----------------------------------------
286+;; Title, display, browser, clipboard. None of it is drawing, and all of it is
287+;; what a client asks the toolkit for because the toolkit is the only thing
288+;; holding a window handle.
289+
290+(ffi/defcfn set-window-title! "SDL_SetWindowTitle" [:pointer :string] :bool)
291+(ffi/defcfn open-url! "SDL_OpenURL" [:string] :bool)
292+(ffi/defcfn display-for-window "SDL_GetDisplayForWindow" [:pointer] :uint)
293+(ffi/defcfn ^:private raw-display-bounds "SDL_GetDisplayUsableBounds"
294+ [:uint :pointer] :bool)
295+(ffi/defcfn has-clipboard-data "SDL_HasClipboardData" [:string] :bool)
296+(ffi/defcfn ^:private raw-clipboard-data "SDL_GetClipboardData"
297+ [:string :pointer] :pointer)
298+(ffi/defcfn sdl-free! "SDL_free" [:pointer] :void)
299+
300+(defn display-bounds
301+ "[w h] of the display `window` is on, minus whatever the desktop reserves
302+ for panels — usable bounds rather than raw, because a window sized to the
303+ whole display is one with its bottom edge under a taskbar."
304+ [window]
305+ (ffi/with-alloc [r 16]
306+ (when (raw-display-bounds (display-for-window window) r)
307+ [(ffi/read (+ r 8) :int) (ffi/read (+ r 12) :int)])))
308+
309+(defn clipboard-data
310+ "The clipboard's contents for `mime`, as [pointer length], or nil.
311+
312+ The pointer is SDL's and the caller must hand it back to `sdl-free!`."
313+ [mime]
314+ (ffi/with-alloc [sz 8]
315+ (let [p (raw-clipboard-data mime sz)
316+ n (ffi/read sz :uint64)]
317+ (when (and p (not (ffi/null? p)) (pos? n))
318+ [p n]))))
319+
285 (defn update-texture-raw!320 (defn update-texture-raw!
286 "Upload `h` rows of `pitch` bytes from FOREIGN memory into the whole of `tex`.321 "Upload `h` rows of `pitch` bytes from FOREIGN memory into the whole of `tex`.
287 322
added jvui/test/jvui/host_check.clj +52 -0
new file mode 100644
@@ -0,0 +1,52 @@
1+(ns jvui.host-check
2+ "The seven platform calls, against a real window.
3+
4+ Under SDL's dummy video driver, which gives a window and a renderer and no
5+ display. That limits what can be claimed and the claims are limited to
6+ match: `open-url!` is not invoked, because succeeding would mean a browser
7+ opening on somebody's desktop and failing would only mean this container
8+ has none. Everything else is checked for real."
9+ (:require [jvui.app :as app]
10+ [jvui.host :as host]
11+ [jvui.widgets :as w]))
12+
13+(defn -main [& _]
14+ (let [out (atom [])
15+ ck! (fn [n ok?] (swap! out conj [n (boolean ok?)]))
16+ seen (atom 0)]
17+ ;; Before any window: everything must answer harmlessly rather than throw.
18+ (ck! "no window: screen-size is nil" (nil? (host/screen-size)))
19+ (ck! "no window: window-width is 0" (zero? (host/window-width)))
20+ (ck! "no window: set-title! is quiet" (nil? (host/set-title! "x")))
21+ (ck! "no window: no clipboard picture" (false? (host/clipboard-image-png! "/tmp/nope.png")))
22+ (ck! "no chooser on a desktop" (false? (host/pick-image!)))
23+ (ck! "and nothing ever chosen" (nil? (host/picked-image! "/tmp/nope.png")))
24+
25+ (app/run!
26+ (fn []
27+ (swap! seen inc)
28+ (when (= 1 @seen)
29+ (ck! "a window is installed" (some? (host/window)))
30+ (ck! "window-width is positive" (pos? (host/window-width)))
31+ (let [[sw sh] (or (host/screen-size) [0 0])]
32+ (ck! "screen-size answers a display" (and (pos? sw) (pos? sh))))
33+ (ck! "set-title! does not throw" (nil? (host/set-title! "retitled")))
34+ ;; No picture on this clipboard, so false — the path that matters
35+ ;; is that asking is safe, not that a picture appears.
36+ (ck! "clipboard with no picture is false"
37+ (false? (host/clipboard-image-png! "/tmp/jvui-clip.png"))))
38+ ;; Ask to close on the third frame; the loop is allowed six, so
39+ ;; stopping earlier is the evidence.
40+ (when (= 3 @seen) (host/quit!))
41+ (w/page* {} (fn [_ _] (w/label "host"))))
42+ {:title "host" :width 200 :height 140 :frames 6})
43+
44+ (ck! "quit! stopped the loop early" (< @seen 6))
45+ (ck! "and the window is released" (nil? (host/window)))
46+
47+ (doseq [[n ok?] @out] (println (if ok? "- " "FAIL ") n))
48+ (let [bad (remove second @out)]
49+ (println (if (seq bad)
50+ (str (count bad) " of " (count @out) " checks FAILED")
51+ (str "all " (count @out) " checks passed")))
52+ (when (seq bad) (System/exit 1)))))
new file mode 100644
@@ -0,0 +1,52 @@
1+(ns jvui.host-check
2+ "The seven platform calls, against a real window.
3+
4+ Under SDL's dummy video driver, which gives a window and a renderer and no
5+ display. That limits what can be claimed and the claims are limited to
6+ match: `open-url!` is not invoked, because succeeding would mean a browser
7+ opening on somebody's desktop and failing would only mean this container
8+ has none. Everything else is checked for real."
9+ (:require [jvui.app :as app]
10+ [jvui.host :as host]
11+ [jvui.widgets :as w]))
12+
13+(defn -main [& _]
14+ (let [out (atom [])
15+ ck! (fn [n ok?] (swap! out conj [n (boolean ok?)]))
16+ seen (atom 0)]
17+ ;; Before any window: everything must answer harmlessly rather than throw.
18+ (ck! "no window: screen-size is nil" (nil? (host/screen-size)))
19+ (ck! "no window: window-width is 0" (zero? (host/window-width)))
20+ (ck! "no window: set-title! is quiet" (nil? (host/set-title! "x")))
21+ (ck! "no window: no clipboard picture" (false? (host/clipboard-image-png! "/tmp/nope.png")))
22+ (ck! "no chooser on a desktop" (false? (host/pick-image!)))
23+ (ck! "and nothing ever chosen" (nil? (host/picked-image! "/tmp/nope.png")))
24+
25+ (app/run!
26+ (fn []
27+ (swap! seen inc)
28+ (when (= 1 @seen)
29+ (ck! "a window is installed" (some? (host/window)))
30+ (ck! "window-width is positive" (pos? (host/window-width)))
31+ (let [[sw sh] (or (host/screen-size) [0 0])]
32+ (ck! "screen-size answers a display" (and (pos? sw) (pos? sh))))
33+ (ck! "set-title! does not throw" (nil? (host/set-title! "retitled")))
34+ ;; No picture on this clipboard, so false — the path that matters
35+ ;; is that asking is safe, not that a picture appears.
36+ (ck! "clipboard with no picture is false"
37+ (false? (host/clipboard-image-png! "/tmp/jvui-clip.png"))))
38+ ;; Ask to close on the third frame; the loop is allowed six, so
39+ ;; stopping earlier is the evidence.
40+ (when (= 3 @seen) (host/quit!))
41+ (w/page* {} (fn [_ _] (w/label "host"))))
42+ {:title "host" :width 200 :height 140 :frames 6})
43+
44+ (ck! "quit! stopped the loop early" (< @seen 6))
45+ (ck! "and the window is released" (nil? (host/window)))
46+
47+ (doseq [[n ok?] @out] (println (if ok? "- " "FAIL ") n))
48+ (let [bad (remove second @out)]
49+ (println (if (seq bad)
50+ (str (count bad) " of " (count @out) " checks FAILED")
51+ (str "all " (count @out) " checks passed")))
52+ (when (seq bad) (System/exit 1)))))