@gjsify/devtools 0.21.0 → 0.23.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.
@@ -43,6 +43,10 @@ import"./_virtual/_rolldown/runtime.js";import{DEVTOOLS_INTERFACE as e}from"@gjs
43
43
  <method name="GetFocused">
44
44
  <arg type="s" direction="out" name="focused_json"/>
45
45
  </method>
46
+ <method name="ActivateWidget">
47
+ <arg type="s" direction="in" name="path"/>
48
+ <arg type="b" direction="out" name="activated"/>
49
+ </method>
46
50
  <method name="DumpGSettings">
47
51
  <arg type="s" direction="in" name="schema_id"/>
48
52
  <arg type="s" direction="out" name="settings_json"/>
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{activateAction as e,changeActionState as t,describeActions as n}from"./actions.js";import{dumpCss as r,swapCss as i}from"./css.js";import{buildDevtoolsIfaceXml as a}from"./devtools-iface.js";import{captureWidgetPng as o}from"./screenshot.js";import{dumpGSettings as s}from"./gsettings.js";import{dumpTree as c,getWidgetProperty as l,listToplevels as u,pathOfWidget as d,resolveWidgetPath as f,widgetType as p}from"./widget-tree.js";import m from"@girs/glib-2.0";import h from"@girs/gtk-4.0";import{GENERIC_METHODS as g,formatDbusErrorMessage as _}from"@gjsify/devtools-protocol";import v from"@girs/gio-2.0";function frameDelay(e){return new Promise(t=>{m.timeout_add(m.PRIORITY_DEFAULT,e,()=>(t(),m.SOURCE_REMOVE))})}async function captureWidgetWhenRenderable(e,t=12,n=50){for(let r=0;r<t;r++){let t=o(e);if(t)return t;await frameDelay(n)}return o(e)}var DevtoolsService=class{constructor(e,t){this._app=e,this._options=t,this._exported=null,this._kinds=new Map,this._extensions=t.extend??[];for(let[e,t]of Object.entries(g))this._kinds.set(e,t);for(let e of this._extensions){for(let[t,n]of Object.entries(e.methodKinds))this._kinds.set(t,n);for(let[t,n]of Object.entries(e.handlers)){let e=n;this[t]=(...n)=>(this._guard(t),e(...n))}}}export(e,t){if(this._exported)return;let n=a(this._extensions.flatMap(e=>e.methodsXml??[])),r=v.DBusExportedObject.wrapJSObject(n,this);r.export(e,t),this._exported=r}unexport(){this._exported?.unexport(),this._exported=null}GetStatus(){let e=this._app.get_active_window(),t={appId:this._app.get_application_id()??`unknown`,instance:this._options.instance??`default`,activeWindow:e?{id:e.get_name()||`window`,title:e.get_title()??``,mapped:e.get_mapped()}:null,toplevelCount:h.Window.get_toplevels().get_n_items(),focusedWidget:null,paused:this._isPaused()};for(let e of this._extensions){let n=e.contributeStatus?.();n&&Object.assign(t,n)}return JSON.stringify(t)}async Screenshot(e){let t=this._app.get_active_window();if(!t)return new Uint8Array;let n=o(t);return n||=(t.present(),await captureWidgetWhenRenderable(t)),n??new Uint8Array}ListActions(){let e={app:n(this._actionGroup(`app`)),win:n(this._actionGroup(`win`))};return JSON.stringify(e)}ActivateAction(t,n,r){this._guard(`ActivateAction`);let i=this._scope(t);e(this._requireGroup(i,n),n,this._parseValue(r))}ChangeActionState(e,n,r){this._guard(`ChangeActionState`);let i=this._scope(e);t(this._requireGroup(i,n),n,this._parseValue(r))}PresentWindow(){this._app.get_active_window()?.present()}ResizeWindow(e,t){this._guard(`ResizeWindow`);let n=this._app.get_active_window();if(!n)throw Error(_(`unavailable`,`no active window to resize`));let r=Math.max(1,Math.round(e)),i=Math.max(1,Math.round(t));return n.is_maximized()&&n.unmaximize(),n.is_fullscreen()&&n.unfullscreen(),n.set_default_size(r,i),[r,i]}ListToplevels(){return JSON.stringify(u())}DumpTree(e,t){let n=this._resolveRootWidget(e);if(!n)throw Error(_(`not-found`,`no widget at '${e||`active window`}'`));return JSON.stringify(c(n.widget,t>0?t:8,n.path))}GetProperty(e,t){let n=this._resolveRootWidget(e);if(!n)throw Error(_(`not-found`,`no widget at '${e}'`));let r=l(n.widget,t);if(r===void 0)throw Error(_(`not-found`,`widget has no property '${t}'`));return JSON.stringify(r)}GetFocused(){let e=this._app.get_active_window(),t=e?e.get_focus():null;return JSON.stringify(t?{path:d(t),name:t.get_name()||null,type:p(t)}:null)}DumpGSettings(e){return JSON.stringify(s(e))}DumpCss(){return JSON.stringify(r())}SwapCss(e,t){return this._guard(`SwapCss`),i(e,t)}_resolveRootWidget(e){if(!e||e===`window`||e===`active`){let e=this._app.get_active_window();return e?{widget:e,path:d(e)??`toplevel:0`}:null}let t=f(e);return t?{widget:t,path:e}:null}_isPaused(){return this._options.paused?.()??!1}_guard(e){let t=this._kinds.get(e);if(!t)throw Error(_(`internal`,`unclassified devtools method '${e}'`));if(t===`mutating`&&this._isPaused())throw Error(_(`paused`,`${e} rejected — external control is paused. Read-only methods keep working.`))}_scope(e){return e===`app`?`app`:`win`}_actionGroup(e){if(e===`app`)return this._app;if(this._options.winActionGroup)return this._options.winActionGroup;let t=this._app.get_active_window();return t instanceof h.ApplicationWindow?t:null}_requireGroup(e,t){let n=this._actionGroup(e);if(!n)throw Error(_(`unavailable`,`no '${e}' action group`));if(!n.has_action(t))throw Error(_(`not-found`,`unknown action ${e}.${t}`));return n}_parseValue(e){if(!e||e===`null`)return null;try{return JSON.parse(e)}catch{throw Error(_(`invalid-params`,`value_json is not valid JSON: ${e}`))}}};export{DevtoolsService};
1
+ import"./_virtual/_rolldown/runtime.js";import{activateAction as e,changeActionState as t,describeActions as n}from"./actions.js";import{dumpCss as r,swapCss as i}from"./css.js";import{buildDevtoolsIfaceXml as a}from"./devtools-iface.js";import{captureWidgetPng as o}from"./screenshot.js";import{dumpGSettings as s}from"./gsettings.js";import{activateWidget as c,dumpTree as l,getWidgetProperty as u,listToplevels as d,pathOfWidget as f,resolveWidgetPath as p,widgetType as m}from"./widget-tree.js";import h from"@girs/glib-2.0";import g from"@girs/gtk-4.0";import{GENERIC_METHODS as _,formatDbusErrorMessage as v}from"@gjsify/devtools-protocol";import y from"@girs/gio-2.0";function frameDelay(e){return new Promise(t=>{h.timeout_add(h.PRIORITY_DEFAULT,e,()=>(t(),h.SOURCE_REMOVE))})}async function captureWidgetWhenRenderable(e,t=12,n=50){for(let r=0;r<t;r++){let t=o(e);if(t)return t;await frameDelay(n)}return o(e)}var DevtoolsService=class{constructor(e,t){this._app=e,this._options=t,this._exported=null,this._kinds=new Map,this._extensions=t.extend??[];for(let[e,t]of Object.entries(_))this._kinds.set(e,t);for(let e of this._extensions){for(let[t,n]of Object.entries(e.methodKinds))this._kinds.set(t,n);for(let[t,n]of Object.entries(e.handlers)){let e=n;this[t]=(...n)=>(this._guard(t),e(...n))}}}export(e,t){if(this._exported)return;let n=a(this._extensions.flatMap(e=>e.methodsXml??[])),r=y.DBusExportedObject.wrapJSObject(n,this);r.export(e,t),this._exported=r}unexport(){this._exported?.unexport(),this._exported=null}GetStatus(){let e=this._app.get_active_window(),t={appId:this._app.get_application_id()??`unknown`,instance:this._options.instance??`default`,activeWindow:e?{id:e.get_name()||`window`,title:e.get_title()??``,mapped:e.get_mapped()}:null,toplevelCount:g.Window.get_toplevels().get_n_items(),focusedWidget:null,paused:this._isPaused()};for(let e of this._extensions){let n=e.contributeStatus?.();n&&Object.assign(t,n)}return JSON.stringify(t)}async Screenshot(e){let t=this._app.get_active_window();if(!t)return new Uint8Array;let n=o(t);return n||=(t.present(),await captureWidgetWhenRenderable(t)),n??new Uint8Array}ListActions(){let e={app:n(this._actionGroup(`app`)),win:n(this._actionGroup(`win`))};return JSON.stringify(e)}ActivateAction(t,n,r){this._guard(`ActivateAction`);let i=this._scope(t);e(this._requireGroup(i,n),n,this._parseValue(r))}ChangeActionState(e,n,r){this._guard(`ChangeActionState`);let i=this._scope(e);t(this._requireGroup(i,n),n,this._parseValue(r))}PresentWindow(){this._app.get_active_window()?.present()}ResizeWindow(e,t){this._guard(`ResizeWindow`);let n=this._app.get_active_window();if(!n)throw Error(v(`unavailable`,`no active window to resize`));let r=Math.max(1,Math.round(e)),i=Math.max(1,Math.round(t));return n.is_maximized()&&n.unmaximize(),n.is_fullscreen()&&n.unfullscreen(),n.set_default_size(r,i),[r,i]}ListToplevels(){return JSON.stringify(d())}DumpTree(e,t){let n=this._resolveRootWidget(e);if(!n)throw Error(v(`not-found`,`no widget at '${e||`active window`}'`));return JSON.stringify(l(n.widget,t>0?t:8,n.path))}GetProperty(e,t){let n=this._resolveRootWidget(e);if(!n)throw Error(v(`not-found`,`no widget at '${e}'`));let r=u(n.widget,t);if(r===void 0)throw Error(v(`not-found`,`widget has no property '${t}'`));return JSON.stringify(r)}GetFocused(){let e=this._app.get_active_window(),t=e?e.get_focus():null;return JSON.stringify(t?{path:f(t),name:t.get_name()||null,type:m(t)}:null)}ActivateWidget(e){this._guard(`ActivateWidget`);let t=this._resolveRootWidget(e);if(!t)throw Error(v(`not-found`,`no widget at '${e}'`));return c(t.widget)}DumpGSettings(e){return JSON.stringify(s(e))}DumpCss(){return JSON.stringify(r())}SwapCss(e,t){return this._guard(`SwapCss`),i(e,t)}_resolveRootWidget(e){if(!e||e===`window`||e===`active`){let e=this._app.get_active_window();return e?{widget:e,path:f(e)??`toplevel:0`}:null}let t=p(e);return t?{widget:t,path:e}:null}_isPaused(){return this._options.paused?.()??!1}_guard(e){let t=this._kinds.get(e);if(!t)throw Error(v(`internal`,`unclassified devtools method '${e}'`));if(t===`mutating`&&this._isPaused())throw Error(v(`paused`,`${e} rejected — external control is paused. Read-only methods keep working.`))}_scope(e){return e===`app`?`app`:`win`}_actionGroup(e){if(e===`app`)return this._app;if(this._options.winActionGroup)return this._options.winActionGroup;let t=this._app.get_active_window();return t instanceof g.ApplicationWindow?t:null}_requireGroup(e,t){let n=this._actionGroup(e);if(!n)throw Error(v(`unavailable`,`no '${e}' action group`));if(!n.has_action(t))throw Error(v(`not-found`,`unknown action ${e}.${t}`));return n}_parseValue(e){if(!e||e===`null`)return null;try{return JSON.parse(e)}catch{throw Error(v(`invalid-params`,`value_json is not valid JSON: ${e}`))}}};export{DevtoolsService};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import e from"@girs/gtk-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){let t=e;return typeof t.$typeName==`string`&&t.$typeName.length>0?t.$typeName:t.constructor?.$gtype?.name??`GtkWidget`}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 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:[]};if(t<=0)return r;let i=e.get_first_child(),a=0;for(;i;)r.children.push(dumpTree(i,t-1,`${n}/child:${a}`)),i=i.get_next_sibling(),a++;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{buildWidgetPath,dumpTree,getWidgetProperty,listToplevels,parseWidgetPath,pathOfWidget,resolveWidgetPath,widgetType};
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/gtk-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){let t=e;return typeof t.$typeName==`string`&&t.$typeName.length>0?t.$typeName:t.constructor?.$gtype?.name??`GtkWidget`}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&&widgetType(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 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 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:[]};if(t<=0)return r;let i=e.get_first_child(),a=0;for(;i;)r.children.push(dumpTree(i,t-1,`${n}/child:${a}`)),i=i.get_next_sibling(),a++;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{activateWidget,buildWidgetPath,dumpTree,getWidgetProperty,listToplevels,parseWidgetPath,pathOfWidget,resolveWidgetPath,widgetType};
@@ -42,6 +42,13 @@ export declare class DevtoolsService {
42
42
  GetProperty(path: string, prop: string): string;
43
43
  /** `GetFocused() -> s` — JSON {path,name,type} of the focused widget, or null. */
44
44
  GetFocused(): string;
45
+ /** `ActivateWidget(path) -> b` — activate the widget at `path`: its own
46
+ * default activation (Button→clicked, Entry→activate, Toggle) or, for a
47
+ * GtkListBoxRow/AdwActionRow, a click on the owning GtkListBox (select the
48
+ * row + row-activated) so nav / preference rows drive. The click-drive
49
+ * counterpart of DumpTree/GetProperty. Throws if the path resolves to no
50
+ * live widget; returns whether it activated. */
51
+ ActivateWidget(path: string): boolean;
45
52
  /** `DumpGSettings(schema_id) -> s` — JSON of a schema's keys + values. */
46
53
  DumpGSettings(schemaId: string): string;
47
54
  /** `DumpCss() -> s` — JSON of the devtools-installed CSS provider names. */
@@ -25,6 +25,31 @@ 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
+ * Activate a widget — how external tooling click-drives a running GUI: resolve a
30
+ * widget by path, then activate it. Two paths, tried in order:
31
+ *
32
+ * 1. **`gtk_widget_activate()`** — the widget's own default-activation signal
33
+ * (a `Gtk.Button` emits `clicked`, a `Gtk.Entry` emits `activate`, a
34
+ * `Gtk.ToggleButton` toggles). Returns true when it fires.
35
+ * 2. **Row fallback** — a `GtkListBoxRow` / `AdwActionRow` is NOT activatable
36
+ * via `gtk_widget_activate()` (returns false): a row's interaction routes
37
+ * through the owning `GtkListBox`, not the row's own signal. So driving a
38
+ * sidebar nav row or a preference row over path #1 alone silently no-ops.
39
+ * When the widget's parent is a `GtkListBox`, reproduce what a real click on
40
+ * a row does — it both SELECTS the row (`select_row` → the `row-selected`
41
+ * signal selection-driven nav shells listen to) AND ACTIVATES it
42
+ * (`row-activated`, for activatable rows). Doing both drives either wiring
43
+ * (a GtkListBox only ever parents rows, so the parent-type check is enough
44
+ * to know the widget is the row to pass). `select_row` is a no-op under
45
+ * selection-mode `none`, so it is always safe to call.
46
+ *
47
+ * Returns true if either path activated the widget, false if neither applies
48
+ * (e.g. a plain container). Duck-typed on the accessors it reads for the same
49
+ * cross-runtime reason as {@link widgetType} (GJS vs node-gi both expose them,
50
+ * so the spec can feed plain mock shapes).
51
+ */
52
+ export declare function activateWidget(widget: Gtk.Widget): boolean;
28
53
  /** Enumerate live toplevel windows as `{ path, type, title, mapped, focused }`. */
29
54
  export declare function listToplevels(): Array<{
30
55
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/devtools",
3
- "version": "0.21.0",
3
+ "version": "0.23.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",
@@ -43,11 +43,11 @@
43
43
  "@girs/gobject-2.0": "^4.1.0",
44
44
  "@girs/graphene-1.0": "^4.1.0",
45
45
  "@girs/gtk-4.0": "^4.1.0",
46
- "@gjsify/devtools-protocol": "^0.21.0"
46
+ "@gjsify/devtools-protocol": "^0.23.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@gjsify/cli": "^0.21.0",
50
- "@gjsify/unit": "^0.21.0",
49
+ "@gjsify/cli": "^0.23.0",
50
+ "@gjsify/unit": "^0.23.0",
51
51
  "@types/node": "^25.9.2",
52
52
  "typescript": "^6.0.3"
53
53
  },