tabby-rdp 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 meanaverage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # tabby-rdp
2
+
3
+ Remote desktop in Tabby (RDP, powered by IronRDP): shows a remote Linux desktop in a Tabby tab, using RDP through the SSH tab's own connection. Tabby opens nothing on the network: it reaches the remote through an SSH `direct-tcpip` channel to the remote's loopback port 3389.
4
+
5
+ Status: prototype. It works end to end against GNOME Remote Desktop 46 in headless mode (Ubuntu 24.04), and against Windows 11 through an SSH host.
6
+
7
+ ## Use
8
+
9
+ Works in **SSH tabs** (Tabby SSH profiles) and in **plain terminals where you typed `ssh …`**. For the latter, the plugin spots the `ssh` process under the shell, reads its command line (`ps`), keeps the connection-relevant options (`-p -i -J -l -F -o …`, not forwards or TTY flags), and opens its *own* non-interactive connections with the system `ssh` (`BatchMode=yes`): `ssh -W 127.0.0.1:3389` for the desktop and `ssh … sh -s` for setup. So that host needs key/agent auth (or a ControlMaster). A password-only login in the terminal can't be reused, and the desktop says so. Accounts are compared as `user@hostname:port` resolved with `ssh -G`, so a profile tab and a plain `ssh` to the same machine share one desktop. Detection checks the active tab every 2 seconds; not on Windows.
10
+
11
+ The desktop is a layer over the terminal. Every entry point switches between desktop and console. The RDP session keeps running in the background until you disconnect or close the tab.
12
+
13
+ - **Hotkey:** `⌘⇧G` (`Ctrl+Shift+G` on Windows/Linux). It also works while the desktop has focus, and can be rebound in Settings › Hotkeys.
14
+ - **Toolbar:** a **Desktop** button in the SSH tab's floating toolbar, next to Reconnect / SFTP / Ports.
15
+ - **Right-click menus:** in the terminal and on the tab header: *Open / Show remote desktop*, *Back to console*, *Disconnect remote desktop*.
16
+ - **Tabby's header:** left of the settings gear, shown while the active tab is an SSH tab (or a terminal running `ssh`). A desktop/console switch, plus **Disconnect** when a desktop is open. Nothing is drawn over the remote desktop itself.
17
+
18
+ **Clipboard:** copy on the Mac, paste on the remote desktop, and the other way around (text and images).
19
+
20
+ **One desktop per remote account (`user@host:port`).** If another tab already has that desktop open, any entry point switches to that tab and shows its desktop. It doesn't connect a second client, which in GNOME's headless mode would only get an extra, empty monitor.
21
+
22
+ ### Settings
23
+
24
+ Right-click the header's desktop button, or open **Remote desktop settings** in the terminal or tab right-click menu. The settings are stored in Tabby's config under `remoteDesktop`:
25
+
26
+ | Setting | Values |
27
+ |---|---|
28
+ | When the pane is resized (`resize`) | **Resize the remote desktop to fit** (`live`, default): the remote monitor changes resolution in place (RDP display control; grd answers with an EGFX ResetGraphics). **Reconnect at the new size** (`reconnect`). **Keep the resolution** (`off`): scaled to fit. |
29
+ | Sharpness (`sharpness`) | **Standard** (`standard`, default): one remote pixel per screen point. **Retina** (`retina`): device pixels, with GNOME's scale set to match through Mutter's DisplayConfig (temporarily), so it's sharper at the same size. |
30
+ | Bring the console along with `desk` (`desk`) | Off by default. When on, SSH logins on remotes where you open a desktop run in a shareable session, so `desk` works ([below](#desk-bring-the-console-to-the-desktop)). Takes effect on the next desktop connect to each remote. |
31
+ | Session backend (`sessionBackend`) | `native` (default) or `tmux`, for `desk`. Config file only. |
32
+
33
+ Resizes are debounced (300 ms). A desktop that is hidden catches up when shown again.
34
+
35
+ **Keyboard focus:** IronRDP only sends keys while its element has focus, and Tabby re-focuses the terminal whenever a pane gets focus. So while the desktop shows, the terminal's input textarea is disabled (nothing can type into the shell underneath), and the desktop takes focus back on pane focus and on click.
36
+
37
+ ## Desktops behind a host (Windows VMs)
38
+
39
+ An SSH host can also lead to other desktops it can reach, such as a Windows VM whose RDP port it forwards. Add one with **Add a desktop behind <host>…** in that host's menus (or the header button's right-click menu): name, address as seen from the host, type, and optionally the user name. It's saved and opened right away. **Remote desktop settings › Remove a desktop** takes one out again, along with its saved account. In Tabby's config file they look like this:
40
+
41
+ ```yaml
42
+ remoteDesktop:
43
+ desktops:
44
+ - name: Windows VM
45
+ via: myhost # the SSH host: alias, hostname, user@hostname or user@hostname:port
46
+ host: 127.0.0.1 # the RDP server as seen from that host
47
+ port: 13389
48
+ kind: windows # or gnome (an RDP server that needs the graphics pipeline)
49
+ username: alice # optional; DOMAIN\user works too
50
+ ```
51
+
52
+ The host's menus then offer **Open Windows VM** next to its own desktop, and right-clicking the header button lists them too. A pane shows one desktop at a time: opening the other one replaces it. The hotkey, header and toolbar buttons toggle the pane's current desktop, or the one last used for that host.
53
+
54
+ - **Tunnel:** the same SSH connection carries it (`direct-tcpip` to `host:port` from the SSH host). Nothing is opened on the network, and nothing is installed on either machine.
55
+ - **Sign-in:** a form in the desktop layer asks for the Windows account. With **Remember in the keychain** it's stored in the macOS keychain (service `tabby-rdp`), through the `keytar` module Tabby already uses for SSH passwords. A wrong password brings the form back and drops the saved one.
56
+ - **Graphics:** Windows gets IronRDP's standard bitmap path. The graphics pipeline (EGFX), which GNOME requires, is off for it: IronRDP's web client decodes EGFX without H.264, which Windows would want.
57
+ - **Resize and Retina:** Windows follows the pane through display control and honors the scale factor itself, so there is no GNOME scaling step. `desk` only applies to the host's own desktop.
58
+ - **TLS:** Windows' self-signed RDP certificate only allows key encipherment, and Electron's TLS library (BoringSSL) enforces that, which rules out every modern key exchange. When a handshake fails for that reason, the proxy repeats the X.224 exchange on a fresh tunnel and uses TLS 1.2 with RSA key exchange for that desktop. The connection is still encrypted, but without forward secrecy. Installing a certificate with the digital-signature usage on the Windows side avoids the fallback.
59
+ - **Windows-key shortcuts:** ⌘ is sent as the Windows key. Pressed alone it opens Start, but combinations held with it (⌘R for Win+R) didn't reach Windows in testing.
60
+
61
+ ## `desk`: bring the console to the desktop
62
+
63
+ Type `desk` in an SSH console to show that machine's desktop, with a maximized terminal attached to your *exact* console: same shell, history, running programs and scrollback. Typing on either side shows on both. The hotkey or header button takes you back to the console.
64
+
65
+ `desk` is **off by default**, because it changes how SSH logins start on the remote. Turn it on in **Remote desktop settings**. The next time a desktop opens on a remote, the setup installs `~/.local/share/tabby-remote-desktop/` (`login.sh`, `bin/desk`, `bin/trd-pty`, `tmux.conf`, `backend`) and one guarded line in `~/.bashrc` (and `~/.zshrc`). Interactive SSH logins then run inside a shareable session. The backend comes from the `remoteDesktop.sessionBackend` setting:
66
+
67
+ - **`native`** (default): [`trd-pty`](docs/native-pty.md), a small per-session PTY owner on a private Unix socket. It passes output through byte for byte and doesn't take Ctrl-B. The PTY size follows the terminal used or focused last.
68
+ - **`tmux`**: the earlier setup (`tmux -L tabby`, status bar off, `window-size latest`, `focus-events`, passthrough).
69
+
70
+ Turning `desk` off removes the `~/.bashrc`/`~/.zshrc` line and those helpers on the next desktop connect (sessions already running keep going). While it's off, typing `desk` where an old hook is still active only shows a notice.
71
+
72
+ If `trd-pty` can't start, a login falls back to tmux, then a plain shell. `TABBY_NO_SESSION=1` skips it for a login. `desk` outside a shared session opens a terminal in the same folder. Sessions that were open before the hook was installed need a reconnect.
73
+
74
+ Design, tests, timings and limitations: [docs/native-pty.md](docs/native-pty.md).
75
+
76
+ ## How it works
77
+
78
+ ```
79
+ iron-remote-desktop (IronRDP WASM, in the tab)
80
+ │ WebSocket, RDCleanPath
81
+ ▼
82
+ RDCleanPath proxy (src/rdcleanpath.ts, in Tabby's renderer, 127.0.0.1, per-tab token)
83
+ │ X.224 request/confirm, then TLS (server cert chain handed back to the client for CredSSP)
84
+ ▼
85
+ SSH direct-tcpip channel from the SSH tab (src/sshChannelStream.ts) ──► remote 127.0.0.1:3389 (grd)
86
+ ```
87
+
88
+ ## IronRDP
89
+
90
+ `vendor/` holds the IronRDP web bundles built from `ironrdp/BASE_COMMIT` plus `ironrdp/gnome-egfx.patch`. Rebuild them with `npm run build:ironrdp`. No IronRDP release works with GNOME yet, so the patch:
91
+
92
+ - **Graphics pipeline:** advertises the graphics pipeline (`support_dyn_vc_gfx_protocol: true`) in the web and native clients. grd closes the connection without it.
93
+ - **Web client:** registers the EGFX channel (`GraphicsPipelineClient`) when the new `graphicsPipeline(true)` extension asks for it (the plugin does for GNOME, not for Windows). `ActiveStage` already composites its frames.
94
+ - **Declined channels:** drops data for a dynamic channel the client declined, instead of ending the session. grd sends `AUDIO_PLAYBACK_DVC` data anyway.
95
+ - **Live resize:** the web client follows an EGFX ResetGraphics (grd's answer to a display-control monitor layout). It resizes the canvas and fires the canvas-resized callback, which upstream ignored.
96
+ - **Fit to the pane:** the web component sizes itself to its host element, not the whole window, which matters for split panes.
97
+ - **Clipboard:** the component fires `ready` only after its clipboard setup. Before, `ready` came first, a client connecting on it had no clipboard callbacks registered yet, and the session never opened the clipboard channel.
98
+
99
+ grd's login-screen (system) mode does not work. It hands clients off with a Server Redirection PDU, which IronRDP can't decode yet (Devolutions/IronRDP#139). Use headless per-user mode.
100
+
101
+ ## Remote setup (automatic, no root)
102
+
103
+ Nothing is copied to the remote ahead of time, and nothing needs root. The desktop comes from GNOME's own RDP server (grd), which needs a password, a TLS certificate and a running headless session before it accepts a client, so on connect the plugin runs a small script over an SSH exec channel (`src/remoteSetup.ts`), as the SSH user. The script:
104
+
105
+ 1. **Stores keys and password:** it creates `~/.local/share/tabby-remote-desktop/` (mode 0700) holding a self-signed TLS key/cert and a random RDP password (0600, generated once).
106
+ 2. **Configures grd:** it sets up headless grd (`grdctl --headless rdp …`) with user `tabby` and that password.
107
+ 3. **Starts a session if needed:** if no headless shell is running, it starts one: `systemd-run --user --unit=tabby-headless-shell gnome-shell --headless`.
108
+ 4. **`desk` (only when that setting is on):** installs the login hook and helpers described [above](#desk-bring-the-console-to-the-desktop); when it's off, removes them.
109
+ 5. **Restarts grd only when needed:** it (re)starts `gnome-remote-desktop-headless` only when its configuration changed. grd reads credentials only at startup, and a restart drops live sessions.
110
+
111
+ The remote folder keeps the plugin's earlier name (`tabby-remote-desktop`) so existing setups carry over.
112
+
113
+ The remote needs `gnome-remote-desktop` (≥ 46) and `gnome-shell`. Don't pass `--virtual-monitor` to `gnome-shell`: grd adds a virtual monitor per client, so with a preset monitor the client only sees an empty extended display. For the same reason, **a second client on the same remote user gets a second, empty monitor**, not a mirror.
114
+
115
+ grd has no bind-address setting, so port 3389 is reachable from the LAN (password-protected). The plugin doesn't touch the firewall. For loopback-only access, per machine, as root:
116
+ `iptables -I INPUT -p tcp --dport 3389 ! -i lo -j DROP`. Do the same with `ip6tables`.
117
+
118
+ ## Build and test
119
+
120
+ The tests drive a sandbox Tabby over the DevTools protocol against real machines: set `RDP_HOST` (and `BASE_PROFILE`, an SSH profile id prefix to copy settings from); their defaults are the author's hosts.
121
+
122
+ ```sh
123
+ npm install
124
+ npm run build # tsc; no Angular components, so no AOT step
125
+ scripts/sandbox.sh # isolated Tabby with this plugin, DevTools on :9334
126
+ scripts/install-test-app.sh # "Tabby Test.app" in ~/Applications (Spotlight): opens that copy (settings in ~/tabby-work/tabby-test, or a folder you pass)
127
+ node test/e2e.mjs # SSH tabs + a plain terminal running ssh to $RDP_HOST: every entry point, typing, one desktop per account
128
+ BACKEND=native node test/desk.mjs # desk acceptance (also BACKEND=tmux)
129
+ node test/resize.mjs # live / reconnect / keep resizing, Retina with remote scaling
130
+ node test/windows.mjs # a Windows VM behind an SSH host ($RDP_HOST, WIN_HOST/WIN_PORT): menus, sign-in, keychain, typing and clipboard (WinRM checks), resize
131
+ node test/clipboard.mjs # Mac ⇄ GNOME clipboard through the desk terminal (saves and restores the Mac clipboard text)
132
+ node test/desktops.mjs # "Add a desktop behind…" form, sign-in, keychain, "Remove a desktop" (the host's own grd as the extra)
133
+ # trd-pty behavior on a host: scp remote/trd-pty.py test/trd-pty-test.py host:/tmp/ && ssh host python3 /tmp/trd-pty-test.py /tmp/trd-pty.py
134
+ ```
135
+
136
+ Build notes:
137
+
138
+ - **Angular's zone:** anything that watches the DOM or runs on timers stays outside Angular's zone (see `src/header.ts`). Inside it, each callback triggers app-wide change detection. With no tabs open, that looped with the start page and froze Tabby.
139
+ - **Background windows:** `scripts/sandbox.sh` disables Chromium's background throttling. Otherwise timers and animation frames stall while the sandbox window is behind others. Tests also run tab actions inside the zone (DevTools evaluation runs outside it), and they close the tabs they open.
140
+
141
+ - **`author` field:** Tabby silently skips a plugin whose package.json has no `author`.
142
+ - **Screenshots:** page screenshots of the sandbox come back white while its window is behind others. The test reads the decoded canvas instead.
143
+
144
+ ## Known gaps
145
+
146
+ - **Other machines:** a client on another machine (or another Tabby window) still gets an empty extra monitor. Within one Tabby window, reuse is handled.
147
+ - **Not a full GNOME session:** the headless desktop is `gnome-shell --headless` with a session identity (`XDG_CURRENT_DESKTOP=ubuntu:GNOME`, Ubuntu session mode), without `gnome-session`. So there are no settings daemons, and Xwayland (X11-only apps such as `nvidia-settings`) doesn't start. Settings, Calculator, Firefox, Terminal and DGX Dashboard work.
148
+ - **Windows:** ⌘ combinations as Windows-key shortcuts don't get through. Desktops can be added and removed from the menus, not edited (remove and add again, or edit the config file).
149
+ - **Clipboard:** text and images (PNG) both ways, through the browser clipboard API in Tabby's window. Files aren't copied.
150
+ - **Error handling:** errors show inside the desktop layer. There's no retry except switching to the desktop again, which reconnects.
package/dist/desk.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DeskTriggerDecorator = void 0;
13
+ const core_1 = require("@angular/core");
14
+ const tabby_core_1 = require("tabby-core");
15
+ const tabby_terminal_1 = require("tabby-terminal");
16
+ const desktop_service_1 = require("./desktop.service");
17
+ const targets_1 = require("./targets");
18
+ const PREFIX = Buffer.from('\x1b]7777;desk;');
19
+ const BEL = 0x07;
20
+ const ST = Buffer.from('\x1b\\');
21
+ function parse(payload) {
22
+ const [session, socket, cwd, kind] = payload.split(';').map(f => Buffer.from(f ?? '', 'base64').toString('utf8'));
23
+ // Older `desk` scripts sent no kind: they only knew tmux.
24
+ return { session: session ?? '', socket: socket ?? '', cwd: cwd ?? '', kind: kind ?? (session ? 'tmux' : '') };
25
+ }
26
+ /** Strips `desk` requests out of a session's output and reports them. */
27
+ class DeskTrigger extends tabby_terminal_1.SessionMiddleware {
28
+ constructor(onDesk) {
29
+ super();
30
+ this.onDesk = onDesk;
31
+ }
32
+ feedFromSession(data) {
33
+ // Tabby's OSC processor runs first and passes unknown OSCs through whole, so each request
34
+ // arrives complete within one chunk.
35
+ let start = data.indexOf(PREFIX);
36
+ while (start !== -1) {
37
+ const body = start + PREFIX.length;
38
+ const bel = data.indexOf(BEL, body);
39
+ const st = data.indexOf(ST, body);
40
+ const end = bel === -1 ? st : st === -1 ? bel : Math.min(bel, st);
41
+ if (end === -1) {
42
+ break;
43
+ }
44
+ const request = parse(data.subarray(body, end).toString('utf8'));
45
+ data = Buffer.concat([data.subarray(0, start), data.subarray(end + (end === st ? ST.length : 1))]);
46
+ setTimeout(() => this.onDesk(request));
47
+ start = data.indexOf(PREFIX, start);
48
+ }
49
+ if (data.length) {
50
+ super.feedFromSession(data);
51
+ }
52
+ }
53
+ }
54
+ /** Listens for `desk` in every terminal (SSH tabs and local terminals running ssh). */
55
+ let DeskTriggerDecorator = class DeskTriggerDecorator extends tabby_terminal_1.TerminalDecorator {
56
+ constructor(desktop, zone, notifications) {
57
+ super();
58
+ this.desktop = desktop;
59
+ this.zone = zone;
60
+ this.notifications = notifications;
61
+ }
62
+ attach(terminal) {
63
+ const install = () => {
64
+ terminal.session?.middleware.push(new DeskTrigger(request => this.zone.run(() => {
65
+ const pane = (0, targets_1.desktopPaneOf)(terminal);
66
+ if (!pane) {
67
+ return;
68
+ }
69
+ if (!this.desktop.settings().desk) {
70
+ // A hook left from when it was on; it goes away the next time a desktop opens there.
71
+ this.notifications.info('`desk` is off: turn it on in Remote desktop settings');
72
+ return;
73
+ }
74
+ this.desktop.openConsole(pane, request);
75
+ })));
76
+ };
77
+ install();
78
+ this.subscribeUntilDetached(terminal, terminal.sessionChanged$.subscribe(() => install()));
79
+ }
80
+ };
81
+ exports.DeskTriggerDecorator = DeskTriggerDecorator;
82
+ exports.DeskTriggerDecorator = DeskTriggerDecorator = __decorate([
83
+ (0, core_1.Injectable)(),
84
+ __metadata("design:paramtypes", [desktop_service_1.RemoteDesktopService, core_1.NgZone, tabby_core_1.NotificationsService])
85
+ ], DeskTriggerDecorator);
86
+ //# sourceMappingURL=desk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desk.js","sourceRoot":"","sources":["../src/desk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shq = shq;
4
+ exports.consoleScript = consoleScript;
5
+ /** Single-quotes a value for sh. */
6
+ function shq(value) {
7
+ return `'${value.replace(/'/g, `'\\''`)}'`;
8
+ }
9
+ /**
10
+ * Remote script: open a maximized terminal on the headless desktop, attached to the given tmux
11
+ * session (unless something already is), or in the given folder. Prints `RD_OK …` or `RD_ERR …`.
12
+ */
13
+ function consoleScript(request) {
14
+ return `SESS=${shq(request.session)}
15
+ SOCK=${shq(request.socket)}
16
+ DIR=${shq(request.cwd)}
17
+ KIND=${shq(request.kind)}
18
+ ` + String.raw `
19
+ command -v gnome-terminal >/dev/null 2>&1 || { echo "RD_ERR gnome-terminal is not installed"; exit 0; }
20
+ W=$(systemctl --user show-environment 2>/dev/null | sed -n 's/^WAYLAND_DISPLAY=//p')
21
+ export WAYLAND_DISPLAY=${"$"}{W:-wayland-0}
22
+ TRD="$HOME/.local/share/tabby-remote-desktop/bin/trd-pty"
23
+ if [ "$KIND" = native ] && [ -n "$SESS" ] && [ -x "$TRD" ] && [ "$("$TRD" clients "$SESS")" -ge 1 ]; then
24
+ # The console itself is one attached terminal; a second one means a desktop terminal is already attached.
25
+ if [ "$("$TRD" clients "$SESS")" -ge 2 ]; then
26
+ echo "RD_OK attached"; exit 0
27
+ fi
28
+ gnome-terminal --maximize -- "$TRD" attach "$SESS" >/dev/null 2>&1 \
29
+ && echo "RD_OK opened" || echo "RD_ERR could not open a terminal"
30
+ elif [ -n "$SESS" ] && [ -S "$SOCK" ] && tmux -S "$SOCK" has-session -t "=$SESS" 2>/dev/null; then
31
+ # The console itself is one client; a second one means a desktop terminal is already attached.
32
+ if [ "$(tmux -S "$SOCK" list-clients -t "=$SESS" 2>/dev/null | wc -l)" -ge 2 ]; then
33
+ echo "RD_OK attached"; exit 0
34
+ fi
35
+ gnome-terminal --maximize -- tmux -S "$SOCK" attach-session -t "=$SESS" >/dev/null 2>&1 \
36
+ && echo "RD_OK opened" || echo "RD_ERR could not open a terminal"
37
+ else
38
+ [ -d "$DIR" ] || DIR=$HOME
39
+ gnome-terminal --maximize --working-directory="$DIR" >/dev/null 2>&1 \
40
+ && echo "RD_OK opened-folder" || echo "RD_ERR could not open a terminal"
41
+ fi
42
+ `;
43
+ }
44
+ //# sourceMappingURL=deskScript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deskScript.js","sourceRoot":"","sources":["../src/deskScript.ts"],"names":[],"mappings":";;AAgBA,kBAEC;AAMD,sCA8BC;AAvCD,oCAAoC;AACpC,SAAgB,GAAG,CAAE,KAAa;IAC9B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAA;AAC9C,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAE,OAAoB;IAC/C,OAAO,QAAQ,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;OAChC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;MACpB,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC;OACf,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;CACvB,GAAG,MAAM,CAAC,GAAG,CAAA;;;yBAGW,GAAG;;;;;;;;;;;;;;;;;;;;;CAqB3B,CAAA;AACD,CAAC"}