summaryrefslogtreecommitdiff
path: root/src/utilities/bindingManager.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/utilities/bindingManager.vala')
-rw-r--r--src/utilities/bindingManager.vala428
1 files changed, 428 insertions, 0 deletions
diff --git a/src/utilities/bindingManager.vala b/src/utilities/bindingManager.vala
new file mode 100644
index 0000000..ac5a8fb
--- /dev/null
+++ b/src/utilities/bindingManager.vala
@@ -0,0 +1,428 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// Globally binds key stroke to given ID's. When one of the bound
+/// strokes is invoked, a signal with the according ID is emitted.
+/////////////////////////////////////////////////////////////////////////
+
+public class BindingManager : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a stored binding is invoked. The according ID is
+ /// passed as argument.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_press(string id);
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list storing bindings, which are invoked even if Gnome-Pie
+ /// doesn't have the current focus
+ /////////////////////////////////////////////////////////////////////
+
+ private Gee.List<Keybinding> bindings = new Gee.ArrayList<Keybinding>();
+
+ /////////////////////////////////////////////////////////////////////
+ /// Ignored modifier masks, used to grab all keys even if these locks
+ /// are active.
+ /////////////////////////////////////////////////////////////////////
+
+ private static uint[] lock_modifiers = {
+ 0,
+ Gdk.ModifierType.MOD2_MASK,
+ Gdk.ModifierType.LOCK_MASK,
+ Gdk.ModifierType.MOD5_MASK,
+
+ Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK,
+ Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.MOD5_MASK,
+ Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK,
+
+ Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK
+ };
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some variables to remember which delayed binding was delayed.
+ /// When the delay passes without another event indicating that the
+ /// Trigger was released, the stored binding will be activated.
+ /////////////////////////////////////////////////////////////////////
+
+ private uint32 delayed_count = 0;
+ private X.Event? delayed_event = null;
+ private Keybinding? delayed_binding = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper class to store keybinding
+ /////////////////////////////////////////////////////////////////////
+
+ private class Keybinding {
+
+ public Keybinding(Trigger trigger, string id) {
+ this.trigger = trigger;
+ this.id = id;
+ }
+
+ public Trigger trigger { get; set; }
+ public string id { get; set; }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor adds the event filter to the root window.
+ /////////////////////////////////////////////////////////////////////
+
+ public BindingManager() {
+ // init filter to retrieve X.Events
+ Gdk.Window rootwin = Gdk.get_default_root_window();
+ if(rootwin != null) {
+ rootwin.add_filter(event_filter);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Binds the ID to the given accelerator.
+ /////////////////////////////////////////////////////////////////////
+
+ public void bind(Trigger trigger, string id) {
+ if (trigger.key_code != 0) {
+ unowned X.Display display = Gdk.X11.get_default_xdisplay();
+ X.ID xid = Gdk.X11.get_default_root_xwindow();
+
+ Gdk.error_trap_push();
+
+ // if bound to super key we need to grab MOD4 instead
+ // (for whatever reason...)
+ var modifiers = prepare_modifiers(trigger.modifiers);
+
+ foreach(uint lock_modifier in lock_modifiers) {
+ if (trigger.with_mouse) {
+ display.grab_button(trigger.key_code, modifiers|lock_modifier, xid, false,
+ X.EventMask.ButtonPressMask | X.EventMask.ButtonReleaseMask,
+ X.GrabMode.Async, X.GrabMode.Async, xid, 0);
+ } else {
+ display.grab_key(trigger.key_code, modifiers|lock_modifier,
+ xid, false, X.GrabMode.Async, X.GrabMode.Async);
+ }
+ }
+
+ Gdk.flush();
+ Keybinding binding = new Keybinding(trigger, id);
+ bindings.add(binding);
+ display.flush();
+ } else {
+ //no key_code: just add the bindind to the list to save optional trigger parameters
+ Keybinding binding = new Keybinding(trigger, id);
+ bindings.add(binding);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Unbinds the accelerator of the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void unbind(string id) {
+ foreach (var binding in bindings) {
+ if (id == binding.id) {
+ if (binding.trigger.key_code == 0) {
+ //no key_code: just remove the bindind from the list
+ bindings.remove(binding);
+ return;
+ }
+ break;
+ }
+ }
+
+ unowned X.Display display = Gdk.X11.get_default_xdisplay();
+ X.ID xid = Gdk.X11.get_default_root_xwindow();
+
+ Gee.List<Keybinding> remove_bindings = new Gee.ArrayList<Keybinding>();
+ foreach(var binding in bindings) {
+ if(id == binding.id) {
+
+ // if bound to super key we need to ungrab MOD4 instead
+ // (for whatever reason...)
+ var modifiers = prepare_modifiers(binding.trigger.modifiers);
+
+ foreach(uint lock_modifier in lock_modifiers) {
+ if (binding.trigger.with_mouse) {
+ display.ungrab_button(binding.trigger.key_code, modifiers|lock_modifier, xid);
+ } else {
+ display.ungrab_key(binding.trigger.key_code, modifiers|lock_modifier, xid);
+ }
+ }
+ remove_bindings.add(binding);
+ }
+ }
+
+ bindings.remove_all(remove_bindings);
+ display.flush();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human readable accelerator for the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_accelerator_label_of(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.label_with_specials;
+ }
+ }
+
+ return _("Not bound");
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the accelerator to which the given ID is bound.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_accelerator_of(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.name;
+ }
+ }
+
+ return "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID is in turbo mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_turbo(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.turbo;
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID opens centered.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_centered(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.centered;
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID is in warp mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_warp(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.warp;
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID is auto shaped
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_auto_shape(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return (binding.trigger.shape == 0);
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the prefered pie shape number
+ /////////////////////////////////////////////////////////////////////
+
+ public int get_shape_number(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ if (binding.trigger.shape == 0)
+ break; //return default if auto-shaped
+ return binding.trigger.shape; //use selected shape
+ }
+ }
+
+ return 5; //default= full pie
+ }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the name ID of the Pie bound to the given Trigger.
+ /// Returns "" if there is nothing bound to this trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_assigned_id(Trigger trigger) {
+ var second = Trigger.remove_optional(trigger.name);
+ if (second != "") {
+ foreach (var binding in bindings) {
+ var first = Trigger.remove_optional(binding.trigger.name);
+ if (first == second) {
+ return binding.id;
+ }
+ }
+ }
+ return "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// If SUPER_MASK is set in the input, it will be replaced with
+ /// MOD4_MASK. For some reason this is required to listen for key
+ /// presses of the super button....
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.ModifierType prepare_modifiers(Gdk.ModifierType mods) {
+ if ((mods & Gdk.ModifierType.SUPER_MASK) > 0) {
+ mods |= Gdk.ModifierType.MOD4_MASK;
+ mods = mods & ~ Gdk.ModifierType.SUPER_MASK;
+ }
+
+ return mods & ~lock_modifiers[7];
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Event filter method needed to fetch X.Events.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event) {
+
+ #if VALA_0_16 || VALA_0_17
+ X.Event* xevent = (X.Event*) gdk_xevent;
+ #else
+ void* pointer = &gdk_xevent;
+ X.Event* xevent = (X.Event*) pointer;
+ #endif
+
+ if(xevent->type == X.EventType.KeyPress) {
+ foreach(var binding in bindings) {
+
+ // remove NumLock, CapsLock and ScrollLock from key state
+ var event_mods = prepare_modifiers((Gdk.ModifierType)xevent.xkey.state);
+ var bound_mods = prepare_modifiers(binding.trigger.modifiers);
+
+ if(xevent->xkey.keycode == binding.trigger.key_code &&
+ event_mods == bound_mods) {
+
+ if (binding.trigger.delayed) {
+ this.activate_delayed(binding, *xevent);
+ } else {
+ on_press(binding.id);
+ }
+ }
+ }
+ }
+ else if(xevent->type == X.EventType.ButtonPress) {
+ foreach(var binding in bindings) {
+
+ // remove NumLock, CapsLock and ScrollLock from key state
+ var event_mods = prepare_modifiers((Gdk.ModifierType)xevent.xbutton.state);
+ var bound_mods = prepare_modifiers(binding.trigger.modifiers);
+
+ if(xevent->xbutton.button == binding.trigger.key_code &&
+ event_mods == bound_mods) {
+
+ if (binding.trigger.delayed) {
+ this.activate_delayed(binding, *xevent);
+ } else {
+ on_press(binding.id);
+ }
+ }
+ }
+ }
+ else if(xevent->type == X.EventType.ButtonRelease || xevent->type == X.EventType.KeyRelease) {
+ this.activate_delayed(null, *xevent);
+ }
+
+ return Gdk.FilterReturn.CONTINUE;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This method is always called when a trigger is activated which is
+ /// delayed. Therefore on_press() is only emitted, when this method
+ /// is not called again within 300 milliseconds. Else a fake event is
+ /// sent in order to simulate the actual key which has been pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void activate_delayed(Keybinding? binding , X.Event event) {
+ // increase event count, so any waiting event will realize that
+ // something happened in the meantime
+ var current_count = ++this.delayed_count;
+
+ if (binding == null && this.delayed_event != null) {
+ // if the trigger is released and an event is currently waiting
+ // simulate that the trigger has been pressed without any inter-
+ // ference of Gnome-Pie
+ unowned X.Display display = Gdk.X11.get_default_xdisplay();
+
+ // unbind the trigger, else we'll capture that event again ;)
+ unbind(delayed_binding.id);
+
+ if (this.delayed_binding.trigger.with_mouse) {
+ // simulate mouse click
+ XTest.fake_button_event(display, this.delayed_event.xbutton.button, true, 0);
+ display.flush();
+
+ XTest.fake_button_event(display, this.delayed_event.xbutton.button, false, 0);
+ display.flush();
+
+ } else {
+ // simulate key press
+ XTest.fake_key_event(display, this.delayed_event.xkey.keycode, true, 0);
+ display.flush();
+
+ XTest.fake_key_event(display, this.delayed_event.xkey.keycode, false, 0);
+ display.flush();
+ }
+
+ // bind it again
+ bind(delayed_binding.trigger, delayed_binding.id);
+
+ this.delayed_binding = null;
+ this.delayed_event = null;
+
+ } else if (binding != null) {
+ // if the trigger has been pressed, store it and wait for any interuption
+ // within the next 300 milliseconds
+ this.delayed_event = event;
+ this.delayed_binding = binding;
+
+ Timeout.add(300, () => {
+ // if nothing has been pressed in the meantime
+ if (current_count == this.delayed_count) {
+ this.delayed_binding = null;
+ this.delayed_event = null;
+ on_press(binding.id);
+ }
+ return false;
+ });
+ }
+ }
+}
+
+}