@gjsify/devtools 0.49.0 → 0.50.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 +2 -2
- package/lib/esm/index.js +1 -1
- package/lib/esm/widget-tree.js +1 -1
- package/lib/types/devtools-service.d.ts +3 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/widget-tree.d.ts +46 -0
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ A peer call carries no destination bus name at all, so `nameHasOwner` / `listIns
|
|
|
130
130
|
| `DumpTree` | `-> s` | the widget tree as JSON, with stable `toplevel:N/child:M` paths. |
|
|
131
131
|
| `GetProperty` | `(path, prop) -> s` | read one property off the widget at `path`. |
|
|
132
132
|
| `GetFocused` / `ListToplevels` | `-> s` | the focused widget's path; the toplevel list. |
|
|
133
|
-
| `FindWidget` | `(selector) -> s` | first VISIBLE+mapped match for `Type`, `:css-class` or `Type:css-class`, depth-first from the active window; `''` when none. |
|
|
133
|
+
| `FindWidget` | `(selector) -> s` | first VISIBLE+mapped match for `Type`, `:css-class` or `Type:css-class`, depth-first from the active window; `''` when none. `Type` matches IS-A. |
|
|
134
134
|
| `SendKey` | `(accel, path) -> b` | deliver a key to a widget's key controllers; an empty path means the FOCUSED widget. |
|
|
135
135
|
| `ActivateWidget` | `(path) -> b` | click-drive: `gtk_widget_activate` for Button/Entry/Toggle, with a `GtkListBox` select-row / `row-activated` fallback for nav and preference rows. |
|
|
136
136
|
| `GetStatus` | `-> s` | liveness + whatever extensions contribute. |
|
|
@@ -143,7 +143,7 @@ GActions are auto-bridged into the command registry (handling the `Adw.Applicati
|
|
|
143
143
|
|
|
144
144
|
Three of those need their reason stated, because the obvious use is the wrong one:
|
|
145
145
|
|
|
146
|
-
- **`FindWidget` exists because widget paths are POSITIONAL.** A `toplevel:0/child:3` written into a script is wrong the moment a widget is inserted above it, and every click-driving caller was re-walking `DumpTree` JSON in its own language to avoid that.
|
|
146
|
+
- **`FindWidget` exists because widget paths are POSITIONAL.** A `toplevel:0/child:3` written into a script is wrong the moment a widget is inserted above it, and every click-driving caller was re-walking `DumpTree` JSON in its own language to avoid that. **Its `Type` half matches IS-A**, because an application subclassing `Adw.Dialog` or `Gtk.Box` is the ordinary way to write a GTK app, and the exact-GType comparison excluded exactly that: a rig waiting for `AdwDialog` reported "opened no AdwDialog" for a dialog the same run's `DumpTree` showed mapped and visible (#1582). An EXACT type still wins over a subclass **within one search**, so every selector that resolved before the widening resolves to the same path after it. `g_type_is_a` also answers for an INTERFACE, so `GtkOrientable` matches every box and `GtkAccessible` matches the first visible widget in any tree — name a capability on purpose, not by accident. The same is-a reading holds `ActivateWidget`'s `GtkListBox` parent check and `SendKey`'s `GtkEventControllerKey` check — `widgetIsA` is the one helper all three call.
|
|
147
147
|
- **`SendKey` is the half of headless driving `ActivateWidget` does not cover.** Nothing could be TYPED, so every `Gtk.EventControllerKey` handler was unverifiable. It emits `key-pressed` on the widget's own controllers rather than fabricating a `Gdk.Event` — GTK4 made events opaque with no public constructor — so it proves the HANDLER, not GDK's routing. Pair it with `GetProperty(path, "focusable")`: an unfocusable widget swallows every real key silently.
|
|
148
148
|
- **`ActivateWidget` is the mutating counterpart of `GetProperty`.**
|
|
149
149
|
|
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildVariant as e,variantKindFor as t}from"./gvariant.js";import{activateAction as n,changeActionState as r,describeActions as i}from"./actions.js";import{dumpCss as a,removeCss as o,swapCss as s}from"./css.js";import{buildDevtoolsIfaceXml as c}from"./devtools-iface.js";import{DevtoolsPeerServerError as l,removeDevtoolsAddressFile as u,startDevtoolsPeerServer as d,writeDevtoolsAddressFile as f}from"./peer-transport.js";import{captureWidget as p,captureWidgetPng as m}from"./screenshot.js";import{dumpGSettings as h}from"./gsettings.js";import{buildWidgetPath as g,dumpTree as _,getWidgetProperty as v,listToplevels as y,parseWidgetPath as b,pathOfWidget as x,resolveWidgetPath as S,widgetType as
|
|
1
|
+
import{buildVariant as e,variantKindFor as t}from"./gvariant.js";import{activateAction as n,changeActionState as r,describeActions as i}from"./actions.js";import{dumpCss as a,removeCss as o,swapCss as s}from"./css.js";import{buildDevtoolsIfaceXml as c}from"./devtools-iface.js";import{DevtoolsPeerServerError as l,removeDevtoolsAddressFile as u,startDevtoolsPeerServer as d,writeDevtoolsAddressFile as f}from"./peer-transport.js";import{captureWidget as p,captureWidgetPng as m}from"./screenshot.js";import{dumpGSettings as h}from"./gsettings.js";import{buildWidgetPath as g,dumpTree as _,getWidgetProperty as v,listToplevels as y,parseWidgetPath as b,pathOfWidget as x,resolveWidgetPath as S,widgetIsA as C,widgetType as w}from"./widget-tree.js";import{DevtoolsService as T}from"./devtools-service.js";import{chooseDevtoolsTransport as E,installDevtools as D,uninstallDevtools as O}from"./install.js";export*from"@gjsify/devtools-protocol";export{l as DevtoolsPeerServerError,T as DevtoolsService,n as activateAction,c as buildDevtoolsIfaceXml,e as buildVariant,g as buildWidgetPath,p as captureWidget,m as captureWidgetPng,r as changeActionState,E as chooseDevtoolsTransport,i as describeActions,a as dumpCss,h as dumpGSettings,_ as dumpTree,v as getWidgetProperty,D as installDevtools,y as listToplevels,b as parseWidgetPath,x as pathOfWidget,o as removeCss,u as removeDevtoolsAddressFile,S as resolveWidgetPath,d as startDevtoolsPeerServer,s as swapCss,O as uninstallDevtools,t as variantKindFor,C as widgetIsA,w as widgetType,f as writeDevtoolsAddressFile};
|
package/lib/esm/widget-tree.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import e from"gi://Gtk?version=4.0";function parseWidgetPath(e){let t=e.split(`/`).filter(e=>e.length>0);if(t.length===0)return null;let n=t[0].match(/^toplevel:(\d+)$/);if(!n)return null;let r=[];for(let e of t.slice(1)){let t=e.match(/^child:(\d+)$/);if(!t)return null;r.push(Number(t[1]))}return{toplevel:Number(n[1]),children:r}}function buildWidgetPath(e,t){return[`toplevel:${e}`,...t.map(e=>`child:${e}`)].join(`/`)}function widgetType(e){return gtypeName(e)??`GtkWidget`}function gtypeName(e){let t=e;return typeof t?.$typeName==`string`&&t.$typeName.length>0?t.$typeName:t?.constructor?.$gtype?.name??null}function activateWidget(e){let t=e;if(typeof t.activate==`function`&&t.activate()===!0)return!0;let n=typeof t.get_parent==`function`?t.get_parent():null;if(n&&
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://Gtk?version=4.0";import t from"gi://GObject?version=2.0";function parseWidgetPath(e){let t=e.split(`/`).filter(e=>e.length>0);if(t.length===0)return null;let n=t[0].match(/^toplevel:(\d+)$/);if(!n)return null;let r=[];for(let e of t.slice(1)){let t=e.match(/^child:(\d+)$/);if(!t)return null;r.push(Number(t[1]))}return{toplevel:Number(n[1]),children:r}}function buildWidgetPath(e,t){return[`toplevel:${e}`,...t.map(e=>`child:${e}`)].join(`/`)}function widgetType(e){return gtypeName(e)??`GtkWidget`}function gtypeName(e){let t=e;return typeof t?.$typeName==`string`&&t.$typeName.length>0?t.$typeName:t?.constructor?.$gtype?.name??null}function widgetIsA(e,n){let r=gtypeName(e);if(r===null||!n)return!1;if(r===n)return!0;let i=t.type_from_name(r),a=t.type_from_name(n);return!i||!a?!1:t.type_is_a(i,a)}function activateWidget(e){let t=e;if(typeof t.activate==`function`&&t.activate()===!0)return!0;let n=typeof t.get_parent==`function`?t.get_parent():null;if(n&&widgetIsA(n,`GtkListBox`)){let t=n,r=!1;return typeof t.select_row==`function`&&(t.select_row(e),r=!0),typeof t.emit==`function`&&(t.emit(`row-activated`,e),r=!0),r}return!1}function sendKeyToWidget(e,t,n){let r=e.observe_controllers(),i=!1;for(let e=0;e<r.get_n_items();e++){let a=r.get_item(e);widgetIsA(a,`GtkEventControllerKey`)&&(a.emit(`key-pressed`,t,0,n),i=!0)}return i}function nthChild(e,t){let n=e.get_first_child(),r=0;for(;n&&r<t;)n=n.get_next_sibling(),r++;return r===t?n:null}function listToplevels(){let t=e.Window.get_toplevels(),n=t.get_n_items(),r=[];for(let e=0;e<n;e++){let n=t.get_item(e);n&&r.push({path:`toplevel:${e}`,type:widgetType(n),title:n.get_title?.()??null,mapped:n.get_mapped(),focused:n.is_active??!1})}return r}function resolveWidgetPath(t){let n=parseWidgetPath(t);if(!n)return null;let r=e.Window.get_toplevels().get_item(n.toplevel);if(!r)return null;for(let e of n.children)if(r=nthChild(r,e),!r)return null;return r}function parseWidgetSelector(e){let t=e.indexOf(`:`),n=(t<0?e:e.slice(0,t)).trim(),r=(t<0?``:e.slice(t+1)).trim();return!n&&!r?null:{type:n,cssClass:r}}function findWidgetPath(e,t,n){let r={path:null};return walkForSelector(e,t,n,r)??r.path}function walkForSelector(e,t,n,r){if(!e.get_visible()||!e.get_mapped())return null;if(!t.cssClass||e.get_css_classes().includes(t.cssClass)){if(!t.type||widgetType(e)===t.type)return n;r.path===null&&widgetIsA(e,t.type)&&(r.path=n)}let i=e.get_first_child(),a=0;for(;i;){let e=walkForSelector(i,t,`${n}/child:${a}`,r);if(e)return e;i=i.get_next_sibling(),a++}return null}const n=40;function geometryOf(t){if(!t.get_mapped())return;let n=t.get_allocation(),r=n.width+t.get_margin_start()+t.get_margin_end(),i=n.height+t.get_margin_top()+t.get_margin_bottom(),[a,o]=t.measure(e.Orientation.HORIZONTAL,-1),s=r>0?Math.max(r,a):-1,[c,l]=t.measure(e.Orientation.VERTICAL,s),u={width:r,height:i,widthRequest:[a,o],heightRequest:[c,l]};return(r<a||i<c)&&(u.short=!0),u}function dumpTree(e,t,n){let r={path:n,type:widgetType(e),name:e.get_name()||null,cssClasses:e.get_css_classes(),mapped:e.get_mapped(),visible:e.get_visible(),children:[]},i=geometryOf(e);if(i!==void 0&&(r.geometry=i),t<=0)return e.get_first_child()&&(r.truncated=!0),r;let a=e.get_first_child(),o=0;for(;a;)r.children.push(dumpTree(a,t-1,`${n}/child:${o}`)),a=a.get_next_sibling(),o++;return r}function jsonSafe(e){let t=typeof e;return e==null||t===`string`||t===`number`||t===`boolean`?e??null:`[${t}]`}function getWidgetProperty(e,t){let n=e,r=t.replace(/-/g,`_`),i=t.replace(/[-_]([a-z])/g,(e,t)=>t.toUpperCase());for(let e of[t,r,i])if(e in n)return jsonSafe(n[e])}function childIndex(e,t){let n=e.get_first_child(),r=0;for(;n;){if(n===t)return r;n=n.get_next_sibling(),r++}return-1}function pathOfWidget(t){let n=[],r=t,i=r.get_parent();for(;i;){let e=childIndex(i,r);if(e<0)return null;n.unshift(e),r=i,i=r.get_parent()}let a=e.Window.get_toplevels(),o=a.get_n_items();for(let e=0;e<o;e++)if(a.get_item(e)===r)return buildWidgetPath(e,n);return null}export{n as DEFAULT_DUMP_DEPTH,activateWidget,buildWidgetPath,dumpTree,findWidgetPath,getWidgetProperty,listToplevels,parseWidgetPath,parseWidgetSelector,pathOfWidget,resolveWidgetPath,sendKeyToWidget,widgetIsA,widgetType};
|
|
@@ -135,6 +135,9 @@ export declare class DevtoolsService {
|
|
|
135
135
|
* rather than an error for "no match": not finding a widget is an ordinary
|
|
136
136
|
* answer a caller acts on (wait and retry, or report), not a fault.
|
|
137
137
|
*
|
|
138
|
+
* The type half matches IS-A, so `AdwDialog` reaches an application's own
|
|
139
|
+
* `Adw.Dialog` subclass, and an INTERFACE name (`GtkOrientable`, `GtkEditable`) matches
|
|
140
|
+
* every implementor; an exact-type match is still preferred over any of them.
|
|
138
141
|
* Invisible and unmapped subtrees are skipped — see {@link findWidgetPath}. */
|
|
139
142
|
FindWidget(selector: string): string;
|
|
140
143
|
/** `SendKey(accelerator, path) -> b` — deliver a key to the key controllers of the
|
package/lib/types/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { buildVariant, variantKindFor } from './gvariant.js';
|
|
|
9
9
|
export type { VariantKind } from './gvariant.js';
|
|
10
10
|
export { activateAction, changeActionState, describeActions } from './actions.js';
|
|
11
11
|
export { buildDevtoolsIfaceXml } from './devtools-iface.js';
|
|
12
|
-
export { buildWidgetPath, dumpTree, getWidgetProperty, listToplevels, parseWidgetPath, pathOfWidget, resolveWidgetPath, widgetType, } from './widget-tree.js';
|
|
12
|
+
export { buildWidgetPath, dumpTree, getWidgetProperty, listToplevels, parseWidgetPath, pathOfWidget, resolveWidgetPath, widgetIsA, widgetType, } from './widget-tree.js';
|
|
13
13
|
export { dumpCss, removeCss, swapCss } from './css.js';
|
|
14
14
|
export { dumpGSettings } from './gsettings.js';
|
|
15
15
|
export * from '@gjsify/devtools-protocol';
|
|
@@ -25,6 +25,32 @@ export declare function buildWidgetPath(toplevel: number, children: readonly num
|
|
|
25
25
|
* `$typeName` — prefer it when present. (GJS has no `$typeName` on instances.)
|
|
26
26
|
*/
|
|
27
27
|
export declare function widgetType(widget: Gtk.Widget): string;
|
|
28
|
+
/**
|
|
29
|
+
* Does `object`'s runtime GType derive from `typeName` — the type itself, an ancestor, or an
|
|
30
|
+
* INTERFACE it implements?
|
|
31
|
+
*
|
|
32
|
+
* The interface arm is `g_type_is_a`'s and is kept rather than filtered out: `GtkOrientable`,
|
|
33
|
+
* `GtkEditable` and `GtkAccessible` describe what a widget can DO, which is the same question
|
|
34
|
+
* a selector asks, and a rig author who tries one is not making a mistake. It does mean
|
|
35
|
+
* `GtkAccessible` and `GtkBuildable` match the first visible widget in any GTK tree — every
|
|
36
|
+
* widget implements both — so a selector that broad answers a widget rather than nothing.
|
|
37
|
+
*
|
|
38
|
+
* **The name comparison comes first and is the whole answer for a type GObject does not
|
|
39
|
+
* know.** That covers a unit-test shape and a runtime that never registered the type, and
|
|
40
|
+
* it is what makes every exact-name spec in this module still mean what it says.
|
|
41
|
+
*
|
|
42
|
+
* Where GObject does know both names, `g_type_is_a` answers. Registration is a RUNTIME
|
|
43
|
+
* fact and not a static one — measured under GJS 1.88.1, `GObject.type_from_name('AdwDialog')`
|
|
44
|
+
* answers null until something in JS touches `Adw.Dialog`, and registering a SUBCLASS
|
|
45
|
+
* registers its whole ancestor chain. So the lookup happens per call and is never cached:
|
|
46
|
+
* the base type of a live instance is always registered (the instance's class registered
|
|
47
|
+
* it), and a name that resolves to nothing today may resolve a moment later.
|
|
48
|
+
*
|
|
49
|
+
* Written against the NAME rather than `instanceof` for the same reason as
|
|
50
|
+
* {@link widgetType}: under node-gi a returned handle is a GENERIC wrapper, so
|
|
51
|
+
* `instanceof Gtk.ListBox` answers false for an object that is one.
|
|
52
|
+
*/
|
|
53
|
+
export declare function widgetIsA(object: unknown, typeName: string): boolean;
|
|
28
54
|
/**
|
|
29
55
|
* Activate a widget — how external tooling click-drives a running GUI: resolve a
|
|
30
56
|
* widget by path, then activate it. Two paths, tried in order:
|
|
@@ -107,6 +133,26 @@ export declare function parseWidgetSelector(selector: string): WidgetSelector |
|
|
|
107
133
|
* split view — and activating one of those proves nothing about what a user can reach. Skipping the
|
|
108
134
|
* subtree as well as the node matters: the children of an unmapped stack page are unmapped too, but
|
|
109
135
|
* a caller checking only the leaf would happily match one.
|
|
136
|
+
*
|
|
137
|
+
* ## The type half matches IS-A, with the exact type preferred
|
|
138
|
+
*
|
|
139
|
+
* `Type` used to mean the exact GType name, which excludes the ordinary way GTK applications
|
|
140
|
+
* are written: three of bauplaner's six dialogs are `Adw.Dialog` SUBCLASSES, so a rig waiting
|
|
141
|
+
* for `AdwDialog` reported "opened no AdwDialog" for a dialog the tree dump showed mapped and
|
|
142
|
+
* visible (#1582). The failure reads like a defect in the consumer, and the workaround every
|
|
143
|
+
* consumer invents — matching a STYLE CLASS instead — addresses the widget by how it looks
|
|
144
|
+
* rather than by what it is.
|
|
145
|
+
*
|
|
146
|
+
* An EXACT match anywhere in THIS CALL's subtree still wins over a subclass match, even one
|
|
147
|
+
* earlier in reading order, and that ordering is the compatibility guarantee: every selector
|
|
148
|
+
* that resolved before this widening resolves to the SAME path after it, and the subclass leg
|
|
149
|
+
* is consulted only where the answer used to be none. It costs one full walk in the case that
|
|
150
|
+
* used to walk the tree anyway — the miss.
|
|
151
|
+
*
|
|
152
|
+
* PER CALL is the whole scope of that guarantee, and a caller sweeping several roots has to
|
|
153
|
+
* say so itself: `FindWidget` passes ONE root (the active window), but a consumer looping over
|
|
154
|
+
* `ListToplevels` gets a subclass hit in an early toplevel ahead of an exact hit in a later
|
|
155
|
+
* one. Collect the per-root answers and prefer the exact one, or pass the exact type.
|
|
110
156
|
*/
|
|
111
157
|
export declare function findWidgetPath(root: Gtk.Widget, selector: WidgetSelector, basePath: string): string | null;
|
|
112
158
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "In-app DBus devtools control plane for GJS/GTK apps — inspect, screenshot, and drive a running app over org.gjsify.Devtools (gdbus or the MCP bridge)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
"mcp"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@girs/adw-1": "
|
|
41
|
-
"@girs/gdk-4.0": "
|
|
42
|
-
"@girs/gio-2.0": "
|
|
43
|
-
"@girs/gjs": "
|
|
44
|
-
"@girs/glib-2.0": "
|
|
45
|
-
"@girs/gobject-2.0": "
|
|
46
|
-
"@girs/graphene-1.0": "
|
|
47
|
-
"@girs/gtk-4.0": "
|
|
48
|
-
"@gjsify/devtools-protocol": "^0.
|
|
40
|
+
"@girs/adw-1": "5.0.0",
|
|
41
|
+
"@girs/gdk-4.0": "5.0.0",
|
|
42
|
+
"@girs/gio-2.0": "5.0.0",
|
|
43
|
+
"@girs/gjs": "5.0.0",
|
|
44
|
+
"@girs/glib-2.0": "5.0.0",
|
|
45
|
+
"@girs/gobject-2.0": "5.0.0",
|
|
46
|
+
"@girs/graphene-1.0": "5.0.0",
|
|
47
|
+
"@girs/gtk-4.0": "5.0.0",
|
|
48
|
+
"@gjsify/devtools-protocol": "^0.50.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@gjsify/cli": "^0.
|
|
51
|
+
"@gjsify/cli": "^0.50.0",
|
|
52
52
|
"@gjsify/node-gi": "file:../../node-gi/node-gi",
|
|
53
|
-
"@gjsify/unit": "^0.
|
|
53
|
+
"@gjsify/unit": "^0.50.0",
|
|
54
54
|
"@types/node": "^25.9.2",
|
|
55
55
|
"typescript": "^6.0.3"
|
|
56
56
|
},
|