mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 20:11:29 +11:00
feat: implement NativeMenuItem
(#9)
* feat: implement `NativeMenuItem` * windows: refactor native menu item handle in window proc * native menu items on linux * change about status to not implemented on windows
This commit is contained in:
parent
6f1c8cc9c9
commit
943beda6df
|
@ -21,11 +21,13 @@ features = [
|
|||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_Globalization"
|
||||
"Win32_Globalization",
|
||||
"Win32_UI_Input_KeyboardAndMouse"
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gtk = "0.15"
|
||||
libxdo = "0.6.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.24"
|
||||
|
@ -33,4 +35,4 @@ objc = "0.2"
|
|||
|
||||
[dev-dependencies]
|
||||
winit = { git = "https://github.com/rust-windowing/winit" }
|
||||
tao = { git = "https://github.com/tauri-apps/tao", branch = "muda/disable-gtk-menu-creation" }
|
||||
tao = { git = "https://github.com/tauri-apps/tao", branch = "muda" }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use muda::{menu_event_receiver, Menu};
|
||||
use muda::{menu_event_receiver, Menu, NativeMenuItem};
|
||||
#[cfg(target_os = "linux")]
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -16,16 +16,19 @@ fn main() {
|
|||
let window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
let mut menu_bar = Menu::new();
|
||||
|
||||
let mut file_menu = menu_bar.add_submenu("&File", true);
|
||||
let mut edit_menu = menu_bar.add_submenu("&Edit", true);
|
||||
|
||||
let mut open_item = file_menu.add_text_item("&Open", true, None);
|
||||
|
||||
let mut save_item = file_menu.add_text_item("&Save", true, Some("CommandOrCtrl+S"));
|
||||
let _quit_item = file_menu.add_text_item("&Quit", true, None);
|
||||
file_menu.add_native_item(NativeMenuItem::Minimize);
|
||||
file_menu.add_native_item(NativeMenuItem::CloseWindow);
|
||||
file_menu.add_native_item(NativeMenuItem::Quit);
|
||||
|
||||
let _copy_item = edit_menu.add_text_item("&Copy", true, None);
|
||||
let _cut_item = edit_menu.add_text_item("C&ut", true, None);
|
||||
let mut edit_menu = menu_bar.add_submenu("&Edit", true);
|
||||
edit_menu.add_native_item(NativeMenuItem::Cut);
|
||||
edit_menu.add_native_item(NativeMenuItem::Copy);
|
||||
edit_menu.add_native_item(NativeMenuItem::Paste);
|
||||
edit_menu.add_native_item(NativeMenuItem::SelectAll);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
|
@ -38,11 +41,6 @@ fn main() {
|
|||
menu_bar.init_for_gtk_window(window2.gtk_window());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
menu_bar.init_for_nsapp();
|
||||
}
|
||||
|
||||
let menu_channel = menu_event_receiver();
|
||||
let mut open_item_disabled = false;
|
||||
let mut counter = 0;
|
||||
|
@ -50,6 +48,21 @@ fn main() {
|
|||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
#[cfg(target_os = "macos")]
|
||||
Event::NewEvents(tao::event::StartCause::Init) => {
|
||||
menu_bar.init_for_nsapp();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::MainEventsCleared => {
|
||||
// window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Ok(event) = menu_channel.try_recv() {
|
||||
match event.id {
|
||||
_ if event.id == save_item.id() => {
|
||||
|
@ -66,16 +79,5 @@ fn main() {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use muda::{menu_event_receiver, Menu};
|
||||
use muda::{menu_event_receiver, Menu, NativeMenuItem};
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macOS::EventLoopExtMacOS;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -36,15 +36,17 @@ fn main() {
|
|||
let _window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
let mut file_menu = menu_bar.add_submenu("&File", true);
|
||||
let mut edit_menu = menu_bar.add_submenu("&Edit", true);
|
||||
|
||||
let mut open_item = file_menu.add_text_item("&Open", true, Some("Ctrl+O"));
|
||||
|
||||
let mut open_item = file_menu.add_text_item("&Open", true, None);
|
||||
let mut save_item = file_menu.add_text_item("&Save", true, Some("CommandOrCtrl+S"));
|
||||
let _quit_item = file_menu.add_text_item("&Quit", true, None);
|
||||
file_menu.add_native_item(NativeMenuItem::Minimize);
|
||||
file_menu.add_native_item(NativeMenuItem::CloseWindow);
|
||||
file_menu.add_native_item(NativeMenuItem::Quit);
|
||||
|
||||
let _copy_item = edit_menu.add_text_item("&Copy", true, Some("Ctrl+C"));
|
||||
let _cut_item = edit_menu.add_text_item("C&ut", true, None);
|
||||
let mut edit_menu = menu_bar.add_submenu("&Edit", true);
|
||||
edit_menu.add_native_item(NativeMenuItem::Cut);
|
||||
edit_menu.add_native_item(NativeMenuItem::Copy);
|
||||
edit_menu.add_native_item(NativeMenuItem::Paste);
|
||||
edit_menu.add_native_item(NativeMenuItem::SelectAll);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
|
@ -64,6 +66,21 @@ fn main() {
|
|||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
#[cfg(target_os = "macos")]
|
||||
Event::NewEvents(winit::event::StartCause::Init) => {
|
||||
menu_bar.init_for_nsapp();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Ok(event) = menu_channel.try_recv() {
|
||||
match event.id {
|
||||
_ if event.id == save_item.id() => {
|
||||
|
@ -80,20 +97,5 @@ fn main() {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
#[cfg(target_os = "macos")]
|
||||
Event::NewEvents(winit::event::StartCause::Init) => {
|
||||
menu_bar.init_for_nsapp();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
148
src/lib.rs
148
src/lib.rs
|
@ -114,7 +114,7 @@ impl Menu {
|
|||
Submenu(self.0.add_submenu(label, enabled))
|
||||
}
|
||||
|
||||
/// Adds this menu to a [`gtk::Window`]
|
||||
/// Adds this menu to a [`gtk::ApplicationWindow`]
|
||||
///
|
||||
/// This method adds a [`gtk::Box`] then adds a [`gtk::MenuBar`] as its first child and returns the [`gtk::Box`].
|
||||
/// So if more widgets need to be added, then [`gtk::prelude::BoxExt::pack_start`] or
|
||||
|
@ -130,6 +130,7 @@ impl Menu {
|
|||
#[cfg(target_os = "linux")]
|
||||
pub fn init_for_gtk_window<W>(&self, w: &W) -> std::rc::Rc<gtk::Box>
|
||||
where
|
||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||
W: gtk::prelude::IsA<gtk::Container>,
|
||||
W: gtk::prelude::IsA<gtk::Window>,
|
||||
{
|
||||
|
@ -172,15 +173,11 @@ impl Menu {
|
|||
self.0.haccel()
|
||||
}
|
||||
|
||||
/// Removes this menu from a [`gtk::Window`]
|
||||
///
|
||||
/// ## Panics:
|
||||
///
|
||||
/// Panics if the window doesn't have a menu created by this crate.
|
||||
/// Removes this menu from a [`gtk::ApplicationWindow`]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn remove_for_gtk_window<W>(&self, w: &W)
|
||||
where
|
||||
W: gtk::prelude::IsA<gtk::Container>,
|
||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||
W: gtk::prelude::IsA<gtk::Window>,
|
||||
{
|
||||
self.0.remove_for_gtk_window(w)
|
||||
|
@ -192,12 +189,11 @@ impl Menu {
|
|||
self.0.remove_for_hwnd(hwnd)
|
||||
}
|
||||
|
||||
/// Hides this menu from a [`gtk::Window`]
|
||||
/// Hides this menu from a [`gtk::ApplicationWindow`]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn hide_for_gtk_window<W>(&self, w: &W)
|
||||
where
|
||||
W: gtk::prelude::IsA<gtk::Container>,
|
||||
W: gtk::prelude::IsA<gtk::Window>,
|
||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||
{
|
||||
self.0.hide_for_gtk_window(w)
|
||||
}
|
||||
|
@ -208,12 +204,11 @@ impl Menu {
|
|||
self.0.hide_for_hwnd(hwnd)
|
||||
}
|
||||
|
||||
/// Shows this menu from a [`gtk::Window`]
|
||||
/// Shows this menu from a [`gtk::ApplicationWindow`]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn show_for_gtk_window<W>(&self, w: &W)
|
||||
where
|
||||
W: gtk::prelude::IsA<gtk::Container>,
|
||||
W: gtk::prelude::IsA<gtk::Window>,
|
||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||
{
|
||||
self.0.show_for_gtk_window(w)
|
||||
}
|
||||
|
@ -290,6 +285,10 @@ impl Submenu {
|
|||
) -> TextMenuItem {
|
||||
TextMenuItem(self.0.add_text_item(label, enabled, accelerator))
|
||||
}
|
||||
|
||||
pub fn add_native_item(&mut self, item: NativeMenuItem) {
|
||||
self.0.add_native_item(item)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a Text menu item within a [`Submenu`].
|
||||
|
@ -322,3 +321,126 @@ impl TextMenuItem {
|
|||
self.0.id()
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NativeMenuItem {
|
||||
/// A native “About” menu item.
|
||||
///
|
||||
/// The first value is the application name, and the second is its metadata.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **macOS**: the metadata is ignore.
|
||||
/// - **Windows**: Not implemented.
|
||||
About(String, AboutMetadata),
|
||||
/// A native “hide the app” menu item.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
Hide,
|
||||
/// A native “hide all other windows" menu item.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
HideOthers,
|
||||
/// A native "Show all windows for this app" menu item.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
ShowAll,
|
||||
/// A native "Services" menu item.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
Services,
|
||||
/// A native "Close current window" menu item.
|
||||
CloseWindow,
|
||||
/// A native "Quit///
|
||||
Quit,
|
||||
/// A native "Copy" menu item.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS**: macOS require this menu item to enable "Copy" keyboard shortcut for your app.
|
||||
/// - **Linux Wayland**: Not implmeneted.
|
||||
Copy,
|
||||
/// A native "Cut" menu item.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS**: macOS require this menu item to enable "Cut" keyboard shortcut for your app.
|
||||
/// - **Linux Wayland**: Not implmeneted.
|
||||
Cut,
|
||||
/// A native "Paste" menu item.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS**: macOS require this menu item to enable "Paste" keyboard shortcut for your app.
|
||||
/// - **Linux Wayland**: Not implmeneted.
|
||||
Paste,
|
||||
/// A native "Undo" menu item.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS**: macOS require this menu item to enable "Undo" keyboard shortcut for your app.
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
Undo,
|
||||
/// A native "Redo" menu item.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS**: macOS require this menu item to enable "Redo" keyboard shortcut for your app.
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
Redo,
|
||||
/// A native "Select All" menu item.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **macOS**: macOS require this menu item to enable "Select All" keyboard shortcut for your app.
|
||||
/// - **Linux Wayland**: Not implmeneted.
|
||||
SelectAll,
|
||||
/// A native "Enter fullscreen" menu item.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
EnterFullScreen,
|
||||
/// A native "Minimize current window" menu item.
|
||||
Minimize,
|
||||
/// A native "Zoom" menu item.
|
||||
///
|
||||
/// ## platform-specific:
|
||||
///
|
||||
/// - **Windows / Linux**: Unsupported.
|
||||
Zoom,
|
||||
/// Represends a Separator in the menu.
|
||||
Separator,
|
||||
}
|
||||
|
||||
/// Application metadata for the [`NativeMenuItem::About`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS**: The metadata is ignored.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AboutMetadata {
|
||||
/// The application name.
|
||||
pub version: Option<String>,
|
||||
/// The authors of the application.
|
||||
pub authors: Option<Vec<String>>,
|
||||
/// Application comments.
|
||||
pub comments: Option<String>,
|
||||
/// The copyright of the application.
|
||||
pub copyright: Option<String>,
|
||||
/// The license of the application.
|
||||
pub license: Option<String>,
|
||||
/// The application website.
|
||||
pub website: Option<String>,
|
||||
/// The website label.
|
||||
pub website_label: Option<String>,
|
||||
}
|
||||
|
|
|
@ -1,41 +1,50 @@
|
|||
mod accelerator;
|
||||
|
||||
use crate::counter::Counter;
|
||||
use crate::{counter::Counter, NativeMenuItem};
|
||||
use accelerator::{to_gtk_accelerator, to_gtk_menemenoic};
|
||||
use gtk::{prelude::*, Orientation};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
static COUNTER: Counter = Counter::new();
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum MenuEntryType {
|
||||
Submenu,
|
||||
Text,
|
||||
}
|
||||
|
||||
/// Generic shared type describing a menu entry. It can be one of [`MenuEntryType`]
|
||||
#[derive(Debug, Default)]
|
||||
struct MenuEntry {
|
||||
label: String,
|
||||
enabled: bool,
|
||||
r#type: MenuEntryType,
|
||||
item_id: Option<u64>,
|
||||
accelerator: Option<String>,
|
||||
// NOTE(amrbashir): because gtk doesn't allow using the same `gtk::MenuItem`
|
||||
native_menu_item: Option<NativeMenuItem>,
|
||||
// NOTE(amrbashir): because gtk doesn't allow using the same [`gtk::MenuItem`]
|
||||
// multiple times, and thus can't be used in multiple windows, each entry
|
||||
// keeps a vector of a `gtk::MenuItem` or a tuple of `gtk::MenuItem` and `gtk::Menu`
|
||||
// and push to it every time `Menu::init_for_gtk_window` is called.
|
||||
item_gtk_items: Option<Rc<RefCell<Vec<gtk::MenuItem>>>>,
|
||||
menu_gtk_items: Option<Rc<RefCell<Vec<(gtk::MenuItem, gtk::Menu)>>>>,
|
||||
// keeps a vector of a [`gtk::MenuItem`] or a tuple of [`gtk::MenuItem`] and [`gtk::Menu`] if its a menu
|
||||
// and push to it every time [`Menu::init_for_gtk_window`] is called.
|
||||
native_items: Option<Rc<RefCell<Vec<gtk::MenuItem>>>>,
|
||||
native_menus: Option<Rc<RefCell<Vec<(gtk::MenuItem, gtk::Menu)>>>>,
|
||||
entries: Option<Vec<Rc<RefCell<MenuEntry>>>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
enum MenuEntryType {
|
||||
Submenu,
|
||||
Text,
|
||||
Native,
|
||||
}
|
||||
|
||||
impl Default for MenuEntryType {
|
||||
fn default() -> Self {
|
||||
MenuEntryType::Text
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerMenu {
|
||||
entries: Vec<Rc<RefCell<MenuEntry>>>,
|
||||
// NOTE(amrbashir): because gtk doesn't allow using the same `gtk::MenuBar` and `gtk::Box`
|
||||
// multiple times, and thus can't be used in multiple windows, entry
|
||||
// keeps a vector of a tuple of `gtk::MenuBar` and `gtk::Box`
|
||||
// NOTE(amrbashir): because gtk doesn't allow using the same [`gtk::MenuBar`] and [`gtk::Box`]
|
||||
// multiple times, and thus can't be used in multiple windows. each menu
|
||||
// keeps a hashmap of window pointer as the key and a tuple of [`gtk::MenuBar`] and [`gtk::Box`] as the value
|
||||
// and push to it every time `Menu::init_for_gtk_window` is called.
|
||||
gtk_items: HashMap<isize, (Option<gtk::MenuBar>, Rc<gtk::Box>)>,
|
||||
native_menus: HashMap<isize, (Option<gtk::MenuBar>, Rc<gtk::Box>)>,
|
||||
accel_group: gtk::AccelGroup,
|
||||
}
|
||||
|
||||
|
@ -46,156 +55,119 @@ impl Menu {
|
|||
pub fn new() -> Self {
|
||||
Self(Rc::new(RefCell::new(InnerMenu {
|
||||
entries: Vec::new(),
|
||||
gtk_items: HashMap::new(),
|
||||
native_menus: HashMap::new(),
|
||||
accel_group: gtk::AccelGroup::new(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||
let label = label.as_ref().to_string();
|
||||
let entry = Rc::new(RefCell::new(MenuEntry {
|
||||
label: label.clone(),
|
||||
label: label.as_ref().to_string(),
|
||||
enabled,
|
||||
entries: Some(Vec::new()),
|
||||
r#type: MenuEntryType::Submenu,
|
||||
item_id: None,
|
||||
accelerator: None,
|
||||
menu_gtk_items: Some(Rc::new(RefCell::new(Vec::new()))),
|
||||
item_gtk_items: None,
|
||||
native_menus: Some(Rc::new(RefCell::new(Vec::new()))),
|
||||
..Default::default()
|
||||
}));
|
||||
self.0.borrow_mut().entries.push(entry.clone());
|
||||
Submenu(entry)
|
||||
}
|
||||
|
||||
pub fn init_for_gtk_window<W>(&self, w: &W) -> Rc<gtk::Box>
|
||||
pub fn init_for_gtk_window<W>(&self, window: &W) -> Rc<gtk::Box>
|
||||
where
|
||||
W: IsA<gtk::ApplicationWindow>,
|
||||
W: IsA<gtk::Container>,
|
||||
W: IsA<gtk::Window>,
|
||||
{
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
// This is the first time this method has been called on a window
|
||||
if inner.gtk_items.get(&(w.as_ptr() as _)).is_none() {
|
||||
// This is the first time this method has been called on this window
|
||||
// so we need to create the menubar and its parent box
|
||||
if inner.native_menus.get(&(window.as_ptr() as _)).is_none() {
|
||||
let menu_bar = gtk::MenuBar::new();
|
||||
let vbox = gtk::Box::new(Orientation::Vertical, 0);
|
||||
w.add(&vbox);
|
||||
window.add(&vbox);
|
||||
inner
|
||||
.gtk_items
|
||||
.insert(w.as_ptr() as _, (Some(menu_bar), Rc::new(vbox)));
|
||||
.native_menus
|
||||
.insert(window.as_ptr() as _, (Some(menu_bar), Rc::new(vbox)));
|
||||
}
|
||||
|
||||
if let Some((menu_bar, vbox)) = inner.gtk_items.get(&(w.as_ptr() as _)) {
|
||||
if let Some((menu_bar, vbox)) = inner.native_menus.get(&(window.as_ptr() as _)) {
|
||||
// This is NOT the first time this method has been called on a window.
|
||||
// So it already contains a `gtk::Box` but it doesn't have a `gtk::MenuBar`
|
||||
// because it was probably removed using `Menu::remove_for_gtk_window`
|
||||
// So it already contains a [`gtk::Box`] but it doesn't have a [`gtk::MenuBar`]
|
||||
// because it was probably removed using [`Menu::remove_for_gtk_window`]
|
||||
// so we only need to create the menubar
|
||||
if menu_bar.is_none() {
|
||||
let vbox = Rc::clone(vbox);
|
||||
inner
|
||||
.gtk_items
|
||||
.insert(w.as_ptr() as _, (Some(gtk::MenuBar::new()), vbox));
|
||||
.native_menus
|
||||
.insert(window.as_ptr() as _, (Some(gtk::MenuBar::new()), vbox));
|
||||
}
|
||||
}
|
||||
|
||||
let (menu_bar, vbox) = inner.gtk_items.get(&(w.as_ptr() as _)).unwrap();
|
||||
|
||||
// Construct the entries of the menubar
|
||||
let (menu_bar, vbox) = inner.native_menus.get(&(window.as_ptr() as _)).unwrap();
|
||||
add_entries_to_menu(
|
||||
menu_bar.as_ref().unwrap(),
|
||||
&inner.entries,
|
||||
&inner.accel_group,
|
||||
);
|
||||
w.add_accel_group(&inner.accel_group);
|
||||
window.add_accel_group(&inner.accel_group);
|
||||
|
||||
// Show the menubar on the window
|
||||
vbox.pack_start(menu_bar.as_ref().unwrap(), false, false, 0);
|
||||
vbox.show_all();
|
||||
|
||||
Rc::clone(vbox)
|
||||
}
|
||||
|
||||
pub fn remove_for_gtk_window<W>(&self, w: &W)
|
||||
pub fn remove_for_gtk_window<W>(&self, window: &W)
|
||||
where
|
||||
W: IsA<gtk::Container>,
|
||||
W: IsA<gtk::ApplicationWindow>,
|
||||
W: IsA<gtk::Window>,
|
||||
{
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
if let Some((menu_bar, vbox)) = inner.gtk_items.get(&(w.as_ptr() as _)) {
|
||||
vbox.remove(menu_bar.as_ref().unwrap());
|
||||
w.remove_accel_group(&inner.accel_group);
|
||||
if let Some((Some(menu_bar), vbox)) = inner.native_menus.get(&(window.as_ptr() as _)) {
|
||||
// Remove the [`gtk::Menubar`] from the widget tree
|
||||
unsafe { menu_bar.destroy() };
|
||||
// Detach the accelerators from the window
|
||||
window.remove_accel_group(&inner.accel_group);
|
||||
// Remove the removed [`gtk::Menubar`] from our cache
|
||||
let vbox = Rc::clone(vbox);
|
||||
inner.gtk_items.insert(w.as_ptr() as _, (None, vbox));
|
||||
inner
|
||||
.native_menus
|
||||
.insert(window.as_ptr() as _, (None, vbox));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide_for_gtk_window<W>(&self, w: &W)
|
||||
pub fn hide_for_gtk_window<W>(&self, window: &W)
|
||||
where
|
||||
W: IsA<gtk::Container>,
|
||||
W: IsA<gtk::Window>,
|
||||
W: IsA<gtk::ApplicationWindow>,
|
||||
{
|
||||
if let Some((Some(menu_bar), _)) = self
|
||||
.0
|
||||
.borrow()
|
||||
.native_menus
|
||||
.get(&(window.as_ptr() as isize))
|
||||
{
|
||||
if let Some((menu_bar, _)) = self.0.borrow().gtk_items.get(&(w.as_ptr() as isize)) {
|
||||
if let Some(menu_bar) = menu_bar {
|
||||
menu_bar.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_for_gtk_window<W>(&self, w: &W)
|
||||
pub fn show_for_gtk_window<W>(&self, window: &W)
|
||||
where
|
||||
W: IsA<gtk::Container>,
|
||||
W: IsA<gtk::Window>,
|
||||
W: IsA<gtk::ApplicationWindow>,
|
||||
{
|
||||
if let Some((Some(menu_bar), _)) = self
|
||||
.0
|
||||
.borrow()
|
||||
.native_menus
|
||||
.get(&(window.as_ptr() as isize))
|
||||
{
|
||||
if let Some((menu_bar, _)) = self.0.borrow().gtk_items.get(&(w.as_ptr() as isize)) {
|
||||
if let Some(menu_bar) = menu_bar {
|
||||
menu_bar.show_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
|
||||
gtk_menu: &M,
|
||||
entries: &Vec<Rc<RefCell<MenuEntry>>>,
|
||||
accel_group: >k::AccelGroup,
|
||||
) {
|
||||
for entry in entries {
|
||||
let mut entry = entry.borrow_mut();
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(&entry.label));
|
||||
gtk_menu.append(>k_item);
|
||||
gtk_item.set_sensitive(entry.enabled);
|
||||
if entry.r#type == MenuEntryType::Submenu {
|
||||
let gtk_menu = gtk::Menu::new();
|
||||
gtk_item.set_submenu(Some(>k_menu));
|
||||
add_entries_to_menu(>k_menu, entry.entries.as_ref().unwrap(), accel_group);
|
||||
entry
|
||||
.menu_gtk_items
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.push((gtk_item, gtk_menu));
|
||||
} else {
|
||||
if let Some(accelerator) = &entry.accelerator {
|
||||
let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator));
|
||||
gtk_item.add_accelerator(
|
||||
"activate",
|
||||
accel_group,
|
||||
key,
|
||||
modifiers,
|
||||
gtk::AccelFlags::VISIBLE,
|
||||
);
|
||||
}
|
||||
|
||||
let id = entry.item_id.unwrap_or_default();
|
||||
gtk_item.connect_activate(move |_| {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||
});
|
||||
entry
|
||||
.item_gtk_items
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.push(gtk_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -209,8 +181,8 @@ impl Submenu {
|
|||
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||
let label = label.as_ref().to_string();
|
||||
let mut entry = self.0.borrow_mut();
|
||||
for (item, _) in entry.menu_gtk_items.as_ref().unwrap().borrow().iter() {
|
||||
item.set_label(&label);
|
||||
for (item, _) in entry.native_menus.as_ref().unwrap().borrow().iter() {
|
||||
item.set_label(&to_gtk_menemenoic(&label));
|
||||
}
|
||||
entry.label = label;
|
||||
}
|
||||
|
@ -222,7 +194,7 @@ impl Submenu {
|
|||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
let mut entry = self.0.borrow_mut();
|
||||
entry.enabled = true;
|
||||
for (item, _) in entry.menu_gtk_items.as_ref().unwrap().borrow().iter() {
|
||||
for (item, _) in entry.native_menus.as_ref().unwrap().borrow().iter() {
|
||||
item.set_sensitive(enabled);
|
||||
}
|
||||
}
|
||||
|
@ -233,10 +205,8 @@ impl Submenu {
|
|||
enabled,
|
||||
entries: Some(Vec::new()),
|
||||
r#type: MenuEntryType::Submenu,
|
||||
item_id: None,
|
||||
accelerator: None,
|
||||
menu_gtk_items: Some(Rc::new(RefCell::new(Vec::new()))),
|
||||
item_gtk_items: None,
|
||||
native_menus: Some(Rc::new(RefCell::new(Vec::new()))),
|
||||
..Default::default()
|
||||
}));
|
||||
self.0
|
||||
.borrow_mut()
|
||||
|
@ -254,14 +224,13 @@ impl Submenu {
|
|||
accelerator: Option<&str>,
|
||||
) -> TextMenuItem {
|
||||
let entry = Rc::new(RefCell::new(MenuEntry {
|
||||
label: to_gtk_menemenoic(label),
|
||||
label: label.as_ref().to_string(),
|
||||
enabled,
|
||||
entries: None,
|
||||
r#type: MenuEntryType::Text,
|
||||
item_id: Some(COUNTER.next()),
|
||||
accelerator: accelerator.map(|s| s.to_string()),
|
||||
menu_gtk_items: None,
|
||||
item_gtk_items: Some(Rc::new(RefCell::new(Vec::new()))),
|
||||
native_items: Some(Rc::new(RefCell::new(Vec::new()))),
|
||||
..Default::default()
|
||||
}));
|
||||
self.0
|
||||
.borrow_mut()
|
||||
|
@ -271,6 +240,15 @@ impl Submenu {
|
|||
.push(entry.clone());
|
||||
TextMenuItem(entry)
|
||||
}
|
||||
|
||||
pub fn add_native_item(&mut self, item: NativeMenuItem) {
|
||||
let entry = Rc::new(RefCell::new(MenuEntry {
|
||||
r#type: MenuEntryType::Native,
|
||||
native_menu_item: Some(item),
|
||||
..Default::default()
|
||||
}));
|
||||
self.0.borrow_mut().entries.as_mut().unwrap().push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -284,8 +262,8 @@ impl TextMenuItem {
|
|||
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||
let label = label.as_ref().to_string();
|
||||
let mut entry = self.0.borrow_mut();
|
||||
for item in entry.item_gtk_items.as_ref().unwrap().borrow().iter() {
|
||||
item.set_label(&label);
|
||||
for item in entry.native_items.as_ref().unwrap().borrow().iter() {
|
||||
item.set_label(&to_gtk_menemenoic(&label));
|
||||
}
|
||||
entry.label = label;
|
||||
}
|
||||
|
@ -296,7 +274,7 @@ impl TextMenuItem {
|
|||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
let mut entry = self.0.borrow_mut();
|
||||
for item in entry.item_gtk_items.as_ref().unwrap().borrow().iter() {
|
||||
for item in entry.native_items.as_ref().unwrap().borrow().iter() {
|
||||
item.set_sensitive(enabled);
|
||||
}
|
||||
entry.enabled = enabled;
|
||||
|
@ -306,3 +284,156 @@ impl TextMenuItem {
|
|||
self.0.borrow().item_id.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn add_entries_to_menu<M>(
|
||||
gtk_menu: &M,
|
||||
entries: &Vec<Rc<RefCell<MenuEntry>>>,
|
||||
accel_group: >k::AccelGroup,
|
||||
) where
|
||||
M: IsA<gtk::MenuShell>,
|
||||
{
|
||||
for entry in entries {
|
||||
let mut entry = entry.borrow_mut();
|
||||
match entry.r#type {
|
||||
MenuEntryType::Submenu => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(&entry.label));
|
||||
gtk_menu.append(>k_item);
|
||||
gtk_item.set_sensitive(entry.enabled);
|
||||
let gtk_menu = gtk::Menu::new();
|
||||
gtk_item.set_submenu(Some(>k_menu));
|
||||
add_entries_to_menu(>k_menu, entry.entries.as_ref().unwrap(), accel_group);
|
||||
entry
|
||||
.native_menus
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.push((gtk_item, gtk_menu));
|
||||
}
|
||||
MenuEntryType::Text => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(&entry.label));
|
||||
gtk_menu.append(>k_item);
|
||||
gtk_item.set_sensitive(entry.enabled);
|
||||
if let Some(accelerator) = &entry.accelerator {
|
||||
let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator));
|
||||
gtk_item.add_accelerator(
|
||||
"activate",
|
||||
accel_group,
|
||||
key,
|
||||
modifiers,
|
||||
gtk::AccelFlags::VISIBLE,
|
||||
);
|
||||
}
|
||||
|
||||
let id = entry.item_id.unwrap();
|
||||
gtk_item.connect_activate(move |_| {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||
});
|
||||
entry
|
||||
.native_items
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.push(gtk_item);
|
||||
}
|
||||
MenuEntryType::Native => match entry.native_menu_item.as_ref().unwrap() {
|
||||
NativeMenuItem::Copy => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("_Copy");
|
||||
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>X");
|
||||
gtk_item
|
||||
.child()
|
||||
.unwrap()
|
||||
.downcast::<gtk::AccelLabel>()
|
||||
.unwrap()
|
||||
.set_accel(key, modifiers);
|
||||
gtk_item.connect_activate(move |_| {
|
||||
// TODO: wayland
|
||||
if let Ok(xdo) = libxdo::XDo::new(None) {
|
||||
let _ = xdo.send_keysequence("ctrl+c", 0);
|
||||
}
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
NativeMenuItem::Cut => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("Cu_t");
|
||||
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>X");
|
||||
gtk_item
|
||||
.child()
|
||||
.unwrap()
|
||||
.downcast::<gtk::AccelLabel>()
|
||||
.unwrap()
|
||||
.set_accel(key, modifiers);
|
||||
gtk_item.connect_activate(move |_| {
|
||||
// TODO: wayland
|
||||
if let Ok(xdo) = libxdo::XDo::new(None) {
|
||||
let _ = xdo.send_keysequence("ctrl+x", 0);
|
||||
}
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
NativeMenuItem::Paste => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("_Paste");
|
||||
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>V");
|
||||
gtk_item
|
||||
.child()
|
||||
.unwrap()
|
||||
.downcast::<gtk::AccelLabel>()
|
||||
.unwrap()
|
||||
.set_accel(key, modifiers);
|
||||
gtk_item.connect_activate(move |_| {
|
||||
// TODO: wayland
|
||||
if let Ok(xdo) = libxdo::XDo::new(None) {
|
||||
let _ = xdo.send_keysequence("ctrl+v", 0);
|
||||
}
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
NativeMenuItem::SelectAll => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("Select _All");
|
||||
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>A");
|
||||
gtk_item
|
||||
.child()
|
||||
.unwrap()
|
||||
.downcast::<gtk::AccelLabel>()
|
||||
.unwrap()
|
||||
.set_accel(key, modifiers);
|
||||
gtk_item.connect_activate(move |_| {
|
||||
// TODO: wayland
|
||||
if let Ok(xdo) = libxdo::XDo::new(None) {
|
||||
let _ = xdo.send_keysequence("ctrl+a", 0);
|
||||
}
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
NativeMenuItem::Separator => {
|
||||
gtk_menu.append(>k::SeparatorMenuItem::new());
|
||||
}
|
||||
NativeMenuItem::Minimize => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("_Minimize");
|
||||
gtk_item.connect_activate(move |m| {
|
||||
if let Some(window) = m.window() {
|
||||
window.iconify()
|
||||
}
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
NativeMenuItem::CloseWindow => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("C_lose Window");
|
||||
gtk_item.connect_activate(move |m| {
|
||||
if let Some(window) = m.window() {
|
||||
window.destroy()
|
||||
}
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
NativeMenuItem::Quit => {
|
||||
let gtk_item = gtk::MenuItem::with_mnemonic("_Quit");
|
||||
gtk_item.connect_activate(move |_| {
|
||||
std::process::exit(0);
|
||||
});
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,19 @@
|
|||
mod accelerator;
|
||||
mod util;
|
||||
|
||||
use crate::counter::Counter;
|
||||
use crate::{counter::Counter, NativeMenuItem};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use util::{decode_wide, encode_wide, LOWORD};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
|
||||
UI::{
|
||||
Input::KeyboardAndMouse::{SendInput, INPUT, INPUT_KEYBOARD, KEYEVENTF_KEYUP, VK_CONTROL},
|
||||
Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass},
|
||||
WindowsAndMessaging::{
|
||||
AppendMenuW, CreateAcceleratorTableW, CreateMenu, DrawMenuBar, EnableMenuItem,
|
||||
GetMenuItemInfoW, SetMenu, SetMenuItemInfoW, ACCEL, HACCEL, HMENU, MENUITEMINFOW,
|
||||
MFS_DISABLED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MIIM_STATE, MIIM_STRING,
|
||||
AppendMenuW, CloseWindow, CreateAcceleratorTableW, CreateMenu, DrawMenuBar,
|
||||
EnableMenuItem, GetMenuItemInfoW, PostQuitMessage, SetMenu, SetMenuItemInfoW,
|
||||
ShowWindow, ACCEL, HACCEL, HMENU, MENUITEMINFOW, MFS_DISABLED, MF_DISABLED, MF_ENABLED,
|
||||
MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING, MIIM_STATE, MIIM_STRING, SW_MINIMIZE,
|
||||
WM_COMMAND,
|
||||
},
|
||||
},
|
||||
|
@ -21,6 +23,7 @@ use windows_sys::Win32::{
|
|||
|
||||
use self::accelerator::parse_accelerator;
|
||||
|
||||
const COUNTER_START: u64 = 563;
|
||||
static COUNTER: Counter = Counter::new_with_start(563);
|
||||
const MENU_SUBCLASS_ID: usize = 232;
|
||||
|
||||
|
@ -189,7 +192,7 @@ impl Submenu {
|
|||
accelerator: Option<&str>,
|
||||
) -> TextMenuItem {
|
||||
let id = COUNTER.next();
|
||||
let mut flags = MF_POPUP;
|
||||
let mut flags = MF_STRING;
|
||||
if !enabled {
|
||||
flags |= MF_GRAYED;
|
||||
}
|
||||
|
@ -218,6 +221,28 @@ impl Submenu {
|
|||
parent_hmenu: self.hmenu,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_native_item(&mut self, item: NativeMenuItem) {
|
||||
let (label, flags) = match item {
|
||||
NativeMenuItem::Copy => ("&Copy\tCtrl+C", MF_STRING),
|
||||
NativeMenuItem::Cut => ("Cu&t\tCtrl+X", MF_STRING),
|
||||
NativeMenuItem::Paste => ("&Paste\tCtrl+V", MF_STRING),
|
||||
NativeMenuItem::SelectAll => ("Select&All", MF_STRING),
|
||||
NativeMenuItem::Separator => ("", MF_SEPARATOR),
|
||||
NativeMenuItem::Minimize => ("&Minimize", MF_STRING),
|
||||
NativeMenuItem::CloseWindow => ("Close", MF_STRING),
|
||||
NativeMenuItem::Quit => ("Exit", MF_STRING),
|
||||
_ => return,
|
||||
};
|
||||
unsafe {
|
||||
AppendMenuW(
|
||||
self.hmenu,
|
||||
flags,
|
||||
item.id() as _,
|
||||
encode_wide(label).as_ptr(),
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -302,10 +327,107 @@ unsafe extern "system" fn menu_subclass_proc(
|
|||
_uidsubclass: usize,
|
||||
_dwrefdata: usize,
|
||||
) -> LRESULT {
|
||||
let id = LOWORD(wparam as _);
|
||||
if msg == WM_COMMAND && 0 < id && (id as u64) < COUNTER.current() {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id: id as _ });
|
||||
let mut ret = -1;
|
||||
if msg == WM_COMMAND {
|
||||
let id = LOWORD(wparam as _) as u64;
|
||||
|
||||
// Custom menu items
|
||||
if COUNTER_START < id && id < COUNTER.current() {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||
ret = 0;
|
||||
};
|
||||
|
||||
// Native menu items
|
||||
if NativeMenuItem::is_id_of_native(id) {
|
||||
ret = 0;
|
||||
match id {
|
||||
_ if id == NativeMenuItem::Copy.id() => {
|
||||
execute_edit_command(EditCommand::Copy);
|
||||
}
|
||||
_ if id == NativeMenuItem::Cut.id() => {
|
||||
execute_edit_command(EditCommand::Cut);
|
||||
}
|
||||
_ if id == NativeMenuItem::Paste.id() => {
|
||||
execute_edit_command(EditCommand::Paste);
|
||||
}
|
||||
_ if id == NativeMenuItem::SelectAll.id() => {
|
||||
execute_edit_command(EditCommand::SelectAll);
|
||||
}
|
||||
_ if id == NativeMenuItem::Minimize.id() => {
|
||||
ShowWindow(hwnd, SW_MINIMIZE);
|
||||
}
|
||||
_ if id == NativeMenuItem::CloseWindow.id() => {
|
||||
CloseWindow(hwnd);
|
||||
}
|
||||
_ if id == NativeMenuItem::Quit.id() => {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ret == -1 {
|
||||
DefSubclassProc(hwnd, msg, wparam, lparam)
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
enum EditCommand {
|
||||
Copy,
|
||||
Cut,
|
||||
Paste,
|
||||
SelectAll,
|
||||
}
|
||||
|
||||
fn execute_edit_command(command: EditCommand) {
|
||||
let key = match command {
|
||||
EditCommand::Copy => 0x43, // c
|
||||
EditCommand::Cut => 0x58, // x
|
||||
EditCommand::Paste => 0x56, // v
|
||||
EditCommand::SelectAll => 0x41, // a
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut inputs: [INPUT; 4] = std::mem::zeroed();
|
||||
inputs[0].r#type = INPUT_KEYBOARD;
|
||||
inputs[0].Anonymous.ki.wVk = VK_CONTROL;
|
||||
inputs[2].Anonymous.ki.dwFlags = 0;
|
||||
|
||||
inputs[1].r#type = INPUT_KEYBOARD;
|
||||
inputs[1].Anonymous.ki.wVk = key;
|
||||
inputs[2].Anonymous.ki.dwFlags = 0;
|
||||
|
||||
inputs[2].r#type = INPUT_KEYBOARD;
|
||||
inputs[2].Anonymous.ki.wVk = key;
|
||||
inputs[2].Anonymous.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
inputs[3].r#type = INPUT_KEYBOARD;
|
||||
inputs[3].Anonymous.ki.wVk = VK_CONTROL;
|
||||
inputs[3].Anonymous.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
let ret = SendInput(4, &inputs as *const _, std::mem::size_of::<INPUT>() as _);
|
||||
dbg!(ret);
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeMenuItem {
|
||||
fn id(&self) -> u64 {
|
||||
match self {
|
||||
NativeMenuItem::Copy => 301,
|
||||
NativeMenuItem::Cut => 302,
|
||||
NativeMenuItem::Paste => 303,
|
||||
NativeMenuItem::SelectAll => 304,
|
||||
NativeMenuItem::Separator => 305,
|
||||
NativeMenuItem::Minimize => 306,
|
||||
NativeMenuItem::CloseWindow => 307,
|
||||
NativeMenuItem::Quit => 308,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_id_of_native(id: u64) -> bool {
|
||||
(301..=308).contains(&id)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue