tabby-rdp 0.1.0 → 0.2.1
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/CHANGELOG.md +36 -0
- package/README.md +163 -100
- package/dist/audio.js +87 -0
- package/dist/audio.js.map +1 -0
- package/dist/deskScript.js +1 -1
- package/dist/desktop.service.js +169 -35
- package/dist/desktop.service.js.map +1 -1
- package/dist/fileTransfer.js +174 -0
- package/dist/fileTransfer.js.map +1 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/keyboard.js +143 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/remoteSetup.js +18 -6
- package/dist/remoteSetup.js.map +1 -1
- package/dist/ssh.js +26 -2
- package/dist/ssh.js.map +1 -1
- package/dist/targets.js +6 -0
- package/dist/targets.js.map +1 -1
- package/dist/ui.js +19 -0
- package/dist/ui.js.map +1 -1
- package/package.json +18 -4
- package/vendor/IRONRDP-LICENSE +2 -2
- package/vendor/iron-remote-desktop-rdp.js +197 -193
- package/vendor/iron-remote-desktop.js +661 -651
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
The 0.2.0 release, published again: npm never released 0.2.0 (it stayed in a staged state), and a version number
|
|
6
|
+
can't be reused there.
|
|
7
|
+
|
|
8
|
+
## 0.2.0
|
|
9
|
+
|
|
10
|
+
**New**
|
|
11
|
+
- Windows desktops, and other desktops an SSH host can reach, next to the host's own: added and removed from the
|
|
12
|
+
menus (**Add a desktop behind \<host\>…**, **Remove a desktop**), with a sign-in form and an optional keychain entry.
|
|
13
|
+
- Files both ways through the clipboard: drop files on the desktop (or **Send files to the remote desktop…**) and
|
|
14
|
+
paste them there; files copied on the remote offer **Save to Downloads**.
|
|
15
|
+
- Sound from the remote desktop.
|
|
16
|
+
- Mac shortcuts on macOS (on by default): ⌘ acts as Ctrl on the remote, and a ⌘ tap is the Windows key.
|
|
17
|
+
- Reconnecting: a dropped connection is noticed and reconnected by itself once SSH is back ("Reconnect SSH" when SSH
|
|
18
|
+
is down); ended or failed desktops offer Reconnect / Try again.
|
|
19
|
+
|
|
20
|
+
**Fixed**
|
|
21
|
+
- While a desktop covered a pane, Tabby's shortcuts acted on the terminal underneath: ⌘V pasted into the hidden
|
|
22
|
+
console, ⌘W closed the tab. Only switching tabs and returning to the console stay Tabby's now.
|
|
23
|
+
- Clipboard: IronRDP's component signaled `ready` before setting up the clipboard, so connections got none.
|
|
24
|
+
- A desktop froze instead of noticing that its SSH connection was gone.
|
|
25
|
+
- Retina: GNOME Remote Desktop could reset the scale right after a resize.
|
|
26
|
+
- Windows' self-signed RDP certificate failed TLS in Tabby; such desktops now use TLS 1.2 with RSA key exchange.
|
|
27
|
+
|
|
28
|
+
**Changed**
|
|
29
|
+
- The remote folder is now `~/.local/share/tabby-rdp` (was `tabby-remote-desktop`); existing installs are moved over
|
|
30
|
+
on the next connection.
|
|
31
|
+
- IronRDP is built from a series of focused patches (`ironrdp/patches`).
|
|
32
|
+
|
|
33
|
+
## 0.1.0
|
|
34
|
+
|
|
35
|
+
First release: GNOME desktops in SSH tabs and in local terminals running `ssh`, one desktop per account, live resize
|
|
36
|
+
and Retina, text and image clipboard, and `desk` (opt-in) with the trd-pty shared session.
|
package/README.md
CHANGED
|
@@ -1,150 +1,213 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="docs/images/logo.svg" width="96" height="96" alt="">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# tabby-rdp
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Remote desktops in your Tabby SSH tabs.**<br>
|
|
8
|
+
Linux (GNOME) and Windows, through the SSH connection you already have.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
[](https://www.npmjs.com/package/tabby-rdp)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://tabby.sh)
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
<img src="docs/images/desktop.png" width="860" alt="A GNOME desktop in a Tabby SSH tab">
|
|
12
15
|
|
|
13
|
-
|
|
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.
|
|
16
|
+
</div>
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
---
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
tabby-rdp is a plugin for the [Tabby](https://tabby.sh) terminal. Press a key in an SSH tab and the machine's desktop
|
|
21
|
+
appears in its place; press it again and you're back at the shell. Everything travels inside the SSH connection:
|
|
22
|
+
nothing is opened on the network, and nothing has to be installed on the remote machine beforehand.
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
- **Linux desktops without a monitor or a login.** The plugin starts a headless GNOME session for your SSH user with
|
|
25
|
+
GNOME Remote Desktop, on the fly, without root. A server in a rack works as well as a workstation.
|
|
26
|
+
- **Windows desktops** behind any SSH host, such as a VM on your build server.
|
|
27
|
+
- **A proper desktop experience:** clipboard and files both ways, sound, live resize to the pane, sharp Retina
|
|
28
|
+
rendering, Mac keyboard shortcuts, and automatic reconnects after sleep.
|
|
29
|
+
- **`desk`:** type `desk` in the console and the same shell, with its history and running programs, moves into a
|
|
30
|
+
terminal on the desktop.
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
The remote desktop itself is [IronRDP](https://github.com/Devolutions/IronRDP)'s web client, running inside Tabby.
|
|
33
|
+
Making it work with GNOME took fixes in IronRDP, which we contribute upstream ([below](#built-on-ironrdp)).
|
|
25
34
|
|
|
26
|
-
|
|
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. |
|
|
35
|
+
## Contents
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
- [Install](#install)
|
|
38
|
+
- [Use](#use)
|
|
39
|
+
- [Features](#features)
|
|
40
|
+
- [Windows and other desktops behind a host](#windows-and-other-desktops-behind-a-host)
|
|
41
|
+
- [desk: the console on the desktop](#desk-the-console-on-the-desktop)
|
|
42
|
+
- [Settings](#settings)
|
|
43
|
+
- [Requirements](#requirements)
|
|
44
|
+
- [Built on IronRDP](#built-on-ironrdp)
|
|
45
|
+
- [Development](#development)
|
|
46
|
+
- [Limitations](#limitations)
|
|
47
|
+
- [License](#license)
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
## Install
|
|
36
50
|
|
|
37
|
-
|
|
51
|
+
In Tabby: **Settings › Plugins**, search for **tabby-rdp**, install, and restart Tabby.
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
Or from a terminal, into Tabby's plugin folder (on macOS `~/Library/Application Support/tabby/plugins`, on Linux
|
|
54
|
+
`~/.config/tabby/plugins`, on Windows `%APPDATA%\tabby\plugins`):
|
|
40
55
|
|
|
41
|
-
```
|
|
42
|
-
|
|
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
|
|
56
|
+
```sh
|
|
57
|
+
npm install tabby-rdp
|
|
50
58
|
```
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
## Use
|
|
61
|
+
|
|
62
|
+
Open an SSH tab to a Linux machine with GNOME, and:
|
|
53
63
|
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
-
|
|
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.
|
|
64
|
+
- press **⌘⇧G** (Ctrl+Shift+G on Windows and Linux), or
|
|
65
|
+
- click **Desktop** in the SSH tab's toolbar, or the desktop button in Tabby's header, or
|
|
66
|
+
- right-click the terminal or the tab: **Open remote desktop**.
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
The first connection to a machine takes a few seconds while it prepares GNOME Remote Desktop and a session; later
|
|
69
|
+
ones take well under a second. The same shortcut switches between the desktop and the console, and the desktop keeps
|
|
70
|
+
running in the background until you disconnect or close the tab.
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
It also works in a plain local terminal where you typed `ssh host` (macOS and Linux), as long as that host accepts
|
|
73
|
+
your key without a password prompt.
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
One desktop per account: a second tab to the same `user@host` switches to the tab that has it open, rather than
|
|
76
|
+
connecting again.
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
- **`tmux`**: the earlier setup (`tmux -L tabby`, status bar off, `window-size latest`, `focus-events`, passthrough).
|
|
78
|
+
## Features
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
| | |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **Clipboard** | Copy and paste text and images in both directions. |
|
|
83
|
+
| **Files** | Drop files from Finder onto the desktop, or use **Send files to the remote desktop…**, then paste them in Files or Explorer. Files copied on the remote offer **Save to Downloads**. |
|
|
84
|
+
| **Sound** | The remote desktop's sound plays locally. |
|
|
85
|
+
| **Resize** | The remote resolution follows the pane as you resize the window or split it. Or reconnect at the new size, or keep a fixed resolution. |
|
|
86
|
+
| **Retina** | Optionally renders at device pixels, with the remote's UI scaled to match, for sharp text. |
|
|
87
|
+
| **Keyboard on macOS** | ⌘C, ⌘V, ⌘Z and the rest work as on a Mac; tapping ⌘ alone is the Windows key. Tabby's own shortcuts stay out of the way while a desktop is showing, except switching tabs and returning to the console. |
|
|
88
|
+
| **Reconnecting** | After sleep or a network change, the desktop reconnects by itself once the connection is back. |
|
|
89
|
+
| **Sign-in** | GNOME desktops need none: the plugin manages their credentials. Windows desktops ask for the account once and can remember it in the system keychain. |
|
|
71
90
|
|
|
72
|
-
|
|
91
|
+
<p align="center"><img src="docs/images/files.png" width="760" alt="A file copied on the remote desktop, offered for saving"></p>
|
|
73
92
|
|
|
74
|
-
|
|
93
|
+
## Windows and other desktops behind a host
|
|
75
94
|
|
|
76
|
-
|
|
95
|
+
An SSH host can lead to other desktops it can reach, such as a Windows VM on the same machine. In that host's menu,
|
|
96
|
+
choose **Add a desktop behind \<host\>…** and enter a name, the address as seen from the host, and optionally the
|
|
97
|
+
user name. It opens right away, and from then on appears next to the host's own desktop in its menus. **Remote
|
|
98
|
+
desktop settings › Remove a desktop** removes one, along with its saved password.
|
|
77
99
|
|
|
100
|
+
In Tabby's config file, they look like this:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
remoteDesktop:
|
|
104
|
+
desktops:
|
|
105
|
+
- name: Windows VM
|
|
106
|
+
via: buildhost # the SSH host: alias, hostname, user@hostname or user@hostname:port
|
|
107
|
+
host: 192.168.122.20 # the RDP server as seen from that host
|
|
108
|
+
port: 3389
|
|
109
|
+
kind: windows # or gnome
|
|
110
|
+
username: alice # optional; DOMAIN\user works too
|
|
78
111
|
```
|
|
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
112
|
|
|
88
|
-
|
|
113
|
+
The connection runs through the same SSH connection; the Windows machine needs Remote Desktop turned on, and nothing
|
|
114
|
+
else.
|
|
115
|
+
|
|
116
|
+
## desk: the console on the desktop
|
|
89
117
|
|
|
90
|
-
|
|
118
|
+
Type `desk` in an SSH console, and the tab switches to that machine's desktop with a terminal attached to the very same
|
|
119
|
+
shell: its scrollback, working directory, jobs and running programs. Type on either side; both show it.
|
|
91
120
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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.
|
|
121
|
+
<p align="center">
|
|
122
|
+
<img src="docs/images/console.png" width="49%" alt="A console">
|
|
123
|
+
<img src="docs/images/desk.png" width="49%" alt="The same shell on the desktop, after typing desk">
|
|
124
|
+
</p>
|
|
98
125
|
|
|
99
|
-
|
|
126
|
+
`desk` is off by default, because it makes interactive SSH logins on that machine start inside a shareable session.
|
|
127
|
+
Turn it on in **Remote desktop settings**. The sessions are handled by a small helper, `trd-pty`, rather than tmux, so
|
|
128
|
+
your terminal's scrollback, colors and shortcuts stay as they are. Turning it off removes it again. Details:
|
|
129
|
+
[docs/desk.md](docs/desk.md).
|
|
100
130
|
|
|
101
|
-
##
|
|
131
|
+
## Settings
|
|
102
132
|
|
|
103
|
-
|
|
133
|
+
Right-click the desktop button in Tabby's header, or open **Remote desktop settings** in a terminal's or tab's menu.
|
|
134
|
+
They are stored in Tabby's config under `remoteDesktop`:
|
|
104
135
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
| Setting | Default | |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| When the pane is resized (`resize`) | Resize the remote desktop to fit (`live`) | Or reconnect at the new size (`reconnect`), or keep the resolution, scaled to fit (`off`). |
|
|
139
|
+
| Sharpness (`sharpness`) | Standard (`standard`) | Retina (`retina`): device pixels, with the remote's scale set to match. |
|
|
140
|
+
| Sound (`sound`) | On | Applies on the next connection. |
|
|
141
|
+
| Mac shortcuts (`macShortcuts`) | On | macOS. Off: ⌘ is the Windows key. |
|
|
142
|
+
| Bring the console along with `desk` (`desk`) | Off | Installs `desk` and a login line on each machine you open a desktop on; applies on the next connection there. |
|
|
143
|
+
| Session backend (`sessionBackend`) | `native` | For `desk`: `native` (trd-pty) or `tmux`. Config file only. |
|
|
110
144
|
|
|
111
|
-
|
|
145
|
+
## Requirements
|
|
112
146
|
|
|
113
|
-
|
|
147
|
+
- **Tabby 1.0.236 or newer.** Developed and tested on macOS. On Windows, the plugin loads and runs; connecting from a
|
|
148
|
+
Windows Tabby hasn't been tested end to end yet. Linux hasn't been tested.
|
|
149
|
+
- **Linux desktops:** GNOME Shell and GNOME Remote Desktop 46 or newer (Ubuntu 24.04, for example), a systemd user
|
|
150
|
+
session, and SSH access with a key or agent. `desk` also needs `python3` and GNOME Terminal. No root, no display, no
|
|
151
|
+
login screen.
|
|
152
|
+
- **Windows desktops:** Windows 10 or 11 Pro, or Windows Server, with Remote Desktop turned on, reachable from an SSH
|
|
153
|
+
host.
|
|
114
154
|
|
|
115
|
-
|
|
116
|
-
`iptables -I INPUT -p tcp --dport 3389 ! -i lo -j DROP`. Do the same with `ip6tables`.
|
|
155
|
+
Tested with Ubuntu 24.04 (GNOME Remote Desktop 46.3) and Windows 11 Pro.
|
|
117
156
|
|
|
118
|
-
##
|
|
157
|
+
## Built on IronRDP
|
|
119
158
|
|
|
120
|
-
The
|
|
159
|
+
The desktop is [IronRDP](https://github.com/Devolutions/IronRDP)'s web client: an RDP implementation in Rust, compiled
|
|
160
|
+
to WebAssembly, with a web component. It connects to Windows, but a browser-based client for GNOME Remote Desktop
|
|
161
|
+
needed more: GNOME only speaks RDP's graphics pipeline, which IronRDP's web client didn't offer; it resizes its
|
|
162
|
+
monitor in a way the web client didn't follow; and it depends on details of the sound and dynamic-channel protocols
|
|
163
|
+
that IronRDP got slightly wrong. The web client also had no sound at all.
|
|
164
|
+
|
|
165
|
+
tabby-rdp ships IronRDP with a short series of patches ([ironrdp/](ironrdp)). The fixes go upstream, one by one:
|
|
166
|
+
|
|
167
|
+
| Change | Upstream |
|
|
168
|
+
|---|---|
|
|
169
|
+
| Keep the session when a server sends data on a channel the client declined | [Devolutions/IronRDP#2005](https://github.com/Devolutions/IronRDP/pull/2005) |
|
|
170
|
+
| Fit the web component to its container, not the whole window | [Devolutions/IronRDP#2006](https://github.com/Devolutions/IronRDP/pull/2006) |
|
|
171
|
+
| Set up the clipboard before signaling `ready`, so it works for apps that connect right away | [Devolutions/IronRDP#2018](https://github.com/Devolutions/IronRDP/pull/2018) |
|
|
172
|
+
| Echo the correct size in the sound channel's Training Confirm | Next |
|
|
173
|
+
| The graphics pipeline in the web client, per connection, following its resets | Planned |
|
|
174
|
+
| Sound in the web client | Planned |
|
|
175
|
+
|
|
176
|
+
The aim is to make IronRDP's browser client work well with both Linux and Windows desktops, for everyone who embeds
|
|
177
|
+
it, not just this plugin.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
121
180
|
|
|
122
181
|
```sh
|
|
123
182
|
npm install
|
|
124
|
-
npm run build
|
|
125
|
-
scripts/sandbox.sh
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
183
|
+
npm run build # TypeScript to dist/
|
|
184
|
+
scripts/sandbox.sh # a separate Tabby with this plugin linked in (macOS), DevTools on port 9334
|
|
185
|
+
npm test # end-to-end suites against a test machine: see test/README.md
|
|
186
|
+
npm run build:ironrdp # rebuild vendor/ from IronRDP and ironrdp/patches: see ironrdp/README.md
|
|
134
187
|
```
|
|
135
188
|
|
|
136
|
-
|
|
189
|
+
- [docs/architecture.md](docs/architecture.md): how the pieces fit, the remote setup, and security notes.
|
|
190
|
+
- [docs/desk.md](docs/desk.md): `desk` and trd-pty.
|
|
191
|
+
- [test/README.md](test/README.md) and [testbed/README.md](testbed/README.md): the tests, and setting up test
|
|
192
|
+
machines for them.
|
|
193
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
194
|
+
|
|
195
|
+
## Limitations
|
|
137
196
|
|
|
138
|
-
- **
|
|
139
|
-
|
|
197
|
+
- **One client per account on GNOME.** A second client (another machine, or another Tabby window) gets an extra,
|
|
198
|
+
empty monitor instead of the same screen, which is how GNOME Remote Desktop's headless mode works.
|
|
199
|
+
- **The headless GNOME session is a shell without `gnome-session`:** apps work (Settings, Files, Terminal, Firefox), but
|
|
200
|
+
GNOME's background settings daemons don't run, and X11-only apps don't start.
|
|
201
|
+
- **GNOME's login-screen mode** isn't supported: it hands clients over with a server redirection, which IronRDP can't
|
|
202
|
+
follow yet. The plugin uses per-user headless sessions instead.
|
|
203
|
+
- **GNOME Remote Desktop listens on all interfaces** (password-protected, TLS); it has no setting to listen on
|
|
204
|
+
loopback only. See [docs/architecture.md](docs/architecture.md#security-notes) to restrict it.
|
|
205
|
+
- **Windows-key combinations** such as Win+R don't come through from macOS. Tapping ⌘ for the Windows key does.
|
|
206
|
+
- **Desktops behind a host** can be added and removed from the menus, but not edited there.
|
|
140
207
|
|
|
141
|
-
|
|
142
|
-
- **Screenshots:** page screenshots of the sandbox come back white while its window is behind others. The test reads the decoded canvas instead.
|
|
208
|
+
## License
|
|
143
209
|
|
|
144
|
-
|
|
210
|
+
[MIT](LICENSE). The bundled IronRDP build is MIT or Apache-2.0 ([vendor/IRONRDP-LICENSE](vendor/IRONRDP-LICENSE)).
|
|
211
|
+
Icons in the header are from [Font Awesome Free](https://fontawesome.com) (CC BY 4.0).
|
|
145
212
|
|
|
146
|
-
-
|
|
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.
|
|
213
|
+
tabby-rdp is an independent project, not affiliated with Tabby, Devolutions or the GNOME Project.
|
package/dist/audio.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AudioPlayer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Plays a remote desktop's audio (IronRDP's `audioPlayback` extension): blocks of 16-bit PCM, queued
|
|
6
|
+
* back to back on a Web Audio clock. A little lead absorbs network jitter; if playback falls too far
|
|
7
|
+
* behind (a stall, a busy tab), it skips ahead rather than letting the delay grow.
|
|
8
|
+
*/
|
|
9
|
+
class AudioPlayer {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.context = null;
|
|
12
|
+
this.gain = null;
|
|
13
|
+
/** Web Audio time at which the next block starts. */
|
|
14
|
+
this.next = 0;
|
|
15
|
+
/** Seconds of audio received and scheduled (for tests and troubleshooting). */
|
|
16
|
+
this.received = 0;
|
|
17
|
+
/** Loudest sample seen, 0..1 (for tests: tells sound from silence). */
|
|
18
|
+
this.peak = 0;
|
|
19
|
+
/** The callback for IronRDP's audioPlayback extension. */
|
|
20
|
+
this.callback = (message) => {
|
|
21
|
+
try {
|
|
22
|
+
if (message?.type === 'wave') {
|
|
23
|
+
this.play(message.sampleRate, message.channels, message.data);
|
|
24
|
+
}
|
|
25
|
+
else if (message?.type === 'volume') {
|
|
26
|
+
this.volume((Number(message.left) + Number(message.right)) / 2);
|
|
27
|
+
}
|
|
28
|
+
else if (message?.type === 'close') {
|
|
29
|
+
this.next = 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
console.warn('remote desktop audio:', e);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
play(sampleRate, channels, data) {
|
|
38
|
+
const frames = Math.floor(data.byteLength / 2 / channels);
|
|
39
|
+
if (!frames || !channels) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.context ??= new AudioContext({ latencyHint: 'interactive' });
|
|
43
|
+
if (!this.gain) {
|
|
44
|
+
this.gain = this.context.createGain();
|
|
45
|
+
this.gain.connect(this.context.destination);
|
|
46
|
+
}
|
|
47
|
+
if (this.context.state === 'suspended') {
|
|
48
|
+
this.context.resume().catch(() => null);
|
|
49
|
+
}
|
|
50
|
+
const buffer = this.context.createBuffer(channels, frames, sampleRate);
|
|
51
|
+
const samples = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
52
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
53
|
+
const out = buffer.getChannelData(ch);
|
|
54
|
+
for (let i = 0; i < frames; i++) {
|
|
55
|
+
const v = samples.getInt16((i * channels + ch) * 2, true) / 32768;
|
|
56
|
+
out[i] = v;
|
|
57
|
+
if (v > this.peak || -v > this.peak) {
|
|
58
|
+
this.peak = Math.abs(v);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const now = this.context.currentTime;
|
|
63
|
+
if (this.next < now || this.next > now + AudioPlayer.MAX_AHEAD) {
|
|
64
|
+
this.next = now + AudioPlayer.LEAD;
|
|
65
|
+
}
|
|
66
|
+
const source = this.context.createBufferSource();
|
|
67
|
+
source.buffer = buffer;
|
|
68
|
+
source.connect(this.gain);
|
|
69
|
+
source.start(this.next);
|
|
70
|
+
this.next += buffer.duration;
|
|
71
|
+
this.received += buffer.duration;
|
|
72
|
+
}
|
|
73
|
+
volume(level) {
|
|
74
|
+
if (this.gain && Number.isFinite(level)) {
|
|
75
|
+
this.gain.gain.value = Math.max(0, Math.min(1, level));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
this.context?.close().catch(() => null);
|
|
80
|
+
this.context = null;
|
|
81
|
+
this.gain = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.AudioPlayer = AudioPlayer;
|
|
85
|
+
AudioPlayer.LEAD = 0.08;
|
|
86
|
+
AudioPlayer.MAX_AHEAD = 0.5;
|
|
87
|
+
//# sourceMappingURL=audio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"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"}
|
package/dist/deskScript.js
CHANGED
|
@@ -19,7 +19,7 @@ KIND=${shq(request.kind)}
|
|
|
19
19
|
command -v gnome-terminal >/dev/null 2>&1 || { echo "RD_ERR gnome-terminal is not installed"; exit 0; }
|
|
20
20
|
W=$(systemctl --user show-environment 2>/dev/null | sed -n 's/^WAYLAND_DISPLAY=//p')
|
|
21
21
|
export WAYLAND_DISPLAY=${"$"}{W:-wayland-0}
|
|
22
|
-
TRD="$HOME/.local/share/tabby-
|
|
22
|
+
TRD="$HOME/.local/share/tabby-rdp/bin/trd-pty"
|
|
23
23
|
if [ "$KIND" = native ] && [ -n "$SESS" ] && [ -x "$TRD" ] && [ "$("$TRD" clients "$SESS")" -ge 1 ]; then
|
|
24
24
|
# The console itself is one attached terminal; a second one means a desktop terminal is already attached.
|
|
25
25
|
if [ "$("$TRD" clients "$SESS")" -ge 2 ]; then
|