@gtkx/testing 1.0.0-rc.4 → 1.1.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.
Files changed (212) hide show
  1. package/README.md +5 -5
  2. package/dist/accessible-native.d.ts +16 -0
  3. package/dist/accessible-native.d.ts.map +1 -0
  4. package/dist/accessible-native.js +206 -0
  5. package/dist/accessible-native.js.map +1 -0
  6. package/dist/act.d.ts +1 -3
  7. package/dist/act.d.ts.map +1 -1
  8. package/dist/act.js +1 -1
  9. package/dist/act.js.map +1 -1
  10. package/dist/build-queries.d.ts +27 -10
  11. package/dist/build-queries.d.ts.map +1 -1
  12. package/dist/build-queries.js +22 -24
  13. package/dist/build-queries.js.map +1 -1
  14. package/dist/cleanup-registry.d.ts +1 -1
  15. package/dist/cleanup-registry.d.ts.map +1 -1
  16. package/dist/cleanup-registry.js.map +1 -1
  17. package/dist/config.d.ts +8 -1
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +1 -0
  20. package/dist/config.js.map +1 -1
  21. package/dist/errors.d.ts +10 -1
  22. package/dist/errors.d.ts.map +1 -1
  23. package/dist/errors.js +10 -1
  24. package/dist/errors.js.map +1 -1
  25. package/dist/fire-event.d.ts +35 -6
  26. package/dist/fire-event.d.ts.map +1 -1
  27. package/dist/fire-event.js +23 -7
  28. package/dist/fire-event.js.map +1 -1
  29. package/dist/frame-sync.d.ts +3 -2
  30. package/dist/frame-sync.d.ts.map +1 -1
  31. package/dist/frame-sync.js +19 -9
  32. package/dist/frame-sync.js.map +1 -1
  33. package/dist/harness-window.d.ts +5 -0
  34. package/dist/harness-window.d.ts.map +1 -0
  35. package/dist/harness-window.js +17 -0
  36. package/dist/harness-window.js.map +1 -0
  37. package/dist/index.d.ts +14 -7
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +9 -5
  40. package/dist/index.js.map +1 -1
  41. package/dist/internal.d.ts +5 -0
  42. package/dist/internal.d.ts.map +1 -0
  43. package/dist/internal.js +5 -0
  44. package/dist/internal.js.map +1 -0
  45. package/dist/matchers.d.ts +88 -46
  46. package/dist/matchers.d.ts.map +1 -1
  47. package/dist/matchers.js +178 -20
  48. package/dist/matchers.js.map +1 -1
  49. package/dist/pretty-format.d.ts +2 -0
  50. package/dist/pretty-format.d.ts.map +1 -0
  51. package/dist/pretty-format.js +2 -0
  52. package/dist/pretty-format.js.map +1 -0
  53. package/dist/pretty-widget.d.ts +5 -2
  54. package/dist/pretty-widget.d.ts.map +1 -1
  55. package/dist/pretty-widget.js +75 -57
  56. package/dist/pretty-widget.js.map +1 -1
  57. package/dist/production.d.ts +8 -0
  58. package/dist/production.d.ts.map +1 -0
  59. package/dist/production.js +34 -0
  60. package/dist/production.js.map +1 -0
  61. package/dist/queries.d.ts +98 -56
  62. package/dist/queries.d.ts.map +1 -1
  63. package/dist/queries.js +123 -83
  64. package/dist/queries.js.map +1 -1
  65. package/dist/query-helpers.d.ts +44 -0
  66. package/dist/query-helpers.d.ts.map +1 -0
  67. package/dist/query-helpers.js +57 -0
  68. package/dist/query-helpers.js.map +1 -0
  69. package/dist/render-children.d.ts +9 -0
  70. package/dist/render-children.d.ts.map +1 -0
  71. package/dist/render-children.js +11 -0
  72. package/dist/render-children.js.map +1 -0
  73. package/dist/render.d.ts +12 -1
  74. package/dist/render.d.ts.map +1 -1
  75. package/dist/render.js +52 -16
  76. package/dist/render.js.map +1 -1
  77. package/dist/role-helpers.d.ts +12 -3
  78. package/dist/role-helpers.d.ts.map +1 -1
  79. package/dist/role-helpers.js +15 -5
  80. package/dist/role-helpers.js.map +1 -1
  81. package/dist/role-naming.d.ts +5 -0
  82. package/dist/role-naming.d.ts.map +1 -0
  83. package/dist/role-naming.js +96 -0
  84. package/dist/role-naming.js.map +1 -0
  85. package/dist/screenshot.d.ts +12 -20
  86. package/dist/screenshot.d.ts.map +1 -1
  87. package/dist/screenshot.js +179 -93
  88. package/dist/screenshot.js.map +1 -1
  89. package/dist/timers.d.ts +9 -0
  90. package/dist/timers.d.ts.map +1 -0
  91. package/dist/timers.js +21 -0
  92. package/dist/timers.js.map +1 -0
  93. package/dist/traversal.d.ts +7 -1
  94. package/dist/traversal.d.ts.map +1 -1
  95. package/dist/traversal.js +40 -6
  96. package/dist/traversal.js.map +1 -1
  97. package/dist/types.d.ts +32 -15
  98. package/dist/types.d.ts.map +1 -1
  99. package/dist/types.js.map +1 -1
  100. package/dist/user-event/adjustment.d.ts +3 -0
  101. package/dist/user-event/adjustment.d.ts.map +1 -1
  102. package/dist/user-event/adjustment.js +25 -3
  103. package/dist/user-event/adjustment.js.map +1 -1
  104. package/dist/user-event/click.d.ts +44 -8
  105. package/dist/user-event/click.d.ts.map +1 -1
  106. package/dist/user-event/click.js +176 -44
  107. package/dist/user-event/click.js.map +1 -1
  108. package/dist/user-event/column-header-click.d.ts +4 -0
  109. package/dist/user-event/column-header-click.d.ts.map +1 -0
  110. package/dist/user-event/column-header-click.js +55 -0
  111. package/dist/user-event/column-header-click.js.map +1 -0
  112. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  113. package/dist/user-event/event-wrapper.js +34 -20
  114. package/dist/user-event/event-wrapper.js.map +1 -1
  115. package/dist/user-event/index.d.ts +80 -13
  116. package/dist/user-event/index.d.ts.map +1 -1
  117. package/dist/user-event/index.js +44 -26
  118. package/dist/user-event/index.js.map +1 -1
  119. package/dist/user-event/indexed-children.d.ts +10 -0
  120. package/dist/user-event/indexed-children.d.ts.map +1 -0
  121. package/dist/user-event/indexed-children.js +50 -0
  122. package/dist/user-event/indexed-children.js.map +1 -0
  123. package/dist/user-event/keyboard.d.ts.map +1 -1
  124. package/dist/user-event/keyboard.js +115 -31
  125. package/dist/user-event/keyboard.js.map +1 -1
  126. package/dist/user-event/list-row-click.d.ts +4 -0
  127. package/dist/user-event/list-row-click.d.ts.map +1 -0
  128. package/dist/user-event/list-row-click.js +38 -0
  129. package/dist/user-event/list-row-click.js.map +1 -0
  130. package/dist/user-event/native-click.d.ts +6 -0
  131. package/dist/user-event/native-click.d.ts.map +1 -0
  132. package/dist/user-event/native-click.js +36 -0
  133. package/dist/user-event/native-click.js.map +1 -0
  134. package/dist/user-event/notebook-tab-click.d.ts +5 -0
  135. package/dist/user-event/notebook-tab-click.d.ts.map +1 -0
  136. package/dist/user-event/notebook-tab-click.js +39 -0
  137. package/dist/user-event/notebook-tab-click.js.map +1 -0
  138. package/dist/user-event/pointer.d.ts +1 -1
  139. package/dist/user-event/pointer.d.ts.map +1 -1
  140. package/dist/user-event/pointer.js +33 -9
  141. package/dist/user-event/pointer.js.map +1 -1
  142. package/dist/user-event/selection.d.ts +11 -8
  143. package/dist/user-event/selection.d.ts.map +1 -1
  144. package/dist/user-event/selection.js +49 -38
  145. package/dist/user-event/selection.js.map +1 -1
  146. package/dist/user-event/text.d.ts +12 -5
  147. package/dist/user-event/text.d.ts.map +1 -1
  148. package/dist/user-event/text.js +151 -46
  149. package/dist/user-event/text.js.map +1 -1
  150. package/dist/wait-for.d.ts.map +1 -1
  151. package/dist/wait-for.js +4 -3
  152. package/dist/wait-for.js.map +1 -1
  153. package/dist/widget-accessible-properties.d.ts +14 -26
  154. package/dist/widget-accessible-properties.d.ts.map +1 -1
  155. package/dist/widget-accessible-properties.js +135 -190
  156. package/dist/widget-accessible-properties.js.map +1 -1
  157. package/dist/widget-getters.d.ts +11 -0
  158. package/dist/widget-getters.d.ts.map +1 -0
  159. package/dist/widget-getters.js +46 -0
  160. package/dist/widget-getters.js.map +1 -0
  161. package/dist/window-state.d.ts +10 -0
  162. package/dist/window-state.d.ts.map +1 -0
  163. package/dist/window-state.js +41 -0
  164. package/dist/window-state.js.map +1 -0
  165. package/dist/within.d.ts +3 -2
  166. package/dist/within.d.ts.map +1 -1
  167. package/dist/within.js +3 -2
  168. package/dist/within.js.map +1 -1
  169. package/package.json +21 -8
  170. package/src/accessible-native.ts +333 -0
  171. package/src/act.ts +1 -1
  172. package/src/build-queries.ts +47 -41
  173. package/src/cleanup-registry.ts +1 -1
  174. package/src/config.ts +10 -1
  175. package/src/errors.ts +12 -1
  176. package/src/fire-event.ts +61 -8
  177. package/src/frame-sync.ts +30 -9
  178. package/src/harness-window.ts +23 -0
  179. package/src/index.ts +18 -11
  180. package/src/internal.ts +12 -0
  181. package/src/matchers.ts +457 -72
  182. package/src/pretty-format.ts +1 -0
  183. package/src/pretty-widget.ts +98 -70
  184. package/src/production.ts +51 -0
  185. package/src/queries.ts +127 -89
  186. package/src/query-helpers.ts +97 -0
  187. package/src/render-children.ts +25 -0
  188. package/src/render.tsx +77 -17
  189. package/src/role-helpers.ts +16 -5
  190. package/src/role-naming.ts +99 -0
  191. package/src/screenshot.ts +266 -104
  192. package/src/timers.ts +31 -0
  193. package/src/traversal.ts +66 -6
  194. package/src/types.ts +34 -17
  195. package/src/user-event/adjustment.ts +31 -3
  196. package/src/user-event/click.ts +248 -52
  197. package/src/user-event/column-header-click.ts +76 -0
  198. package/src/user-event/event-wrapper.ts +41 -23
  199. package/src/user-event/index.ts +130 -40
  200. package/src/user-event/indexed-children.ts +75 -0
  201. package/src/user-event/keyboard.ts +161 -43
  202. package/src/user-event/list-row-click.ts +54 -0
  203. package/src/user-event/native-click.ts +51 -0
  204. package/src/user-event/notebook-tab-click.ts +57 -0
  205. package/src/user-event/pointer.ts +48 -9
  206. package/src/user-event/selection.ts +67 -41
  207. package/src/user-event/text.ts +192 -46
  208. package/src/wait-for.ts +4 -4
  209. package/src/widget-accessible-properties.ts +183 -230
  210. package/src/widget-getters.ts +85 -0
  211. package/src/window-state.ts +70 -0
  212. package/src/within.ts +3 -2
@@ -1,28 +1,20 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
- import type { ScreenshotOptions, ScreenshotResult, WindowSelector } from "./types.js";
2
+ import type { ScreenshotOptions, ScreenshotResult } from "./types.js";
3
3
  /**
4
- * Captures a PNG snapshot of a widget, retrying until it has a paintable size.
4
+ * Captures a PNG snapshot of a widget, retrying until it has a paintable size,
5
+ * and writes the image to `options.path` when one is given, creating any
6
+ * missing parent directories. Popovers open inside the widget render on their
7
+ * own surfaces, so they are composited into the image at their on-screen
8
+ * positions. A display that never presents a frame leaves GTK without a cached
9
+ * render node, so the widget's contents are then snapshotted directly; when
10
+ * even that yields nothing, the failure names the display instead of blaming
11
+ * the widget.
5
12
  *
6
13
  * @param widget The widget to render to an image.
7
- * @param options Optional scale, timeout, and retry interval.
14
+ * @param options Optional scale, timeout, retry interval, and output path.
8
15
  * @returns The base64-encoded PNG data along with its mime type and dimensions.
9
16
  */
10
17
  declare const screenshot: (widget: Gtk.Widget, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
11
- /**
12
- * Prints a clickable file URL for a saved screenshot to the console.
13
- *
14
- * @param filepath Absolute path of the saved screenshot file.
15
- */
16
- declare const logScreenshotPath: (filepath: string) => void;
17
- /**
18
- * Captures a screenshot of a toplevel window, writes it to a temporary file,
19
- * logs its path, and returns the image data.
20
- *
21
- * @param selector Chooses the window by index, title substring, or regular
22
- * expression; defaults to the first toplevel.
23
- * @param options Optional scale, timeout, and retry interval.
24
- * @returns The base64-encoded PNG data along with its mime type and dimensions.
25
- */
26
- declare const captureAndSaveScreenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
27
- export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
18
+ declare const captureScreen: (options?: ScreenshotOptions) => Promise<ScreenshotResult>;
19
+ export { captureScreen, screenshot };
28
20
  //# sourceMappingURL=screenshot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAIpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAuDtF;;;;;;GAMG;AACH,QAAA,MAAM,UAAU,GAAU,QAAQ,GAAG,CAAC,MAAM,EAAE,UAAU,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAWnG,CAAC;AAqEF;;;;GAIG;AACH,QAAA,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,IAE7C,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,wBAAwB,GAC1B,WAAW,cAAc,EACzB,UAAU,iBAAiB,KAC5B,OAAO,CAAC,gBAAgB,CAM1B,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,CAAC"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAGpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA6RtE;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,UAAU,GAAU,QAAQ,GAAG,CAAC,MAAM,EAAE,UAAU,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAkBnG,CAAC;AAYF,QAAA,MAAM,aAAa,GAAU,UAAU,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAC5C,CAAC;AAExC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC"}
@@ -1,55 +1,198 @@
1
+ import * as Gdk from "@gtkx/gi/gdk";
2
+ import * as Graphene from "@gtkx/gi/graphene";
1
3
  import * as Gsk from "@gtkx/gi/gsk";
2
4
  import * as Gtk from "@gtkx/gi/gtk";
3
- import { mkdtempSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
5
+ import { mkdirSync, writeFileSync } from "node:fs";
6
+ import { dirname } from "node:path";
7
+ import { getConfig } from "./config.js";
8
+ import { now } from "./timers.js";
9
+ import { descendants } from "./traversal.js";
6
10
  import { waitFor } from "./wait-for.js";
11
+ import { activeToplevel, mappedToplevels } from "./window-state.js";
7
12
  const DEFAULT_SCREENSHOT_INTERVAL = 10;
13
+ const PRESENTATION_PROBE_MS = 250;
14
+ const NOT_PRESENTING_MESSAGE = "Screenshot failed: the display is not presenting frames to this window, so GTK has no rendered " +
15
+ "content to capture. The window is hidden, minimized, or its compositor is not presenting it; this " +
16
+ "is a display problem, not a widget problem.";
17
+ const EMPTY_WIDGET_HINT = "The display is presenting frames to this window, so the widget itself is empty: it painted nothing.";
18
+ const OTHER_FAILURE_HINT = "The display is presenting frames to this window, so the capture failed for another reason.";
19
+ const NOTHING_ON_SCREEN_MESSAGE = "Nothing is on screen to capture: no toplevel window is mapped";
8
20
  const bytesToBase64 = (bytes) => {
9
21
  return Buffer.from(bytes).toString("base64");
10
22
  };
11
23
  const describeWidgetState = (widget) => `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +
12
24
  `visible=${String(widget.getVisible())}`;
13
- const captureSnapshot = (widget, scale) => {
14
- const paintable = new Gtk.WidgetPaintable({ widget });
25
+ const getMappedPopovers = (widget) => {
26
+ const popovers = [];
27
+ for (const descendant of descendants(widget)) {
28
+ if (descendant instanceof Gtk.Popover && descendant.getMapped()) {
29
+ popovers.push(descendant);
30
+ }
31
+ }
32
+ return popovers;
33
+ };
34
+ const appendPopover = (snapshot, target, popover) => {
35
+ const [hasBounds, bounds] = popover.computeBounds(target);
36
+ const paintable = new Gtk.WidgetPaintable({ widget: popover });
15
37
  const width = paintable.getIntrinsicWidth();
16
38
  const height = paintable.getIntrinsicHeight();
17
- if (width <= 0 || height <= 0) {
18
- throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);
39
+ if (!hasBounds || width <= 0 || height <= 0) {
40
+ return;
41
+ }
42
+ snapshot.save();
43
+ snapshot.translate(new Graphene.Point({ x: bounds.origin.x, y: bounds.origin.y }));
44
+ paintable.snapshot(snapshot, width, height);
45
+ snapshot.restore();
46
+ };
47
+ const appendPopovers = (snapshot, target) => {
48
+ for (const popover of getMappedPopovers(target)) {
49
+ appendPopover(snapshot, target, popover);
50
+ }
51
+ };
52
+ const getSurface = (widget) => widget.getNative()?.getSurface() ?? null;
53
+ const isSurfaceOnScreen = (widget) => getSurface(widget)?.getMapped() ?? false;
54
+ const getFrameClock = (widget) => widget.getRoot()?.getFrameClock() ?? widget.getFrameClock();
55
+ const getFrameCounter = (widget) => getFrameClock(widget)?.getFrameCounter() ?? null;
56
+ const describePresentation = (widget) => `${describeWidgetState(widget)} surfaceMapped=${String(isSurfaceOnScreen(widget))} ` +
57
+ `frameCounter=${String(getFrameCounter(widget))}`;
58
+ const requestFrame = (widget) => {
59
+ getFrameClock(widget)?.requestPhase(Gdk.FrameClockPhase.UPDATE);
60
+ };
61
+ const startFrameProbe = (widget) => {
62
+ requestFrame(widget);
63
+ return { counter: getFrameCounter(widget), startedAt: now() };
64
+ };
65
+ const updateFrameProbe = (widget, probe) => {
66
+ requestFrame(widget);
67
+ if (probe.counter !== null) {
68
+ return;
69
+ }
70
+ const counter = getFrameCounter(widget);
71
+ if (counter !== null) {
72
+ probe.counter = counter;
73
+ probe.startedAt = now();
74
+ }
75
+ };
76
+ const hasFrameAdvanced = (widget, probe) => {
77
+ const counter = getFrameCounter(widget);
78
+ return counter !== null && probe.counter !== null && counter > probe.counter;
79
+ };
80
+ const isPresenting = (widget, probe) => isSurfaceOnScreen(widget) && hasFrameAdvanced(widget, probe);
81
+ const isPresentationStalled = (widget, probe) => now() - probe.startedAt >= PRESENTATION_PROBE_MS && !isPresenting(widget, probe);
82
+ const allocateRoot = (widget) => {
83
+ const root = widget.getRoot();
84
+ if (!root) {
85
+ return;
19
86
  }
87
+ const width = root.getWidth();
88
+ const height = root.getHeight();
89
+ if (width > 0 && height > 0) {
90
+ root.allocate(width, height, -1, null);
91
+ }
92
+ };
93
+ const isDirectlyPaintable = (child) => child.getMapped() && child.getWidth() > 0 && !(child instanceof Gtk.Popover);
94
+ const getPaintableNode = (widget, scale, width, height) => {
95
+ const paintable = new Gtk.WidgetPaintable({ widget });
20
96
  const snapshot = new Gtk.Snapshot();
21
97
  snapshot.scale(scale, scale);
22
98
  paintable.snapshot(snapshot, width, height);
23
- const renderNode = snapshot.toNode();
24
- if (!renderNode) {
25
- throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);
99
+ appendPopovers(snapshot, widget);
100
+ return snapshot.toNode();
101
+ };
102
+ const getChildrenNode = (widget, scale) => {
103
+ const snapshot = new Gtk.Snapshot();
104
+ snapshot.scale(scale, scale);
105
+ for (let child = widget.getFirstChild(); child !== null; child = child.getNextSibling()) {
106
+ if (isDirectlyPaintable(child)) {
107
+ widget.snapshotChild(child, snapshot);
108
+ }
26
109
  }
27
- const display = widget.getDisplay();
110
+ appendPopovers(snapshot, widget);
111
+ return snapshot.toNode();
112
+ };
113
+ const getRenderNode = (widget, scale, width, height) => {
114
+ const presented = getPaintableNode(widget, scale, width, height);
115
+ if (presented) {
116
+ return presented;
117
+ }
118
+ allocateRoot(widget);
119
+ return getPaintableNode(widget, scale, width, height) ?? getChildrenNode(widget, scale);
120
+ };
121
+ const renderToPng = (widget, node, width, height) => {
28
122
  const renderer = new Gsk.CairoRenderer();
29
- renderer.realizeForDisplay(display);
123
+ renderer.realizeForDisplay(widget.getDisplay());
30
124
  try {
31
- const texture = renderer.renderTexture(renderNode, null);
32
- const pngBytes = texture.saveToPngBytes();
33
- const data = pngBytes.getData();
125
+ const viewport = Graphene.Rect.alloc().init(0, 0, width, height);
126
+ const texture = renderer.renderTexture(node, viewport);
127
+ const data = texture.saveToPngBytes().getData();
34
128
  if (!data) {
35
129
  throw new Error("Failed to serialize screenshot to PNG");
36
130
  }
37
- return {
38
- data: bytesToBase64(data),
39
- mimeType: "image/png",
40
- width: Math.round(width * scale),
41
- height: Math.round(height * scale),
42
- };
131
+ return { data: bytesToBase64(data), mimeType: "image/png", width, height };
43
132
  }
44
133
  finally {
45
134
  renderer.unrealize();
46
135
  }
47
136
  };
137
+ const captureSnapshot = (widget, scale) => {
138
+ const paintable = new Gtk.WidgetPaintable({ widget });
139
+ const width = paintable.getIntrinsicWidth();
140
+ const height = paintable.getIntrinsicHeight();
141
+ if (width <= 0 || height <= 0) {
142
+ return { status: "failed", failure: "no-size" };
143
+ }
144
+ const renderNode = getRenderNode(widget, scale, width, height);
145
+ if (!renderNode) {
146
+ return { status: "failed", failure: "no-content" };
147
+ }
148
+ const scaledWidth = Math.round(width * scale);
149
+ const scaledHeight = Math.round(height * scale);
150
+ return { status: "captured", result: renderToPng(widget, renderNode, scaledWidth, scaledHeight) };
151
+ };
152
+ const failureMessage = (widget, failure) => failure === "no-size"
153
+ ? `Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`
154
+ : `Widget produced no render content (${describeWidgetState(widget)})`;
155
+ const notPresentingMessage = (widget) => `${NOT_PRESENTING_MESSAGE} (${describePresentation(widget)})`;
156
+ const stalledMessage = (widget, failure) => `${notPresentingMessage(widget)}\n\n${failureMessage(widget, failure)}`;
157
+ const presentingHint = (failure) => failure === "no-content" ? EMPTY_WIDGET_HINT : OTHER_FAILURE_HINT;
158
+ const timeoutHint = (widget, state) => isPresenting(widget, state.probe) ? presentingHint(state.failure) : notPresentingMessage(widget);
159
+ const explainTimeout = (error, widget, state) => getConfig().getElementError(`${error.message}\n\n${timeoutHint(widget, state)}`);
160
+ const attemptCapture = (widget, scale, state) => {
161
+ const attempt = captureSnapshot(widget, scale);
162
+ if (attempt.status === "captured") {
163
+ return attempt;
164
+ }
165
+ state.failure = attempt.failure;
166
+ updateFrameProbe(widget, state.probe);
167
+ if (isPresentationStalled(widget, state.probe)) {
168
+ return { status: "stalled", failure: attempt.failure };
169
+ }
170
+ throw new Error(failureMessage(widget, attempt.failure));
171
+ };
172
+ const writeScreenshot = (result, path) => {
173
+ mkdirSync(dirname(path), { recursive: true });
174
+ writeFileSync(path, Buffer.from(result.data, "base64"));
175
+ };
176
+ const captureUntilPaintable = (widget, scale, options) => {
177
+ const state = { probe: startFrameProbe(widget), failure: null };
178
+ return waitFor(() => attemptCapture(widget, scale, state), {
179
+ timeout: options?.timeout,
180
+ interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
181
+ onTimeout: (error) => explainTimeout(error, widget, state),
182
+ });
183
+ };
48
184
  /**
49
- * Captures a PNG snapshot of a widget, retrying until it has a paintable size.
185
+ * Captures a PNG snapshot of a widget, retrying until it has a paintable size,
186
+ * and writes the image to `options.path` when one is given, creating any
187
+ * missing parent directories. Popovers open inside the widget render on their
188
+ * own surfaces, so they are composited into the image at their on-screen
189
+ * positions. A display that never presents a frame leaves GTK without a cached
190
+ * render node, so the widget's contents are then snapshotted directly; when
191
+ * even that yields nothing, the failure names the display instead of blaming
192
+ * the widget.
50
193
  *
51
194
  * @param widget The widget to render to an image.
52
- * @param options Optional scale, timeout, and retry interval.
195
+ * @param options Optional scale, timeout, retry interval, and output path.
53
196
  * @returns The base64-encoded PNG data along with its mime type and dimensions.
54
197
  */
55
198
  const screenshot = async (widget, options) => {
@@ -57,79 +200,22 @@ const screenshot = async (widget, options) => {
57
200
  if (!Number.isFinite(scale) || scale <= 0) {
58
201
  throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);
59
202
  }
60
- return waitFor(() => captureSnapshot(widget, scale), {
61
- timeout: options?.timeout,
62
- interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
63
- });
64
- };
65
- const firstToplevelWindow = (windows) => {
66
- const [first] = windows;
67
- if (!(first instanceof Gtk.Window)) {
68
- throw new TypeError("First toplevel is not a Window");
69
- }
70
- return first;
71
- };
72
- const windowAtIndex = (windows, index) => {
73
- const indexed = windows[index];
74
- if (!(indexed instanceof Gtk.Window)) {
75
- throw new TypeError(`Window at index ${String(index)} not found`);
203
+ const outcome = await captureUntilPaintable(widget, scale, options);
204
+ if (outcome.status === "stalled") {
205
+ throw new Error(stalledMessage(widget, outcome.failure));
76
206
  }
77
- return indexed;
78
- };
79
- const isWindow = (widget) => widget instanceof Gtk.Window;
80
- const hasMatchingTitle = (window, selector) => {
81
- const title = window.getTitle() ?? "";
82
- return selector instanceof RegExp ? selector.test(title) : title.includes(selector);
83
- };
84
- const describeTitleSelector = (selector) => selector instanceof RegExp ? selector.toString() : `"${selector}"`;
85
- const windowByTitle = (windows, selector) => {
86
- const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));
87
- if (!found) {
88
- throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);
207
+ if (options?.path) {
208
+ writeScreenshot(outcome.result, options.path);
89
209
  }
90
- return found;
210
+ return outcome.result;
91
211
  };
92
- const resolveWindow = (selector) => {
93
- const windows = Gtk.Window.listToplevels();
94
- if (windows.length === 0) {
95
- throw new Error("No windows available for screenshot");
212
+ const screenTarget = () => {
213
+ const target = activeToplevel() ?? mappedToplevels()[0];
214
+ if (!target) {
215
+ throw new Error(NOTHING_ON_SCREEN_MESSAGE);
96
216
  }
97
- if (selector === undefined) {
98
- return firstToplevelWindow(windows);
99
- }
100
- if (typeof selector === "number") {
101
- return windowAtIndex(windows, selector);
102
- }
103
- return windowByTitle(windows, selector);
104
- };
105
- const saveScreenshotToTempFile = (result) => {
106
- const dir = mkdtempSync(join(tmpdir(), "gtkx-screenshots-"));
107
- const filepath = join(dir, "screenshot.png");
108
- writeFileSync(filepath, Buffer.from(result.data, "base64"));
109
- return filepath;
110
- };
111
- /**
112
- * Prints a clickable file URL for a saved screenshot to the console.
113
- *
114
- * @param filepath Absolute path of the saved screenshot file.
115
- */
116
- const logScreenshotPath = (filepath) => {
117
- console.log(`Screenshot saved: file://${filepath}`);
118
- };
119
- /**
120
- * Captures a screenshot of a toplevel window, writes it to a temporary file,
121
- * logs its path, and returns the image data.
122
- *
123
- * @param selector Chooses the window by index, title substring, or regular
124
- * expression; defaults to the first toplevel.
125
- * @param options Optional scale, timeout, and retry interval.
126
- * @returns The base64-encoded PNG data along with its mime type and dimensions.
127
- */
128
- const captureAndSaveScreenshot = async (selector, options) => {
129
- const target = resolveWindow(selector);
130
- const result = await screenshot(target, options);
131
- logScreenshotPath(saveScreenshotToTempFile(result));
132
- return result;
217
+ return target;
133
218
  };
134
- export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
219
+ const captureScreen = async (options) => screenshot(screenTarget(), options);
220
+ export { captureScreen, screenshot };
135
221
  //# sourceMappingURL=screenshot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ScreenshotOptions, ScreenshotResult, WindowSelector } from \"./types.js\";\nimport { waitFor } from \"./wait-for.js\";\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +\n `visible=${String(widget.getVisible())}`;\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): ScreenshotResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);\n }\n\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n const renderNode = snapshot.toNode();\n\n if (!renderNode) {\n throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);\n }\n\n const display = widget.getDisplay();\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(display);\n\n try {\n const texture = renderer.renderTexture(renderNode, null);\n const pngBytes = texture.saveToPngBytes();\n const data = pngBytes.getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return {\n data: bytesToBase64(data),\n mimeType: \"image/png\",\n width: Math.round(width * scale),\n height: Math.round(height * scale),\n };\n } finally {\n renderer.unrealize();\n }\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);\n }\n\n return waitFor(() => captureSnapshot(widget, scale), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n });\n};\n\nconst firstToplevelWindow = (windows: Gtk.Widget[]): Gtk.Window => {\n const [first] = windows;\n\n if (!(first instanceof Gtk.Window)) {\n throw new TypeError(\"First toplevel is not a Window\");\n }\n\n return first;\n};\n\nconst windowAtIndex = (windows: Gtk.Widget[], index: number): Gtk.Window => {\n const indexed = windows[index];\n\n if (!(indexed instanceof Gtk.Window)) {\n throw new TypeError(`Window at index ${String(index)} not found`);\n }\n\n return indexed;\n};\n\nconst isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;\n\nconst hasMatchingTitle = (window: Gtk.Window, selector: string | RegExp): boolean => {\n const title = window.getTitle() ?? \"\";\n\n return selector instanceof RegExp ? selector.test(title) : title.includes(selector);\n};\n\nconst describeTitleSelector = (selector: string | RegExp): string =>\n selector instanceof RegExp ? selector.toString() : `\"${selector}\"`;\n\nconst windowByTitle = (windows: Gtk.Widget[], selector: string | RegExp): Gtk.Window => {\n const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));\n\n if (!found) {\n throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);\n }\n\n return found;\n};\n\nconst resolveWindow = (selector?: WindowSelector): Gtk.Window => {\n const windows = Gtk.Window.listToplevels();\n\n if (windows.length === 0) {\n throw new Error(\"No windows available for screenshot\");\n }\n\n if (selector === undefined) {\n return firstToplevelWindow(windows);\n }\n\n if (typeof selector === \"number\") {\n return windowAtIndex(windows, selector);\n }\n\n return windowByTitle(windows, selector);\n};\n\nconst saveScreenshotToTempFile = (result: ScreenshotResult): string => {\n const dir = mkdtempSync(join(tmpdir(), \"gtkx-screenshots-\"));\n const filepath = join(dir, \"screenshot.png\");\n writeFileSync(filepath, Buffer.from(result.data, \"base64\"));\n\n return filepath;\n};\n\n/**\n * Prints a clickable file URL for a saved screenshot to the console.\n *\n * @param filepath Absolute path of the saved screenshot file.\n */\nconst logScreenshotPath = (filepath: string): void => {\n console.log(`Screenshot saved: file://${filepath}`);\n};\n\n/**\n * Captures a screenshot of a toplevel window, writes it to a temporary file,\n * logs its path, and returns the image data.\n *\n * @param selector Chooses the window by index, title substring, or regular\n * expression; defaults to the first toplevel.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst captureAndSaveScreenshot = async (\n selector?: WindowSelector,\n options?: ScreenshotOptions,\n): Promise<ScreenshotResult> => {\n const target = resolveWindow(selector);\n const result = await screenshot(target, options);\n logScreenshotPath(saveScreenshotToTempFile(result));\n\n return result;\n};\n\nexport { screenshot, logScreenshotPath, captureAndSaveScreenshot };\n"]}
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gdk from \"@gtkx/gi/gdk\";\nimport * as Graphene from \"@gtkx/gi/graphene\";\nimport * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { ScreenshotOptions, ScreenshotResult } from \"./types.js\";\nimport { getConfig } from \"./config.js\";\nimport { now } from \"./timers.js\";\nimport { descendants } from \"./traversal.js\";\nimport { waitFor } from \"./wait-for.js\";\nimport { activeToplevel, mappedToplevels } from \"./window-state.js\";\n\ntype FrameProbe = {\n counter: bigint | null;\n startedAt: number;\n};\n\ntype CaptureFailure = \"no-size\" | \"no-content\";\n\ntype CaptureState = {\n probe: FrameProbe;\n failure: CaptureFailure | null;\n};\n\ntype CaptureResult =\n { status: \"captured\"; result: ScreenshotResult } |\n { status: \"failed\"; failure: CaptureFailure };\n\ntype CaptureOutcome =\n { status: \"captured\"; result: ScreenshotResult } |\n { status: \"stalled\"; failure: CaptureFailure };\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\nconst PRESENTATION_PROBE_MS = 250;\n\nconst NOT_PRESENTING_MESSAGE =\n \"Screenshot failed: the display is not presenting frames to this window, so GTK has no rendered \" +\n \"content to capture. The window is hidden, minimized, or its compositor is not presenting it; this \" +\n \"is a display problem, not a widget problem.\";\n\nconst EMPTY_WIDGET_HINT =\n \"The display is presenting frames to this window, so the widget itself is empty: it painted nothing.\";\n\nconst OTHER_FAILURE_HINT =\n \"The display is presenting frames to this window, so the capture failed for another reason.\";\n\nconst NOTHING_ON_SCREEN_MESSAGE = \"Nothing is on screen to capture: no toplevel window is mapped\";\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +\n `visible=${String(widget.getVisible())}`;\n\nconst getMappedPopovers = (widget: Gtk.Widget): Gtk.Popover[] => {\n const popovers: Gtk.Popover[] = [];\n\n for (const descendant of descendants(widget)) {\n if (descendant instanceof Gtk.Popover && descendant.getMapped()) {\n popovers.push(descendant);\n }\n }\n\n return popovers;\n};\n\nconst appendPopover = (snapshot: Gtk.Snapshot, target: Gtk.Widget, popover: Gtk.Popover): void => {\n const [hasBounds, bounds] = popover.computeBounds(target);\n const paintable = new Gtk.WidgetPaintable({ widget: popover });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (!hasBounds || width <= 0 || height <= 0) {\n return;\n }\n\n snapshot.save();\n snapshot.translate(new Graphene.Point({ x: bounds.origin.x, y: bounds.origin.y }));\n paintable.snapshot(snapshot, width, height);\n snapshot.restore();\n};\n\nconst appendPopovers = (snapshot: Gtk.Snapshot, target: Gtk.Widget): void => {\n for (const popover of getMappedPopovers(target)) {\n appendPopover(snapshot, target, popover);\n }\n};\n\nconst getSurface = (widget: Gtk.Widget): Gdk.Surface | null => widget.getNative()?.getSurface() ?? null;\nconst isSurfaceOnScreen = (widget: Gtk.Widget): boolean => getSurface(widget)?.getMapped() ?? false;\n\nconst getFrameClock = (widget: Gtk.Widget): Gdk.FrameClock | null =>\n widget.getRoot()?.getFrameClock() ?? widget.getFrameClock();\n\nconst getFrameCounter = (widget: Gtk.Widget): bigint | null => getFrameClock(widget)?.getFrameCounter() ?? null;\n\nconst describePresentation = (widget: Gtk.Widget): string =>\n `${describeWidgetState(widget)} surfaceMapped=${String(isSurfaceOnScreen(widget))} ` +\n `frameCounter=${String(getFrameCounter(widget))}`;\n\nconst requestFrame = (widget: Gtk.Widget): void => {\n getFrameClock(widget)?.requestPhase(Gdk.FrameClockPhase.UPDATE);\n};\n\nconst startFrameProbe = (widget: Gtk.Widget): FrameProbe => {\n requestFrame(widget);\n\n return { counter: getFrameCounter(widget), startedAt: now() };\n};\n\nconst updateFrameProbe = (widget: Gtk.Widget, probe: FrameProbe): void => {\n requestFrame(widget);\n\n if (probe.counter !== null) {\n return;\n }\n\n const counter = getFrameCounter(widget);\n\n if (counter !== null) {\n probe.counter = counter;\n probe.startedAt = now();\n }\n};\n\nconst hasFrameAdvanced = (widget: Gtk.Widget, probe: FrameProbe): boolean => {\n const counter = getFrameCounter(widget);\n\n return counter !== null && probe.counter !== null && counter > probe.counter;\n};\n\nconst isPresenting = (widget: Gtk.Widget, probe: FrameProbe): boolean =>\n isSurfaceOnScreen(widget) && hasFrameAdvanced(widget, probe);\n\nconst isPresentationStalled = (widget: Gtk.Widget, probe: FrameProbe): boolean =>\n now() - probe.startedAt >= PRESENTATION_PROBE_MS && !isPresenting(widget, probe);\n\nconst allocateRoot = (widget: Gtk.Widget): void => {\n const root = widget.getRoot();\n\n if (!root) {\n return;\n }\n\n const width = root.getWidth();\n const height = root.getHeight();\n\n if (width > 0 && height > 0) {\n root.allocate(width, height, -1, null);\n }\n};\n\nconst isDirectlyPaintable = (child: Gtk.Widget): boolean =>\n child.getMapped() && child.getWidth() > 0 && !(child instanceof Gtk.Popover);\n\nconst getPaintableNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n appendPopovers(snapshot, widget);\n\n return snapshot.toNode();\n};\n\nconst getChildrenNode = (widget: Gtk.Widget, scale: number): Gsk.RenderNode | null => {\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n\n for (let child = widget.getFirstChild(); child !== null; child = child.getNextSibling()) {\n if (isDirectlyPaintable(child)) {\n widget.snapshotChild(child, snapshot);\n }\n }\n\n appendPopovers(snapshot, widget);\n\n return snapshot.toNode();\n};\n\nconst getRenderNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {\n const presented = getPaintableNode(widget, scale, width, height);\n\n if (presented) {\n return presented;\n }\n\n allocateRoot(widget);\n\n return getPaintableNode(widget, scale, width, height) ?? getChildrenNode(widget, scale);\n};\n\nconst renderToPng = (widget: Gtk.Widget, node: Gsk.RenderNode, width: number, height: number): ScreenshotResult => {\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(widget.getDisplay());\n\n try {\n const viewport = Graphene.Rect.alloc().init(0, 0, width, height);\n const texture = renderer.renderTexture(node, viewport);\n const data = texture.saveToPngBytes().getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return { data: bytesToBase64(data), mimeType: \"image/png\", width, height };\n } finally {\n renderer.unrealize();\n }\n};\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): CaptureResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n return { status: \"failed\", failure: \"no-size\" };\n }\n\n const renderNode = getRenderNode(widget, scale, width, height);\n\n if (!renderNode) {\n return { status: \"failed\", failure: \"no-content\" };\n }\n\n const scaledWidth = Math.round(width * scale);\n const scaledHeight = Math.round(height * scale);\n\n return { status: \"captured\", result: renderToPng(widget, renderNode, scaledWidth, scaledHeight) };\n};\n\nconst failureMessage = (widget: Gtk.Widget, failure: CaptureFailure): string =>\n failure === \"no-size\"\n ? `Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`\n : `Widget produced no render content (${describeWidgetState(widget)})`;\n\nconst notPresentingMessage = (widget: Gtk.Widget): string =>\n `${NOT_PRESENTING_MESSAGE} (${describePresentation(widget)})`;\n\nconst stalledMessage = (widget: Gtk.Widget, failure: CaptureFailure): string =>\n `${notPresentingMessage(widget)}\\n\\n${failureMessage(widget, failure)}`;\n\nconst presentingHint = (failure: CaptureFailure | null): string =>\n failure === \"no-content\" ? EMPTY_WIDGET_HINT : OTHER_FAILURE_HINT;\n\nconst timeoutHint = (widget: Gtk.Widget, state: CaptureState): string =>\n isPresenting(widget, state.probe) ? presentingHint(state.failure) : notPresentingMessage(widget);\n\nconst explainTimeout = (error: Error, widget: Gtk.Widget, state: CaptureState): Error =>\n getConfig().getElementError(`${error.message}\\n\\n${timeoutHint(widget, state)}`);\n\nconst attemptCapture = (widget: Gtk.Widget, scale: number, state: CaptureState): CaptureOutcome => {\n const attempt = captureSnapshot(widget, scale);\n\n if (attempt.status === \"captured\") {\n return attempt;\n }\n\n state.failure = attempt.failure;\n updateFrameProbe(widget, state.probe);\n\n if (isPresentationStalled(widget, state.probe)) {\n return { status: \"stalled\", failure: attempt.failure };\n }\n\n throw new Error(failureMessage(widget, attempt.failure));\n};\n\nconst writeScreenshot = (result: ScreenshotResult, path: string): void => {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, Buffer.from(result.data, \"base64\"));\n};\n\nconst captureUntilPaintable = (\n widget: Gtk.Widget,\n scale: number,\n options: ScreenshotOptions | undefined,\n): Promise<CaptureOutcome> => {\n const state: CaptureState = { probe: startFrameProbe(widget), failure: null };\n\n return waitFor(() => attemptCapture(widget, scale, state), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n onTimeout: (error) => explainTimeout(error, widget, state),\n });\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size,\n * and writes the image to `options.path` when one is given, creating any\n * missing parent directories. Popovers open inside the widget render on their\n * own surfaces, so they are composited into the image at their on-screen\n * positions. A display that never presents a frame leaves GTK without a cached\n * render node, so the widget's contents are then snapshotted directly; when\n * even that yields nothing, the failure names the display instead of blaming\n * the widget.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, retry interval, and output path.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);\n }\n\n const outcome = await captureUntilPaintable(widget, scale, options);\n\n if (outcome.status === \"stalled\") {\n throw new Error(stalledMessage(widget, outcome.failure));\n }\n\n if (options?.path) {\n writeScreenshot(outcome.result, options.path);\n }\n\n return outcome.result;\n};\n\nconst screenTarget = (): Gtk.Window => {\n const target = activeToplevel() ?? mappedToplevels()[0];\n\n if (!target) {\n throw new Error(NOTHING_ON_SCREEN_MESSAGE);\n }\n\n return target;\n};\n\nconst captureScreen = async (options?: ScreenshotOptions): Promise<ScreenshotResult> =>\n screenshot(screenTarget(), options);\n\nexport { captureScreen, screenshot };\n"]}
@@ -0,0 +1,9 @@
1
+ import { setTimeout } from "node:timers";
2
+ type TimeoutHandle = ReturnType<typeof setTimeout>;
3
+ declare const now: () => number;
4
+ declare const scheduleTimeout: (callback: () => void, ms: number) => TimeoutHandle;
5
+ declare const cancelTimeout: (handle: TimeoutHandle) => void;
6
+ declare const delay: (ms: number) => Promise<void>;
7
+ declare const advanceFakeClock: (ms: number) => Promise<void>;
8
+ export { advanceFakeClock, cancelTimeout, delay, now, scheduleTimeout };
9
+ //# sourceMappingURL=timers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timers.d.ts","sourceRoot":"","sources":["../src/timers.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,UAAU,EAAE,MAAM,aAAa,CAAC;AAGvD,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAEnD,QAAA,MAAM,GAAG,QAAO,MAA2B,CAAC;AAC5C,QAAA,MAAM,eAAe,GAAI,UAAU,MAAM,IAAI,EAAE,IAAI,MAAM,KAAG,aAAyC,CAAC;AAEtG,QAAA,MAAM,aAAa,GAAI,QAAQ,aAAa,KAAG,IAE9C,CAAC;AAEF,QAAA,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAKlC,CAAC;AAQP,QAAA,MAAM,gBAAgB,GAAU,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAExD,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC"}
package/dist/timers.js ADDED
@@ -0,0 +1,21 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import { clearTimeout, setTimeout } from "node:timers";
3
+ const now = () => performance.now();
4
+ const scheduleTimeout = (callback, ms) => setTimeout(callback, ms);
5
+ const cancelTimeout = (handle) => {
6
+ clearTimeout(handle);
7
+ };
8
+ const delay = (ms) => new Promise((resolve) => {
9
+ scheduleTimeout(() => {
10
+ resolve();
11
+ }, ms);
12
+ });
13
+ const getFakeClock = () => {
14
+ const { clock } = globalThis.setTimeout;
15
+ return clock ?? null;
16
+ };
17
+ const advanceFakeClock = async (ms) => {
18
+ await getFakeClock()?.tickAsync(ms);
19
+ };
20
+ export { advanceFakeClock, cancelTimeout, delay, now, scheduleTimeout };
21
+ //# sourceMappingURL=timers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timers.js","sourceRoot":"","sources":["../src/timers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKvD,MAAM,GAAG,GAAG,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;AAC5C,MAAM,eAAe,GAAG,CAAC,QAAoB,EAAE,EAAU,EAAiB,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAEtG,MAAM,aAAa,GAAG,CAAC,MAAqB,EAAQ,EAAE;IAClD,YAAY,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CACxC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACpB,eAAe,CAAC,GAAG,EAAE;QACjB,OAAO,EAAE,CAAC;IACd,CAAC,EAAE,EAAE,CAAC,CAAC;AACX,CAAC,CAAC,CAAC;AAEP,MAAM,YAAY,GAAG,GAAqB,EAAE;IACxC,MAAM,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,UAAkE,CAAC;IAEhG,OAAO,KAAK,IAAI,IAAI,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,EAAE,EAAU,EAAiB,EAAE;IACzD,MAAM,YAAY,EAAE,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC","sourcesContent":["import { performance } from \"node:perf_hooks\";\nimport { clearTimeout, setTimeout } from \"node:timers\";\n\ntype FakeClock = { tickAsync: (ms: number) => Promise<unknown> };\ntype TimeoutHandle = ReturnType<typeof setTimeout>;\n\nconst now = (): number => performance.now();\nconst scheduleTimeout = (callback: () => void, ms: number): TimeoutHandle => setTimeout(callback, ms);\n\nconst cancelTimeout = (handle: TimeoutHandle): void => {\n clearTimeout(handle);\n};\n\nconst delay = (ms: number): Promise<void> =>\n new Promise((resolve) => {\n scheduleTimeout(() => {\n resolve();\n }, ms);\n });\n\nconst getFakeClock = (): FakeClock | null => {\n const { clock } = globalThis.setTimeout as typeof globalThis.setTimeout & { clock?: FakeClock };\n\n return clock ?? null;\n};\n\nconst advanceFakeClock = async (ms: number): Promise<void> => {\n await getFakeClock()?.tickAsync(ms);\n};\n\nexport { advanceFakeClock, cancelTimeout, delay, now, scheduleTimeout };\n"]}
@@ -10,11 +10,17 @@ type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk
10
10
  * sentinel representing all current toplevel windows.
11
11
  */
12
12
  type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
13
+ type WidgetClass<T extends Gtk.Widget> = abstract new (...args: never[]) => T;
13
14
  /** Container sentinel that widens a query to every toplevel window currently open. */
14
15
  declare const TOPLEVELS: unique symbol;
16
+ declare const isOnScreen: (widget: Gtk.Widget) => boolean;
17
+ declare const children: (widget: Gtk.Widget) => Generator<Gtk.Widget>;
15
18
  declare const descendants: (widget: Gtk.Widget) => Generator<Gtk.Widget>;
19
+ declare const ancestors: (widget: Gtk.Widget) => Generator<Gtk.Widget>;
20
+ declare const ancestorFor: <T extends Gtk.Widget>(widget: Gtk.Widget, type: WidgetClass<T>) => T | null;
21
+ declare const relationCandidates: (widget: Gtk.Widget) => Gtk.Accessible[];
16
22
  declare const roots: (container: Container) => Generator<Gtk.Widget>;
17
23
  declare const traverse: (container: Container) => Generator<Gtk.Widget>;
18
24
  declare const findAll: (container: Container, isMatch: (node: Gtk.Widget) => boolean) => Gtk.Widget[];
19
- export { TOPLEVELS, descendants, roots, traverse, findAll, type Container };
25
+ export { TOPLEVELS, ancestorFor, ancestors, children, descendants, findAll, isOnScreen, relationCandidates, roots, traverse, type Container, };
20
26
  //# sourceMappingURL=traversal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"traversal.d.ts","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC;;;GAGG;AACH,KAAK,cAAc,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC;AAC3G;;;;GAIG;AACH,KAAK,SAAS,GAAG,cAAc,GAAG,GAAG,CAAC,WAAW,GAAG,OAAO,SAAS,CAAC;AAErE,sFAAsF;AACtF,QAAA,MAAM,SAAS,EAAE,OAAO,MAAiC,CAAC;AAc1D,QAAA,MAAM,WAAW,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAIvE,CAAC;AAsBF,QAAA,MAAM,KAAK,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAYnE,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAItE,CAAC;AAEF,QAAA,MAAM,OAAO,GAAI,WAAW,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,KAAK,OAAO,KAAG,GAAG,CAAC,MAAM,EAUzF,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"traversal.d.ts","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC;;;GAGG;AACH,KAAK,cAAc,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC;AAC3G;;;;GAIG;AACH,KAAK,SAAS,GAAG,cAAc,GAAG,GAAG,CAAC,WAAW,GAAG,OAAO,SAAS,CAAC;AACrE,KAAK,WAAW,CAAC,CAAC,SAAS,GAAG,CAAC,MAAM,IAAI,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE9E,sFAAsF;AACtF,QAAA,MAAM,SAAS,EAAE,OAAO,MAAiC,CAAC;AAI1D,QAAA,MAAM,UAAU,GAAI,QAAQ,GAAG,CAAC,MAAM,KAAG,OAA6B,CAAC;AAkBvE,QAAA,MAAM,QAAQ,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAOpE,CAAC;AAEF,QAAA,MAAM,WAAW,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAIvE,CAAC;AAEF,QAAA,MAAM,SAAS,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAOrE,CAAC;AAEF,QAAA,MAAM,WAAW,GAAI,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC,CAAC,KAAG,CAAC,GAAG,IAQzF,CAAC;AAEF,QAAA,MAAM,kBAAkB,GAAI,QAAQ,GAAG,CAAC,MAAM,KAAG,GAAG,CAAC,UAAU,EAS9D,CAAC;AAsBF,QAAA,MAAM,KAAK,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAYnE,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAItE,CAAC;AAEF,QAAA,MAAM,OAAO,GAAI,WAAW,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,KAAK,OAAO,KAAG,GAAG,CAAC,MAAM,EAUzF,CAAC;AAEF,OAAO,EACH,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,KAAK,EACL,QAAQ,EACR,KAAK,SAAS,GACjB,CAAC"}
package/dist/traversal.js CHANGED
@@ -2,19 +2,53 @@ import * as Gtk from "@gtkx/gi/gtk";
2
2
  /** Container sentinel that widens a query to every toplevel window currently open. */
3
3
  const TOPLEVELS = Symbol("gtkx.toplevels");
4
4
  const isApplication = (container) => container instanceof Gtk.Application;
5
- const traverseWidgetTree = function* (root) {
6
- yield root;
5
+ const isAnyWidget = () => true;
6
+ const isOnScreen = (widget) => widget.getMapped();
7
+ const traverseWidgetTree = function* (root, isIncluded) {
8
+ if (isIncluded(root)) {
9
+ yield root;
10
+ }
7
11
  let child = root.getFirstChild();
8
12
  while (child) {
9
- yield* traverseWidgetTree(child);
13
+ yield* traverseWidgetTree(child, isIncluded);
14
+ child = child.getNextSibling();
15
+ }
16
+ };
17
+ const children = function* (widget) {
18
+ let child = widget.getFirstChild();
19
+ while (child) {
20
+ yield child;
10
21
  child = child.getNextSibling();
11
22
  }
12
23
  };
13
24
  const descendants = function* (widget) {
14
- const tree = traverseWidgetTree(widget);
25
+ const tree = traverseWidgetTree(widget, isAnyWidget);
15
26
  tree.next();
16
27
  yield* tree;
17
28
  };
29
+ const ancestors = function* (widget) {
30
+ let parent = widget.getParent();
31
+ while (parent) {
32
+ yield parent;
33
+ parent = parent.getParent();
34
+ }
35
+ };
36
+ const ancestorFor = (widget, type) => {
37
+ for (const ancestor of ancestors(widget)) {
38
+ if (ancestor instanceof type) {
39
+ return ancestor;
40
+ }
41
+ }
42
+ return null;
43
+ };
44
+ const relationCandidates = (widget) => {
45
+ const pool = [...widget.listMnemonicLabels(), ...descendants(widget)];
46
+ const root = widget.getRoot();
47
+ if (root instanceof Gtk.Widget) {
48
+ pool.push(...traverseWidgetTree(root, isAnyWidget));
49
+ }
50
+ return [...new Set(pool)];
51
+ };
18
52
  const resolveRoot = (container) => {
19
53
  if (container instanceof Gtk.Widget) {
20
54
  return container;
@@ -42,7 +76,7 @@ const roots = function* (container) {
42
76
  };
43
77
  const traverse = function* (container) {
44
78
  for (const root of roots(container)) {
45
- yield* traverseWidgetTree(root);
79
+ yield* traverseWidgetTree(root, isOnScreen);
46
80
  }
47
81
  };
48
82
  const findAll = (container, isMatch) => {
@@ -54,5 +88,5 @@ const findAll = (container, isMatch) => {
54
88
  }
55
89
  return results;
56
90
  };
57
- export { TOPLEVELS, descendants, roots, traverse, findAll };
91
+ export { TOPLEVELS, ancestorFor, ancestors, children, descendants, findAll, isOnScreen, relationCandidates, roots, traverse, };
58
92
  //# sourceMappingURL=traversal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"traversal.js","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAcpC,sFAAsF;AACtF,MAAM,SAAS,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,aAAa,GAAG,CAAC,SAAoB,EAAgC,EAAE,CAAC,SAAS,YAAY,GAAG,CAAC,WAAW,CAAC;AAEnH,MAAM,kBAAkB,GAAG,QAAQ,CAAC,EAAE,IAAgB;IAClD,MAAM,IAAI,CAAC;IACX,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IAEjC,OAAO,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACjC,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACnC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,MAAkB;IAC7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,CAAC;IACZ,KAAK,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,SAAyB,EAAqB,EAAE;IACjE,IAAI,SAAS,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,eAAe,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,aAAa,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,QAAQ,IAAI,SAAS,YAAY,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3E,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,SAAoB;IACzC,IAAI,SAAS,KAAK,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAElC,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEpC,IAAI,IAAI,EAAE,CAAC;QACP,MAAM,IAAI,CAAC;IACf,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,SAAoB;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,SAAoB,EAAE,OAAsC,EAAgB,EAAE;IAC3F,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAkB,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\n\n/**\n * A scope that resolves to a single root widget: the widget itself, the widget a controller or\n * layout manager is attached to, or a list item's or header's child.\n */\ntype QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;\n/**\n * A scope that queries and traversal can run against: a widget, an event\n * controller, a layout manager, a list item or header, an application, or the\n * sentinel representing all current toplevel windows.\n */\ntype Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;\n\n/** Container sentinel that widens a query to every toplevel window currently open. */\nconst TOPLEVELS: unique symbol = Symbol(\"gtkx.toplevels\");\n\nconst isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;\n\nconst traverseWidgetTree = function* (root: Gtk.Widget): Generator<Gtk.Widget> {\n yield root;\n let child = root.getFirstChild();\n\n while (child) {\n yield* traverseWidgetTree(child);\n child = child.getNextSibling();\n }\n};\n\nconst descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n const tree = traverseWidgetTree(widget);\n tree.next();\n yield* tree;\n};\n\nconst resolveRoot = (container: QueryContainer): Gtk.Widget | null => {\n if (container instanceof Gtk.Widget) {\n return container;\n }\n\n if (container instanceof Gtk.EventController) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.LayoutManager) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {\n return container.getChild();\n }\n\n return null;\n};\n\nconst roots = function* (container: Container): Generator<Gtk.Widget> {\n if (container === TOPLEVELS || isApplication(container)) {\n yield* Gtk.Window.listToplevels();\n\n return;\n }\n\n const root = resolveRoot(container);\n\n if (root) {\n yield root;\n }\n};\n\nconst traverse = function* (container: Container): Generator<Gtk.Widget> {\n for (const root of roots(container)) {\n yield* traverseWidgetTree(root);\n }\n};\n\nconst findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {\n const results: Gtk.Widget[] = [];\n\n for (const node of traverse(container)) {\n if (isMatch(node)) {\n results.push(node);\n }\n }\n\n return results;\n};\n\nexport { TOPLEVELS, descendants, roots, traverse, findAll, type Container };\n"]}
1
+ {"version":3,"file":"traversal.js","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAepC,sFAAsF;AACtF,MAAM,SAAS,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,aAAa,GAAG,CAAC,SAAoB,EAAgC,EAAE,CAAC,SAAS,YAAY,GAAG,CAAC,WAAW,CAAC;AACnH,MAAM,WAAW,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC;AACxC,MAAM,UAAU,GAAG,CAAC,MAAkB,EAAW,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;AAEvE,MAAM,kBAAkB,GAAG,QAAQ,CAAC,EAChC,IAAgB,EAChB,UAA2C;IAE3C,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC;IACf,CAAC;IAED,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IAEjC,OAAO,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAC7C,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACnC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,MAAkB;IAC1C,IAAI,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;IAEnC,OAAO,KAAK,EAAE,CAAC;QACX,MAAM,KAAK,CAAC;QACZ,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACnC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,MAAkB;IAC7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrD,IAAI,CAAC,IAAI,EAAE,CAAC;IACZ,KAAK,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,MAAkB;IAC3C,IAAI,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAEhC,OAAO,MAAM,EAAE,CAAC;QACZ,MAAM,MAAM,CAAC;QACb,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAuB,MAAkB,EAAE,IAAoB,EAAY,EAAE;IAC7F,KAAK,MAAM,QAAQ,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,IAAI,QAAQ,YAAY,IAAI,EAAE,CAAC;YAC3B,OAAO,QAAQ,CAAC;QACpB,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,MAAkB,EAAoB,EAAE;IAChE,MAAM,IAAI,GAAqB,CAAC,GAAG,MAAM,CAAC,kBAAkB,EAAE,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAE9B,IAAI,IAAI,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,SAAyB,EAAqB,EAAE;IACjE,IAAI,SAAS,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,eAAe,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,aAAa,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,QAAQ,IAAI,SAAS,YAAY,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3E,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,SAAoB;IACzC,IAAI,SAAS,KAAK,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAElC,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEpC,IAAI,IAAI,EAAE,CAAC;QACP,MAAM,IAAI,CAAC;IACf,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,SAAoB;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAChD,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,SAAoB,EAAE,OAAsC,EAAgB,EAAE;IAC3F,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF,OAAO,EACH,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,KAAK,EACL,QAAQ,GAEX,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\n\n/**\n * A scope that resolves to a single root widget: the widget itself, the widget a controller or\n * layout manager is attached to, or a list item's or header's child.\n */\ntype QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;\n/**\n * A scope that queries and traversal can run against: a widget, an event\n * controller, a layout manager, a list item or header, an application, or the\n * sentinel representing all current toplevel windows.\n */\ntype Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;\ntype WidgetClass<T extends Gtk.Widget> = abstract new (...args: never[]) => T;\n\n/** Container sentinel that widens a query to every toplevel window currently open. */\nconst TOPLEVELS: unique symbol = Symbol(\"gtkx.toplevels\");\n\nconst isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;\nconst isAnyWidget = (): boolean => true;\nconst isOnScreen = (widget: Gtk.Widget): boolean => widget.getMapped();\n\nconst traverseWidgetTree = function* (\n root: Gtk.Widget,\n isIncluded: (widget: Gtk.Widget) => boolean,\n): Generator<Gtk.Widget> {\n if (isIncluded(root)) {\n yield root;\n }\n\n let child = root.getFirstChild();\n\n while (child) {\n yield* traverseWidgetTree(child, isIncluded);\n child = child.getNextSibling();\n }\n};\n\nconst children = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n let child = widget.getFirstChild();\n\n while (child) {\n yield child;\n child = child.getNextSibling();\n }\n};\n\nconst descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n const tree = traverseWidgetTree(widget, isAnyWidget);\n tree.next();\n yield* tree;\n};\n\nconst ancestors = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n let parent = widget.getParent();\n\n while (parent) {\n yield parent;\n parent = parent.getParent();\n }\n};\n\nconst ancestorFor = <T extends Gtk.Widget>(widget: Gtk.Widget, type: WidgetClass<T>): T | null => {\n for (const ancestor of ancestors(widget)) {\n if (ancestor instanceof type) {\n return ancestor;\n }\n }\n\n return null;\n};\n\nconst relationCandidates = (widget: Gtk.Widget): Gtk.Accessible[] => {\n const pool: Gtk.Accessible[] = [...widget.listMnemonicLabels(), ...descendants(widget)];\n const root = widget.getRoot();\n\n if (root instanceof Gtk.Widget) {\n pool.push(...traverseWidgetTree(root, isAnyWidget));\n }\n\n return [...new Set(pool)];\n};\n\nconst resolveRoot = (container: QueryContainer): Gtk.Widget | null => {\n if (container instanceof Gtk.Widget) {\n return container;\n }\n\n if (container instanceof Gtk.EventController) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.LayoutManager) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {\n return container.getChild();\n }\n\n return null;\n};\n\nconst roots = function* (container: Container): Generator<Gtk.Widget> {\n if (container === TOPLEVELS || isApplication(container)) {\n yield* Gtk.Window.listToplevels();\n\n return;\n }\n\n const root = resolveRoot(container);\n\n if (root) {\n yield root;\n }\n};\n\nconst traverse = function* (container: Container): Generator<Gtk.Widget> {\n for (const root of roots(container)) {\n yield* traverseWidgetTree(root, isOnScreen);\n }\n};\n\nconst findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {\n const results: Gtk.Widget[] = [];\n\n for (const node of traverse(container)) {\n if (isMatch(node)) {\n results.push(node);\n }\n }\n\n return results;\n};\n\nexport {\n TOPLEVELS,\n ancestorFor,\n ancestors,\n children,\n descendants,\n findAll,\n isOnScreen,\n relationCandidates,\n roots,\n traverse,\n type Container,\n};\n"]}