From 560f1d969374cfb7842ca737cb53d44c6b0a0e41 Mon Sep 17 00:00:00 2001
From: Alex Janka <alex@alexjanka.com>
Date: Mon, 27 Nov 2023 12:58:13 +1100
Subject: [PATCH] keyboard input works on mac gui (LMAO)

---
 Cargo.lock                              |   2 +-
 gui/src/macos/cacao_window_manager.rs   | 164 ++++++++++++++++++++----
 gui/src/macos/mod.rs                    |   7 +-
 lib/src/processor/memory/mmio/joypad.rs |   1 +
 4 files changed, 146 insertions(+), 28 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index c0b04e1..edb91a9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -487,7 +487,7 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 [[package]]
 name = "cacao"
 version = "0.4.0-beta2"
-source = "git+https://github.com/italicsjenga/cacao#6144e9f244abfd15687de00b6cfda6b4606f351a"
+source = "git+https://github.com/italicsjenga/cacao#4fd93e3faeec5e80aef36779745b3506c9b5017d"
 dependencies = [
  "bitmask-enum",
  "block2 0.2.0-alpha.6",
diff --git a/gui/src/macos/cacao_window_manager.rs b/gui/src/macos/cacao_window_manager.rs
index 62d2d20..bd12f66 100644
--- a/gui/src/macos/cacao_window_manager.rs
+++ b/gui/src/macos/cacao_window_manager.rs
@@ -9,13 +9,16 @@ use std::{
 };
 
 use cacao::{
-    appkit::window::{Window, WindowConfig, WindowDelegate, WindowStyle},
+    appkit::{
+        window::{Window, WindowConfig, WindowDelegate, WindowStyle},
+        Event, EventMask, EventMonitor,
+    },
     core_foundation::base::TCFTypeRef,
 };
 use cpal::Stream;
 use frontend_common::window::{RendererChannel, WindowManager};
 use gb_emu_lib::{
-    connect::{EmulatorMessage, RendererMessage, ResolutionData},
+    connect::{EmulatorMessage, JoypadButtons, JoypadState, RendererMessage, ResolutionData},
     renderer::{RendererBackend, RendererBackendManager, WindowOptions},
 };
 use raw_window_handle::{
@@ -27,8 +30,15 @@ use uuid::Uuid;
 use super::{dispatch, AppMessage};
 
 pub enum EmuWindowMessage {
-    Closing,
-    Renderer(RendererMessage<[u8; 4]>),
+    Closing {
+        id: Uuid,
+    },
+    Renderer {
+        id: Uuid,
+        renderer_message: RendererMessage<[u8; 4]>,
+    },
+    KeyDown(JoypadButtons),
+    KeyUp(JoypadButtons),
 }
 
 pub struct EmulatorHandles {
@@ -60,18 +70,101 @@ impl Drop for EmulatorHandles {
     }
 }
 
+struct ButtonHandler {
+    _down_monitor: EventMonitor,
+    _up_monitor: EventMonitor,
+}
+
+impl ButtonHandler {
+    fn new() -> Self {
+        let _down_monitor = Event::local_monitor(EventMask::KeyDown, |v| {
+            if let Some(button) = get_buttons(&v) {
+                dispatch(AppMessage::EmuWindow(EmuWindowMessage::KeyDown(button)));
+                None
+            } else {
+                Some(v)
+            }
+        });
+        let _up_monitor = Event::local_monitor(EventMask::KeyUp, |v| {
+            if let Some(button) = get_buttons(&v) {
+                dispatch(AppMessage::EmuWindow(EmuWindowMessage::KeyUp(button)));
+                None
+            } else {
+                Some(v)
+            }
+        });
+
+        Self {
+            _down_monitor,
+            _up_monitor,
+        }
+    }
+}
+
+trait GetKeyChar {
+    fn get_key_char(&self) -> char;
+}
+
+impl GetKeyChar for JoypadButtons {
+    fn get_key_char(&self) -> char {
+        match self {
+            JoypadButtons::Down => 's',
+            JoypadButtons::Up => 'w',
+            JoypadButtons::Left => 'a',
+            JoypadButtons::Right => 'd',
+            JoypadButtons::Start => '=',
+            JoypadButtons::Select => '-',
+            JoypadButtons::B => ';',
+            JoypadButtons::A => '\'',
+        }
+    }
+}
+
+const ALL_BUTTONS: [JoypadButtons; 8] = [
+    JoypadButtons::Down,
+    JoypadButtons::Up,
+    JoypadButtons::Left,
+    JoypadButtons::Right,
+    JoypadButtons::Start,
+    JoypadButtons::Select,
+    JoypadButtons::B,
+    JoypadButtons::A,
+];
+
+fn get_buttons(event: &Event) -> Option<JoypadButtons> {
+    let characters = event.characters();
+    if characters.len() != 1 {
+        panic!("ok that assumption was wrong lol. event characters CAN be != 1");
+    }
+
+    if event.current_modifier_flags().is_empty() {
+        for button in ALL_BUTTONS {
+            if characters.contains(button.get_key_char()) {
+                return Some(button);
+            }
+        }
+    }
+    None
+}
+
 pub struct CacaoWindowManager {
     backend_manager: Arc<RendererBackendManager>,
     windows: HashMap<Uuid, Window<CacaoWindow>>,
     handles: Option<EmulatorHandles>,
+    joypad_state: JoypadState,
+    _button_handler: ButtonHandler,
 }
 
 impl CacaoWindowManager {
     pub fn new(display_handle: RawDisplayHandle) -> Self {
+        let _button_handler = ButtonHandler::new();
+
         Self {
             backend_manager: Arc::new(RendererBackendManager::new(display_handle)),
             windows: HashMap::new(),
             handles: None,
+            joypad_state: Default::default(),
+            _button_handler,
         }
     }
 
@@ -89,19 +182,22 @@ impl CacaoWindowManager {
         }
     }
 
-    pub fn message(&mut self, id: Uuid, message: EmuWindowMessage) {
+    pub fn message(&mut self, message: EmuWindowMessage) {
         match message {
-            EmuWindowMessage::Closing => {
+            EmuWindowMessage::Closing { id } => {
                 self.windows.remove(&id);
                 if self.windows.is_empty() {
                     let _ = self.handles.take();
                 }
             }
-            EmuWindowMessage::Renderer(message) => {
+            EmuWindowMessage::Renderer {
+                id,
+                renderer_message,
+            } => {
                 if let Some(window) = self.windows.get_mut(&id) {
                     if let Some(delegate) = window.delegate.as_mut() {
                         delegate.message(
-                            message,
+                            renderer_message,
                             Window {
                                 delegate: None,
                                 objc: window.objc.clone(),
@@ -110,6 +206,30 @@ impl CacaoWindowManager {
                     }
                 }
             }
+            EmuWindowMessage::KeyDown(key) => {
+                if let Some(handles) = self.handles.as_ref() {
+                    let old_state = self.joypad_state;
+                    self.joypad_state.set(key, true);
+                    if self.joypad_state != old_state {
+                        handles
+                            .sender
+                            .send(EmulatorMessage::JoypadUpdate(self.joypad_state))
+                            .unwrap();
+                    }
+                }
+            }
+            EmuWindowMessage::KeyUp(key) => {
+                if let Some(handles) = self.handles.as_ref() {
+                    let old_state = self.joypad_state;
+                    self.joypad_state.set(key, false);
+                    if self.joypad_state != old_state {
+                        handles
+                            .sender
+                            .send(EmulatorMessage::JoypadUpdate(self.joypad_state))
+                            .unwrap();
+                    }
+                }
+            }
         }
     }
 }
@@ -172,17 +292,18 @@ enum MonitorThreadState {
 impl MonitorThread {
     fn new(receiver: Receiver<RendererMessage<[u8; 4]>>, id: Uuid) -> Self {
         let (destroy, destroy_recv) = mpsc::channel();
-        let thread = std::thread::spawn(move || loop {
-            if let Ok(message) = receiver.recv() {
-                dispatch(AppMessage::EmuWindow {
-                    id,
-                    message: EmuWindowMessage::Renderer(message),
-                })
-            }
-            if let Ok(true) = destroy_recv.try_recv() {
-                return;
-            }
-        });
+        let thread =
+            std::thread::spawn(move || loop {
+                if let Ok(renderer_message) = receiver.recv() {
+                    dispatch(AppMessage::EmuWindow(EmuWindowMessage::Renderer {
+                        id,
+                        renderer_message,
+                    }));
+                }
+                if let Ok(true) = destroy_recv.try_recv() {
+                    return;
+                }
+            });
         Self {
             state: MonitorThreadState::Live { thread, destroy },
         }
@@ -292,10 +413,7 @@ impl WindowDelegate for CacaoWindow {
     }
 
     fn will_close(&self) {
-        dispatch(AppMessage::EmuWindow {
-            id: self.id,
-            message: EmuWindowMessage::Closing,
-        })
+        dispatch(AppMessage::EmuWindow(EmuWindowMessage::Closing { id: self.id }));
     }
 }
 
diff --git a/gui/src/macos/mod.rs b/gui/src/macos/mod.rs
index 818bf5f..d7aca06 100644
--- a/gui/src/macos/mod.rs
+++ b/gui/src/macos/mod.rs
@@ -10,7 +10,6 @@ use cacao::notification_center::Dispatcher;
 use frontend_common::audio;
 use gb_emu_lib::connect::{EmulatorCoreTrait, EmulatorMessage};
 use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
-use uuid::Uuid;
 
 use self::cacao_window_manager::{CacaoWindowManager, EmuWindowMessage};
 use self::preferences::{PreferencesMessage, PreferencesUi};
@@ -21,7 +20,7 @@ mod preferences;
 pub(crate) enum AppMessage {
     Core(CoreMessage),
     Preferences(PreferencesMessage),
-    EmuWindow { id: Uuid, message: EmuWindowMessage },
+    EmuWindow(EmuWindowMessage),
 }
 
 pub(crate) enum CoreMessage {
@@ -122,9 +121,9 @@ impl Dispatcher for TwincUiApp {
                     }
                 }
             }
-            AppMessage::EmuWindow { id, message } => {
+            AppMessage::EmuWindow(window_message) => {
                 if let Ok(mut window_manager) = self.current_game.write() {
-                    window_manager.message(id, message);
+                    window_manager.message(window_message);
                 }
             }
         }
diff --git a/lib/src/processor/memory/mmio/joypad.rs b/lib/src/processor/memory/mmio/joypad.rs
index 729509b..d9af209 100644
--- a/lib/src/processor/memory/mmio/joypad.rs
+++ b/lib/src/processor/memory/mmio/joypad.rs
@@ -22,6 +22,7 @@ pub struct JoypadState {
     pub a: bool,
 }
 
+#[derive(Debug, Clone, Copy, PartialEq)]
 pub enum JoypadButtons {
     Down,
     Up,