react-x11 2.14.0 → 2.15.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.
@@ -16,6 +16,7 @@
16
16
  // | `minimized` `maximized` `fullscreen` `states` | `_NET_WM_STATE`, as ntk's `statechange` event |
17
17
  // | `obscured` | VisibilityNotify |
18
18
  // | `desktop` | `_NET_WM_DESKTOP` |
19
+ // | `presenting` | the Wayland frame clock, where the backend has one |
19
20
  //
20
21
  // Nothing here is asked for until something subscribes. `_NET_WM_STATE`
21
22
  // costs a PropertyChange selection and a round trip per change,
@@ -35,6 +36,18 @@
35
36
  // is the field to branch on. An animation paused on `visible` stops when the
36
37
  // window is minimized everywhere, and additionally when it is buried on a
37
38
  // desktop with no compositor.
39
+ //
40
+ // ## Wayland answers the same question from the other end
41
+ //
42
+ // A Wayland compositor never says a surface is covered either — but it stops
43
+ // sending it frame callbacks, and the backend's frame loop notices the
44
+ // silence (`src/wayland/framewatch.js`, #567). That arrives here as
45
+ // `presenting`, which folds into `visible` the way `minimized` does: a
46
+ // surface the compositor has stopped scheduling frames for is one nobody is
47
+ // looking at, and an animation paused on `visible` is right to stop. It
48
+ // costs a couple of seconds of silence to be sure, so it is a signal to stop
49
+ // work on, not a frame-accurate one — and it is `true` on every backend
50
+ // whose windows have no such signal, X11 and Cocoa among them.
38
51
 
39
52
  import { useCallback, useSyncExternalStore } from 'react';
40
53
 
@@ -62,6 +75,7 @@ const UNKNOWN = Object.freeze({
62
75
  focused: true,
63
76
  visible: true,
64
77
  obscured: false,
78
+ presenting: true,
65
79
  minimized: false,
66
80
  maximized: false,
67
81
  fullscreen: false,
@@ -75,6 +89,7 @@ const SAME = (a, b) =>
75
89
  a.focused === b.focused &&
76
90
  a.visible === b.visible &&
77
91
  a.obscured === b.obscured &&
92
+ a.presenting === b.presenting &&
78
93
  a.minimized === b.minimized &&
79
94
  a.maximized === b.maximized &&
80
95
  a.fullscreen === b.fullscreen &&
@@ -119,7 +134,7 @@ class WindowStateSession {
119
134
  });
120
135
  const settled = Object.freeze({
121
136
  ...next,
122
- visible: !next.minimized && !next.obscured,
137
+ visible: !next.minimized && !next.obscured && next.presenting,
123
138
  });
124
139
  if (SAME(settled, this.snapshot)) return;
125
140
  this.snapshot = settled;
@@ -262,9 +277,33 @@ function arm(session) {
262
277
  );
263
278
  }
264
279
 
280
+ watchPresenting(session);
265
281
  watchVisibility(session);
266
282
  }
267
283
 
284
+ /**
285
+ * The Wayland backend's frame-loop liveness, where the window has it — the
286
+ * X11 and Cocoa windows do not, and the absence of the method is the answer
287
+ * (`presenting` stays true, as it does on a backend whose frames are paced
288
+ * by anything but the compositor's goodwill).
289
+ *
290
+ * Nothing is selected and nothing is asked: the window is already watching
291
+ * its own frame callbacks, so this is a listener on a signal that exists
292
+ * whether or not a component ever reads it.
293
+ */
294
+ function watchPresenting(session) {
295
+ const wnd = session.node.window;
296
+ if (typeof wnd?.onPresentingChange !== 'function') return;
297
+ session._offs.push(
298
+ wnd.onPresentingChange((presenting) => {
299
+ if (!session.stopped) session.publish({ presenting });
300
+ }),
301
+ );
302
+ // Whatever it is now: a window can have parked before anything subscribed.
303
+ if (typeof wnd.presenting === 'boolean')
304
+ session.publish({ presenting: wnd.presenting });
305
+ }
306
+
268
307
  function readDesktop(session) {
269
308
  Promise.resolve(
270
309
  session.node.window?.getProperty?.(DESKTOP_PROPERTY, { as: 'numbers' }),
@@ -370,6 +409,7 @@ export function endWindowState(node) {
370
409
  * | `maximized` | both axes; one axis alone shows up in `states` |
371
410
  * | `fullscreen` | what the WM actually did, not what `<window fullscreen>` asked for |
372
411
  * | `obscured` | fully covered by other windows — **always false under a compositor** |
412
+ * | `presenting` | the display is still scheduling frames for this window — Wayland; always true elsewhere |
373
413
  * | `states` | the raw `_NET_WM_STATE` names, e.g. `['maximized_vert', 'focused']` |
374
414
  * | `desktop` | the workspace index, or null (a sticky window shows up in `states`) |
375
415
  *