diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index c58d346..30d6021 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -24,13 +24,14 @@ HKL currentKeyboardLayout; HWND window; const wchar_t* const winclass = L"Espanso"; -KeypressCallback keypress_callback; + // UI #define APPWM_ICON_CLICK (WM_APP + 1) #define APPWM_NOTIFICATION_POPUP (WM_APP + 2) #define APPWM_NOTIFICATION_CLOSE (WM_APP + 3) +#define APPWM_SHOW_CONTEXT_MENU (WM_APP + 4) const wchar_t* const notification_winclass = L"EspansoNotification"; HWND nw = NULL; @@ -38,18 +39,24 @@ HWND hwnd_st_u = NULL; HBITMAP g_espanso_bmp = NULL; HICON g_espanso_ico = NULL; -MenuItemCallback menu_item_callback = NULL; +// Callbacks -// Callback registration - -void register_menu_item_callback(MenuItemCallback callback) { - menu_item_callback = callback; -} +KeypressCallback keypress_callback = NULL; +IconClickCallback icon_click_callback = NULL; +ContextMenuClickCallback context_menu_click_callback = NULL; void register_keypress_callback(KeypressCallback callback) { keypress_callback = callback; } +void register_icon_click_callback(IconClickCallback callback) { + icon_click_callback = callback; +} + +void register_context_menu_click_callback(ContextMenuClickCallback callback) { + context_menu_click_callback = callback; +} + /* * Message handler procedure for the windows */ @@ -65,21 +72,20 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR DeleteObject(g_espanso_bmp); DeleteObject(g_espanso_ico); return 0L; - case WM_MENUSELECT: // Click on the tray icon context menu + case WM_COMMAND: // Click on the tray icon context menu { - HMENU hmenu = (HMENU)lp; UINT idItem = (UINT)LOWORD(wp); UINT flags = (UINT)HIWORD(wp); - if (flags & MF_CHECKED) { - // TODO: callback + if (flags == 0) { + context_menu_click_callback(manager_instance, (int32_t)idItem); } break; } case APPWM_NOTIFICATION_POPUP: // Request to show a notification { - std::unique_ptr ptr(reinterpret_cast(wp)); + std::unique_ptr ptr(reinterpret_cast(wp)); SetWindowText(hwnd_st_u, L" "); // Clear the previous text SetWindowText(hwnd_st_u, ptr.get()); @@ -94,32 +100,36 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR ShowWindow(nw, SW_HIDE); break; } + case APPWM_SHOW_CONTEXT_MENU: // Request to show context menu + { + HMENU hPopupMenu = CreatePopupMenu(); + + // Create the menu + + int32_t count = static_cast(lp); + std::unique_ptr items(reinterpret_cast(wp)); + + for (int i = 0; i(items_buffer), static_cast(count)); + return 1; + } + + return -1; } \ No newline at end of file diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index 871eba3..903ac98 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -60,6 +60,13 @@ extern "C" int32_t get_active_window_executable(wchar_t * buffer, int32_t size); // UI +/* + * Called when the tray icon is clicked + */ +typedef void (*IconClickCallback)(void * self); +extern IconClickCallback icon_click_callback; +extern "C" void register_icon_click_callback(IconClickCallback callback); + // CONTEXT MENU typedef struct { @@ -68,13 +75,14 @@ typedef struct { wchar_t name[100]; } MenuItem; -/* - * Called when the context menu is created. - */ -typedef void (*MenuItemCallback)(void * self, MenuItem * items, int32_t * item_count); +extern "C" int32_t show_context_menu(MenuItem * items, int32_t count); -extern MenuItemCallback menu_item_callback; -extern "C" void register_menu_item_callback(MenuItemCallback callback); +/* + * Called when the context menu is clicked + */ +typedef void (*ContextMenuClickCallback)(void * self, int32_t id); +extern ContextMenuClickCallback context_menu_click_callback; +extern "C" void register_context_menu_click_callback(ContextMenuClickCallback callback); // NOTIFICATION diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index 2d86124..a220b6b 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -19,8 +19,9 @@ extern { // UI pub fn show_notification(message: *const u16) -> i32; pub fn close_notification(); - pub fn register_menu_item_callback(cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem, - *mut i32)); + pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32; + pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void)); + pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32)); // KEYBOARD pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32, diff --git a/src/context/mod.rs b/src/context/mod.rs index b8e6b02..5bce035 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -15,15 +15,6 @@ pub trait Context { fn eventloop(&self); } -pub enum MenuItemType { - Button, - Separator -} - -pub struct MenuItem { - -} - // MAC IMPLEMENTATION #[cfg(target_os = "macos")] pub fn new(send_channel: Sender) -> Box { // TODO diff --git a/src/context/windows.rs b/src/context/windows.rs index 2936ba1..4bf02b1 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -1,6 +1,6 @@ use std::sync::mpsc::Sender; use crate::bridge::windows::*; -use crate::event::{Event, KeyEvent, KeyModifier}; +use crate::event::{Event, KeyEvent, KeyModifier, ActionEvent}; use crate::event::KeyModifier::*; use std::ffi::c_void; use std::fs::create_dir_all; @@ -63,7 +63,8 @@ impl WindowsContext { // Register callbacks register_keypress_callback(keypress_callback); - register_menu_item_callback(menu_item_callback); + register_icon_click_callback(icon_click_callback); + register_context_menu_click_callback(context_menu_click_callback); let ico_file_c = U16CString::from_str(ico_icon).unwrap(); let bmp_file_c = U16CString::from_str(bmp_icon).unwrap(); @@ -122,22 +123,21 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const i32, len: i32 } } -extern fn menu_item_callback(_self: *mut c_void, items: *mut WindowsMenuItem, count: *mut i32) { +extern fn icon_click_callback(_self: *mut c_void) { unsafe { let _self = _self as *mut WindowsContext; - let str = U16CString::from_str("Test").unwrap_or_default(); - let mut str_buff : [u16; 100] = [0; 100]; - std::ptr::copy(str.as_ptr(), str_buff.as_mut_ptr(), str.len()); - let item = WindowsMenuItem { - item_id: 1, - item_type: 1, - item_name: str_buff, - }; + let event = Event::Action(ActionEvent::IconClick); + (*_self).send_channel.send(event).unwrap(); + } +} - let items = unsafe { std::slice::from_raw_parts_mut(items, 100) }; - items[0] = item; - *count = 1; +extern fn context_menu_click_callback(_self: *mut c_void, id: i32) { + unsafe { + let _self = _self as *mut WindowsContext; + + let event = Event::Action(ActionEvent::ContextMenuClick(id)); + (*_self).send_channel.send(event).unwrap(); } } \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index edf22b0..a9fabf6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,8 +4,9 @@ use crate::config::ConfigManager; use crate::config::BackendType; use crate::clipboard::ClipboardManager; use log::{info}; -use crate::ui::UIManager; -use crate::event::{ActionEventReceiver, Event}; +use crate::ui::{UIManager, MenuItem, MenuItemType}; +use crate::event::{ActionEventReceiver, Event, ActionEvent}; +use std::cell::RefCell; pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager> { @@ -13,12 +14,44 @@ pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<' clipboard_manager: &'a C, config_manager: &'a M, ui_manager: &'a U, + enabled: RefCell, } impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager> Engine<'a, S, C, M, U> { pub fn new(sender: S, clipboard_manager: &'a C, config_manager: &'a M, ui_manager: &'a U) -> Engine<'a, S, C, M, U> { - Engine{sender, clipboard_manager, config_manager, ui_manager } + let enabled = RefCell::new(true); + Engine{sender, clipboard_manager, config_manager, ui_manager, enabled } + } + + fn build_menu(&self) -> Vec { + let mut menu = Vec::new(); + + let enabled = self.enabled.borrow(); + let toggle_text = if *enabled { + "Disable" + }else{ + "Enable" + }.to_owned(); + menu.push(MenuItem{ + item_type: MenuItemType::Button, + item_name: toggle_text, + item_id: 2, + }); + + menu.push(MenuItem{ + item_type: MenuItemType::Separator, + item_name: "".to_owned(), + item_id: 999, + }); + + menu.push(MenuItem{ + item_type: MenuItemType::Button, + item_name: "Exit".to_owned(), + item_id: 1, + }); + + menu } } @@ -70,6 +103,9 @@ impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIMan info!("Toggled: {}", message); + let mut enabled_ref = self.enabled.borrow_mut(); + *enabled_ref = status; + self.ui_manager.notify(message); } } @@ -77,7 +113,14 @@ impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIMan impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager> ActionEventReceiver for Engine<'a, S, C, M, U>{ - fn on_action_event(&self, e: Event) { - unimplemented!() + fn on_action_event(&self, e: ActionEvent) { + match e { + ActionEvent::IconClick => { + self.ui_manager.show_menu(self.build_menu()); + }, + ActionEvent::ContextMenuClick(id) => { + println!("{}", id); + } + } } } \ No newline at end of file diff --git a/src/event/manager.rs b/src/event/manager.rs index f9f5e0a..bd3117b 100644 --- a/src/event/manager.rs +++ b/src/event/manager.rs @@ -31,8 +31,8 @@ impl <'a, K: KeyEventReceiver, A: ActionEventReceiver> EventManager for DefaultE Event::Key(key_event) => { self.key_receiver.on_key_event(key_event); }, - Event::Action => { - self.action_receiver.on_action_event(event); // TODO: action event + Event::Action(action_event) => { + self.action_receiver.on_action_event(action_event); } } }, diff --git a/src/event/mod.rs b/src/event/mod.rs index a2c7617..f474635 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -4,10 +4,16 @@ use serde::{Serialize, Deserialize}; #[derive(Debug)] pub enum Event { - Action, + Action(ActionEvent), Key(KeyEvent) } +#[derive(Debug)] +pub enum ActionEvent { + IconClick, + ContextMenuClick(i32) +} + #[derive(Debug)] pub enum KeyEvent { Char(char), @@ -36,5 +42,5 @@ pub trait KeyEventReceiver { } pub trait ActionEventReceiver { - fn on_action_event(&self, e: Event); // TODO: Action event + fn on_action_event(&self, e: ActionEvent); // TODO: Action event } \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f12e2b9..958a7b3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -9,15 +9,18 @@ mod macos; pub trait UIManager { fn notify(&self, message: &str); + fn show_menu(&self, menu: Vec); } pub enum MenuItemType { Button, - Separator + Separator, } pub struct MenuItem { - + pub item_id: i32, + pub item_type: MenuItemType, + pub item_name: String, } // MAC IMPLEMENTATION diff --git a/src/ui/windows.rs b/src/ui/windows.rs index 2943bc4..dc6c53c 100644 --- a/src/ui/windows.rs +++ b/src/ui/windows.rs @@ -1,5 +1,5 @@ use std::process::Command; -use crate::bridge::windows::{show_notification, close_notification, WindowsMenuItem}; +use crate::bridge::windows::{show_notification, close_notification, WindowsMenuItem, show_context_menu}; use widestring::U16CString; use std::{fs, thread, time}; use log::{info, debug}; @@ -7,6 +7,7 @@ use std::sync::Mutex; use std::sync::Arc; use std::fs::create_dir_all; use std::os::raw::c_void; +use crate::ui::{MenuItem, MenuItemType}; const BMP_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.bmp"); const ICO_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.ico"); @@ -49,6 +50,33 @@ impl super::UIManager for WindowsUIManager { } } + + fn show_menu(&self, menu: Vec) { + let mut raw_menu = Vec::new(); + + for item in menu.iter() { + let text = U16CString::from_str(item.item_name.clone()).unwrap_or_default(); + let mut str_buff : [u16; 100] = [0; 100]; + unsafe { + std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), text.len()); + } + + let menu_type = match item.item_type { + MenuItemType::Button => {1}, + MenuItemType::Separator => {2}, + }; + + let raw_item = WindowsMenuItem { + item_id: item.item_id, + item_type: menu_type, + item_name: str_buff, + }; + + raw_menu.push(raw_item); + } + + unsafe { show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32); } + } } impl WindowsUIManager { @@ -61,26 +89,4 @@ impl WindowsUIManager { manager } -} - -// NATIVE - -extern fn menu_item_callback(_self: *mut c_void, items: *mut WindowsMenuItem, count: *mut i32) { - unsafe { - let _self = _self as *mut WindowsUIManager; - - let str = U16CString::from_str("Test").unwrap_or_default(); - let mut str_buff : [u16; 100] = [0; 100]; - std::ptr::copy(str.as_ptr(), str_buff.as_mut_ptr(), str.len()); - let item = WindowsMenuItem { - item_id: 1, - item_type: 1, - item_name: str_buff, - }; - - let items = unsafe { std::slice::from_raw_parts_mut(items, 100) }; - - items[0] = item; - *count = 1; - } } \ No newline at end of file