Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ else()
add_compile_definitions(_CRT_SECURE_NO_WARNINGS _GNU_SOURCE)
endif()

set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c)
set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c src/notify.c)

include(FindPackageHandleStandardArgs)

Expand All @@ -57,8 +57,10 @@ endif()

find_package(ZLIB REQUIRED)
find_package(Libwebsockets 3.2.0 REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBSYSTEMD IMPORTED_TARGET libsystemd)

set(INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ${LIBWEBSOCKETS_INCLUDE_DIRS} ${JSON-C_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS})
set(INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ${LIBWEBSOCKETS_INCLUDE_DIRS} ${JSON-C_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS} ${LIBSYSTEMD_INCLUDE_DIRS})
if(MSVC)
# vcpkg's LIBWEBSOCKETS_LIBRARIES lists both 'websockets' and 'websockets_shared'
# but only one target exists per triplet, so resolve the correct one.
Expand All @@ -68,7 +70,7 @@ if(MSVC)
set(LINK_LIBS ${ZLIB_LIBRARIES} websockets ${JSON-C_LIBRARIES} ${LIBUV_LIBRARIES})
endif()
else()
set(LINK_LIBS ${ZLIB_LIBRARIES} ${LIBWEBSOCKETS_LIBRARIES} ${JSON-C_LIBRARIES} ${LIBUV_LIBRARIES})
set(LINK_LIBS ${ZLIB_LIBRARIES} ${LIBWEBSOCKETS_LIBRARIES} ${JSON-C_LIBRARIES} ${LIBUV_LIBRARIES} ${LIBSYSTEMD_LIBRARIES})
endif()

set (CMAKE_REQUIRED_INCLUDES ${INCLUDE_DIRS})
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ttyd is a simple command-line tool for sharing terminal over the web.
- Run any custom command with options
- Basic authentication support and many other custom options
- Cross platform: macOS, Linux, FreeBSD/OpenBSD, [OpenWrt](https://openwrt.org), Windows
- Desktop notification bridging on Linux: registers as a D-Bus notification daemon, forwarding desktop notifications to the browser

# Installation

Expand Down Expand Up @@ -75,6 +76,8 @@ OPTIONS:
-I, --index Custom index.html path
-b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
-P, --ping-interval Websocket ping interval(sec) (default: 5)
-f, --srv-buf-size Maximum chunk of file (in bytes) that can be sent at once, a larger value may improve throughput (default: 4096)
-N, --monitor-notifications Monitor desktop notifications via D-Bus and forward to browser
-6, --ipv6 Enable IPv6 support
-S, --ssl Enable SSL
-C, --ssl-cert SSL certificate file path
Expand All @@ -87,6 +90,20 @@ OPTIONS:

Read the example usage on the [wiki](https://github.com/tsl0922/ttyd/wiki/Example-Usage).

## Desktop Notification Bridging

Pass `-N` (or `--monitor-notifications`) to forward desktop notifications to the browser:

```
ttyd -N -W bash
```

When enabled, ttyd registers as a D-Bus notification daemon on the session bus (falling back to the system bus on headless servers). Any application that sends notifications via `org.freedesktop.Notifications` will have them forwarded to all connected browser clients as native [Browser Notifications](https://developer.mozilla.org/en-US/docs/Web/API/Notification).

If a notification daemon is already running (e.g. KDE Plasma, GNOME Shell), ttyd falls back to monitoring with `dbus-monitor`.

Build requirement: `libsystemd-dev` (for `sd-bus`).

## Browser Support

Modern browsers, See [Browser Support](https://github.com/xtermjs/xterm.js#browser-support).
Expand Down
7 changes: 6 additions & 1 deletion html/src/components/terminal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ export class Terminal extends Component<Props, State> {

render({ id }: Props, { modal }: State) {
return (
<div id={id} ref={c => (this.container = c as HTMLElement)}>
<div
id={id}
ref={c => {
this.container = c as HTMLElement;
}}
>
<Modal show={modal}>
<label class="file-label">
<input onChange={this.sendFile} class="file-input" type="file" multiple />
Expand Down
26 changes: 25 additions & 1 deletion html/src/components/terminal/xterm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum Command {
OUTPUT = '0',
SET_WINDOW_TITLE = '1',
SET_PREFERENCES = '2',
NOTIFICATION = '3',

// client side
INPUT = '0',
Expand Down Expand Up @@ -192,7 +193,7 @@ export class Xterm {
terminal.onSelectionChange(() => {
if (this.terminal.getSelection() === '') return;
try {
document.execCommand('copy');
navigator.clipboard.writeText(this.terminal.getSelection()).catch(() => {});
} catch (e) {
return;
}
Expand All @@ -201,6 +202,8 @@ export class Xterm {
);
register(addEventListener(window, 'resize', () => fitAddon.fit()));
register(addEventListener(window, 'beforeunload', this.onWindowUnload));
register(addEventListener(window, 'focus', () => this.sendData('\x1b[I')));
register(addEventListener(window, 'blur', () => this.sendData('\x1b[O')));
}

@bind
Expand Down Expand Up @@ -264,6 +267,9 @@ export class Xterm {
const msg = JSON.stringify({ AuthToken: this.token, columns: terminal.cols, rows: terminal.rows });
this.socket?.send(textEncoder.encode(msg));

// Enable focus reporting so DECRPM ?1004$p returns "supported" (Crush notifications)
terminal.write('\x1b[?1004h');

if (this.opened) {
terminal.reset();
terminal.options.disableStdin = false;
Expand Down Expand Up @@ -361,12 +367,30 @@ export class Xterm {
...this.parseOptsFromUrlQuery(window.location.search),
} as Preferences);
break;
case Command.NOTIFICATION:
this.showNotification(textDecoder.decode(data));
break;
default:
console.warn(`[ttyd] unknown command: ${cmd}`);
break;
}
}

@bind
private showNotification(jsonStr: string) {
if (Notification.permission === 'denied') return;
if (Notification.permission === 'default') {
Notification.requestPermission();
return;
}
try {
const { app, summary, body } = JSON.parse(jsonStr);
new Notification(`[${app}] ${summary}`, { body: body });
} catch (e) {
console.warn('[ttyd] failed to parse notification:', e);
}
}

@bind
private applyPreferences(prefs: Preferences) {
const { terminal, fitAddon, register } = this;
Expand Down
4 changes: 4 additions & 0 deletions man/ttyd.1
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
-B, --browser
Open terminal with the default system browser

.PP
-N, --monitor-notifications
Monitor desktop notifications via D-Bus and forward to browser (Linux only, requires a D-Bus session bus)

.PP
-I, --index
Custom index.html path
Expand Down
14 changes: 14 additions & 0 deletions scripts/ttyd-dbus.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.freedesktop.Notifications"/>
<allow send_destination="org.freedesktop.Notifications"/>
<allow receive_sender="org.freedesktop.Notifications"/>
</policy>
<policy context="default">
<allow send_destination="org.freedesktop.Notifications"/>
<allow receive_sender="org.freedesktop.Notifications"/>
</policy>
</busconfig>
Loading