use gdk::{keys::Key, Display, ModifierType};
use gtk::{prelude::*, traits::WidgetExt, AccelFlags, AccelGroup, Menu};
use std::{env, mem, thread::sleep, time};
use tauri::{Runtime, Window};
use crate::keymap::{get_key_map, get_mod_map};
use crate::theme::Theme;
use crate::{MenuItem, Position};
pub fn on_context_menu<R: Runtime>(
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
window: Window<R>,
) {
let gtk_window = window.gtk_window().unwrap();
if !gtk_window.is_realized() {
gtk_window.realize();
}
let menu = Menu::new();
if let Some(menu_items) = items {
for item in menu_items.iter() {
append_menu_item(&window, >k_window, &menu, item);
}
}
let (mut x, mut y) = match pos {
Some(ref position) => (position.x as i32, position.y as i32),
None => {
if let Some(display) = Display::default() {
if let Some(seat) = display.default_seat() {
let pointer = seat.pointer();
let (_screen, x, y) = match pointer {
Some(p) => p.position(),
None => {
eprintln!("Failed to get pointer position");
(display.default_screen(), 0, 0)
}
};
(x, y)
} else {
eprintln!("Failed to get default seat");
(0, 0)
}
} else {
eprintln!("Failed to get default display");
(0, 0)
}
}
};
let is_x11 = env::var("GDK_BACKEND").map_or(false, |v| v == "x11");
if is_x11 {
let display = gdk::Display::default().expect("Failed to get default display");
let gdk_window = gtk_window.window().unwrap();
let monitor = display
.monitor_at_window(&gdk_window)
.expect("Failed to get monitor at window");
let scale_factor = monitor.scale_factor();
let monitor_geometry = monitor.geometry();
x = (x + monitor_geometry.x()) * scale_factor;
y = (y + monitor_geometry.y()) * scale_factor;
}
let is_absolute = if let Some(position) = pos.clone() {
position.is_absolute
} else {
Some(false)
};
if is_absolute.unwrap_or(false) || pos.is_none() {
let window_position = window.outer_position().unwrap();
x -= window_position.x;
y -= window_position.y;
}
sleep(time::Duration::from_millis(100));
glib::idle_add_local(move || {
let gdk_window = gtk_window.window().unwrap();
let rect = &gdk::Rectangle::new(x, y, 0, 0);
let mut event = gdk::Event::new(gdk::EventType::ButtonPress);
event.set_device(
gdk_window
.display()
.default_seat()
.and_then(|d| d.pointer())
.as_ref(),
);
menu.show_all();
menu.popup_at_rect(
&gdk_window,
rect,
gdk::Gravity::NorthWest,
gdk::Gravity::NorthWest,
Some(&event),
);
Continue(false)
});
}
pub fn show_context_menu<R: Runtime>(
window: Window<R>,
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
_theme: Option<Theme>,
) {
on_context_menu(pos, items, window);
}
fn append_menu_item<R: Runtime>(
window: &Window<R>,
gtk_window: >k::ApplicationWindow,
menu: &Menu,
item: &MenuItem,
) {
if item.is_separator.unwrap_or(false) {
menu.append(>k::SeparatorMenuItem::builder().visible(true).build());
} else {
let menu_item = match item.checked {
Some(state) => {
let check_menu_item = gtk::CheckMenuItem::new();
check_menu_item.set_active(state);
check_menu_item.upcast()
}
None => {
gtk::MenuItem::new()
}
};
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0);
hbox.set_homogeneous(false);
if let Some(icon) = &item.icon {
let image = gtk::Image::from_file(&icon.path);
if let Some(width) = icon.width {
if let Some(height) = icon.height {
image.set_pixel_size(width as i32);
image.set_pixel_size(height as i32);
}
}
hbox.pack_start(&image, false, false, 0);
}
let label = item.label.as_deref().unwrap_or("");
let accel_label = gtk::AccelLabel::new(label);
accel_label.set_xalign(0.0); hbox.pack_start(&accel_label, true, true, 0);
menu_item.add(&hbox);
if item.disabled.unwrap_or(false) {
menu_item.set_sensitive(false);
}
if let Some(event) = &item.event {
let window_clone = window.clone();
let payload_clone = item.payload.clone();
let event_clone = event.clone();
menu_item.connect_activate(move |_| {
window_clone
.emit(event_clone.as_str(), &payload_clone)
.unwrap(); });
}
if let Some(shortcut) = &item.shortcut {
let accel_group = AccelGroup::new();
gtk_window.add_accel_group(&accel_group);
let (key, mods) = parse_shortcut(shortcut);
accel_label.set_accel_widget(Some(&menu_item));
menu_item.add_accelerator("activate", &accel_group, key, mods, AccelFlags::VISIBLE);
}
if let Some(subitems) = &item.subitems {
let submenu = Menu::new();
for subitem in subitems.iter() {
append_menu_item(window, gtk_window, &submenu, subitem);
}
menu_item.set_submenu(Some(&submenu));
}
menu.append(&menu_item);
}
}
fn key_to_u32(key: gdk::keys::Key) -> u32 {
unsafe { mem::transmute(key) }
}
fn parse_shortcut(shortcut: &str) -> (u32, ModifierType) {
let key_map = get_key_map();
let mod_map = get_mod_map(); let parts: Vec<&str> = shortcut.split('+').collect();
let key_str = parts.last().unwrap_or(&"");
let key = if let Some(key) = key_map.get(key_str) {
key.clone()
} else {
Key::from_name(key_str)
};
let key_u32 = key_to_u32(key);
let mut mods = ModifierType::empty();
for &mod_str in &parts[..parts.len() - 1] {
if let Some(&mod_type) = mod_map.get(mod_str) {
mods.insert(mod_type);
}
}
(key_u32, mods)
}