react-x11 2.15.2 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/package.json +4 -3
- package/src/Reconciler.js +85 -22
- package/src/anchor.js +60 -18
- package/src/application.js +25 -1
- package/src/capabilities.js +349 -0
- package/src/cocoa/app.js +28 -9
- package/src/cocoa/context2d.js +139 -6
- package/src/cocoa/fonts.js +78 -0
- package/src/cocoa/presenter.js +17 -0
- package/src/cocoa/promotion.js +20 -0
- package/src/cocoa/relaunch.js +8 -3
- package/src/cocoa/symbols.js +64 -0
- package/src/cocoa/threaded.js +24 -4
- package/src/cocoa/window.js +362 -139
- package/src/components/ProgressBar.js +1 -1
- package/src/components/Slider.js +72 -39
- package/src/components/anchor.js +7 -2
- package/src/components/index.js +1 -0
- package/src/components/theme.js +32 -28
- package/src/dbusmenuexport.js +243 -0
- package/src/desktopcapabilityhooks.js +160 -0
- package/src/filedialoghooks.js +3 -5
- package/src/frame/childmain.js +8 -20
- package/src/frame/env.js +2 -10
- package/src/globalmenu.js +3 -205
- package/src/icontheme.js +240 -0
- package/src/imagesource.js +98 -3
- package/src/index.d.ts +1 -0
- package/src/index.js +11 -2
- package/src/launcher.js +235 -32
- package/src/launcherhooks.js +47 -28
- package/src/node.d.ts +7 -0
- package/src/nodes/animation.js +17 -47
- package/src/nodes/cascade.js +17 -2
- package/src/nodes/image.js +65 -2
- package/src/nodes/kinds.js +12 -0
- package/src/nodes/layout.js +5 -1
- package/src/nodes/node.js +17 -3
- package/src/nodes/paint.js +117 -0
- package/src/nodes/scope.js +259 -0
- package/src/nodes/scrollable.js +53 -6
- package/src/nodes/text.js +2 -0
- package/src/nodes/textarea.js +1 -1
- package/src/nodes/textinput.js +1 -1
- package/src/nodes/window/anchoring.js +45 -18
- package/src/nodes/window/flush.js +6 -5
- package/src/nodes/window/popup.js +10 -0
- package/src/nodes/window/size.js +40 -2
- package/src/nodes/window/window.js +41 -14
- package/src/registry.js +2 -1
- package/src/settings.js +332 -0
- package/src/statusnotifier.js +752 -0
- package/src/styles.js +212 -8
- package/src/symbols.js +200 -0
- package/src/testing/mock-app.js +10 -0
- package/src/trayhooks.js +193 -29
- package/src/types/capabilities.d.ts +139 -0
- package/src/types/components.d.ts +33 -0
- package/src/types/elements.d.ts +57 -6
- package/src/types/launcher.d.ts +50 -4
- package/src/types/style.d.ts +57 -0
- package/src/types/system.d.ts +104 -0
- package/src/types/tray.d.ts +64 -6
package/src/icontheme.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// The freedesktop icon theme: an icon found by name the way the Icon Theme
|
|
2
|
+
// Specification finds it, for a symbol drawn inside a window on Linux (#591).
|
|
3
|
+
//
|
|
4
|
+
// A theme is a directory of the same name under any of the base directories
|
|
5
|
+
// — `~/.icons`, `$XDG_DATA_HOME/icons`, each `$XDG_DATA_DIRS/icons` — with an
|
|
6
|
+
// `index.theme` saying which of its subdirectories hold which sizes and which
|
|
7
|
+
// themes it inherits from. A lookup tries the user's theme, then everything
|
|
8
|
+
// it inherits, then `hicolor`, the theme every other one falls back on, and
|
|
9
|
+
// last the loose files in `/usr/share/pixmaps`. Inside a theme it takes the
|
|
10
|
+
// first directory whose size matches, and failing that the closest one.
|
|
11
|
+
//
|
|
12
|
+
// The directories are listed rather than probed file by file: one `readdir`
|
|
13
|
+
// per directory, once, answers every name looked up there afterwards, where
|
|
14
|
+
// a `stat` per name per directory per theme is thousands of calls for a
|
|
15
|
+
// toolbar. Both run synchronously, since a layout pass cannot wait — the
|
|
16
|
+
// first lookup in a theme pays for the listing, and later ones are a `Map`.
|
|
17
|
+
|
|
18
|
+
import * as nodeFs from 'node:fs';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
/** The formats a lookup answers with, in the specification's order. XPM is
|
|
23
|
+
* left out: nothing here decodes it. */
|
|
24
|
+
const EXTENSIONS = ['png', 'svg'];
|
|
25
|
+
|
|
26
|
+
/** Every base directory the specification searches, in its order. */
|
|
27
|
+
export function iconBaseDirs(env = process.env, home = homedir()) {
|
|
28
|
+
const dataHome = env.XDG_DATA_HOME || join(home, '.local', 'share');
|
|
29
|
+
const dataDirs = (env.XDG_DATA_DIRS || '/usr/local/share:/usr/share')
|
|
30
|
+
.split(':')
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
return [
|
|
33
|
+
join(home, '.icons'),
|
|
34
|
+
join(dataHome, 'icons'),
|
|
35
|
+
...dataDirs.map((dir) => join(dir, 'icons')),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Where loose icons with no theme live, searched last. */
|
|
40
|
+
export const PIXMAP_DIRS = ['/usr/share/pixmaps'];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `index.theme`'s keys, section by section — the desktop-entry format, which
|
|
44
|
+
* is an INI file with `#` comments. Values stay strings.
|
|
45
|
+
*/
|
|
46
|
+
export function parseIndexTheme(text) {
|
|
47
|
+
const sections = new Map();
|
|
48
|
+
let current = null;
|
|
49
|
+
for (const raw of String(text).split(/\r?\n/)) {
|
|
50
|
+
const line = raw.trim();
|
|
51
|
+
if (!line || line.startsWith('#')) continue;
|
|
52
|
+
const header = /^\[(.+)\]$/.exec(line);
|
|
53
|
+
if (header) {
|
|
54
|
+
current = new Map();
|
|
55
|
+
sections.set(header[1], current);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const eq = line.indexOf('=');
|
|
59
|
+
if (current && eq > 0) {
|
|
60
|
+
current.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return sections;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const list = (value) =>
|
|
67
|
+
(value ?? '')
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((s) => s.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
|
|
72
|
+
const int = (value, fallback) => {
|
|
73
|
+
const n = Number.parseInt(value, 10);
|
|
74
|
+
return Number.isFinite(n) ? n : fallback;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** One subdirectory's size rules, with the specification's defaults. */
|
|
78
|
+
function directoryRules(keys) {
|
|
79
|
+
const size = int(keys?.get('Size'), 0);
|
|
80
|
+
return {
|
|
81
|
+
size,
|
|
82
|
+
scale: int(keys?.get('Scale'), 1),
|
|
83
|
+
type: keys?.get('Type') ?? 'Threshold',
|
|
84
|
+
minSize: int(keys?.get('MinSize'), size),
|
|
85
|
+
maxSize: int(keys?.get('MaxSize'), size),
|
|
86
|
+
threshold: int(keys?.get('Threshold'), 2),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The specification's DirectoryMatchesSize. */
|
|
91
|
+
function matchesSize(dir, size, scale) {
|
|
92
|
+
if (dir.scale !== scale) return false;
|
|
93
|
+
if (dir.type === 'Fixed') return dir.size === size;
|
|
94
|
+
if (dir.type === 'Scalable')
|
|
95
|
+
return dir.minSize <= size && size <= dir.maxSize;
|
|
96
|
+
return dir.size - dir.threshold <= size && size <= dir.size + dir.threshold;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The specification's DirectorySizeDistance, with its Threshold branch's
|
|
100
|
+
* typos read as what they mean: the threshold's bounds, times the scale. */
|
|
101
|
+
function sizeDistance(dir, size, scale) {
|
|
102
|
+
const want = size * scale;
|
|
103
|
+
if (dir.type === 'Fixed') return Math.abs(dir.size * dir.scale - want);
|
|
104
|
+
const low = dir.type === 'Scalable' ? dir.minSize : dir.size - dir.threshold;
|
|
105
|
+
const high = dir.type === 'Scalable' ? dir.maxSize : dir.size + dir.threshold;
|
|
106
|
+
if (want < low * dir.scale) return low * dir.scale - want;
|
|
107
|
+
if (want > high * dir.scale) return want - high * dir.scale;
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Icon lookup in one theme and everything under it. `find(name, size,
|
|
113
|
+
* scale)` answers an absolute path, or null.
|
|
114
|
+
*/
|
|
115
|
+
export class IconTheme {
|
|
116
|
+
constructor({
|
|
117
|
+
theme = 'hicolor',
|
|
118
|
+
baseDirs = iconBaseDirs(),
|
|
119
|
+
pixmapDirs = PIXMAP_DIRS,
|
|
120
|
+
fs = nodeFs,
|
|
121
|
+
} = {}) {
|
|
122
|
+
this.theme = theme;
|
|
123
|
+
this.baseDirs = baseDirs;
|
|
124
|
+
this.pixmapDirs = pixmapDirs;
|
|
125
|
+
this.fs = fs;
|
|
126
|
+
this._themes = new Map(); // name -> { dirs, inherits } | null
|
|
127
|
+
this._listings = new Map(); // absolute dir -> Set of file names | null
|
|
128
|
+
this._found = new Map(); // name|size|scale -> path | null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
find(name, size, scale = 1) {
|
|
132
|
+
const key = `${name}\u0000${size}\u0000${scale}`;
|
|
133
|
+
if (this._found.has(key)) return this._found.get(key);
|
|
134
|
+
const path =
|
|
135
|
+
this._inTheme(name, size, scale, this.theme, new Set()) ??
|
|
136
|
+
(this.theme === 'hicolor'
|
|
137
|
+
? null
|
|
138
|
+
: this._inTheme(name, size, scale, 'hicolor', new Set())) ??
|
|
139
|
+
this._loose(name);
|
|
140
|
+
this._found.set(key, path);
|
|
141
|
+
return path;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** FindIconHelper: this theme, then the ones it inherits, depth first. */
|
|
145
|
+
_inTheme(name, size, scale, theme, seen) {
|
|
146
|
+
if (seen.has(theme)) return null;
|
|
147
|
+
seen.add(theme);
|
|
148
|
+
const index = this._index(theme);
|
|
149
|
+
if (!index) return null;
|
|
150
|
+
const found = this._lookup(name, size, scale, theme, index);
|
|
151
|
+
if (found) return found;
|
|
152
|
+
for (const parent of index.inherits) {
|
|
153
|
+
const inherited = this._inTheme(name, size, scale, parent, seen);
|
|
154
|
+
if (inherited) return inherited;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** LookupIcon: a directory of the right size, else the closest one. */
|
|
160
|
+
_lookup(name, size, scale, theme, index) {
|
|
161
|
+
let closest = null;
|
|
162
|
+
let distance = Infinity;
|
|
163
|
+
for (const dir of index.dirs) {
|
|
164
|
+
const fits = matchesSize(dir.rules, size, scale);
|
|
165
|
+
const d = fits ? 0 : sizeDistance(dir.rules, size, scale);
|
|
166
|
+
if (!fits && d >= distance) continue;
|
|
167
|
+
for (const base of this.baseDirs) {
|
|
168
|
+
const at = join(base, theme, dir.path);
|
|
169
|
+
const file = this._fileIn(at, name);
|
|
170
|
+
if (!file) continue;
|
|
171
|
+
if (fits) return file;
|
|
172
|
+
closest = file;
|
|
173
|
+
distance = d;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return closest;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The loose files: an icon with no theme at all. */
|
|
181
|
+
_loose(name) {
|
|
182
|
+
for (const dir of this.pixmapDirs) {
|
|
183
|
+
const file = this._fileIn(dir, name);
|
|
184
|
+
if (file) return file;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_fileIn(dir, name) {
|
|
190
|
+
const names = this._listing(dir);
|
|
191
|
+
if (!names) return null;
|
|
192
|
+
for (const ext of EXTENSIONS) {
|
|
193
|
+
const file = `${name}.${ext}`;
|
|
194
|
+
if (names.has(file)) return join(dir, file);
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
_listing(dir) {
|
|
200
|
+
if (this._listings.has(dir)) return this._listings.get(dir);
|
|
201
|
+
let names = null;
|
|
202
|
+
try {
|
|
203
|
+
names = new Set(this.fs.readdirSync(dir));
|
|
204
|
+
} catch {
|
|
205
|
+
// not there, which is most directories in most base directories
|
|
206
|
+
}
|
|
207
|
+
this._listings.set(dir, names);
|
|
208
|
+
return names;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A theme's `index.theme`, from the first base directory that has one. */
|
|
212
|
+
_index(theme) {
|
|
213
|
+
if (this._themes.has(theme)) return this._themes.get(theme);
|
|
214
|
+
let index = null;
|
|
215
|
+
for (const base of this.baseDirs) {
|
|
216
|
+
let text;
|
|
217
|
+
try {
|
|
218
|
+
text = this.fs.readFileSync(join(base, theme, 'index.theme'), 'utf8');
|
|
219
|
+
} catch {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const sections = parseIndexTheme(text);
|
|
223
|
+
const head = sections.get('Icon Theme');
|
|
224
|
+
const names = [
|
|
225
|
+
...list(head?.get('Directories')),
|
|
226
|
+
...list(head?.get('ScaledDirectories')),
|
|
227
|
+
];
|
|
228
|
+
index = {
|
|
229
|
+
inherits: list(head?.get('Inherits')),
|
|
230
|
+
dirs: [...new Set(names)].map((path) => ({
|
|
231
|
+
path,
|
|
232
|
+
rules: directoryRules(sections.get(path)),
|
|
233
|
+
})),
|
|
234
|
+
};
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
this._themes.set(theme, index);
|
|
238
|
+
return index;
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/imagesource.js
CHANGED
|
@@ -44,6 +44,73 @@ export function isPathImageSource(src) {
|
|
|
44
44
|
export const toLoadablePath = (src) =>
|
|
45
45
|
typeof src === 'string' || src instanceof URL ? src : new URL(src.href);
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* A symbol by name: `{ symbol, weight?, scale?, variableValue? }` — the
|
|
49
|
+
* platform's own icons, drawn in the text colour (#591, src/symbols.js). Not
|
|
50
|
+
* pixels at all, so none of the decoding or caching below applies: the node
|
|
51
|
+
* asks the platform to draw the name at paint.
|
|
52
|
+
*/
|
|
53
|
+
export function isSymbolImageSource(src) {
|
|
54
|
+
return (
|
|
55
|
+
src != null &&
|
|
56
|
+
typeof src === 'object' &&
|
|
57
|
+
!(src instanceof Uint8Array) &&
|
|
58
|
+
'symbol' in src
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SYMBOL_SCALES = new Set(['small', 'medium', 'large']);
|
|
63
|
+
|
|
64
|
+
function validateSymbolSource(src) {
|
|
65
|
+
const at = `react-x11: <image src={{ symbol: ${JSON.stringify(src.symbol)} }}>`;
|
|
66
|
+
if (typeof src.symbol !== 'string' || src.symbol === '') {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`${at} needs a name: an SF Symbol on macOS, like 'speaker.wave.3.fill', ` +
|
|
69
|
+
"or an icon theme's name on Linux, like 'audio-volume-high'.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const { weight, scale, variableValue } = src;
|
|
73
|
+
if (
|
|
74
|
+
weight !== undefined &&
|
|
75
|
+
!(
|
|
76
|
+
weight === 'normal' ||
|
|
77
|
+
weight === 'bold' ||
|
|
78
|
+
(typeof weight === 'number' && weight >= 1 && weight <= 1000)
|
|
79
|
+
)
|
|
80
|
+
) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`${at} has weight ${JSON.stringify(weight)}, expected what fontWeight ` +
|
|
83
|
+
"takes — 'normal', 'bold' or a number from 1 to 1000.",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (scale !== undefined && !SYMBOL_SCALES.has(scale)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${at} has scale ${JSON.stringify(scale)}, expected 'small', 'medium' ` +
|
|
89
|
+
"or 'large'.",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
variableValue !== undefined &&
|
|
94
|
+
!(
|
|
95
|
+
typeof variableValue === 'number' &&
|
|
96
|
+
variableValue >= 0 &&
|
|
97
|
+
variableValue <= 1
|
|
98
|
+
)
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`${at} has variableValue ${JSON.stringify(variableValue)}, expected a ` +
|
|
102
|
+
'number from 0 to 1.',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Two symbol sources naming the same drawing, however fresh the objects. */
|
|
108
|
+
const sameSymbol = (a, b) =>
|
|
109
|
+
a.symbol === b.symbol &&
|
|
110
|
+
a.weight === b.weight &&
|
|
111
|
+
a.scale === b.scale &&
|
|
112
|
+
a.variableValue === b.variableValue;
|
|
113
|
+
|
|
47
114
|
/** Raw straight-RGBA pixels: `{ width, height, data }` — the shape
|
|
48
115
|
* `getImageData` hands back. (A `Buffer` of encoded bytes is a `Uint8Array`
|
|
49
116
|
* subclass, so the two byte forms are one check elsewhere.) */
|
|
@@ -54,6 +121,7 @@ export function isRawImageSource(src) {
|
|
|
54
121
|
!(src instanceof Uint8Array) &&
|
|
55
122
|
!isPathImageSource(src) &&
|
|
56
123
|
!isDirectImageSource(src) &&
|
|
124
|
+
!isSymbolImageSource(src) &&
|
|
57
125
|
'data' in src
|
|
58
126
|
);
|
|
59
127
|
}
|
|
@@ -70,7 +138,8 @@ const describe = (value) =>
|
|
|
70
138
|
/** Stated once, so every error lists the same set of accepted forms. */
|
|
71
139
|
const SRC_FORMS =
|
|
72
140
|
'a file path or file URL (PNG/JPEG), encoded PNG/JPEG bytes (Buffer or ' +
|
|
73
|
-
'Uint8Array), raw RGBA ({ width, height, data }),
|
|
141
|
+
'Uint8Array), raw RGBA ({ width, height, data }), an ntk Image/Surface, ' +
|
|
142
|
+
"or a symbol by name ({ symbol: 'speaker.wave.3.fill' })";
|
|
74
143
|
|
|
75
144
|
function validateServerSource(kind, desc) {
|
|
76
145
|
const shape =
|
|
@@ -163,6 +232,16 @@ export function validateImageProps(props) {
|
|
|
163
232
|
if (props.drawable != null) validateServerSource('drawable', props.drawable);
|
|
164
233
|
const src = props.src;
|
|
165
234
|
if (src == null) return;
|
|
235
|
+
if (isSymbolImageSource(src)) {
|
|
236
|
+
if (props.cacheKey != null) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
'react-x11: <image cacheKey> names decoded pixels, and a symbol is ' +
|
|
239
|
+
'drawn by name, not decoded — there is nothing to cache. Drop the ' +
|
|
240
|
+
'cacheKey.',
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return validateSymbolSource(src);
|
|
244
|
+
}
|
|
166
245
|
if (isPathImageSource(src)) return;
|
|
167
246
|
if (src instanceof Uint8Array) return;
|
|
168
247
|
if (isDirectImageSource(src)) return;
|
|
@@ -207,6 +286,9 @@ export function imageSourceChanged(next, prev) {
|
|
|
207
286
|
if (next.cacheKey !== prev.cacheKey) return true;
|
|
208
287
|
if (next.src === prev.src) return false;
|
|
209
288
|
if ((next.src == null) !== (prev.src == null)) return true;
|
|
289
|
+
if (isSymbolImageSource(next.src) && isSymbolImageSource(prev.src)) {
|
|
290
|
+
return !sameSymbol(next.src, prev.src);
|
|
291
|
+
}
|
|
210
292
|
if (isDirectImageSource(next.src) || isDirectImageSource(prev.src))
|
|
211
293
|
return true;
|
|
212
294
|
return next.cacheKey == null;
|
|
@@ -250,7 +332,7 @@ export function acquireImageSource(app, key, load) {
|
|
|
250
332
|
entry.promise = null;
|
|
251
333
|
// every holder unmounted while it decoded — free, don't adopt
|
|
252
334
|
if (entry.released) {
|
|
253
|
-
image
|
|
335
|
+
freeImage(app, image);
|
|
254
336
|
return null;
|
|
255
337
|
}
|
|
256
338
|
entry.image = image;
|
|
@@ -269,10 +351,23 @@ export function releaseImageSource(app, entry) {
|
|
|
269
351
|
if (--entry.refs > 0) return;
|
|
270
352
|
sourceCaches.get(app)?.delete(entry.key);
|
|
271
353
|
entry.released = true;
|
|
272
|
-
entry.image
|
|
354
|
+
freeImage(app, entry.image);
|
|
273
355
|
entry.image = null;
|
|
274
356
|
}
|
|
275
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Free an `Image` this module or a node owns, on every backend it may have
|
|
360
|
+
* been drawn on: ntk's `destroy()` frees the pixmaps it uploaded per X
|
|
361
|
+
* connection, and knows nothing of an upload a backend keeps for itself —
|
|
362
|
+
* the Cocoa app's CG bitmap — which that app's `releaseImage` seam frees.
|
|
363
|
+
* An X app has no such seam, so there it is `destroy()` alone.
|
|
364
|
+
*/
|
|
365
|
+
export function freeImage(app, image) {
|
|
366
|
+
if (!image) return;
|
|
367
|
+
image.destroy();
|
|
368
|
+
app?.releaseImage?.(image);
|
|
369
|
+
}
|
|
370
|
+
|
|
276
371
|
// --- server-side sources ----------------------------------------------------
|
|
277
372
|
|
|
278
373
|
/** RENDER's depth-implied standard formats — the ones a drawable can be
|
package/src/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from './types/screencolor.js';
|
|
|
25
25
|
export * from './types/appearance.js';
|
|
26
26
|
export * from './types/fonts.js';
|
|
27
27
|
export * from './types/system.js';
|
|
28
|
+
export * from './types/capabilities.js';
|
|
28
29
|
export * from './types/launcher.js';
|
|
29
30
|
export * from './types/tray.js';
|
|
30
31
|
export * from './types/permissions.js';
|
package/src/index.js
CHANGED
|
@@ -14,9 +14,15 @@ export {
|
|
|
14
14
|
registerApplication,
|
|
15
15
|
} from './application.js';
|
|
16
16
|
export { useAppActivate, useAppOpen } from './apphooks.js';
|
|
17
|
-
export { setBadge } from './launcher.js';
|
|
18
|
-
export { useBadge, useDockMenu } from './launcherhooks.js';
|
|
17
|
+
export { setBadge, setProgress, setQuicklist, setUrgent } from './launcher.js';
|
|
18
|
+
export { useBadge, useDockMenu, useProgress } from './launcherhooks.js';
|
|
19
19
|
export { useTray } from './trayhooks.js';
|
|
20
|
+
export {
|
|
21
|
+
CAPABILITIES,
|
|
22
|
+
NO_CAPABILITY,
|
|
23
|
+
desktopCapability,
|
|
24
|
+
} from './capabilities.js';
|
|
25
|
+
export { useDesktopCapability } from './desktopcapabilityhooks.js';
|
|
20
26
|
export {
|
|
21
27
|
NoPermissionServiceError,
|
|
22
28
|
openPrivacySettings,
|
|
@@ -87,6 +93,8 @@ export { useKeyboardState } from './keyboardstatehooks.js';
|
|
|
87
93
|
export { matchesShortcut } from './accelerators.js';
|
|
88
94
|
export { useAccelerator } from './acceleratorhooks.js';
|
|
89
95
|
export { useDesktopSettings } from './desktopsettingshooks.js';
|
|
96
|
+
// what the app itself remembers between launches (#592)
|
|
97
|
+
export { createSettings } from './settings.js';
|
|
90
98
|
export { loadFont, openFont } from './fonts.js';
|
|
91
99
|
export { useFont } from './fonthooks.js';
|
|
92
100
|
export { systemLocale } from './locale.js';
|
|
@@ -121,6 +129,7 @@ export {
|
|
|
121
129
|
useAnchorTracking,
|
|
122
130
|
anchorArea,
|
|
123
131
|
anchorRect,
|
|
132
|
+
anchorScreenRect,
|
|
124
133
|
centerRect,
|
|
125
134
|
screenRect,
|
|
126
135
|
} from './components/index.js';
|