Add context menu handling on windows

This commit is contained in:
Federico Terzi 2019-09-12 23:24:55 +02:00
parent 714dffe6c1
commit 8f47c6b216
10 changed files with 177 additions and 97 deletions

View File

@ -24,13 +24,14 @@ HKL currentKeyboardLayout;
HWND window; HWND window;
const wchar_t* const winclass = L"Espanso"; const wchar_t* const winclass = L"Espanso";
KeypressCallback keypress_callback;
// UI // UI
#define APPWM_ICON_CLICK (WM_APP + 1) #define APPWM_ICON_CLICK (WM_APP + 1)
#define APPWM_NOTIFICATION_POPUP (WM_APP + 2) #define APPWM_NOTIFICATION_POPUP (WM_APP + 2)
#define APPWM_NOTIFICATION_CLOSE (WM_APP + 3) #define APPWM_NOTIFICATION_CLOSE (WM_APP + 3)
#define APPWM_SHOW_CONTEXT_MENU (WM_APP + 4)
const wchar_t* const notification_winclass = L"EspansoNotification"; const wchar_t* const notification_winclass = L"EspansoNotification";
HWND nw = NULL; HWND nw = NULL;
@ -38,18 +39,24 @@ HWND hwnd_st_u = NULL;
HBITMAP g_espanso_bmp = NULL; HBITMAP g_espanso_bmp = NULL;
HICON g_espanso_ico = NULL; HICON g_espanso_ico = NULL;
MenuItemCallback menu_item_callback = NULL; // Callbacks
// Callback registration KeypressCallback keypress_callback = NULL;
IconClickCallback icon_click_callback = NULL;
void register_menu_item_callback(MenuItemCallback callback) { ContextMenuClickCallback context_menu_click_callback = NULL;
menu_item_callback = callback;
}
void register_keypress_callback(KeypressCallback callback) { void register_keypress_callback(KeypressCallback callback) {
keypress_callback = 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 * 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_bmp);
DeleteObject(g_espanso_ico); DeleteObject(g_espanso_ico);
return 0L; 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 idItem = (UINT)LOWORD(wp);
UINT flags = (UINT)HIWORD(wp); UINT flags = (UINT)HIWORD(wp);
if (flags & MF_CHECKED) { if (flags == 0) {
// TODO: callback context_menu_click_callback(manager_instance, (int32_t)idItem);
} }
break; break;
} }
case APPWM_NOTIFICATION_POPUP: // Request to show a notification case APPWM_NOTIFICATION_POPUP: // Request to show a notification
{ {
std::unique_ptr<wchar_t> ptr(reinterpret_cast<wchar_t*>(wp)); std::unique_ptr<wchar_t[]> ptr(reinterpret_cast<wchar_t*>(wp));
SetWindowText(hwnd_st_u, L" "); // Clear the previous text SetWindowText(hwnd_st_u, L" "); // Clear the previous text
SetWindowText(hwnd_st_u, ptr.get()); SetWindowText(hwnd_st_u, ptr.get());
@ -94,25 +100,20 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
ShowWindow(nw, SW_HIDE); ShowWindow(nw, SW_HIDE);
break; break;
} }
case APPWM_ICON_CLICK: // Click on the tray icon case APPWM_SHOW_CONTEXT_MENU: // Request to show context menu
{ {
switch (lp)
{
case WM_LBUTTONUP:
case WM_RBUTTONUP:
HMENU hPopupMenu = CreatePopupMenu(); HMENU hPopupMenu = CreatePopupMenu();
// Create the menu // Create the menu
int32_t count; int32_t count = static_cast<int32_t>(lp);
MenuItem items[100]; std::unique_ptr<MenuItem[]> items(reinterpret_cast<MenuItem*>(wp));
// Load the items
menu_item_callback(manager_instance, items, &count);
for (int i = 0; i<count; i++) { for (int i = 0; i<count; i++) {
if (items[i].type == 1) { if (items[i].type == 1) {
InsertMenu(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name); InsertMenu(hPopupMenu, i, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name);
}else{
InsertMenu(hPopupMenu, i, MF_BYPOSITION | MF_SEPARATOR, items[i].id, NULL);
} }
} }
@ -122,6 +123,15 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL); TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
break; break;
} }
case APPWM_ICON_CLICK: // Click on the tray icon
{
switch (lp)
{
case WM_LBUTTONUP:
case WM_RBUTTONUP:
icon_click_callback(manager_instance);
break;
}
} }
case WM_PAINT: case WM_PAINT:
{ {
@ -490,3 +500,15 @@ void close_notification() {
PostMessage(nw, APPWM_NOTIFICATION_CLOSE, 0, 0); PostMessage(nw, APPWM_NOTIFICATION_CLOSE, 0, 0);
} }
} }
int32_t show_context_menu(MenuItem * items, int32_t count) {
if (nw != NULL) {
MenuItem * items_buffer = new MenuItem[count];
memcpy(items_buffer, items, sizeof(MenuItem)*count);
PostMessage(nw, APPWM_SHOW_CONTEXT_MENU, reinterpret_cast<WPARAM>(items_buffer), static_cast<LPARAM>(count));
return 1;
}
return -1;
}

View File

@ -60,6 +60,13 @@ extern "C" int32_t get_active_window_executable(wchar_t * buffer, int32_t size);
// UI // 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 // CONTEXT MENU
typedef struct { typedef struct {
@ -68,13 +75,14 @@ typedef struct {
wchar_t name[100]; wchar_t name[100];
} MenuItem; } MenuItem;
/* extern "C" int32_t show_context_menu(MenuItem * items, int32_t count);
* Called when the context menu is created.
*/
typedef void (*MenuItemCallback)(void * self, MenuItem * items, int32_t * item_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 // NOTIFICATION

View File

@ -19,8 +19,9 @@ extern {
// UI // UI
pub fn show_notification(message: *const u16) -> i32; pub fn show_notification(message: *const u16) -> i32;
pub fn close_notification(); pub fn close_notification();
pub fn register_menu_item_callback(cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem, pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32;
*mut 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 // KEYBOARD
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32, pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32,

View File

@ -15,15 +15,6 @@ pub trait Context {
fn eventloop(&self); fn eventloop(&self);
} }
pub enum MenuItemType {
Button,
Separator
}
pub struct MenuItem {
}
// MAC IMPLEMENTATION // MAC IMPLEMENTATION
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> { // TODO pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> { // TODO

View File

@ -1,6 +1,6 @@
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use crate::bridge::windows::*; use crate::bridge::windows::*;
use crate::event::{Event, KeyEvent, KeyModifier}; use crate::event::{Event, KeyEvent, KeyModifier, ActionEvent};
use crate::event::KeyModifier::*; use crate::event::KeyModifier::*;
use std::ffi::c_void; use std::ffi::c_void;
use std::fs::create_dir_all; use std::fs::create_dir_all;
@ -63,7 +63,8 @@ impl WindowsContext {
// Register callbacks // Register callbacks
register_keypress_callback(keypress_callback); 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 ico_file_c = U16CString::from_str(ico_icon).unwrap();
let bmp_file_c = U16CString::from_str(bmp_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 { unsafe {
let _self = _self as *mut WindowsContext; let _self = _self as *mut WindowsContext;
let str = U16CString::from_str("Test").unwrap_or_default(); let event = Event::Action(ActionEvent::IconClick);
let mut str_buff : [u16; 100] = [0; 100]; (*_self).send_channel.send(event).unwrap();
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, extern fn context_menu_click_callback(_self: *mut c_void, id: i32) {
}; unsafe {
let _self = _self as *mut WindowsContext;
let items = unsafe { std::slice::from_raw_parts_mut(items, 100) };
let event = Event::Action(ActionEvent::ContextMenuClick(id));
items[0] = item; (*_self).send_channel.send(event).unwrap();
*count = 1;
} }
} }

View File

@ -4,8 +4,9 @@ use crate::config::ConfigManager;
use crate::config::BackendType; use crate::config::BackendType;
use crate::clipboard::ClipboardManager; use crate::clipboard::ClipboardManager;
use log::{info}; use log::{info};
use crate::ui::UIManager; use crate::ui::{UIManager, MenuItem, MenuItemType};
use crate::event::{ActionEventReceiver, Event}; use crate::event::{ActionEventReceiver, Event, ActionEvent};
use std::cell::RefCell;
pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>,
U: UIManager> { U: UIManager> {
@ -13,12 +14,44 @@ pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'
clipboard_manager: &'a C, clipboard_manager: &'a C,
config_manager: &'a M, config_manager: &'a M,
ui_manager: &'a U, ui_manager: &'a U,
enabled: RefCell<bool>,
} }
impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager> impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
Engine<'a, S, C, M, U> { 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> { 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<MenuItem> {
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); info!("Toggled: {}", message);
let mut enabled_ref = self.enabled.borrow_mut();
*enabled_ref = status;
self.ui_manager.notify(message); 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, impl <'a, S: KeyboardSender, C: ClipboardManager,
M: ConfigManager<'a>, U: UIManager> ActionEventReceiver for Engine<'a, S, C, M, U>{ M: ConfigManager<'a>, U: UIManager> ActionEventReceiver for Engine<'a, S, C, M, U>{
fn on_action_event(&self, e: Event) { fn on_action_event(&self, e: ActionEvent) {
unimplemented!() match e {
ActionEvent::IconClick => {
self.ui_manager.show_menu(self.build_menu());
},
ActionEvent::ContextMenuClick(id) => {
println!("{}", id);
}
}
} }
} }

View File

@ -31,8 +31,8 @@ impl <'a, K: KeyEventReceiver, A: ActionEventReceiver> EventManager for DefaultE
Event::Key(key_event) => { Event::Key(key_event) => {
self.key_receiver.on_key_event(key_event); self.key_receiver.on_key_event(key_event);
}, },
Event::Action => { Event::Action(action_event) => {
self.action_receiver.on_action_event(event); // TODO: action event self.action_receiver.on_action_event(action_event);
} }
} }
}, },

View File

@ -4,10 +4,16 @@ use serde::{Serialize, Deserialize};
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event {
Action, Action(ActionEvent),
Key(KeyEvent) Key(KeyEvent)
} }
#[derive(Debug)]
pub enum ActionEvent {
IconClick,
ContextMenuClick(i32)
}
#[derive(Debug)] #[derive(Debug)]
pub enum KeyEvent { pub enum KeyEvent {
Char(char), Char(char),
@ -36,5 +42,5 @@ pub trait KeyEventReceiver {
} }
pub trait ActionEventReceiver { pub trait ActionEventReceiver {
fn on_action_event(&self, e: Event); // TODO: Action event fn on_action_event(&self, e: ActionEvent); // TODO: Action event
} }

View File

@ -9,15 +9,18 @@ mod macos;
pub trait UIManager { pub trait UIManager {
fn notify(&self, message: &str); fn notify(&self, message: &str);
fn show_menu(&self, menu: Vec<MenuItem>);
} }
pub enum MenuItemType { pub enum MenuItemType {
Button, Button,
Separator Separator,
} }
pub struct MenuItem { pub struct MenuItem {
pub item_id: i32,
pub item_type: MenuItemType,
pub item_name: String,
} }
// MAC IMPLEMENTATION // MAC IMPLEMENTATION

View File

@ -1,5 +1,5 @@
use std::process::Command; 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 widestring::U16CString;
use std::{fs, thread, time}; use std::{fs, thread, time};
use log::{info, debug}; use log::{info, debug};
@ -7,6 +7,7 @@ use std::sync::Mutex;
use std::sync::Arc; use std::sync::Arc;
use std::fs::create_dir_all; use std::fs::create_dir_all;
use std::os::raw::c_void; use std::os::raw::c_void;
use crate::ui::{MenuItem, MenuItemType};
const BMP_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.bmp"); const BMP_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.bmp");
const ICO_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.ico"); 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<MenuItem>) {
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 { impl WindowsUIManager {
@ -62,25 +90,3 @@ impl WindowsUIManager {
manager 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;
}
}