* work * moar work * notes * work * separate to terminal and control channels * stdin working * serve html web client initial * serve static assets loaded with include_dir * merge * enable_web_server config parameter * compile time flag to disable web server capability * rustfmt * add license to all xterm.js assets * mouse working except copy/paste * helpful comment * web client improvements - move script to js file - add favicon - add nerd font - change title TODO: investigate if font license embedded in otf is sufficient * get mouse to work properly * kitty keyboard support initial * fix wrong type in preload link * wip axum websocket handlers - upgrade axum to v0.8.1, enable ws feature - begin setup of websocket handlers - tidy up imports * replace control listener * handle terminal websocket with axum * cleanup Cargo.toml * kitty fixes and bracketed paste * fix(mouse): pane not found crash * initial session switching infra * add `web_client_font` option * session switching, creation and resurrection working through the session manager * move session module to zellij-utils and share logic with web-client * some cleanups * require restart for enable-web-server * use session name from router * write config to disk and watch for config changes * rename session name to ipc path * add basic panic handler, make render_to_client exit on channel close * use while let instead of loop * handle websocket close * add mouse motions * make clipboard work * add weblink handling and webgl rendering * add todo * fix: use session name instead of patch on session switch * use "default" layout for new sessions * ui indication for session being shared * share this session ui * plugin assets * Fix process crash on mac with notify watcher. Use poll watcher instead of recommended as a workaround. * make url session switching and creation work * start welcome screen on root url * scaffold control messages, set font from config * set dimensions on session start * bring back session name from url * send bytes on terminal websocket instead of json - create web client os input and id before websocket connection * draft ui * work * refactor ui * remove otf font, remove margins to avoid scrollbar * version query endpoint for server status * web session info query endpoint * refactor: move stuff around * add web client info to session metadata * make tests pass * populate real data in session list * remove unnecessary endpoint * add web_client node to config, add font option * remove web_client_font * allow disabling the web session through the config - WIP * formalize sharing/not-sharing configuration * fix tests * allow shutting down web server * display error when web clients are forbidden to attach * only show sessions that allow web clients if this is a web client * style(fmt): rustfmt * fix: query web server from Zellij rather than from each plugin * remove log spam * handle some error paths better in the web client * allow controlling the web server through the cli * allow configuring the web server's ip/port * fix tests and format code * use direct WebServerStatus event instead of piggy-backing on SessionInfo * plugin revamp initial * make plugin responsive * adjust plugin title * refactor: share plugin * refactor: share plugin * add cors middleware * some fixes for running without a compiled web server capability * display error when starting the share plugin without web server support * clarify config * add pipelines to compile zellij without web support * display error when unable to start web server * only query web server when share plugin is running * refactor(web-client): connection table * give zellij_server_listener access to the control channel * fixes and clarifications * refactor: consolidate generate_unique_session_name * give proper error when trying to attach to a forbidden session * change browser URL when switching sessions * add keyboard shortcut * enforce https when bound to non-loopback ip * initial authentication token implementation * background color from theme * initial web client theme config * basic token generation ui * refactor set config message creation * also set body background * allow editing scrollback for plugins too * set scrollback to 0 * properly parse colors in config * generate token from plugin * nice login modals * initial token management screen * implement token authentication * refactor(share): token management screen * style(fmt): rustfmt * fix(plugin): some minor bugs * refactor(share): main screen * refactor(share): token screen * refactor(share): main * refactor(share): ui components * fix(responsiveness): properly send usage_width to the render function * fix cli commands and add some verbosity * add support for settings ansi and selection colors * add cursor and cursor accent * basic web client tests * fix tests * refactor: web client * use session tokens for authentication * improve modals * move shutdown to ipc * refactor: ipc logic * serialize theme config for web client * update tests * refactor: move some stuff around to prepare for config hot reload * config live reloading for the web clients * change remember-me UI wording * improve xterm.js link handling * make sure terminal is focused on mousemove * remove deprecated sharing indication from compact-bar * gate deps and functionality behind the web_server_compatibility feature * feat(build): add --no-web flag in all the places * fix some other build flows * add new assets * update CI for no-web (untested) * make more dependencies optional * update axum-extra * add web client configuration options * gracefully close connections on server exit * tests for graceful connection closing * handle client-side reconnect when server is down * fix: make sure ipc bus folder exists before starting * add commands to manage login tokens from the cli * style(fmt): rustfmt * some cleanups * fix(ux): allow alt-right-click on the web client without opening the context menu * fix: prevent attaching to welcome screen * fix: reload config issues * fix long socket path on macos * normalize config conversion and fix color gap in browser * revoke session_token cookie if it is not valid * fix: visual bug with multiple clients in extremely small screen sizes * fix: only include rusqlite for the web server capability builds * update e2e snapshots * refactor(web): client side js * some cleanups * moar cleanups * fix(tests): wait for server instead of using a fixed timeout * debug CI * fix(tests): use spawn_blocking for running the test web server * fix(tests): wait for http rather than tcp port * fix(tests): properly pass config path - hopefully this is the issue... * success! bring back the rest of the tests * attempt to fix the macos CI issue * docs(changelog): add PR --------- Co-authored-by: Thomas Linford <linford.t@gmail.com>
202 lines
6.7 KiB
Rust
202 lines
6.7 KiB
Rust
use crate::web_client::control_message::{
|
|
SetConfigPayload, WebClientToWebServerControlMessage,
|
|
WebClientToWebServerControlMessagePayload, WebServerToWebClientControlMessage,
|
|
};
|
|
use crate::web_client::message_handlers::{
|
|
parse_stdin, render_to_client, send_control_messages_to_client,
|
|
};
|
|
use crate::web_client::server_listener::zellij_server_listener;
|
|
use crate::web_client::types::{AppState, TerminalParams};
|
|
|
|
use axum::{
|
|
extract::{
|
|
ws::{Message, WebSocket, WebSocketUpgrade},
|
|
Path as AxumPath, Query, State,
|
|
},
|
|
response::IntoResponse,
|
|
};
|
|
use futures::StreamExt;
|
|
use tokio_util::sync::CancellationToken;
|
|
use zellij_utils::{input::mouse::MouseEvent, ipc::ClientToServerMsg};
|
|
|
|
pub async fn ws_handler_control(
|
|
ws: WebSocketUpgrade,
|
|
_path: Option<AxumPath<String>>,
|
|
State(state): State<AppState>,
|
|
) -> impl IntoResponse {
|
|
ws.on_upgrade(move |socket| handle_ws_control(socket, state))
|
|
}
|
|
|
|
pub async fn ws_handler_terminal(
|
|
ws: WebSocketUpgrade,
|
|
session_name: Option<AxumPath<String>>,
|
|
Query(params): Query<TerminalParams>,
|
|
State(state): State<AppState>,
|
|
) -> impl IntoResponse {
|
|
ws.on_upgrade(move |socket| handle_ws_terminal(socket, session_name, params, state))
|
|
}
|
|
|
|
async fn handle_ws_control(socket: WebSocket, state: AppState) {
|
|
let config = SetConfigPayload::from(&state.config);
|
|
let set_config_msg = WebServerToWebClientControlMessage::SetConfig(config);
|
|
|
|
let (control_socket_tx, mut control_socket_rx) = socket.split();
|
|
|
|
let (control_channel_tx, control_channel_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
send_control_messages_to_client(control_channel_rx, control_socket_tx);
|
|
|
|
let _ = control_channel_tx.send(Message::Text(
|
|
serde_json::to_string(&set_config_msg).unwrap().into(),
|
|
));
|
|
|
|
let send_message_to_server = |deserialized_msg: WebClientToWebServerControlMessage| {
|
|
let Some(client_connection) = state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.get_client_os_api(&deserialized_msg.web_client_id)
|
|
.cloned()
|
|
else {
|
|
log::error!("Unknown web_client_id: {}", deserialized_msg.web_client_id);
|
|
return;
|
|
};
|
|
let client_msg = match deserialized_msg.payload {
|
|
WebClientToWebServerControlMessagePayload::TerminalResize(size) => {
|
|
ClientToServerMsg::TerminalResize(size)
|
|
},
|
|
};
|
|
|
|
let _ = client_connection.send_to_server(client_msg);
|
|
};
|
|
|
|
let mut set_client_control_channel = false;
|
|
|
|
while let Some(Ok(msg)) = control_socket_rx.next().await {
|
|
match msg {
|
|
Message::Text(msg) => {
|
|
let deserialized_msg: Result<WebClientToWebServerControlMessage, _> =
|
|
serde_json::from_str(&msg);
|
|
match deserialized_msg {
|
|
Ok(deserialized_msg) => {
|
|
if !set_client_control_channel {
|
|
set_client_control_channel = true;
|
|
state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.add_client_control_tx(
|
|
&deserialized_msg.web_client_id,
|
|
control_channel_tx.clone(),
|
|
);
|
|
}
|
|
send_message_to_server(deserialized_msg);
|
|
},
|
|
Err(e) => {
|
|
log::error!("Failed to deserialize client msg: {:?}", e);
|
|
},
|
|
}
|
|
},
|
|
Message::Close(_) => {
|
|
return;
|
|
},
|
|
_ => {
|
|
log::error!("Unsupported messagetype : {:?}", msg);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn handle_ws_terminal(
|
|
socket: WebSocket,
|
|
session_name: Option<AxumPath<String>>,
|
|
params: TerminalParams,
|
|
state: AppState,
|
|
) {
|
|
let web_client_id = params.web_client_id;
|
|
let Some(os_input) = state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.get_client_os_api(&web_client_id)
|
|
.cloned()
|
|
else {
|
|
log::error!("Unknown web_client_id: {}", web_client_id);
|
|
return;
|
|
};
|
|
|
|
let (client_terminal_channel_tx, mut client_terminal_channel_rx) = socket.split();
|
|
let (stdout_channel_tx, stdout_channel_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.add_client_terminal_tx(&web_client_id, stdout_channel_tx);
|
|
|
|
zellij_server_listener(
|
|
os_input.clone(),
|
|
state.connection_table.clone(),
|
|
session_name.map(|p| p.0),
|
|
state.config.clone(),
|
|
state.config_options.clone(),
|
|
Some(state.config_file_path.clone()),
|
|
web_client_id.clone(),
|
|
state.session_manager.clone(),
|
|
);
|
|
|
|
let terminal_channel_cancellation_token = CancellationToken::new();
|
|
render_to_client(
|
|
stdout_channel_rx,
|
|
client_terminal_channel_tx,
|
|
terminal_channel_cancellation_token.clone(),
|
|
);
|
|
state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.add_client_terminal_channel_cancellation_token(
|
|
&web_client_id,
|
|
terminal_channel_cancellation_token,
|
|
);
|
|
|
|
let explicitly_disable_kitty_keyboard_protocol = state
|
|
.config
|
|
.options
|
|
.support_kitty_keyboard_protocol
|
|
.map(|e| !e)
|
|
.unwrap_or(false);
|
|
let mut mouse_old_event = MouseEvent::new();
|
|
while let Some(Ok(msg)) = client_terminal_channel_rx.next().await {
|
|
match msg {
|
|
Message::Text(msg) => {
|
|
let Some(client_connection) = state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.get_client_os_api(&web_client_id)
|
|
.cloned()
|
|
else {
|
|
log::error!("Unknown web_client_id: {}", web_client_id);
|
|
continue;
|
|
};
|
|
parse_stdin(
|
|
msg.as_bytes(),
|
|
client_connection.clone(),
|
|
&mut mouse_old_event,
|
|
explicitly_disable_kitty_keyboard_protocol,
|
|
);
|
|
},
|
|
Message::Close(_) => {
|
|
state
|
|
.connection_table
|
|
.lock()
|
|
.unwrap()
|
|
.remove_client(&web_client_id);
|
|
break;
|
|
},
|
|
_ => {
|
|
log::error!("Unsupported websocket msg type");
|
|
},
|
|
}
|
|
}
|
|
os_input.send_to_server(ClientToServerMsg::ClientExited);
|
|
}
|