From 43a4a4f4d2f7f36acdbe9c838fb4ad137a08cbc9 Mon Sep 17 00:00:00 2001 From: Condorra Date: Sun, 17 Nov 2024 11:59:13 +1100 Subject: [PATCH] Basic TERMTYPE implementation. --- src/lua_engine.rs | 3 +- src/lua_engine/muds.rs | 69 +++++++++++++++++++++++++++++++++-- src/lua_engine/muds/telopt.rs | 9 ++++- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/lua_engine.rs b/src/lua_engine.rs index b680dab..575e335 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -311,7 +311,8 @@ pub fn install_lua_globals( register_class_function!(mud_class_table, mudoutput_wont); register_class_function!(mud_class_table, mudoutput_do); register_class_function!(mud_class_table, mudoutput_dont); - register_class_function!(mud_class_table, mudoutput_subnegotiation); + register_stateless_class_function!(mud_class_table, mudoutput_subnegotiation); + register_class_function!(mud_class_table, mudoutput_subnegotiation_termtype); register_class_function!(mud_class_table, mudinput_line); register_class_function!(mud_class_table, "closed", mudclass_closed); register_stateless_class_function!(mud_class_table, "new", new_mud); diff --git a/src/lua_engine/muds.rs b/src/lua_engine/muds.rs index 3e42c36..f8fde26 100644 --- a/src/lua_engine/muds.rs +++ b/src/lua_engine/muds.rs @@ -4,6 +4,7 @@ use piccolo::{ self, async_sequence, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, SequenceReturn, StashedTable, StashedUserData, StashedValue, Table, UserData, Value, }; +use telopt::TERMTYPE_TELOPT; use wasm_bindgen::JsValue; use web_sys::{console, window}; use yew::UseStateSetter; @@ -239,6 +240,7 @@ pub(super) fn connect_mud<'gc>( if naws { set_option_supported(ctx, &conntab, &NAWS_TELOPT, &Side::Us); } + set_option_supported(ctx, &conntab, &TERMTYPE_TELOPT, &Side::Us); // Call conntab:new... let seq = async_sequence(&ctx, |locals, mut seq| { @@ -596,11 +598,72 @@ pub(super) fn mudoutput_dont<'gc>( Ok(CallbackReturn::Return) }) } -pub(super) fn mudoutput_subnegotiation<'gc>( +pub(super) fn mudoutput_subnegotiation<'gc>(ctx: Context<'gc>) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let (mud, mut msg): (Table, Vec) = stack.consume(ctx)?; + if msg.is_empty() { + console::log_1(&JsValue::from_str( + "Received invalid subnegotiation with no type", + )); + return Ok(CallbackReturn::Return); + } + let msg_type = msg.remove(0); + let msg_typename = name_telopt(ctx, msg_type)?; + + let seq = async_sequence(&ctx, |locals, mut seq| { + let mud = locals.stash(&ctx, mud); + let msg = locals.stash(&ctx, msg.into_value(ctx)); + async move { + call_checking_metatable( + &mut seq, + mud, + format!("mudoutput_subnegotiation_{}", &msg_typename), + &[msg], + ) + .await?; + Ok(SequenceReturn::Return) + } + }); + + Ok(CallbackReturn::Sequence(seq)) + }) +} + +pub(super) fn mudoutput_subnegotiation_termtype<'gc>( ctx: Context<'gc>, - _global_memo: &GlobalMemoCell, + global_memo: &GlobalMemoCell, ) -> Callback<'gc> { - Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return)) + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let (mud, mut msg): (Table, Vec) = stack.consume(ctx)?; + if msg.is_empty() { + return Ok(CallbackReturn::Return); + } + let cmd = msg.remove(0); + + let socket: Value = mud.get(ctx, "socket")?; + let socket: &WebSocketId = + UserData::from_value(ctx, socket)?.downcast::]>()?; + + const SEND_CMD: u8 = 1; + match cmd { + SEND_CMD => { + send_subnegotiation_if_allowed( + ctx, + &mud, + &TERMTYPE_TELOPT, + &Side::Us, + &mut MudWithMemo { + memo: global_memo.clone(), + mud: socket.clone(), + }, + format!("\x00{}", "xterm").as_bytes(), + ); + Ok(CallbackReturn::Return) + } + _ => Ok(CallbackReturn::Return), + } + }) } pub(super) fn mudinput_line<'gc>( diff --git a/src/lua_engine/muds/telopt.rs b/src/lua_engine/muds/telopt.rs index 93cd112..bf70dff 100644 --- a/src/lua_engine/muds/telopt.rs +++ b/src/lua_engine/muds/telopt.rs @@ -193,6 +193,7 @@ pub fn send_subnegotiation_if_allowed<'gc, T: SendRaw>( if get_option_state(ctx, mud_table, opt, side) == OptionState::Yes { let mut buf: Vec = Vec::with_capacity(msg.len() + 4); buf.extend_from_slice(&[IAC, STARTSUB]); + buf.push(opt.0); for c in msg { if *c == IAC { buf.extend_from_slice(b"\xff\xff"); @@ -426,8 +427,9 @@ pub fn handle_incoming_dont<'gc, T: SendOptNeg>( ); } -pub const GMCP_TELOPT: Telopt = Telopt(201); +pub const TERMTYPE_TELOPT: Telopt = Telopt(24); pub const NAWS_TELOPT: Telopt = Telopt(31); +pub const GMCP_TELOPT: Telopt = Telopt(201); fn negotiate_option_on<'gc, T: SendOptNeg>( ctx: Context<'gc>, @@ -522,7 +524,10 @@ pub fn configure_telopt_table<'gc>(ctx: Context<'gc>, table: &Table<'gc>) { .set(ctx, "naws", NAWS_TELOPT.0) .expect("Can't set NAWS in telopt table"); table - .set(ctx, "gmcp", GMCP_TELOPT.0) + .set(ctx, "naws", NAWS_TELOPT.0) + .expect("Can't set NAWS in telopt table"); + table + .set(ctx, "termtype", TERMTYPE_TELOPT.0) .expect("Can't set GMCP in telopt table"); }