1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
(ns frq.platform
"The few things that are the platform's job rather than the app's."
(:require [jolt.host :as host]))
(defn android?
"Android, told from a desktop by a binary only it has. What hangs on this is
which way the app and the browser sit: side by side, or one behind the other."
[]
(host/file-exists? "/system/bin/am"))
(defonce ^:private desktop (delay (not (android?))))
(defn desktop?
"True where there is a pointer to hover with, rather than a finger. Asked
once per render of every row that has a face on it, so the answer is worked
out once and kept — the question is a file that either exists or does not,
and nothing about it changes while the app runs."
[]
@desktop)
(defonce ^:private overrides
;; What the backend does. Filled in by the entry point that installs it —
;; `frq.cosmic`, `frq.tui` — before the app starts.
;;
;; This used to be a *diff* against glimmer-jvui: every key here fell back to
;; a jvui call, and a backend named only the ones it did differently. jvui is
;; gone, so there is no default backend to fall back to and the fallbacks
;; below are no-ops instead. That is the honest shape — the terminal has no
;; picture chooser and libcosmic has no video texture, and both used to get
;; one that quietly did nothing anyway.
(atom {}))
(defn override!
"Install what this backend does. Keys: :after! :every! :quit! :open-url!
:pick-image! :picked-image! :clipboard-image-png! :screen-size :frame-rgba!
:frame-drop!. Anything left out does nothing."
[m]
(swap! overrides merge m)
nil)
(defn- op
"The backend's answer for `k`, or `default` where it has none."
[k default]
(get @overrides k default))
(defn open-url!
"Hand a URL to whatever shows web pages here. The backend knows what that
means — xdg-open on a desktop, an ACTION_VIEW intent on Android, where no
shelled-out `am start` is allowed to. False leaves the connect screen's
\"if the browser did not open\" line to carry the URL across, which is what
happens under a backend with no browser to hand it to."
[url]
(try
((op :open-url! (fn [_] false)) (or url ""))
(catch Exception _ false)))
(defn after!
"Run `f` on the UI thread in about `ms` milliseconds. jvui's timers only run
inside jvui's loop, so a backend with a loop of its own has to lend its own."
[ms f]
((op :after! (fn [_ _] nil)) ms f))
(defn quit!
"Close the window."
[]
((op :quit! (fn [] nil))))
(defn pick-image!
"Open the platform's picture chooser; true when there is one and it opened."
[]
((op :pick-image! (fn [] false))))
(defn picked-image!
"Write the chosen picture to `path`; true once, when one has been chosen."
[path]
((op :picked-image! (fn [_] nil)) path))
(defn clipboard-image-png!
"Write the picture on the clipboard to `path` as PNG; true when there was one.
jvui reads it through its own window, so another backend lends its own."
[path]
((op :clipboard-image-png! (fn [_] false)) path))
(defn return-url
"The link that brings the app back to the front once the browser is done, or
nil where the browser never covered it. `frq://auth` is the manifest's own
scheme; the activity is `singleTask`, so it is the running app that comes
forward rather than a second copy of it."
[]
(when (android?) "frq://auth"))
;; ------------------------------------------------------- the window itself
(defn every!
"Run `f` on the UI thread every `ms` milliseconds. Returns a timer id where
the backend has one to give."
[ms f]
((op :every! (fn [_ _] nil)) ms f))
(defn screen-size
"The window's content size as `[w h]`, or `[0 0]` under a backend with no
window — which the callers already treat as \"do not lay anything out yet\"."
[]
((op :screen-size (fn [] [0 0]))))
;; --------------------------------------------------------------- video
;; Call frames are pixels pushed straight into a backend texture, never a jolt
;; value — see `frq.av/pump-frames!`. Only a backend with a GPU surface can
;; take them, so the default is to drop them on the floor, which is what
;; `:av? false` at the entry point already says in the other direction.
(defn frame-rgba!
"Hand one decoded frame to the backend's texture for `key`."
[key w h rgba]
((op :frame-rgba! (fn [_ _ _ _] nil)) key w h rgba))
(defn frame-drop!
"Forget the texture for `key`."
[key]
((op :frame-drop! (fn [_] nil)) key))
|