@gjsify/devtools 0.26.0 → 0.27.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.
@@ -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{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
+ 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 ScreenshotAsync(e,t){try{let e=await this._captureActiveWindowPng();t.return_value(new h.Variant(`(ay)`,[e]))}catch(e){t.return_dbus_error(`org.gjsify.Devtools.Error.Screenshot`,e instanceof Error?e.message:String(e))}}async _captureActiveWindowPng(){let e=this._app.get_active_window();if(!e)return new Uint8Array;let t=o(e);return t||=(e.present(),await captureWidgetWhenRenderable(e)),t??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};
@@ -22,8 +22,34 @@ export declare class DevtoolsService {
22
22
  unexport(): void;
23
23
  /** `GetStatus() -> s` — JSON snapshot of the app's live state. */
24
24
  GetStatus(): string;
25
- /** `Screenshot(scope) -> ay` — PNG bytes of the active window via the GSK renderer. */
26
- Screenshot(_scope: string): Promise<Uint8Array>;
25
+ /**
26
+ * `Screenshot(scope) -> ay` — PNG bytes of the active window via the GSK renderer.
27
+ *
28
+ * Implemented with the `<Name>Async` manual-reply convention (the method
29
+ * gets the raw `invocation` and calls `return_value()` itself) rather than a
30
+ * plain `async Screenshot(): Promise<Uint8Array>` ON PURPOSE. On gjs 1.86.0 a
31
+ * Promise-returning exported DBus method mis-marshals its resolved value and
32
+ * the caller sees `org.gnome.gjs.JSError.ValueError: Service implementation
33
+ * returned an incorrect value type` — the `_handleDBusReply` continuation in
34
+ * the Gio override fails for EVERY resolved type (a bare `s`, a pre-packed
35
+ * `GLib.Variant`, an `ay`), not just byte arrays; only Screenshot surfaced it
36
+ * because it is the sole async method in the interface. Replying manually
37
+ * through the invocation is unaffected. The wire method stays `Screenshot`
38
+ * (the interface XML is unchanged) — `wrapJSObject` falls back to
39
+ * `<Name>Async` when the bare name is absent. Tracked upstream in
40
+ * status/upstream-patch-candidates.md; collapse back to a plain `async
41
+ * Screenshot` once the gjs fix ships.
42
+ */
43
+ ScreenshotAsync(_params: unknown[], invocation: Gio.DBusMethodInvocation): Promise<void>;
44
+ /**
45
+ * Capture the active window to PNG, warming up across frames when needed. A
46
+ * just-launched or mid-layout window yields a zero-size GSK frame, so the
47
+ * first `captureWidgetPng` can be empty; presenting it and retrying across a
48
+ * few main-loop iterations turns that into a real screenshot. The empty-bytes
49
+ * contract is preserved as the genuine-failure signal (window truly never
50
+ * realises / is occluded).
51
+ */
52
+ private _captureActiveWindowPng;
27
53
  /** `ListActions() -> s` — JSON of the `app.*` + `win.*` actions. */
28
54
  ListActions(): string;
29
55
  /** `ActivateAction(scope, name, value_json)` — activate a GAction with an optional parameter. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/devtools",
3
- "version": "0.26.0",
3
+ "version": "0.27.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",
@@ -15,7 +15,7 @@
15
15
  "lib"
16
16
  ],
17
17
  "scripts": {
18
- "clear": "rm -rf lib tmp tsconfig.tsbuildinfo test.gjs.mjs || exit 0",
18
+ "clear": "gjsify clear lib tmp tsconfig.tsbuildinfo test.gjs.mjs",
19
19
  "check": "gjsify tsc --noEmit",
20
20
  "build": "gjsify run build:gjsify && gjsify run build:types",
21
21
  "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
@@ -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.26.0"
46
+ "@gjsify/devtools-protocol": "^0.27.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@gjsify/cli": "^0.26.0",
50
- "@gjsify/unit": "^0.26.0",
49
+ "@gjsify/cli": "^0.27.0",
50
+ "@gjsify/unit": "^0.27.0",
51
51
  "@types/node": "^25.9.2",
52
52
  "typescript": "^6.0.3"
53
53
  },