First draft of context menu handling on Windows

This commit is contained in:
Federico Terzi 2021-01-31 13:00:27 +01:00
parent 567c35eb0e
commit 65f9811db3
11 changed files with 25996 additions and 32 deletions

186
Cargo.lock generated
View File

@ -1,5 +1,15 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "anyhow"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.66"
@ -10,6 +20,18 @@ name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.84 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "enum-as-inner"
version = "0.3.3"
@ -28,6 +50,7 @@ dependencies = [
"espanso-detect 0.1.0",
"espanso-ui 0.1.0",
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"simplelog 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -45,9 +68,13 @@ dependencies = [
name = "espanso-ui"
version = "0.1.0"
dependencies = [
"anyhow 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
"lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.123 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.61 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
"widestring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -59,11 +86,21 @@ dependencies = [
"unicode-segmentation 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.14"
@ -77,6 +114,23 @@ name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
@ -93,6 +147,49 @@ dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.123 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.123 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "simplelog"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.60"
@ -103,6 +200,42 @@ dependencies = [
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thiserror"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thiserror-impl 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thiserror-impl"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.84 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
@ -113,22 +246,75 @@ name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum anyhow 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
"checksum cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
"checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
"checksum chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
"checksum enum-as-inner 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
"checksum heck 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
"checksum itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
"checksum lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
"checksum libc 0.2.84 (registry+https://github.com/rust-lang/crates.io-index)" = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
"checksum log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
"checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
"checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
"checksum quote 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
"checksum serde 1.0.123 (registry+https://github.com/rust-lang/crates.io-index)" = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
"checksum serde_derive 1.0.123 (registry+https://github.com/rust-lang/crates.io-index)" = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
"checksum serde_json 1.0.61 (registry+https://github.com/rust-lang/crates.io-index)" = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
"checksum simplelog 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
"checksum syn 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)" = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
"checksum termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
"checksum thiserror 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
"checksum thiserror-impl 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
"checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
"checksum unicode-segmentation 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
"checksum widestring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -7,6 +7,10 @@ build="build.rs"
[dependencies]
log = "0.4.14"
serde_json = "1.0.61"
serde = { version = "1.0.123", features = ["derive"] }
anyhow = "1.0.38"
thiserror = "1.0.23"
[target.'cfg(windows)'.dependencies]
widestring = "0.4.3"

View File

@ -25,6 +25,7 @@ fn cc_config() {
.cpp(true)
.include("src/win32/native.h")
.include("src/win32/WinToast/wintoastlib.h")
.include("src/win32/json/json.hpp")
.file("src/win32/native.cpp")
.file("src/win32/WinToast/wintoastlib.cpp")
.compile("espansoui");

View File

@ -1,3 +1,4 @@
pub mod menu;
pub mod event;
pub mod icons;

203
espanso-ui/src/menu.rs Normal file
View File

@ -0,0 +1,203 @@
use std::collections::{HashMap, HashSet};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug)]
pub struct Menu {
items: Vec<MenuItem>,
// Mapping between the raw numeric ids and string ids
raw_ids: HashMap<u32, String>,
}
impl Menu {
pub fn from(mut items: Vec<MenuItem>) -> Result<Self> {
// Generate the raw ids, also checking for duplicate ids
let mut raw_ids: HashMap<u32, String> = HashMap::new();
let mut ids: HashSet<String> = HashSet::new();
let mut current = 0;
for item in items.iter_mut() {
Self::generate_raw_id(&mut raw_ids, &mut ids, &mut current, item)?;
}
Ok(Self { items, raw_ids })
}
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(&self.items)?)
}
pub fn get_item_id(&self, raw_id: u32) -> Option<String> {
self.raw_ids.get(&raw_id).cloned()
}
fn generate_raw_id(raw_ids: &mut HashMap<u32, String>, ids: &mut HashSet<String>, current: &mut u32, item: &mut MenuItem) -> Result<()> {
match item {
MenuItem::Simple(simple_item) => {
if ids.contains::<str>(&simple_item.id) { // Duplicate id, throw error
Err(MenuError::DuplicateMenuId(simple_item.id.to_string()).into())
} else {
ids.insert(simple_item.id.to_string());
raw_ids.insert(*current, simple_item.id.to_string());
simple_item.raw_id = Some(*current);
*current += 1;
Ok(())
}
}
MenuItem::Sub(SubMenuItem { items, ..}) => {
for sub_item in items.iter_mut() {
Self::generate_raw_id(raw_ids, ids, current, sub_item)?
}
Ok(())
}
MenuItem::Separator => Ok(()),
}
}
}
#[derive(Error, Debug)]
pub enum MenuError {
#[error("json serialization error")]
Serialization(#[from] serde_json::Error),
#[error("two or more menu items share the same id")]
DuplicateMenuId(String),
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum MenuItem {
Simple(SimpleMenuItem),
Sub(SubMenuItem),
Separator,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SimpleMenuItem {
id: String,
label: String,
raw_id: Option<u32>,
}
impl SimpleMenuItem {
pub fn new(id: &str, label: &str) -> Self {
Self {
id: id.to_string(),
label: label.to_string(),
raw_id: None,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SubMenuItem {
label: String,
items: Vec<MenuItem>,
}
impl SubMenuItem {
pub fn new(label: &str, items: Vec<MenuItem>) -> Self {
Self {
label: label.to_string(),
items,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_menu_serializes_correctly() {
let menu = Menu::from(vec![
MenuItem::Simple(SimpleMenuItem::new(
"open",
"Open",
)),
MenuItem::Separator,
MenuItem::Sub(SubMenuItem::new(
"Sub",
vec![
MenuItem::Simple(SimpleMenuItem::new(
"sub1",
"Sub 1",
)),
MenuItem::Simple(SimpleMenuItem::new(
"sub2",
"Sub 2",
)),
]),
),
]).unwrap();
assert_eq!(
menu.to_json().unwrap(),
r#"[{"type":"simple","id":"open","label":"Open","raw_id":0},{"type":"separator"},{"type":"sub","label":"Sub","items":[{"type":"simple","id":"sub1","label":"Sub 1","raw_id":1},{"type":"simple","id":"sub2","label":"Sub 2","raw_id":2}]}]"#
);
}
#[test]
fn test_context_menu_raw_ids_work_correctly() {
let menu = Menu::from(vec![
MenuItem::Simple(SimpleMenuItem::new(
"open",
"Open",
)),
MenuItem::Separator,
MenuItem::Sub(SubMenuItem::new(
"Sub",
vec![
MenuItem::Simple(SimpleMenuItem::new(
"sub1",
"Sub 1",
)),
MenuItem::Simple(SimpleMenuItem::new(
"sub2",
"Sub 2",
)),
]),
),
]).unwrap();
assert_eq!(
menu.get_item_id(0).unwrap(),
"open"
);
assert_eq!(
menu.get_item_id(1).unwrap(),
"sub1"
);
assert_eq!(
menu.get_item_id(2).unwrap(),
"sub2"
);
}
#[test]
fn test_context_menu_detects_duplicate_ids() {
let menu = Menu::from(vec![
MenuItem::Simple(SimpleMenuItem::new(
"open",
"Open",
)),
MenuItem::Separator,
MenuItem::Sub(SubMenuItem::new(
"Sub",
vec![
MenuItem::Simple(SimpleMenuItem::new(
"open",
"Sub 1",
)),
MenuItem::Simple(SimpleMenuItem::new(
"sub2",
"Sub 2",
)),
]),
),
]);
assert!(matches!(menu.unwrap_err().downcast::<MenuError>().unwrap(),
MenuError::DuplicateMenuId(id) if id == "open"));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -17,16 +17,23 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{cmp::min, collections::HashMap, ffi::c_void, sync::{
use std::{
cmp::min,
collections::HashMap,
ffi::{c_void, CString},
os::raw::c_char,
sync::{
atomic::{AtomicPtr, Ordering},
Arc,
}, thread::ThreadId};
},
thread::ThreadId,
};
use lazycell::LazyCell;
use log::{error, trace};
use widestring::{WideCString};
use widestring::WideCString;
use crate::{event::UIEvent, icons::TrayIcon};
use crate::{event::UIEvent, icons::TrayIcon, menu::Menu};
// IMPORTANT: if you change these, also edit the native.h file.
const MAX_FILE_PATH: usize = 260;
@ -66,6 +73,7 @@ extern "C" {
pub fn ui_destroy(window_handle: *const c_void) -> i32;
pub fn ui_update_tray_icon(window_handle: *const c_void, index: i32);
pub fn ui_show_notification(window_handle: *const c_void, message: *const u16);
pub fn ui_show_context_menu(window_handle: *const c_void, payload: *const c_char);
}
pub struct Win32UIOptions<'a> {
@ -142,14 +150,16 @@ impl Win32EventLoop {
let mut icon_paths: [[u16; MAX_FILE_PATH]; MAX_ICON_COUNT] =
[[0; MAX_FILE_PATH]; MAX_ICON_COUNT];
for (i, icon_path) in icon_paths.iter_mut().enumerate().take(self.icons.len()) {
let wide_path = WideCString::from_str(&self.icons[i]).expect("Error while converting icon to wide string");
let wide_path =
WideCString::from_str(&self.icons[i]).expect("Error while converting icon to wide string");
let len = min(wide_path.len(), MAX_FILE_PATH - 1);
icon_path[0..len].clone_from_slice(&wide_path.as_slice()[..len]);
// TODO: test overflow, correct case
}
let wide_notification_icon_path =
widestring::WideCString::from_str(&self.notification_icon_path).expect("Error while converting notification icon to wide string");
widestring::WideCString::from_str(&self.notification_icon_path)
.expect("Error while converting notification icon to wide string");
let mut wide_notification_icon_path_buffer: [u16; MAX_FILE_PATH] = [0; MAX_FILE_PATH];
wide_notification_icon_path_buffer[..wide_notification_icon_path.as_slice().len()]
.clone_from_slice(wide_notification_icon_path.as_slice());
@ -279,7 +289,34 @@ impl Win32Remote {
match wide_message {
Ok(wide_message) => unsafe { ui_show_notification(handle, wide_message.as_ptr()) },
Err(error) => {
error!("Unable to show notification, invalid message encoding {}", error);
error!(
"Unable to show notification, invalid message encoding {}",
error
);
}
}
}
pub fn show_context_menu(&self, menu: &Menu) {
let handle = self.handle.load(Ordering::Acquire);
if handle.is_null() {
error!("Unable to show context menu, pointer is null");
return;
}
match menu.to_json() {
Ok(payload) => {
let c_string = CString::new(payload);
match c_string {
Ok(c_string) => unsafe { ui_show_context_menu(handle, c_string.as_ptr()) },
Err(error) => error!(
"Unable to show context menu, impossible to convert payload to c_string: {}",
error
),
}
}
Err(error) => {
error!("Unable to show context menu, {}", error);
}
}
}

View File

@ -47,6 +47,9 @@
#include "WinToast/wintoastlib.h"
using namespace WinToastLib;
#include "json/json.hpp"
using json = nlohmann::json;
#define APPWM_ICON_CLICK (WM_APP + 1)
#define APPWM_SHOW_CONTEXT_MENU (WM_APP + 2)
#define APPWM_UPDATE_TRAY_ICON (WM_APP + 3)
@ -111,6 +114,7 @@ LRESULT CALLBACK ui_window_procedure(HWND window, unsigned int msg, WPARAM wp, L
if (flags == 0)
{
std::cout << "click menu" << std::flush;
// TODO: click
//context_menu_click_callback(manager_instance, (int32_t)idItem);
}
@ -118,27 +122,12 @@ LRESULT CALLBACK ui_window_procedure(HWND window, unsigned int msg, WPARAM wp, L
}
case APPWM_SHOW_CONTEXT_MENU: // Request to show context menu
{
HMENU hPopupMenu = CreatePopupMenu();
HMENU menu = (HMENU)lp;
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(window);
TrackPopupMenu(menu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, window, NULL);
// Create the menu
/*
int32_t count = static_cast<int32_t>(lp);
std::unique_ptr<MenuItem[]> items(reinterpret_cast<MenuItem*>(wp));
for (int i = 0; i<count; i++) {
if (items[i].type == 1) {
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);
}
}
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(nw);
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
*/
break;
}
case APPWM_UPDATE_TRAY_ICON: // Request to update the tray icon
@ -288,7 +277,7 @@ void *ui_initialize(void *_self, UIOptions _options, int32_t *error_code)
// Initialize the notification handler
WinToast::instance()->setAppName(L"Espanso");
const auto aumi = WinToast::configureAUMI(L"federico.terzi", L"Espanso", L"Espanso", L"1.0.0");
const auto aumi = WinToast::configureAUMI(L"federico.terzi", L"Espanso", L"Core", L"1.0.0");
WinToast::instance()->setAppUserModelId(aumi);
if (!WinToast::instance()->initialize())
{
@ -341,4 +330,69 @@ int32_t ui_show_notification(void *window, wchar_t *message)
return 0;
}
return -1;
}
// Menu related methods
void _insert_separator_menu(HMENU parent)
{
InsertMenu(parent, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
}
void _insert_single_menu(HMENU parent, json item)
{
if (!item["label"].is_string() || !item["raw_id"].is_number())
{
return;
}
std::string label = item["label"];
uint32_t raw_id = item["raw_id"];
// Convert to wide chars
std::wstring wide_label(label.length(), L'#');
mbstowcs(&wide_label[0], label.c_str(), label.length());
InsertMenu(parent, -1, MF_BYPOSITION | MF_STRING, raw_id, wide_label.c_str());
}
void _insert_sub_menu(HMENU parent, json items)
{
for (auto &item : items)
{
if (item["type"] == "simple")
{
_insert_single_menu(parent, item);
}
else if (item["type"] == "separator")
{
_insert_separator_menu(parent);
}
else if (item["type"] == "sub")
{
HMENU subMenu = CreatePopupMenu();
std::string label = item["label"];
// Convert to wide chars
std::wstring wide_label(label.length(), L'#');
mbstowcs(&wide_label[0], label.c_str(), label.length());
InsertMenu(parent, -1, MF_BYPOSITION | MF_POPUP, (UINT_PTR)subMenu, wide_label.c_str());
_insert_sub_menu(subMenu, item["items"]);
}
}
}
int32_t ui_show_context_menu(void *window, char *payload)
{
if (window)
{
auto j_menu = json::parse(payload);
// Generate the menu from the JSON payload
HMENU parentMenu = CreatePopupMenu();
_insert_sub_menu(parentMenu, j_menu);
PostMessage((HWND)window, APPWM_SHOW_CONTEXT_MENU, 0, (LPARAM)parentMenu);
return 0;
}
return -1;
}

View File

@ -60,4 +60,9 @@ extern "C" void ui_update_tray_icon(void * window, int32_t index);
// Show a native Windows 10 notification
extern "C" int32_t ui_show_notification(void * window, wchar_t * message);
// Display the context menu on the tray icon.
// Payload is passed as JSON as given the complex structure, parsing
// this manually would have been complex.
extern "C" int32_t ui_show_context_menu(void * window, char * payload);
#endif //ESPANSO_UI_H

View File

@ -11,4 +11,5 @@ edition = "2018"
[dependencies]
espanso-detect = { path = "../espanso-detect" }
espanso-ui = { path = "../espanso-ui" }
maplit = "1.0.2"
maplit = "1.0.2"
simplelog = "0.9.0"

View File

@ -1,7 +1,18 @@
use espanso_detect::event::{InputEvent, Status};
use espanso_ui::menu::*;
use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
fn main() {
println!("Hello, world!z");
CombinedLogger::init(vec![
TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed),
// WriteLogger::new(
// LevelFilter::Info,
// Config::default(),
// File::create("my_rust_binary.log").unwrap(),
// ),
])
.unwrap();
let icon_paths = vec![
(
@ -17,7 +28,8 @@ fn main() {
let (remote, mut eventloop) = espanso_ui::win32::create(espanso_ui::win32::Win32UIOptions {
show_icon: true,
icon_paths: &icon_paths,
notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string(),
notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png"
.to_string(),
});
std::thread::spawn(move || {
@ -30,7 +42,7 @@ fn main() {
InputEvent::Keyboard(evt) => {
if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed {
//remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
remote.show_notification("Espanso is running!");
//remote.show_notification("Espanso is running!");
}
}
}
@ -38,7 +50,20 @@ fn main() {
});
eventloop.initialize();
eventloop.run(Box::new(|event| {
eventloop.run(Box::new(move |event| {
println!("ui {:?}", event);
let menu = Menu::from(vec![
MenuItem::Simple(SimpleMenuItem::new("open", "Open")),
MenuItem::Separator,
MenuItem::Sub(SubMenuItem::new(
"Sub",
vec![
MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")),
MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")),
],
)),
])
.unwrap();
remote.show_context_menu(&menu);
}))
}