From c531994b1c6dd1625f021c91e337d018f32e8566 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 27 Aug 2022 18:31:00 -0500 Subject: [PATCH] avm2: Fire KeyboardEvent.KEY_UP and KeyboardEvent.KEY_DOWN The 'charCode' and 'keyCode' properties are now implemented on `KeyboardEvent` The input injection code we use does not support keyboard events, so we can't yet write a regression test for this. However, both 'You need to burn the rope' and 'This is the Only Level TOO' now properly handle keyboard events with this PR. --- core/src/avm2/globals.rs | 3 ++ .../globals/flash/events/KeyboardEvent.as | 19 ++++++- core/src/player.rs | 50 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index c1dddc5e9..63132f135 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -100,6 +100,7 @@ pub struct SystemClasses<'gc> { pub illegaloperationerror: ClassObject<'gc>, pub eventdispatcher: ClassObject<'gc>, pub rectangle: ClassObject<'gc>, + pub keyboardevent: ClassObject<'gc>, } impl<'gc> SystemClasses<'gc> { @@ -169,6 +170,7 @@ impl<'gc> SystemClasses<'gc> { illegaloperationerror: object, eventdispatcher: object, rectangle: object, + keyboardevent: object, } } } @@ -711,6 +713,7 @@ fn load_playerglobal<'gc>( ("flash.events", "Event", event), ("flash.events", "TextEvent", textevent), ("flash.events", "ErrorEvent", errorevent), + ("flash.events", "KeyboardEvent", keyboardevent), ("flash.events", "ProgressEvent", progressevent), ("flash.events", "SecurityErrorEvent", securityerrorevent), ("flash.events", "IOErrorEvent", ioerrorevent), diff --git a/core/src/avm2/globals/flash/events/KeyboardEvent.as b/core/src/avm2/globals/flash/events/KeyboardEvent.as index 82174cf21..81b54da90 100644 --- a/core/src/avm2/globals/flash/events/KeyboardEvent.as +++ b/core/src/avm2/globals/flash/events/KeyboardEvent.as @@ -4,11 +4,28 @@ package flash.events { public static const KEY_DOWN:String = "keyDown"; public static const KEY_UP:String = "keyUp"; + private var _charCode:uint; + private var _keyCode:uint; public function KeyboardEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, charCodeValue:uint = 0, keyCodeValue:uint = 0, keyLocationValue:uint = 0, ctrlKeyValue:Boolean = false, altKeyValue:Boolean = false, shiftKeyValue:Boolean = false) { super(type,bubbles,cancelable); - // TODO: fill this up + this._charCode = charCodeValue; + this._keyCode = keyCodeValue; + } + + public function get charCode():uint { + return this._charCode; + } + public function set charCode(val:uint) { + this._charCode = val; + } + + public function get keyCode():uint { + return this._keyCode; + } + public function set keyCode(val:uint) { + this._keyCode = val; } } } diff --git a/core/src/player.rs b/core/src/player.rs index 33635008a..668602432 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -5,6 +5,7 @@ use crate::avm1::object::Object; use crate::avm1::property::Attribute; use crate::avm1::{Avm1, ScriptObject, TObject, Value}; use crate::avm2::object::LoaderInfoObject; +use crate::avm2::object::TObject as _; use crate::avm2::{ Activation as Avm2Activation, Avm2, CallStack, Domain as Avm2Domain, EventObject as Avm2EventObject, @@ -907,6 +908,55 @@ impl Player { } } + if context.is_action_script_3() { + if let PlayerEvent::KeyDown { key_code, key_char } + | PlayerEvent::KeyUp { key_code, key_char } = event + { + let mut activation = Avm2Activation::from_nothing(context.reborrow()); + + let event_name = match event { + PlayerEvent::KeyDown { .. } => "keyDown", + PlayerEvent::KeyUp { .. } => "keyUp", + _ => unreachable!(), + }; + + let keyboardevent_class = activation.avm2().classes().keyboardevent; + let event_name_val: Avm2Value<'_> = + AvmString::new_utf8(activation.context.gc_context, event_name).into(); + let keyboard_event = keyboardevent_class + .construct( + &mut activation, + &[ + event_name_val, + true.into(), /* bubbles */ + false.into(), /* cancelable */ + key_char.map_or(0, |c| c as u32).into(), /* charCode */ + (key_code as u32).into(), /* keyCode */ + ], + ) + .expect("Failed to construct KeyboardEvent"); + + let target = activation + .context + .focus_tracker + .get() + .unwrap_or_else(|| activation.context.stage.into()) + .object2() + .coerce_to_object(&mut activation) + .expect("DisplayObject is not an object!"); + + if let Err(e) = + Avm2::dispatch_event(&mut activation.context, keyboard_event, target) + { + log::error!( + "Encountered AVM2 error when broadcasting `{}` event: {}", + event_name, + e + ); + } + } + } + // keyPress events take precedence over text input. if !key_press_handled { if let PlayerEvent::TextInput { codepoint } = event {