First draft of context menu on windows
This commit is contained in:
parent
1602f1c014
commit
a8300832bc
|
@ -7,6 +7,8 @@
|
||||||
#define UNICODE
|
#define UNICODE
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#include <strsafe.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
// How many milliseconds must pass between keystrokes to refresh the keyboard layout
|
// How many milliseconds must pass between keystrokes to refresh the keyboard layout
|
||||||
const long refreshKeyboardLayoutInterval = 2000;
|
const long refreshKeyboardLayoutInterval = 2000;
|
||||||
|
@ -275,11 +277,21 @@ int32_t get_active_window_executable(wchar_t * buffer, int32_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
|
#define APPWM_ICONNOTIFY (WM_APP + 1)
|
||||||
|
|
||||||
const wchar_t* const notification_winclass = L"EspansoNotification";
|
const wchar_t* const notification_winclass = L"EspansoNotification";
|
||||||
HWND nw = NULL;
|
HWND nw = NULL;
|
||||||
HWND hwnd_st_u = NULL;
|
HWND hwnd_st_u = NULL;
|
||||||
HBITMAP g_espanso_icon = NULL;
|
HBITMAP g_espanso_bmp = NULL;
|
||||||
|
HICON g_espanso_ico = NULL;
|
||||||
|
|
||||||
|
MenuItemCallback menu_item_callback = NULL;
|
||||||
|
void * ui_manager_instance;
|
||||||
|
|
||||||
|
void register_menu_item_callback(void *self, MenuItemCallback callback) {
|
||||||
|
menu_item_callback = callback;
|
||||||
|
ui_manager_instance = self;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Message handler procedure for the notification window
|
* Message handler procedure for the notification window
|
||||||
|
@ -293,8 +305,49 @@ LRESULT CALLBACK notification_worker_procedure(HWND window, unsigned int msg, WP
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
std::cout << "\ndestroying window\n";
|
std::cout << "\ndestroying window\n";
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
DeleteObject(g_espanso_icon);
|
DeleteObject(g_espanso_bmp);
|
||||||
return 0L;
|
return 0L;
|
||||||
|
case WM_MENUSELECT:
|
||||||
|
{
|
||||||
|
HMENU hmenu = (HMENU)lp;
|
||||||
|
UINT idItem = (UINT)LOWORD(wp);
|
||||||
|
UINT flags = (UINT)HIWORD(wp);
|
||||||
|
|
||||||
|
if (flags & MF_CHECKED) {
|
||||||
|
// TODO: callback
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APPWM_ICONNOTIFY:
|
||||||
|
{
|
||||||
|
switch (lp)
|
||||||
|
{
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
HMENU hPopupMenu = CreatePopupMenu();
|
||||||
|
|
||||||
|
// Create the menu
|
||||||
|
|
||||||
|
int32_t count;
|
||||||
|
MenuItem items[100];
|
||||||
|
|
||||||
|
// Load the items
|
||||||
|
menu_item_callback(ui_manager_instance, items, &count);
|
||||||
|
|
||||||
|
for (int i = 0; i<count; i++) {
|
||||||
|
if (items[i].type == 1) {
|
||||||
|
InsertMenu(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT pt;
|
||||||
|
GetCursorPos(&pt);
|
||||||
|
SetForegroundWindow(nw);
|
||||||
|
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
case WM_PAINT:
|
case WM_PAINT:
|
||||||
{
|
{
|
||||||
BITMAP bm;
|
BITMAP bm;
|
||||||
|
@ -303,9 +356,9 @@ LRESULT CALLBACK notification_worker_procedure(HWND window, unsigned int msg, WP
|
||||||
HDC hdc = BeginPaint(window, &ps);
|
HDC hdc = BeginPaint(window, &ps);
|
||||||
|
|
||||||
HDC hdcMem = CreateCompatibleDC(hdc);
|
HDC hdcMem = CreateCompatibleDC(hdc);
|
||||||
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_espanso_icon);
|
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_espanso_bmp);
|
||||||
|
|
||||||
GetObject(g_espanso_icon, sizeof(bm), &bm);
|
GetObject(g_espanso_bmp, sizeof(bm), &bm);
|
||||||
|
|
||||||
BitBlt(hdc, 10, 10, 80, 80, hdcMem, 0, 0, SRCCOPY);
|
BitBlt(hdc, 10, 10, 80, 80, hdcMem, 0, 0, SRCCOPY);
|
||||||
|
|
||||||
|
@ -328,8 +381,9 @@ LRESULT CALLBACK notification_worker_procedure(HWND window, unsigned int msg, WP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t initialize_ui(wchar_t * icon_path) {
|
int32_t initialize_ui(wchar_t * ico_path, wchar_t * bmp_path) {
|
||||||
g_espanso_icon = (HBITMAP)LoadImage(NULL, icon_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
|
g_espanso_bmp = (HBITMAP)LoadImage(NULL, bmp_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
|
||||||
|
g_espanso_ico = (HICON)LoadImage(NULL, ico_path, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_SHARED | LR_DEFAULTSIZE | LR_LOADFROMFILE);
|
||||||
|
|
||||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
||||||
|
|
||||||
|
@ -353,7 +407,7 @@ int32_t initialize_ui(wchar_t * icon_path) {
|
||||||
{
|
{
|
||||||
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
|
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
|
||||||
nw = CreateWindowEx(
|
nw = CreateWindowEx(
|
||||||
WS_EX_TOOLWINDOW, // dwExStyle: The extended window style of the window being created.
|
WS_EX_TOOLWINDOW | WS_EX_TOPMOST, // dwExStyle: The extended window style of the window being created.
|
||||||
notification_winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
notification_winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
||||||
L"Espanso Notification", // lpWindowName: The window name.
|
L"Espanso Notification", // lpWindowName: The window name.
|
||||||
WS_POPUPWINDOW, // dwStyle: The style of the window being created.
|
WS_POPUPWINDOW, // dwStyle: The style of the window being created.
|
||||||
|
@ -388,6 +442,23 @@ int32_t initialize_ui(wchar_t * icon_path) {
|
||||||
// Hide the window
|
// Hide the window
|
||||||
ShowWindow(nw, SW_HIDE);
|
ShowWindow(nw, SW_HIDE);
|
||||||
|
|
||||||
|
// Setup the icon in the notification space
|
||||||
|
|
||||||
|
SendMessage(nw, WM_SETICON, ICON_BIG, (LPARAM)g_espanso_ico);
|
||||||
|
SendMessage(nw, WM_SETICON, ICON_SMALL, (LPARAM)g_espanso_ico);
|
||||||
|
//Notification
|
||||||
|
NOTIFYICONDATA nid = {};
|
||||||
|
nid.cbSize = sizeof(nid);
|
||||||
|
nid.hWnd = nw;
|
||||||
|
nid.uID = 1;
|
||||||
|
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
|
||||||
|
nid.uCallbackMessage = APPWM_ICONNOTIFY;
|
||||||
|
nid.hIcon = g_espanso_ico;
|
||||||
|
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"espanso");
|
||||||
|
|
||||||
|
// Show the notification.
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &nid);
|
||||||
|
|
||||||
// Enter the Event loop
|
// Enter the Event loop
|
||||||
MSG msg;
|
MSG msg;
|
||||||
while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
|
while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
|
||||||
|
@ -403,7 +474,7 @@ int32_t initialize_ui(wchar_t * icon_path) {
|
||||||
|
|
||||||
int32_t show_notification(wchar_t * message) {
|
int32_t show_notification(wchar_t * message) {
|
||||||
if (nw != NULL) {
|
if (nw != NULL) {
|
||||||
SetWindowText(hwnd_st_u, L" ");
|
SetWindowText(hwnd_st_u, L" "); // Clear the previous text
|
||||||
SetWindowText(hwnd_st_u, message);
|
SetWindowText(hwnd_st_u, message);
|
||||||
|
|
||||||
// Show the window
|
// Show the window
|
||||||
|
|
|
@ -59,7 +59,26 @@ extern "C" int32_t get_active_window_executable(wchar_t * buffer, int32_t size);
|
||||||
/*
|
/*
|
||||||
* Initialize the notification window.
|
* Initialize the notification window.
|
||||||
*/
|
*/
|
||||||
extern "C" int32_t initialize_ui(wchar_t * icon_path);
|
extern "C" int32_t initialize_ui(wchar_t * ico_path, wchar_t * bmp_path);
|
||||||
|
|
||||||
|
// CONTEXT MENU
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t id;
|
||||||
|
int32_t type;
|
||||||
|
wchar_t name[100];
|
||||||
|
} MenuItem;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when the context menu is created.
|
||||||
|
*/
|
||||||
|
typedef void (*MenuItemCallback)(void * self, MenuItem * items, int32_t * item_count);
|
||||||
|
|
||||||
|
extern MenuItemCallback menu_item_callback;
|
||||||
|
extern void * ui_manager_instance;
|
||||||
|
extern "C" void register_menu_item_callback(void *self, MenuItemCallback callback);
|
||||||
|
|
||||||
|
// NOTIFICATION
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Show a window containing the notification.
|
* Show a window containing the notification.
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
use std::os::raw::{c_void};
|
use std::os::raw::{c_void};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct WindowsMenuItem {
|
||||||
|
pub item_id: i32,
|
||||||
|
pub item_type: i32,
|
||||||
|
pub item_name: [u16; 100],
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name="winbridge", kind="static")]
|
#[link(name="winbridge", kind="static")]
|
||||||
extern {
|
extern {
|
||||||
|
@ -8,9 +15,12 @@ extern {
|
||||||
pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32;
|
pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32;
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
pub fn initialize_ui(icon_path: *const u16) -> i32;
|
pub fn initialize_ui(ico_path: *const u16, bmp_path: *const u16) -> i32;
|
||||||
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(s: *const c_void,
|
||||||
|
cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem,
|
||||||
|
*mut i32));
|
||||||
|
|
||||||
// KEYBOARD
|
// KEYBOARD
|
||||||
pub fn register_keypress_callback(s: *const c_void,
|
pub fn register_keypress_callback(s: *const c_void,
|
||||||
|
|
BIN
src/res/win/espanso.ico
Normal file
BIN
src/res/win/espanso.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -1,15 +1,17 @@
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use crate::bridge::windows::{show_notification, close_notification, initialize_ui};
|
use crate::bridge::windows::{show_notification, close_notification, initialize_ui, WindowsMenuItem, register_menu_item_callback};
|
||||||
use widestring::U16CString;
|
use widestring::U16CString;
|
||||||
use std::{fs, thread, time};
|
use std::{fs, thread, time};
|
||||||
use log::{info, debug};
|
use log::{info, debug};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::fs::create_dir_all;
|
||||||
|
use std::os::raw::c_void;
|
||||||
|
|
||||||
const ICON_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");
|
||||||
|
|
||||||
pub struct WindowsUIManager {
|
pub struct WindowsUIManager {
|
||||||
icon_file: String,
|
|
||||||
id: Arc<Mutex<i32>>
|
id: Arc<Mutex<i32>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,31 +53,79 @@ impl super::UIManager for WindowsUIManager {
|
||||||
|
|
||||||
impl WindowsUIManager {
|
impl WindowsUIManager {
|
||||||
pub fn new() -> WindowsUIManager {
|
pub fn new() -> WindowsUIManager {
|
||||||
let res = dirs::cache_dir();
|
let data_dir = dirs::data_dir().expect("Can't obtain data_dir(), terminating.");
|
||||||
let mut icon_file:String = "".to_owned();
|
|
||||||
if let Some(cache_dir) = res {
|
|
||||||
let espanso_icon_file = cache_dir.join("espansoicon.bmp");
|
|
||||||
|
|
||||||
fs::write(&espanso_icon_file, ICON_BINARY)
|
let espanso_dir = data_dir.join("espanso");
|
||||||
.expect("Unable to write windows icon file");
|
|
||||||
|
|
||||||
icon_file = espanso_icon_file.to_str().unwrap_or(&"".to_owned()).to_owned();
|
let res = create_dir_all(&espanso_dir);
|
||||||
|
|
||||||
|
info!("Initializing Espanso resources in {}", espanso_dir.as_path().display());
|
||||||
|
|
||||||
|
let espanso_bmp_image = espanso_dir.join("espansoicon.bmp");
|
||||||
|
if espanso_bmp_image.exists() {
|
||||||
|
info!("BMP already initialized, skipping.");
|
||||||
|
}else {
|
||||||
|
fs::write(&espanso_bmp_image, BMP_BINARY)
|
||||||
|
.expect("Unable to write windows bmp file");
|
||||||
|
|
||||||
|
info!("Extracted bmp icon to: {}", espanso_bmp_image.to_str().unwrap_or("error"));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Extracted cached icon to: {}", icon_file);
|
let espanso_ico_image = espanso_dir.join("espanso.ico");
|
||||||
|
if espanso_ico_image.exists() {
|
||||||
|
info!("ICO already initialized, skipping.");
|
||||||
|
}else {
|
||||||
|
fs::write(&espanso_ico_image, ICO_BINARY)
|
||||||
|
.expect("Unable to write windows ico file");
|
||||||
|
|
||||||
|
info!("Extracted 'ico' icon to: {}", espanso_ico_image.to_str().unwrap_or("error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default();
|
||||||
|
let ico_icon = espanso_ico_image.to_str().unwrap_or_default();
|
||||||
|
|
||||||
let id = Arc::new(Mutex::new(0));
|
let id = Arc::new(Mutex::new(0));
|
||||||
let icon_file_c = U16CString::from_str(&icon_file).unwrap();
|
let ico_file_c = U16CString::from_str(ico_icon).unwrap();
|
||||||
|
let bmp_file_c = U16CString::from_str(bmp_icon).unwrap();
|
||||||
|
|
||||||
|
let manager = WindowsUIManager {
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup the menu item callback
|
||||||
|
unsafe {
|
||||||
|
let self_ptr = &manager as *const WindowsUIManager as *const c_void;
|
||||||
|
register_menu_item_callback(self_ptr, menu_item_callback);
|
||||||
|
}
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
unsafe {
|
unsafe {
|
||||||
initialize_ui(icon_file_c.as_ptr());
|
initialize_ui(ico_file_c.as_ptr(), bmp_file_c.as_ptr());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
WindowsUIManager {
|
manager
|
||||||
icon_file,
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user