gtk: inhibit keyboard on click

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
Marc-André Lureau 2021-03-25 21:54:26 +04:00
parent 1cae81e2f3
commit 93afe7b2d6

View File

@ -3,9 +3,9 @@ use glib::subclass::prelude::*;
use gtk::glib::translate::FromGlibPtrBorrow; use gtk::glib::translate::FromGlibPtrBorrow;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{gdk, glib, CompositeTemplate}; use gtk::{gdk, glib, CompositeTemplate};
use log::debug;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::cell::Cell; use std::cell::Cell;
use log::debug;
use keycodemap::*; use keycodemap::*;
use qemu_display_listener::{Console, ConsoleEvent as Event, MouseButton}; use qemu_display_listener::{Console, ConsoleEvent as Event, MouseButton};
@ -14,7 +14,7 @@ mod imp {
use super::*; use super::*;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
#[derive(Debug, CompositeTemplate, Default)] #[derive(CompositeTemplate, Default)]
#[template(resource = "/org/qemu/gtk4/console.ui")] #[template(resource = "/org/qemu/gtk4/console.ui")]
pub struct QemuConsole { pub struct QemuConsole {
#[template_child] #[template_child]
@ -23,6 +23,9 @@ mod imp {
pub label: TemplateChild<gtk::Label>, pub label: TemplateChild<gtk::Label>,
pub console: OnceCell<Console>, pub console: OnceCell<Console>,
pub wait_rendering: Cell<usize>, pub wait_rendering: Cell<usize>,
pub shortcuts_inhibited_id: Cell<Option<glib::SignalHandlerId>>,
pub ungrab_shortcut: OnceCell<gtk::ShortcutTrigger>,
pub key_controller: OnceCell<gtk::EventControllerKey>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -44,22 +47,29 @@ mod imp {
fn constructed(&self, obj: &Self::Type) { fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj); self.parent_constructed(obj);
// TODO: implement a custom trigger with only modifiers, ala spice-gtk?
let ungrab = gtk::ShortcutTrigger::parse_string("<ctrl><alt>g").unwrap();
self.ungrab_shortcut.set(ungrab).unwrap();
let ec = gtk::EventControllerKey::new(); let ec = gtk::EventControllerKey::new();
ec.set_propagation_phase(gtk::PropagationPhase::Capture); ec.set_propagation_phase(gtk::PropagationPhase::Capture);
self.area.add_controller(&ec); self.area.add_controller(&ec);
ec.connect_key_pressed(clone!(@weak obj => move |_, _keyval, keycode, _state| { ec.connect_key_pressed(
let c = obj.qemu_console(); clone!(@weak obj => @default-panic, move |_, _keyval, keycode, _state| {
if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) { let c = obj.qemu_console();
let _ = c.keyboard.press(*qnum as u32); if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) {
} let _ = c.keyboard.press(*qnum as u32);
glib::signal::Inhibit(true) }
})); glib::signal::Inhibit(true)
}),
);
ec.connect_key_released(clone!(@weak obj => move |_, _keyval, keycode, _state| { ec.connect_key_released(clone!(@weak obj => move |_, _keyval, keycode, _state| {
let c = obj.qemu_console(); let c = obj.qemu_console();
if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) { if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) {
let _ = c.keyboard.release(*qnum as u32); let _ = c.keyboard.release(*qnum as u32);
} }
})); }));
self.key_controller.set(ec).unwrap();
let ec = gtk::EventControllerMotion::new(); let ec = gtk::EventControllerMotion::new();
self.area.add_controller(&ec); self.area.add_controller(&ec);
@ -70,11 +80,59 @@ mod imp {
let ec = gtk::GestureClick::new(); let ec = gtk::GestureClick::new();
ec.set_button(0); ec.set_button(0);
self.area.add_controller(&ec); self.area.add_controller(&ec);
ec.connect_pressed(clone!(@weak obj => move |gesture, _n_press, x, y| { ec.connect_pressed(clone!(@weak obj => @default-panic, move |gesture, _n_press, x, y| {
let c = obj.qemu_console(); let c = obj.qemu_console();
let button = from_gdk_button(gesture.get_current_button()); let button = from_gdk_button(gesture.get_current_button());
obj.motion(x, y); obj.motion(x, y);
let _ = c.mouse.press(button); let _ = c.mouse.press(button);
let priv_ = imp::QemuConsole::from_instance(&obj);
if let Some(toplevel) = obj.get_toplevel() {
if !toplevel.get_property_shortcuts_inhibited() {
toplevel.inhibit_system_shortcuts::<gdk::ButtonEvent>(None);
let ec = gtk::EventControllerKey::new();
ec.set_propagation_phase(gtk::PropagationPhase::Capture);
ec.connect_key_pressed(clone!(@weak obj => @default-panic, move |ec, keyval, keycode, state| {
let priv_ = imp::QemuConsole::from_instance(&obj);
if let Some(ref e) = ec.get_current_event() {
if priv_.ungrab_shortcut.get().unwrap().trigger(e, false) == gdk::KeyMatch::Exact {
//widget.remove_controller(ec); here crashes badly
glib::idle_add_local(clone!(@weak ec => @default-panic, move || {
if let Some(widget) = ec.get_widget() {
widget.remove_controller(&ec);
}
glib::Continue(false)
}));
} else {
priv_.key_controller.get().unwrap().emit_by_name("key-pressed", &[&*keyval, &keycode, &state]).unwrap();
}
}
glib::signal::Inhibit(true)
}));
ec.connect_key_released(clone!(@weak obj => @default-panic, move |_ec, keyval, keycode, state| {
let priv_ = imp::QemuConsole::from_instance(&obj);
priv_.key_controller.get().unwrap().emit_by_name("key-released", &[&*keyval, &keycode, &state]).unwrap();
}));
if let Some(root) = priv_.area.get_root() {
root.add_controller(&ec);
}
let id = toplevel.connect_property_shortcuts_inhibited_notify(clone!(@weak obj => @default-panic, move |toplevel| {
let inhibited = toplevel.get_property_shortcuts_inhibited();
debug!("shortcuts-inhibited: {}", inhibited);
if !inhibited {
let priv_ = imp::QemuConsole::from_instance(&obj);
let id = priv_.shortcuts_inhibited_id.take();
toplevel.disconnect(id.unwrap());
}
}));
priv_.shortcuts_inhibited_id.set(Some(id));
}
}
priv_.area.grab_focus();
})); }));
ec.connect_released(clone!(@weak obj => move |gesture, _n_press, x, y| { ec.connect_released(clone!(@weak obj => move |gesture, _n_press, x, y| {
let c = obj.qemu_console(); let c = obj.qemu_console();
@ -85,7 +143,7 @@ mod imp {
let ec = gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES); let ec = gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES);
self.area.add_controller(&ec); self.area.add_controller(&ec);
ec.connect_scroll(clone!(@weak obj => move |_, _dx, dy| { ec.connect_scroll(clone!(@weak obj => @default-panic, move |_, _dx, dy| {
let c = obj.qemu_console(); let c = obj.qemu_console();
let button = if dy >= 1.0 { let button = if dy >= 1.0 {
@ -145,7 +203,7 @@ impl QemuConsole {
.expect("Failed to listen to the console"); .expect("Failed to listen to the console");
priv_ priv_
.area .area
.connect_render(clone!(@weak self as obj => move |_, _| { .connect_render(clone!(@weak self as obj => @default-panic, move |_, _| {
let priv_ = imp::QemuConsole::from_instance(&obj); let priv_ = imp::QemuConsole::from_instance(&obj);
let wait_rendering = priv_.wait_rendering.get(); let wait_rendering = priv_.wait_rendering.get();
if wait_rendering > 0 { if wait_rendering > 0 {
@ -158,7 +216,7 @@ impl QemuConsole {
})); }));
rx.attach( rx.attach(
None, None,
clone!(@weak self as con => move |t| { clone!(@weak self as con => @default-panic, move |t| {
let priv_ = imp::QemuConsole::from_instance(&con); let priv_ = imp::QemuConsole::from_instance(&con);
debug!("Console event: {:?}", t); debug!("Console event: {:?}", t);
match t { match t {
@ -195,7 +253,7 @@ impl QemuConsole {
let cur = gdk::Cursor::from_texture(&tex, hot_x, hot_y, None); let cur = gdk::Cursor::from_texture(&tex, hot_x, hot_y, None);
priv_.area.set_cursor(Some(&cur)); priv_.area.set_cursor(Some(&cur));
} }
t => { dbg!(t); } _t => { }
} }
Continue(true) Continue(true)
}), }),
@ -203,6 +261,16 @@ impl QemuConsole {
priv_.console.set(console).unwrap(); priv_.console.set(console).unwrap();
} }
fn get_toplevel(&self) -> Option<gdk::Toplevel> {
let priv_ = imp::QemuConsole::from_instance(self);
priv_
.area
.get_root()
.and_then(|r| r.get_native())
.and_then(|n| n.get_surface())
.and_then(|s| s.downcast::<gdk::Toplevel>().ok())
}
fn qemu_console(&self) -> &Console { fn qemu_console(&self) -> &Console {
let priv_ = imp::QemuConsole::from_instance(self); let priv_ = imp::QemuConsole::from_instance(self);
priv_.console.get().expect("Console is not yet set!") priv_.console.get().expect("Console is not yet set!")