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 gc_arena::Collect;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::{Element, Node};
|
use web_sys::{console, Element, Node};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -164,7 +164,15 @@ fn get_or_make_term_frame<'a>(
|
|||||||
.ok()
|
.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);
|
term.onResize(&resize_closure);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use self::{frameroutes::*, frames::*, muds::*, storage::*};
|
use self::{frameroutes::*, frames::*, muds::*, storage::*};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
use muds::telopt::configure_telopt_table;
|
||||||
use piccolo::{
|
use piccolo::{
|
||||||
async_callback::{AsyncSequence, Locals},
|
async_callback::{AsyncSequence, Locals},
|
||||||
meta_ops::{self, MetaResult},
|
meta_ops::{self, MetaResult},
|
||||||
@ -127,6 +128,31 @@ impl LuaState {
|
|||||||
self.interp.execute(&self.exec)?;
|
self.interp.execute(&self.exec)?;
|
||||||
Ok(())
|
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(
|
pub fn install_lua_globals(
|
||||||
@ -209,6 +235,11 @@ pub fn install_lua_globals(
|
|||||||
ctx.set_global("commands", cmd_table);
|
ctx.set_global("commands", cmd_table);
|
||||||
let info_table = Table::new(&ctx);
|
let info_table = Table::new(&ctx);
|
||||||
ctx.set_global("info", info_table);
|
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);
|
let muds_table = Table::new(&ctx);
|
||||||
ctx.set_global("muds", muds_table);
|
ctx.set_global("muds", muds_table);
|
||||||
let frames_table = Table::new(&ctx);
|
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, mudinput_line);
|
||||||
register_class_function!(mud_class_table, "closed", mudclass_closed);
|
register_class_function!(mud_class_table, "closed", mudclass_closed);
|
||||||
register_stateless_class_function!(mud_class_table, "new", new_mud);
|
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 {
|
macro_rules! register_class_nop {
|
||||||
($class_table: ident, $sym: ident) => {
|
($class_table: ident, $sym: ident) => {
|
||||||
@ -308,6 +341,7 @@ pub fn install_lua_globals(
|
|||||||
frame_class_table.set(ctx, MetaMethod::Index, frame_class_table)?;
|
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, "new", new_frame);
|
||||||
register_class_function!(frame_class_table, "input", frame_input);
|
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);
|
let frameroute_class_table = Table::new(&ctx);
|
||||||
classes_table.set(ctx, "frameroute", frameroute_class_table)?;
|
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,
|
seq: &mut AsyncSequence,
|
||||||
obj: T,
|
obj: T,
|
||||||
func_name: &'static str,
|
func_name: FN,
|
||||||
arguments: &[StashedValue],
|
arguments: &[StashedValue],
|
||||||
) -> Result<(), StashedError>
|
) -> Result<(), StashedError>
|
||||||
where
|
where
|
||||||
for<'gcb> <T as Fetchable>::Fetched<'gcb>: IntoValue<'gcb>,
|
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 call = seq.try_enter(|ctx, locals, _execution, mut stack| {
|
||||||
let obj = locals.fetch(&obj);
|
let obj = locals.fetch(&obj);
|
||||||
@ -400,11 +435,7 @@ where
|
|||||||
ctx,
|
ctx,
|
||||||
stack,
|
stack,
|
||||||
locals,
|
locals,
|
||||||
meta_ops::index(
|
meta_ops::index(ctx, obj.into_value(ctx), func_name.into_value(ctx))?,
|
||||||
ctx,
|
|
||||||
obj.into_value(ctx),
|
|
||||||
ctx.intern_static(func_name.as_bytes()).into_value(ctx),
|
|
||||||
)?,
|
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
if let Some(call) = call {
|
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 frame = locals.stash(&ctx, intern_id(ctx, frame));
|
||||||
let aliases = locals.stash(&ctx, aliases);
|
let aliases = locals.stash(&ctx, aliases);
|
||||||
async move {
|
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 echo_func = seq.try_enter(|ctx, locals, _ex, mut stack| {
|
||||||
let alias_tab: Table = stack.consume(ctx)?;
|
let alias_tab: Table = stack.consume(ctx)?;
|
||||||
stack.push_back(locals.fetch(&frame).into_value(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_tab = locals.stash(&ctx, frame_tab);
|
||||||
let frame = locals.stash(&ctx, frame);
|
let frame = locals.stash(&ctx, frame);
|
||||||
async move {
|
async move {
|
||||||
call_checking_metatable::<StashedTable>(&mut seq, frame_tab, "new", &[frame])
|
call_checking_metatable::<StashedTable, _>(&mut seq, frame_tab, "new", &[frame])
|
||||||
.await?;
|
.await?;
|
||||||
Ok(SequenceReturn::Return)
|
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 frame = locals.stash(&ctx, frame);
|
||||||
let line = locals.stash(&ctx, line.into_value(ctx));
|
let line = locals.stash(&ctx, line.into_value(ctx));
|
||||||
async move {
|
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)
|
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_tab = locals.stash(&ctx, frame_tab);
|
||||||
let frame = locals.stash(&ctx, frame);
|
let frame = locals.stash(&ctx, frame);
|
||||||
async move {
|
async move {
|
||||||
call_checking_metatable::<StashedUserData>(
|
call_checking_metatable::<StashedUserData, _>(
|
||||||
&mut seq,
|
&mut seq,
|
||||||
aliases.clone(),
|
aliases.clone(),
|
||||||
"try_run_sub",
|
"try_run_sub",
|
||||||
@ -426,7 +428,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
|
|||||||
return Ok(SequenceReturn::Return);
|
return Ok(SequenceReturn::Return);
|
||||||
}
|
}
|
||||||
|
|
||||||
call_checking_metatable::<StashedValue>(
|
call_checking_metatable::<StashedValue, _>(
|
||||||
&mut seq,
|
&mut seq,
|
||||||
linked_mud,
|
linked_mud,
|
||||||
"mudinput_line",
|
"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>(
|
fn list_timers<'gc>(
|
||||||
ctx: Context<'gc>,
|
ctx: Context<'gc>,
|
||||||
global_memo: &GlobalMemoCell,
|
global_memo: &GlobalMemoCell,
|
||||||
|
@ -14,14 +14,15 @@ use crate::{
|
|||||||
id_intern::{intern_id, unintern_id},
|
id_intern::{intern_id, unintern_id},
|
||||||
logging::{start_deleting_logs, start_downloading_logs, start_listing_logs},
|
logging::{start_deleting_logs, start_downloading_logs, start_listing_logs},
|
||||||
match_table::{create_match_table, match_table_add, match_table_remove},
|
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},
|
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
|
||||||
FrameId, GlobalLayoutCell, GlobalMemoCell,
|
FrameId, GlobalLayoutCell, GlobalMemoCell,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::telopt::{
|
use self::telopt::{
|
||||||
handle_incoming_do, handle_incoming_dont, handle_incoming_will, handle_incoming_wont,
|
get_option_state, handle_incoming_do, handle_incoming_dont, handle_incoming_will,
|
||||||
MudWithMemo, Telopt,
|
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};
|
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 seq = piccolo::async_sequence(&ctx, |locals, mut seq| {
|
||||||
let conntab = locals.stash(&ctx, conntab);
|
let conntab = locals.stash(&ctx, conntab);
|
||||||
async move {
|
async move {
|
||||||
call_checking_metatable::<StashedTable>(&mut seq, conntab.clone(), "closed", &[])
|
call_checking_metatable::<StashedTable, _>(
|
||||||
.await?;
|
&mut seq,
|
||||||
|
conntab.clone(),
|
||||||
|
"closed",
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(piccolo::SequenceReturn::Return)
|
Ok(piccolo::SequenceReturn::Return)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -169,12 +175,17 @@ pub(super) fn connect_mud<'gc>(
|
|||||||
let global_memo = global_memo.clone();
|
let global_memo = global_memo.clone();
|
||||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||||
let mut trusted: bool = false;
|
let mut trusted: bool = false;
|
||||||
|
let mut naws: bool = true;
|
||||||
let name: String = loop {
|
let name: String = loop {
|
||||||
let v: String = stack.from_front(ctx)?;
|
let v: String = stack.from_front(ctx)?;
|
||||||
if v == "-trust" {
|
if v == "-trust" {
|
||||||
trusted = true;
|
trusted = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if v == "-no_naws" {
|
||||||
|
naws = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break v;
|
break v;
|
||||||
};
|
};
|
||||||
let name: Value<'gc> = ctx.intern(name.as_bytes()).into();
|
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...
|
// Call conntab:new...
|
||||||
let seq = async_sequence(&ctx, |locals, mut seq| {
|
let seq = async_sequence(&ctx, |locals, mut seq| {
|
||||||
let conntab = locals.stash(&ctx, conntab);
|
let conntab = locals.stash(&ctx, conntab);
|
||||||
async move {
|
async move {
|
||||||
call_checking_metatable::<StashedTable>(&mut seq, conntab, "new", &[]).await?;
|
call_checking_metatable::<StashedTable, _>(&mut seq, conntab, "new", &[]).await?;
|
||||||
Ok(SequenceReturn::Return)
|
Ok(SequenceReturn::Return)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -344,7 +360,7 @@ pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -
|
|||||||
.collect();
|
.collect();
|
||||||
async move {
|
async move {
|
||||||
for (func_name, params) in fns {
|
for (func_name, params) in fns {
|
||||||
call_checking_metatable::<StashedTable>(
|
call_checking_metatable::<StashedTable, _>(
|
||||||
&mut seq,
|
&mut seq,
|
||||||
conntab.clone(),
|
conntab.clone(),
|
||||||
func_name,
|
func_name,
|
||||||
@ -434,7 +450,7 @@ pub(super) fn mudoutput_line<'gc>(
|
|||||||
let triggers = locals.stash(&ctx, triggers);
|
let triggers = locals.stash(&ctx, triggers);
|
||||||
let default_frame = locals.stash(&ctx, default_frame);
|
let default_frame = locals.stash(&ctx, default_frame);
|
||||||
async move {
|
async move {
|
||||||
call_checking_metatable::<StashedUserData>(
|
call_checking_metatable::<StashedUserData, _>(
|
||||||
&mut seq,
|
&mut seq,
|
||||||
triggers,
|
triggers,
|
||||||
"try_run_sub",
|
"try_run_sub",
|
||||||
@ -442,7 +458,7 @@ pub(super) fn mudoutput_line<'gc>(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
for frameroute in frameroutes {
|
for frameroute in frameroutes {
|
||||||
call_checking_metatable::<StashedTable>(
|
call_checking_metatable::<StashedTable, _>(
|
||||||
&mut seq,
|
&mut seq,
|
||||||
frameroute,
|
frameroute,
|
||||||
"route",
|
"route",
|
||||||
@ -463,6 +479,19 @@ pub(super) fn mudoutput_prompt<'gc>(
|
|||||||
) -> Callback<'gc> {
|
) -> Callback<'gc> {
|
||||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
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>(
|
pub(super) fn mudoutput_will<'gc>(
|
||||||
ctx: Context<'gc>,
|
ctx: Context<'gc>,
|
||||||
global_memo: &GlobalMemoCell,
|
global_memo: &GlobalMemoCell,
|
||||||
@ -477,8 +506,26 @@ pub(super) fn mudoutput_will<'gc>(
|
|||||||
memo: global_memo.clone(),
|
memo: global_memo.clone(),
|
||||||
mud: socket.clone(),
|
mud: socket.clone(),
|
||||||
};
|
};
|
||||||
handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno));
|
if handle_incoming_will(ctx, &mut mud_memo, &mud, &Telopt(optno)) {
|
||||||
Ok(CallbackReturn::Return)
|
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>(
|
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(),
|
memo: global_memo.clone(),
|
||||||
mud: socket.clone(),
|
mud: socket.clone(),
|
||||||
};
|
};
|
||||||
handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno));
|
if handle_incoming_do(ctx, &mut mud_memo, &mud, &Telopt(optno)) {
|
||||||
Ok(CallbackReturn::Return)
|
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>(
|
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);
|
let mud = locals.stash(&ctx, mud);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
call_checking_metatable::<StashedTable>(&mut seq, frameroute, "new", &[curr_frame])
|
call_checking_metatable::<StashedTable, _>(
|
||||||
.await?;
|
&mut seq,
|
||||||
|
frameroute,
|
||||||
|
"new",
|
||||||
|
&[curr_frame],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
seq.call(&create_match_tab, 0).await?;
|
seq.call(&create_match_tab, 0).await?;
|
||||||
|
|
||||||
seq.try_enter(|ctx, locals, _ex, mut stack| {
|
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<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> String {
|
||||||
fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result<String> {
|
fn name_mud_internal<'gc>(ctx: Context<'gc>, mud: &Table<'gc>) -> anyhow::Result<String> {
|
||||||
let socket: Value<'gc> = mud.get(ctx, "socket")?;
|
let socket: Value<'gc> = mud.get(ctx, "socket")?;
|
||||||
|
@ -4,7 +4,7 @@ use wasm_bindgen::JsValue;
|
|||||||
use web_sys::console;
|
use web_sys::console;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
telnet::{DO, DONT, IAC, WILL, WONT},
|
telnet::{DO, DONT, ENDSUB, IAC, STARTSUB, WILL, WONT},
|
||||||
websocket::{send_message_to_mud, WebSocketId},
|
websocket::{send_message_to_mud, WebSocketId},
|
||||||
GlobalMemoCell,
|
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>(
|
pub fn set_option_state<'gc>(
|
||||||
ctx: Context<'gc>,
|
ctx: Context<'gc>,
|
||||||
input: &Table<'gc>,
|
input: &Table<'gc>,
|
||||||
@ -214,7 +237,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
|||||||
mud: &mut T,
|
mud: &mut T,
|
||||||
table: &Table<'gc>,
|
table: &Table<'gc>,
|
||||||
opt: &OptionWithActiveSide,
|
opt: &OptionWithActiveSide,
|
||||||
) {
|
) -> bool {
|
||||||
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
match get_option_state(ctx, table, &opt.option, &opt.option_active_on) {
|
||||||
OptionState::No => {
|
OptionState::No => {
|
||||||
if get_option_supported(ctx, table, &opt.option, &opt.option_active_on) {
|
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,
|
&OptionState::Yes,
|
||||||
);
|
);
|
||||||
send_positive(mud, opt);
|
send_positive(mud, opt);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
send_negative(mud, opt);
|
send_negative(mud, opt);
|
||||||
}
|
}
|
||||||
@ -255,15 +279,19 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
|||||||
&opt.option,
|
&opt.option,
|
||||||
&opt.option_active_on,
|
&opt.option_active_on,
|
||||||
&OptionState::Yes,
|
&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 => {
|
OptionState::WantYesOpposite => {
|
||||||
set_option_state(
|
set_option_state(
|
||||||
ctx,
|
ctx,
|
||||||
@ -275,6 +303,7 @@ fn handle_incoming_positive<'gc, T: SendOptNeg>(
|
|||||||
send_negative(mud, opt);
|
send_negative(mud, opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_incoming_negative<'gc, T: SendOptNeg>(
|
fn handle_incoming_negative<'gc, T: SendOptNeg>(
|
||||||
@ -334,7 +363,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>(
|
|||||||
mud: &mut T,
|
mud: &mut T,
|
||||||
table: &Table<'gc>,
|
table: &Table<'gc>,
|
||||||
option: &Telopt,
|
option: &Telopt,
|
||||||
) {
|
) -> bool {
|
||||||
handle_incoming_positive(
|
handle_incoming_positive(
|
||||||
ctx,
|
ctx,
|
||||||
mud,
|
mud,
|
||||||
@ -343,7 +372,7 @@ pub fn handle_incoming_will<'gc, T: SendOptNeg>(
|
|||||||
option: option.clone(),
|
option: option.clone(),
|
||||||
option_active_on: Side::Him,
|
option_active_on: Side::Him,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_incoming_wont<'gc, T: SendOptNeg>(
|
pub fn handle_incoming_wont<'gc, T: SendOptNeg>(
|
||||||
@ -368,7 +397,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>(
|
|||||||
mud: &mut T,
|
mud: &mut T,
|
||||||
table: &Table<'gc>,
|
table: &Table<'gc>,
|
||||||
option: &Telopt,
|
option: &Telopt,
|
||||||
) {
|
) -> bool {
|
||||||
handle_incoming_positive(
|
handle_incoming_positive(
|
||||||
ctx,
|
ctx,
|
||||||
mud,
|
mud,
|
||||||
@ -377,7 +406,7 @@ pub fn handle_incoming_do<'gc, T: SendOptNeg>(
|
|||||||
option: option.clone(),
|
option: option.clone(),
|
||||||
option_active_on: Side::Us,
|
option_active_on: Side::Us,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_incoming_dont<'gc, T: SendOptNeg>(
|
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 GMCP_TELOPT: Telopt = Telopt(201);
|
||||||
|
pub const NAWS_TELOPT: Telopt = Telopt(31);
|
||||||
|
|
||||||
fn negotiate_option_on<'gc, T: SendOptNeg>(
|
fn negotiate_option_on<'gc, T: SendOptNeg>(
|
||||||
ctx: Context<'gc>,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use piccolo::{Context, Lua, Table};
|
use piccolo::{Context, Lua, Table};
|
||||||
|
@ -29,8 +29,8 @@ const ERASECHAR: u8 = 247;
|
|||||||
const ERASELINE: u8 = 248;
|
const ERASELINE: u8 = 248;
|
||||||
const GOAHEAD: u8 = 249;
|
const GOAHEAD: u8 = 249;
|
||||||
const EOR: u8 = 239;
|
const EOR: u8 = 239;
|
||||||
const STARTSUB: u8 = 250;
|
pub const STARTSUB: u8 = 250;
|
||||||
const ENDSUB: u8 = 240;
|
pub const ENDSUB: u8 = 240;
|
||||||
pub const WILL: u8 = 251;
|
pub const WILL: u8 = 251;
|
||||||
pub const WONT: u8 = 252;
|
pub const WONT: u8 = 252;
|
||||||
pub const DO: u8 = 253;
|
pub const DO: u8 = 253;
|
||||||
@ -146,22 +146,22 @@ pub fn parse_telnet_buf(input: &[u8]) -> (Vec<u8>, Option<TelnetOutput>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(&STARTSUB) => {
|
Some(&STARTSUB) => {
|
||||||
match (1..ptr.len() - 1).find(|i| ptr[*i] == IAC && ptr[*i + 1] == ENDSUB) {
|
match try_parse_subneg(&ptr[1..]) {
|
||||||
None => {
|
None => {
|
||||||
// Including the STARTSUB...
|
// Including the STARTSUB...
|
||||||
textbuf.push(IAC);
|
textbuf.push(IAC);
|
||||||
textbuf.extend_from_slice(ptr);
|
textbuf.extend_from_slice(ptr);
|
||||||
return (textbuf, None);
|
return (textbuf, None);
|
||||||
}
|
}
|
||||||
Some(end_idx) => {
|
Some((subneg, new_ptr)) => {
|
||||||
textbuf.extend_from_slice(&ptr[end_idx + 2..]);
|
textbuf.extend_from_slice(new_ptr);
|
||||||
return (
|
return (textbuf, Some(TelnetOutput::Subnegotiation(subneg)));
|
||||||
textbuf,
|
|
||||||
Some(TelnetOutput::Subnegotiation(ptr[1..end_idx].to_owned())),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(&IAC) => {
|
||||||
|
textbuf.push(IAC);
|
||||||
|
}
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
// Completely unexpected command. Warn and ignore.
|
// Completely unexpected command. Warn and ignore.
|
||||||
console::log_1(&JsValue::from_str(&format!(
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -284,5 +312,12 @@ mod tests {
|
|||||||
Some(TelnetOutput::Subnegotiation(b"blah".to_vec()))
|
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