@gjsify/terminal-native 0.53.0 → 0.55.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/lib/esm/index.js +1 -1
- package/lib/esm/raw-mode.js +1 -0
- package/lib/types/index.d.ts +40 -1
- package/lib/types/raw-mode.d.ts +45 -0
- package/package.json +10 -10
- package/src/vala/terminal.vala +48 -3
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{claimRawMode as e,isRawModeClaimed as t,noteRawMode as n,releaseRawMode as r,restoreClaimedRawModes as i}from"./raw-mode.js";import{loadOptionalNativeModule as a}from"@gjsify/utils/core";const o=a(`GjsifyTerminal`,[`Terminal`,`ResizeWatcher`]),s=o.module,c=s;function hasNativeTerminal(){return s!==null}function getNativeTerminalLoadError(){return o.error}function callableModule(){if(!s)return null;try{return s.Terminal.is_tty(2),s}catch{return null}}function nativeIsTty(e){try{return callableModule()?.Terminal.is_tty(e)??null}catch{return null}}function nativeTerminalSize(e){try{let[t,n,r]=callableModule()?.Terminal.get_size(e)??[!1,0,0];return!t||r<=0?null:{rows:n,columns:r}}catch{return null}}function nativeSetRawMode(e,t){try{return callableModule()?.Terminal.set_raw_mode(e,t)??null}catch{return null}}export{e as claimRawMode,getNativeTerminalLoadError,hasNativeTerminal,t as isRawModeClaimed,nativeIsTty,nativeSetRawMode,c as nativeTerminal,nativeTerminalSize,n as noteRawMode,r as releaseRawMode,i as restoreClaimedRawModes};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const e=Symbol.for(`@gjsify/terminal-native:raw-mode-claims`),t=globalThis,n=t[e]??=new Map;function claimRawMode(e,t){n.set(e,t)}function releaseRawMode(e){n.delete(e)}function isRawModeClaimed(e){return n.has(e)}function noteRawMode(e,t,n){let r=n(t);return t?r&&claimRawMode(e,()=>{n(!1)}):releaseRawMode(e),r}function restoreClaimedRawModes(){let e=[...n.entries()];n.clear();let t=0,r;for(let[,n]of e)try{n(),t+=1}catch(e){r??=e}if(r!==void 0)throw r;return t}export{claimRawMode,isRawModeClaimed,noteRawMode,releaseRawMode,restoreClaimedRawModes};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './raw-mode.js';
|
|
1
2
|
export interface NativeTerminal {
|
|
2
3
|
/** Check whether fd is an interactive terminal (Posix.isatty). */
|
|
3
4
|
is_tty(fd: number): boolean;
|
|
@@ -15,6 +16,11 @@ export interface NativeResizeWatcherClass {
|
|
|
15
16
|
export interface NativeResizeWatcher {
|
|
16
17
|
/** Start watching SIGWINCH. Idempotent. */
|
|
17
18
|
start(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Stop watching SIGWINCH and release the source's reference to this
|
|
21
|
+
* watcher. Idempotent. Without it a started watcher is immortal.
|
|
22
|
+
*/
|
|
23
|
+
stop(): void;
|
|
18
24
|
connect(signal: 'resized', handler: (obj: NativeResizeWatcher, rows: number, cols: number) => void): number;
|
|
19
25
|
disconnect(id: number): void;
|
|
20
26
|
}
|
|
@@ -22,7 +28,16 @@ export interface GjsifyTerminalModule {
|
|
|
22
28
|
Terminal: NativeTerminal;
|
|
23
29
|
ResizeWatcher: NativeResizeWatcherClass;
|
|
24
30
|
}
|
|
25
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The native GjsifyTerminal module, or null if not installed.
|
|
33
|
+
*
|
|
34
|
+
* READING this namespace proves less than it looks: girepository opens the
|
|
35
|
+
* shared library at the first member CALL, not at the load, so a namespace
|
|
36
|
+
* that resolved can still raise on every call (see {@link callableModule}).
|
|
37
|
+
* Prefer the `native*` accessors below — they carry that one step and return
|
|
38
|
+
* `null` instead of throwing, which is what an optional prebuild owes a caller
|
|
39
|
+
* that already has a fallback.
|
|
40
|
+
*/
|
|
26
41
|
export declare const nativeTerminal: GjsifyTerminalModule | null;
|
|
27
42
|
/** Returns true when the GjsifyTerminal native library is available. */
|
|
28
43
|
export declare function hasNativeTerminal(): boolean;
|
|
@@ -32,3 +47,27 @@ export declare function hasNativeTerminal(): boolean;
|
|
|
32
47
|
* dependency when it is installed but its library will not open.
|
|
33
48
|
*/
|
|
34
49
|
export declare function getNativeTerminalLoadError(): Error | null;
|
|
50
|
+
/**
|
|
51
|
+
* `Terminal.is_tty(fd)`, or `null` when the native module is absent or its
|
|
52
|
+
* calls throw. `null` is distinct from `false`: a caller that owns a fallback
|
|
53
|
+
* (`process.stdout.isTTY` → GLib's `log_writer_supports_color`) must take it
|
|
54
|
+
* on `null` and answer `false` itself on a real answer.
|
|
55
|
+
*/
|
|
56
|
+
export declare function nativeIsTty(fd: number): boolean | null;
|
|
57
|
+
/**
|
|
58
|
+
* `Terminal.get_size(fd)` as `[rows, columns]`, or `null` when the native
|
|
59
|
+
* module is absent, its calls throw, or the ioctl reports no size. The last
|
|
60
|
+
* collapse is deliberate: every consumer asks the same question — "how wide is
|
|
61
|
+
* this fd, if I can really tell?" — and answered `ok && cols > 0` itself.
|
|
62
|
+
* Returns columns second, matching the bridge's own tuple order.
|
|
63
|
+
*/
|
|
64
|
+
export declare function nativeTerminalSize(fd: number): {
|
|
65
|
+
rows: number;
|
|
66
|
+
columns: number;
|
|
67
|
+
} | null;
|
|
68
|
+
/**
|
|
69
|
+
* `Terminal.set_raw_mode(fd, enable)`, or `null` when the native module is
|
|
70
|
+
* absent or its calls throw. The caller keeps its own `isRaw` bookkeeping, so
|
|
71
|
+
* the return value is only ever read as "did the native call happen".
|
|
72
|
+
*/
|
|
73
|
+
export declare function nativeSetRawMode(fd: number, enable: boolean): boolean | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record that this process put `fd` into raw mode, and how to undo it.
|
|
3
|
+
*
|
|
4
|
+
* Called by whoever performs the transition, on EVERY path — a path that forgets
|
|
5
|
+
* to claim is a terminal nobody will restore, which is the bug this exists for.
|
|
6
|
+
*/
|
|
7
|
+
export declare function claimRawMode(fd: number, restore: () => void): void;
|
|
8
|
+
/**
|
|
9
|
+
* Record that the owner turned `fd` back itself, in the normal close() path.
|
|
10
|
+
*
|
|
11
|
+
* A paid debt: nothing is owed at exit, so the exit hook must not run a restore
|
|
12
|
+
* that could fight whoever claimed the descriptor afterwards.
|
|
13
|
+
*/
|
|
14
|
+
export declare function releaseRawMode(fd: number): void;
|
|
15
|
+
/** Whether this process still owes `fd` a restore. */
|
|
16
|
+
export declare function isRawModeClaimed(fd: number): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Make the raw-mode transition on `fd` and record what it now owes — or that it
|
|
19
|
+
* owes nothing. The rule in one place, because getting it wrong is the whole
|
|
20
|
+
* failure: one call used to take two branches and only one of them remembered,
|
|
21
|
+
* so the terminal was restored where the native module was absent and stranded
|
|
22
|
+
* everywhere else.
|
|
23
|
+
*
|
|
24
|
+
* `setRawMode` performs the change and reports whether it HAPPENED; that verdict
|
|
25
|
+
* is the return value, so a caller with a further fallback (the `stty` spawn in
|
|
26
|
+
* `@gjsify/process`) can take it without asking the terminal twice. It is the
|
|
27
|
+
* same function for both directions, so the undo cannot drift from the change: a
|
|
28
|
+
* claim taken on a transition that did not happen would restore a terminal this
|
|
29
|
+
* process never broke, and turning raw mode off through here PAYS the debt rather
|
|
30
|
+
* than recording it, so no stale undo can outlive the owner that made it — and it
|
|
31
|
+
* pays it whatever the verdict, because an owner that asked to let go has let go
|
|
32
|
+
* whether or not the terminal heard it, and a claim the exit hook would then run
|
|
33
|
+
* is a restore of a state this process may never have reached.
|
|
34
|
+
*/
|
|
35
|
+
export declare function noteRawMode(fd: number, mode: boolean, setRawMode: (enable: boolean) => boolean): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Pay every outstanding debt, and report how many were paid.
|
|
38
|
+
*
|
|
39
|
+
* The map is emptied BEFORE anything runs, so a restore that claims again — or
|
|
40
|
+
* throws — cannot be undone by this same pass. One descriptor failing does not
|
|
41
|
+
* strand the others: every restore is attempted, and the first failure is
|
|
42
|
+
* rethrown afterwards so the caller can see it. A silently empty restore is the
|
|
43
|
+
* exact failure this module exists to remove, so the error is never swallowed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function restoreClaimedRawModes(): number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/terminal-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "Optional Vala/GObject bridge providing Posix.isatty(), ioctl(TIOCGWINSZ), termios raw-mode and SIGWINCH for GJS. Enhances @gjsify/tty and @gjsify/process with correct terminal behaviour when installed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/esm/index.js",
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@girs/glib-2.0": "5.4.0",
|
|
64
64
|
"@girs/gobject-2.0": "5.4.0",
|
|
65
|
-
"@gjsify/utils": "^0.
|
|
65
|
+
"@gjsify/utils": "^0.55.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@gjsify/cli": "^0.
|
|
68
|
+
"@gjsify/cli": "^0.55.0",
|
|
69
69
|
"@ts-for-gir/cli": "^4.1.0",
|
|
70
70
|
"@types/node": "^25.9.2",
|
|
71
71
|
"typescript": "^6.0.3"
|
|
@@ -81,12 +81,12 @@
|
|
|
81
81
|
},
|
|
82
82
|
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/node/terminal-native#readme",
|
|
83
83
|
"optionalDependencies": {
|
|
84
|
-
"@gjsify/terminal-native-darwin-arm64": "0.
|
|
85
|
-
"@gjsify/terminal-native-darwin-x64": "0.
|
|
86
|
-
"@gjsify/terminal-native-linux-arm64": "0.
|
|
87
|
-
"@gjsify/terminal-native-linux-ppc64": "0.
|
|
88
|
-
"@gjsify/terminal-native-linux-riscv64": "0.
|
|
89
|
-
"@gjsify/terminal-native-linux-s390x": "0.
|
|
90
|
-
"@gjsify/terminal-native-linux-x64": "0.
|
|
84
|
+
"@gjsify/terminal-native-darwin-arm64": "0.55.0",
|
|
85
|
+
"@gjsify/terminal-native-darwin-x64": "0.55.0",
|
|
86
|
+
"@gjsify/terminal-native-linux-arm64": "0.55.0",
|
|
87
|
+
"@gjsify/terminal-native-linux-ppc64": "0.55.0",
|
|
88
|
+
"@gjsify/terminal-native-linux-riscv64": "0.55.0",
|
|
89
|
+
"@gjsify/terminal-native-linux-s390x": "0.55.0",
|
|
90
|
+
"@gjsify/terminal-native-linux-x64": "0.55.0"
|
|
91
91
|
}
|
|
92
92
|
}
|
package/src/vala/terminal.vala
CHANGED
|
@@ -104,9 +104,18 @@ namespace GjsifyTerminal {
|
|
|
104
104
|
* ResizeWatcher — fires #GjsifyTerminal.ResizeWatcher::resized whenever
|
|
105
105
|
* the terminal window is resized (SIGWINCH).
|
|
106
106
|
*
|
|
107
|
-
* Call start() once; the watcher stays active for the process lifetime.
|
|
108
107
|
* The signal is dispatched on the GLib main context via GLib.Idle so
|
|
109
108
|
* JavaScript signal handlers run on the main thread.
|
|
109
|
+
*
|
|
110
|
+
* LIFECYCLE: `start()` takes a GLib source on the DEFAULT main context and
|
|
111
|
+
* that source holds a strong reference to the watcher (it is the callback's
|
|
112
|
+
* user_data), so a watcher that was started and never stopped is immortal —
|
|
113
|
+
* it can never be finalized, keeps its signal handlers alive, and is only
|
|
114
|
+
* torn down when GLib unrefs it during the host's own shutdown. `stop()`
|
|
115
|
+
* releases that reference and `dispose()` calls it, so a caller that is
|
|
116
|
+
* done with a watcher MUST stop or dispose it. Keeping the source id is
|
|
117
|
+
* what makes that possible; discarding it (as this class used to) leaves
|
|
118
|
+
* no way to reach the source again.
|
|
110
119
|
*/
|
|
111
120
|
public class ResizeWatcher : GLib.Object {
|
|
112
121
|
|
|
@@ -118,17 +127,19 @@ namespace GjsifyTerminal {
|
|
|
118
127
|
public signal void resized (int rows, int cols);
|
|
119
128
|
|
|
120
129
|
private bool _active = false;
|
|
130
|
+
private uint _winch_source = 0;
|
|
121
131
|
|
|
122
132
|
/**
|
|
123
133
|
* start:
|
|
124
134
|
*
|
|
125
|
-
* Begin watching SIGWINCH. Idempotent — safe to call multiple times
|
|
135
|
+
* Begin watching SIGWINCH. Idempotent — safe to call multiple times,
|
|
136
|
+
* and safe to call again after stop().
|
|
126
137
|
*/
|
|
127
138
|
public void start () {
|
|
128
139
|
if (_active) return;
|
|
129
140
|
_active = true;
|
|
130
141
|
|
|
131
|
-
GLib.Unix.signal_add (Posix.Signal.WINCH, () => {
|
|
142
|
+
_winch_source = (uint) GLib.Unix.signal_add (Posix.Signal.WINCH, () => {
|
|
132
143
|
int r = 0, c = 0, xp = 0, yp = 0;
|
|
133
144
|
if (Terminal.get_size (1, out r, out c, out xp, out yp)) {
|
|
134
145
|
int rows_snap = r;
|
|
@@ -141,5 +152,39 @@ namespace GjsifyTerminal {
|
|
|
141
152
|
return GLib.Source.CONTINUE;
|
|
142
153
|
});
|
|
143
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* stop:
|
|
158
|
+
*
|
|
159
|
+
* Stop watching SIGWINCH and drop the source's reference to this
|
|
160
|
+
* watcher, so it can be finalized. Idempotent — safe on a watcher
|
|
161
|
+
* that was never started, and safe to call twice.
|
|
162
|
+
*
|
|
163
|
+
* A resize already dispatched to GLib.Idle still fires `resized`:
|
|
164
|
+
* that idle holds its own reference, so a watcher is never emitted on
|
|
165
|
+
* after it is gone.
|
|
166
|
+
*/
|
|
167
|
+
public void stop () {
|
|
168
|
+
if (!_active) return;
|
|
169
|
+
_active = false;
|
|
170
|
+
|
|
171
|
+
if (_winch_source != 0) {
|
|
172
|
+
GLib.Source.remove (_winch_source);
|
|
173
|
+
_winch_source = 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* dispose:
|
|
179
|
+
*
|
|
180
|
+
* Release the SIGWINCH source on an explicit `run_dispose()`. This is
|
|
181
|
+
* NOT reached when the last JavaScript reference is dropped: the source
|
|
182
|
+
* holds its own reference, so a started watcher is never disposed by
|
|
183
|
+
* garbage collection. Call stop() for that.
|
|
184
|
+
*/
|
|
185
|
+
public override void dispose () {
|
|
186
|
+
stop ();
|
|
187
|
+
base.dispose ();
|
|
188
|
+
}
|
|
144
189
|
}
|
|
145
190
|
}
|