Implement NAWS (negotiate about window size)
This commit is contained in:
parent
c0739262af
commit
78e36e4dcb
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use gc_arena::Collect;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Element, Node};
|
||||
use web_sys::{console, Element, Node};
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::{
|
||||
@ -164,7 +164,15 @@ fn get_or_make_term_frame<'a>(
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
.unwrap_or(())
|
||||
.unwrap_or(());
|
||||
let mut engine = globals_for_resize.lua_engine.borrow_mut();
|
||||
match engine.dispatch_resize(&frame_for_resize, dims.cols(), dims.rows()) {
|
||||
Err(e) => console::log_1(&JsValue::from_str(&format!(
|
||||
"Error sending resize to Lua: {}",
|
||||
e
|
||||
))),
|
||||
Ok(()) => {}
|
||||
}
|
||||
});
|
||||
term.onResize(&resize_closure);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use self::{frameroutes::*, frames::*, muds::*, storage::*};
|
||||
use anyhow::Error;
|
||||
use muds::telopt::configure_telopt_table;
|
||||
use piccolo::{
|
||||
async_callback::{AsyncSequence, Locals},
|
||||
meta_ops::{self, MetaResult},
|
||||
@ -127,6 +128,31 @@ impl LuaState {
|
||||
self.interp.execute(&self.exec)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_resize(
|
||||
&mut self,
|
||||
frame: &FrameId,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> anyhow::Result<()> {
|
||||
self.interp.try_enter(|ctx| {
|
||||
let frames: Table = ctx.get_global("frames")?;
|
||||
let frame_tab: Value = frames.get(ctx, frame.0 as i64)?;
|
||||
if frame_tab.is_nil() {
|
||||
Err(anyhow::Error::msg(
|
||||
"Dispatching command to frame missing in Lua frames.",
|
||||
))?;
|
||||
}
|
||||
ctx.fetch(&self.exec).restart(
|
||||
ctx,
|
||||
Function::Callback(send_resize_to_frame(ctx)),
|
||||
(frame_tab, width, height),
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
self.interp.execute(&self.exec)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install_lua_globals(
|
||||
@ -209,6 +235,11 @@ pub fn install_lua_globals(
|
||||
ctx.set_global("commands", cmd_table);
|
||||
let info_table = Table::new(&ctx);
|
||||
ctx.set_global("info", info_table);
|
||||
|
||||
let telopt_table = Table::new(&ctx);
|
||||
let _ = info_table.set(ctx, "telopts", telopt_table);
|
||||
configure_telopt_table(ctx, &telopt_table);
|
||||
|
||||
let muds_table = Table::new(&ctx);
|
||||
ctx.set_global("muds", muds_table);
|
||||
let frames_table = Table::new(&ctx);
|
||||
@ -284,6 +315,8 @@ pub fn install_lua_globals(
|
||||
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);
|
||||
register_class_function!(mud_class_table, "term_resized", mud_term_resized);
|
||||
register_class_function!(mud_class_table, local_naws_enabled);
|
||||
|
||||
macro_rules! register_class_nop {
|
||||
($class_table: ident, $sym: ident) => {
|
||||
@ -308,6 +341,7 @@ pub fn install_lua_globals(
|
||||
frame_class_table.set(ctx, MetaMethod::Index, frame_class_table)?;
|
||||
register_class_function!(frame_class_table, "new", new_frame);
|
||||
register_class_function!(frame_class_table, "input", frame_input);
|
||||
register_class_function!(frame_class_table, "resize", frame_resize);
|
||||
|
||||
let frameroute_class_table = Table::new(&ctx);
|
||||
classes_table.set(ctx, "frameroute", frameroute_class_table)?;
|
||||
@ -384,14 +418,15 @@ pub fn prep_metaop_call<'gc, const N: usize>(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_checking_metatable<'gc, T: Fetchable>(
|
||||
pub async fn call_checking_metatable<'gc, T: Fetchable, FN>(
|
||||
seq: &mut AsyncSequence,
|
||||
obj: T,
|
||||
func_name: &'static str,
|
||||
func_name: FN,
|
||||
arguments: &[StashedValue],
|
||||
) -> Result<(), StashedError>
|
||||
where
|
||||
for<'gcb> <T as Fetchable>::Fetched<'gcb>: IntoValue<'gcb>,
|
||||
for<'gcb> FN: IntoValue<'gcb>,
|
||||
{
|
||||
let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
|
||||
let obj = locals.fetch(&obj);
|
||||
@ -400,11 +435,7 @@ where
|
||||
ctx,
|
||||
stack,
|
||||
locals,
|
||||
meta_ops::index(
|
||||
ctx,
|
||||
obj.into_value(ctx),
|
||||
ctx.intern_static(func_name.as_bytes()).into_value(ctx),
|
||||
)?,
|
||||
meta_ops::index(ctx, obj.into_value(ctx), func_name.into_value(ctx))?,
|
||||
))
|
||||
})?;
|
||||
if let Some(call) = call {
|
||||
|
@ -106,7 +106,8 @@ pub fn list_match_tab<'gc>(
|
||||
let frame = locals.stash(&ctx, intern_id(ctx, frame));
|
||||
let aliases = locals.stash(&ctx, aliases);
|
||||
async move {
|
||||
call_checking_metatable::<StashedUserData>(&mut seq, aliases, "lua_table", &[]).await?;
|
||||
call_checking_metatable::<StashedUserData, _>(&mut seq, aliases, "lua_table", &[])
|
||||
.await?;
|
||||
let echo_func = seq.try_enter(|ctx, locals, _ex, mut stack| {
|
||||
let alias_tab: Table = stack.consume(ctx)?;
|
||||
stack.push_back(locals.fetch(&frame).into_value(ctx));
|
||||
@ -321,7 +322,7 @@ pub fn ensure_frame_instance<'gc>(ctx: Context<'gc>, frame: &FrameId) -> Callbac
|
||||
let frame_tab = locals.stash(&ctx, frame_tab);
|
||||
let frame = locals.stash(&ctx, frame);
|
||||
async move {
|
||||
call_checking_metatable::<StashedTable>(&mut seq, frame_tab, "new", &[frame])
|
||||
call_checking_metatable::<StashedTable, _>(&mut seq, frame_tab, "new", &[frame])
|
||||
.await?;
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
@ -372,7 +373,8 @@ pub(super) fn send_command_to_frame<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
|
||||
let frame = locals.stash(&ctx, frame);
|
||||
let line = locals.stash(&ctx, line.into_value(ctx));
|
||||
async move {
|
||||
call_checking_metatable::<StashedTable>(&mut seq, frame, "input", &[line]).await?;
|
||||
call_checking_metatable::<StashedTable, _>(&mut seq, frame, "input", &[line])
|
||||
.await?;
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
@ -403,7 +405,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
|
||||
let frame_tab = locals.stash(&ctx, frame_tab);
|
||||
let frame = locals.stash(&ctx, frame);
|
||||
async move {
|
||||
call_checking_metatable::<StashedUserData>(
|
||||
call_checking_metatable::<StashedUserData, _>(
|
||||
&mut seq,
|
||||
aliases.clone(),
|
||||
"try_run_sub",
|
||||
@ -426,7 +428,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
|
||||
return Ok(SequenceReturn::Return);
|
||||
}
|
||||
|
||||
call_checking_metatable::<StashedValue>(
|
||||
call_checking_metatable::<StashedValue, _>(
|
||||
&mut seq,
|
||||
linked_mud,
|
||||
"mudinput_line",
|
||||
@ -444,6 +446,77 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn send_resize_to_frame<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let (frame, width, height): (Table, u16, u16) = stack.consume(ctx)?;
|
||||
let seq = async_sequence(&ctx, |locals, mut seq| {
|
||||
let frame = locals.stash(&ctx, frame);
|
||||
let width = locals.stash(&ctx, width.into_value(ctx));
|
||||
let height = locals.stash(&ctx, height.into_value(ctx));
|
||||
async move {
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
frame,
|
||||
"resize",
|
||||
&[width, height],
|
||||
)
|
||||
.await?;
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
Ok(CallbackReturn::Sequence(seq))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn frame_resize<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let callback_return = {
|
||||
let frame_tab: Table = Table::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("classes.frame:resize missing object!"))?,
|
||||
)?;
|
||||
let width: Value = stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("classes.frame:resize missing width!"))?;
|
||||
let height: Value = stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("classes.frame:resize missing height!"))?;
|
||||
stack.consume(ctx)?;
|
||||
|
||||
frame_tab.set(ctx, "width", width)?;
|
||||
frame_tab.set(ctx, "height", height)?;
|
||||
|
||||
let linked_mud: Value = frame_tab.get(ctx, "linked_mud")?;
|
||||
|
||||
if linked_mud.is_nil() {
|
||||
return Ok(piccolo::CallbackReturn::Return);
|
||||
}
|
||||
|
||||
let seq = async_sequence(&ctx, |locals, mut seq| {
|
||||
let width = locals.stash(&ctx, width);
|
||||
let height = locals.stash(&ctx, height);
|
||||
let linked_mud = locals.stash(&ctx, linked_mud);
|
||||
async move {
|
||||
call_checking_metatable::<StashedValue, _>(
|
||||
&mut seq,
|
||||
linked_mud,
|
||||
"term_resized",
|
||||
&[width, height],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(piccolo::CallbackReturn::Sequence(seq))
|
||||
};
|
||||
callback_return
|
||||
})
|
||||
}
|
||||
|
||||
fn list_timers<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
|
@ -14,14 +14,15 @@ use crate::{
|
||||
id_intern::{intern_id, unintern_id},
|
||||
logging::{start_deleting_logs, start_downloading_logs, start_listing_logs},
|
||||
match_table::{create_match_table, match_table_add, match_table_remove},
|
||||
telnet::{parse_telnet_buf, TelnetOutput},
|
||||
telnet::{parse_telnet_buf, TelnetOutput, IAC},
|
||||
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
|
||||
FrameId, GlobalLayoutCell, GlobalMemoCell,
|
||||
};
|
||||
|
||||
use self::telopt::{
|
||||
handle_incoming_do, handle_incoming_dont, handle_incoming_will, handle_incoming_wont,
|
||||
MudWithMemo, Telopt,
|
||||
get_option_state, handle_incoming_do, handle_incoming_dont, handle_incoming_will,
|
||||
handle_incoming_wont, send_subnegotiation_if_allowed, set_option_supported, MudWithMemo,
|
||||
OptionState, Side, Telopt, NAWS_TELOPT,
|
||||
};
|
||||
|
||||
use super::{call_checking_metatable, list_match_tab, try_unwrap_frame, LuaState};
|
||||
@ -87,8 +88,13 @@ pub(super) fn mudclose<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) ->
|
||||
let seq = piccolo::async_sequence(&ctx, |locals, mut seq| {
|
||||
let conntab = locals.stash(&ctx, conntab);
|
||||
async move {
|
||||
call_checking_metatable::<StashedTable>(&mut seq, conntab.clone(), "closed", &[])
|
||||
.await?;
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
conntab.clone(),
|
||||
"closed",
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
Ok(piccolo::SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
@ -169,12 +175,17 @@ pub(super) fn connect_mud<'gc>(
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let mut trusted: bool = false;
|
||||
let mut naws: bool = true;
|
||||
let name: String = loop {
|
||||
let v: String = stack.from_front(ctx)?;
|
||||
if v == "-trust" {
|
||||
trusted = true;
|
||||
continue;
|
||||
}
|
||||
if v == "-no_naws" {
|
||||
naws = false;
|
||||
continue;
|
||||
}
|
||||
break v;
|
||||
};
|
||||
let name: Value<'gc> = ctx.intern(name.as_bytes()).into();
|
||||
@ -225,11 +236,16 @@ pub(super) fn connect_mud<'gc>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if naws {
|
||||
set_option_supported(ctx, &conntab, &NAWS_TELOPT, &Side::Us);
|
||||
}
|
||||
|
||||
// Call conntab:new...
|
||||
let seq = async_sequence(&ctx, |locals, mut seq| {
|
||||
let conntab = locals.stash(&ctx, conntab);
|
||||
async move {
|
||||
call_checking_metatable::<StashedTable>(&mut seq, conntab, "new", &[]).await?;
|
||||
call_checking_metatable::<StashedTable, _>(&mut seq, conntab, "new", &[]).await?;
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
@ -344,7 +360,7 @@ pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -
|
||||
.collect();
|
||||
async move {
|
||||
for (func_name, params) in fns {
|
||||
call_checking_metatable::<StashedTable>(
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
conntab.clone(),
|
||||
func_name,
|
||||
@ -434,7 +450,7 @@ pub(super) fn mudoutput_line<'gc>(
|
||||
let triggers = locals.stash(&ctx, triggers);
|
||||
let default_frame = locals.stash(&ctx, default_frame);
|
||||
async move {
|
||||
call_checking_metatable::<StashedUserData>(
|
||||
call_checking_metatable::<StashedUserData, _>(
|
||||
&mut seq,
|
||||
triggers,
|
||||
"try_run_sub",
|
||||
@ -442,7 +458,7 @@ pub(super) fn mudoutput_line<'gc>(
|
||||
)
|
||||
.await?;
|
||||
for frameroute in frameroutes {
|
||||
call_checking_metatable::<StashedTable>(
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
frameroute,
|
||||
"route",
|
||||
@ -463,6 +479,19 @@ pub(super) fn mudoutput_prompt<'gc>(
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
|
||||
fn name_telopt(ctx: Context, optno: u8) -> anyhow::Result<String> {
|
||||
for (k, v) in ctx
|
||||
.get_global::<Table>("info")?
|
||||
.get::<_, Table>(ctx, "telopts")?
|
||||
{
|
||||
if u8::from_value(ctx, v)? == optno {
|
||||
return Ok(String::from_value(ctx, k)?);
|
||||
}
|
||||
}
|
||||
Ok(format!("{}", optno))
|
||||
}
|
||||
|
||||
pub(super) fn mudoutput_will<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
@ -477,8 +506,26 @@ pub(super) fn mudoutput_will<'gc>(
|
||||
memo: global_memo.clone(),
|
||||
mud: socket.clone(),
|
||||
};
|
||||
handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno));
|
||||
Ok(CallbackReturn::Return)
|
||||
if handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno)) {
|
||||
let seq = async_sequence(&ctx, move |locals, mut seq| {
|
||||
let mud = locals.stash(&ctx, mud);
|
||||
async move {
|
||||
let name =
|
||||
seq.try_enter(|ctx, _locals, _ex, _stack| Ok(name_telopt(ctx, optno)?))?;
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
mud,
|
||||
format!("remote_{}_enabled", name),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
Ok(CallbackReturn::Sequence(seq))
|
||||
} else {
|
||||
Ok(CallbackReturn::Return)
|
||||
}
|
||||
})
|
||||
}
|
||||
pub(super) fn mudoutput_wont<'gc>(
|
||||
@ -510,8 +557,26 @@ pub(super) fn mudoutput_do<'gc>(ctx: Context<'gc>, global_memo: &GlobalMemoCell)
|
||||
memo: global_memo.clone(),
|
||||
mud: socket.clone(),
|
||||
};
|
||||
handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno));
|
||||
Ok(CallbackReturn::Return)
|
||||
if handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno)) {
|
||||
let seq = async_sequence(&ctx, move |locals, mut seq| {
|
||||
let mud = locals.stash(&ctx, mud);
|
||||
async move {
|
||||
let name =
|
||||
seq.try_enter(|ctx, _locals, _ex, _stack| Ok(name_telopt(ctx, optno)?))?;
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
mud,
|
||||
format!("local_{}_enabled", name),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
Ok(SequenceReturn::Return)
|
||||
}
|
||||
});
|
||||
Ok(CallbackReturn::Sequence(seq))
|
||||
} else {
|
||||
Ok(CallbackReturn::Return)
|
||||
}
|
||||
})
|
||||
}
|
||||
pub(super) fn mudoutput_dont<'gc>(
|
||||
@ -617,8 +682,13 @@ pub(super) fn new_mud<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
|
||||
let mud = locals.stash(&ctx, mud);
|
||||
|
||||
async move {
|
||||
call_checking_metatable::<StashedTable>(&mut seq, frameroute, "new", &[curr_frame])
|
||||
.await?;
|
||||
call_checking_metatable::<StashedTable, _>(
|
||||
&mut seq,
|
||||
frameroute,
|
||||
"new",
|
||||
&[curr_frame],
|
||||
)
|
||||
.await?;
|
||||
seq.call(&create_match_tab, 0).await?;
|
||||
|
||||
seq.try_enter(|ctx, locals, _ex, mut stack| {
|
||||
@ -734,6 +804,73 @@ pub(super) fn mud_untrigger(ctx: Context<'_>) -> Callback<'_> {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn mud_term_resized<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let (mud, width, height): (Table, u16, u16) = stack.consume(ctx)?;
|
||||
let socket: Value = mud.get(ctx, "socket")?;
|
||||
let socket: &WebSocketId =
|
||||
UserData::from_value(ctx, socket)?.downcast::<Rootable![Gc<'_, WebSocketId>]>()?;
|
||||
send_subnegotiation_if_allowed(
|
||||
ctx,
|
||||
&mud,
|
||||
&NAWS_TELOPT,
|
||||
&Side::Us,
|
||||
&mut MudWithMemo {
|
||||
memo: global_memo.clone(),
|
||||
mud: socket.clone(),
|
||||
},
|
||||
&[
|
||||
(width >> 8) as u8,
|
||||
(width & 0xFF) as u8,
|
||||
(height >> 8) as u8,
|
||||
(height & 0xFF) as u8,
|
||||
],
|
||||
);
|
||||
Ok(CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn local_naws_enabled<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_state_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
let global_state_memo = global_state_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let mud: Table = stack.consume(ctx)?;
|
||||
let frameroutes: Table = mud.get(ctx, "frameroutes")?;
|
||||
let default_frame: FrameId = try_unwrap_frame(
|
||||
ctx,
|
||||
&frameroutes
|
||||
.iter()
|
||||
.map(|(_k, v)| Ok::<Table, Error>(Table::from_value(ctx, v)?))
|
||||
.next()
|
||||
.unwrap_or_else(|| Ok(ctx.get_global::<Table>("frames")?.get(ctx, 1_i64)?))?
|
||||
.get(ctx, "frame")?,
|
||||
)?;
|
||||
let default_frame: Table = ctx
|
||||
.get_global::<Table>("frames")?
|
||||
.get(ctx, default_frame.0 as i64)?;
|
||||
let width: Value = default_frame.get(ctx, "width")?;
|
||||
let height: Value = default_frame.get(ctx, "height")?;
|
||||
if width.is_nil() || height.is_nil() {
|
||||
return Ok(CallbackReturn::Return);
|
||||
}
|
||||
stack.push_back(mud.into_value(ctx));
|
||||
stack.push_back(width);
|
||||
stack.push_back(height);
|
||||
let cb = mud_term_resized(ctx, &global_state_memo);
|
||||
|
||||
Ok(CallbackReturn::Call {
|
||||
function: Function::Callback(cb),
|
||||
then: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn name_mud<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> String {
|
||||
fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result<String> {
|
||||
let socket: Value<'gc> = mud.get(ctx, "socket")?;
|
||||
|
@ -4,7 +4,7 @@ use wasm_bindgen::JsValue;
|
||||
use web_sys::console;
|
||||
|
||||
use crate::{
|
||||
telnet::{DO, DONT, IAC, WILL, WONT},
|
||||
telnet::{DO, DONT, ENDSUB, IAC, STARTSUB, WILL, WONT},
|
||||
websocket::{send_message_to_mud, WebSocketId},
|
||||
GlobalMemoCell,
|
||||
};
|
||||
@ -182,6 +182,29 @@ pub fn get_option_state<'gc>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_subnegotiation_if_allowed<'gc, T: SendRaw>(
|
||||
ctx: Context<'gc>,
|
||||
mud_table: &Table<'gc>,
|
||||
opt: &Telopt,
|
||||
side: &Side,
|
||||
mud: &mut T,
|
||||
msg: &[u8],
|
||||
) {
|
||||
if get_option_state(ctx, mud_table, opt, side) == OptionState::Yes {
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(msg.len() + 4);
|
||||
buf.extend_from_slice(&[IAC, STARTSUB]);
|
||||
for c in msg {
|
||||
if *c == IAC {
|
||||
buf.extend_from_slice(b"\xff\xff");
|
||||
} else {
|
||||
buf.push(*c);
|
||||
}
|
||||
}
|
||||
buf.extend_from_slice(&[IAC, ENDSUB]);
|
||||
mud.send_bytes(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_option_state<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
input: &Table<'gc>,
|
||||
@ -214,7 +237,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
||||
mud: &mut T,
|
||||
table: &Table<'gc>,
|
||||
opt: &OptionWithActiveSide,
|
||||
) {
|
||||
) -> bool {
|
||||
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
||||
OptionState::No => {
|
||||
if get_option_supported(ctx, table, &opt.option, &opt.option_active_on) {
|
||||
@ -226,6 +249,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
||||
&OptionState::Yes,
|
||||
);
|
||||
send_positive(mud, opt);
|
||||
return true;
|
||||
} else {
|
||||
send_negative(mud, opt);
|
||||
}
|
||||
@ -255,15 +279,19 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
||||
&opt.option,
|
||||
&opt.option_active_on,
|
||||
&OptionState::Yes,
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
OptionState::WantYes => {
|
||||
set_option_state(
|
||||
ctx,
|
||||
table,
|
||||
&opt.option,
|
||||
&opt.option_active_on,
|
||||
&OptionState::Yes,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
OptionState::WantYes => set_option_state(
|
||||
ctx,
|
||||
table,
|
||||
&opt.option,
|
||||
&opt.option_active_on,
|
||||
&OptionState::Yes,
|
||||
),
|
||||
OptionState::WantYesOpposite => {
|
||||
set_option_state(
|
||||
ctx,
|
||||
@ -275,6 +303,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
||||
send_negative(mud, opt);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn handle_incoming_negative<'gc, T: SendOptNeg>(
|
||||
@ -334,7 +363,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>(
|
||||
mud: &mut T,
|
||||
table: &Table<'gc>,
|
||||
option: &Telopt,
|
||||
) {
|
||||
) -> bool {
|
||||
handle_incoming_positive(
|
||||
ctx,
|
||||
mud,
|
||||
@ -343,7 +372,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>(
|
||||
option: option.clone(),
|
||||
option_active_on: Side::Him,
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_incoming_wont<'gc, T: SendOptNeg>(
|
||||
@ -368,7 +397,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>(
|
||||
mud: &mut T,
|
||||
table: &Table<'gc>,
|
||||
option: &Telopt,
|
||||
) {
|
||||
) -> bool {
|
||||
handle_incoming_positive(
|
||||
ctx,
|
||||
mud,
|
||||
@ -377,7 +406,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>(
|
||||
option: option.clone(),
|
||||
option_active_on: Side::Us,
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_incoming_dont<'gc, T: SendOptNeg>(
|
||||
@ -398,6 +427,7 @@ pub fn handle_incoming_dont<'gc, T: SendOptNeg>(
|
||||
}
|
||||
|
||||
pub const GMCP_TELOPT: Telopt = Telopt(201);
|
||||
pub const NAWS_TELOPT: Telopt = Telopt(31);
|
||||
|
||||
fn negotiate_option_on<'gc, T: SendOptNeg>(
|
||||
ctx: Context<'gc>,
|
||||
@ -487,6 +517,15 @@ pub fn negotiate_option<'gc, T: SendOptNeg>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_telopt_table<'gc>(ctx: Context<'gc>, table: &Table<'gc>) {
|
||||
table
|
||||
.set(ctx, "naws", NAWS_TELOPT.0)
|
||||
.expect("Can't set NAWS in telopt table");
|
||||
table
|
||||
.set(ctx, "gmcp", GMCP_TELOPT.0)
|
||||
.expect("Can't set GMCP in telopt table");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use piccolo::{Context, Lua, Table};
|
||||
|
@ -29,8 +29,8 @@ const ERASECHAR: u8 = 247;
|
||||
const ERASELINE: u8 = 248;
|
||||
const GOAHEAD: u8 = 249;
|
||||
const EOR: u8 = 239;
|
||||
const STARTSUB: u8 = 250;
|
||||
const ENDSUB: u8 = 240;
|
||||
pub const STARTSUB: u8 = 250;
|
||||
pub const ENDSUB: u8 = 240;
|
||||
pub const WILL: u8 = 251;
|
||||
pub const WONT: u8 = 252;
|
||||
pub const DO: u8 = 253;
|
||||
@ -146,22 +146,22 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec<u8>, Option<TelnetOutput>) {
|
||||
}
|
||||
}
|
||||
Some(&STARTSUB) => {
|
||||
match (1..ptr.len() - 1).find(|i| ptr[*i] == IAC && ptr[*i + 1] == ENDSUB) {
|
||||
match try_parse_subneg(&ptr[1..]) {
|
||||
None => {
|
||||
// Including the STARTSUB...
|
||||
textbuf.push(IAC);
|
||||
textbuf.extend_from_slice(ptr);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(end_idx) => {
|
||||
textbuf.extend_from_slice(&ptr[end_idx + 2..]);
|
||||
return (
|
||||
textbuf,
|
||||
Some(TelnetOutput::Subnegotiation(ptr[1..end_idx].to_owned())),
|
||||
);
|
||||
Some((subneg, new_ptr)) => {
|
||||
textbuf.extend_from_slice(new_ptr);
|
||||
return (textbuf, Some(TelnetOutput::Subnegotiation(subneg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(&IAC) => {
|
||||
textbuf.push(IAC);
|
||||
}
|
||||
Some(c) => {
|
||||
// Completely unexpected command. Warn and ignore.
|
||||
console::log_1(&JsValue::from_str(&format!(
|
||||
@ -177,6 +177,34 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec<u8>, Option<TelnetOutput>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parse_subneg(mut ptr: &[u8]) -> Option<(Vec<u8>, &[u8])> {
|
||||
let mut subneg: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
match ptr.first() {
|
||||
None => return None,
|
||||
Some(&IAC) => {
|
||||
ptr = &ptr[1..];
|
||||
match ptr.first() {
|
||||
None => return None,
|
||||
Some(&ENDSUB) => {
|
||||
ptr = &ptr[1..];
|
||||
return Some((subneg, ptr));
|
||||
}
|
||||
Some(c) => {
|
||||
// For c != IAC this is not expected.
|
||||
subneg.push(*c);
|
||||
ptr = &ptr[1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(c) => {
|
||||
subneg.push(*c);
|
||||
ptr = &ptr[1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -284,5 +312,12 @@ mod tests {
|
||||
Some(TelnetOutput::Subnegotiation(b"blah".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfablah\xff\xff\xff\xf0World!"), // Partial subnegotiation
|
||||
(
|
||||
b"Hello World!".to_vec(),
|
||||
Some(TelnetOutput::Subnegotiation(b"blah\xff".to_vec()))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user