diff options
Diffstat (limited to 'src/utilities')
-rw-r--r-- | src/utilities/animatedValue.vala | 197 | ||||
-rw-r--r-- | src/utilities/archiveReader.vala | 123 | ||||
-rw-r--r-- | src/utilities/archiveWriter.vala | 139 | ||||
-rw-r--r-- | src/utilities/bindingManager.vala | 428 | ||||
-rw-r--r-- | src/utilities/color.vala | 327 | ||||
-rw-r--r-- | src/utilities/config.vala | 239 | ||||
-rw-r--r-- | src/utilities/focusGrabber.vala | 97 | ||||
-rw-r--r-- | src/utilities/key.vala | 161 | ||||
-rw-r--r-- | src/utilities/logger.vala | 270 | ||||
-rw-r--r-- | src/utilities/paths.vala | 286 | ||||
-rw-r--r-- | src/utilities/trigger.vala | 357 |
11 files changed, 2624 insertions, 0 deletions
diff --git a/src/utilities/animatedValue.vala b/src/utilities/animatedValue.vala new file mode 100644 index 0000000..79be155 --- /dev/null +++ b/src/utilities/animatedValue.vala @@ -0,0 +1,197 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// A class which interpolates smoothly between to given values. +/// Duration and interpolation mode can be specified. +///////////////////////////////////////////////////////////////////////// + +public class AnimatedValue : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// The direction of the interpolation. + ///////////////////////////////////////////////////////////////////// + + public enum Direction { IN, OUT, IN_OUT, OUT_IN } + + ///////////////////////////////////////////////////////////////////// + /// Type of the interpolation, linear or cubic. + ///////////////////////////////////////////////////////////////////// + + private enum Type { LINEAR, CUBIC } + + ///////////////////////////////////////////////////////////////////// + /// Current value, interpolated. + ///////////////////////////////////////////////////////////////////// + + public double val { get; private set; } + + ///////////////////////////////////////////////////////////////////// + /// Starting value of the interpolation. + ///////////////////////////////////////////////////////////////////// + + public double start { get; private set; default=0.0; } + + ///////////////////////////////////////////////////////////////////// + /// Final value of the interpolation. + ///////////////////////////////////////////////////////////////////// + + public double end { get; private set; default=0.0; } + + ///////////////////////////////////////////////////////////////////// + /// The current state. In range 0 .. 1 + ///////////////////////////////////////////////////////////////////// + + private double state = 0.0; + + ///////////////////////////////////////////////////////////////////// + /// Duration of the interpolation. Should be in the same unit as + /// taken for the update() method. + ///////////////////////////////////////////////////////////////////// + + private double duration = 0.0; + + ///////////////////////////////////////////////////////////////////// + /// The amount of over-shooting of the cubicly interpolated value. + ///////////////////////////////////////////////////////////////////// + + private double multiplier = 0.0; + + ///////////////////////////////////////////////////////////////////// + /// Type of the interpolation, linear or cubic. + ///////////////////////////////////////////////////////////////////// + + private Type type = Type.LINEAR; + + ///////////////////////////////////////////////////////////////////// + /// The direction of the interpolation. + ///////////////////////////////////////////////////////////////////// + + private Direction direction = Direction.IN; + + ///////////////////////////////////////////////////////////////////// + /// Creates a new linearly interpolated value. + ///////////////////////////////////////////////////////////////////// + + public AnimatedValue.linear(double start, double end, double duration) { + this.val = start; + this.start = start; + this.end = end; + this.duration = duration; + } + + ///////////////////////////////////////////////////////////////////// + /// Creates a new cubicly interpolated value. + ///////////////////////////////////////////////////////////////////// + + public AnimatedValue.cubic(Direction direction, double start, double end, double duration, double multiplier = 0) { + this.val = start; + this.start = start; + this.end = end; + this.duration = duration; + this.direction = direction; + this.type = Type.CUBIC; + this.multiplier = multiplier; + } + + ///////////////////////////////////////////////////////////////////// + /// Resets the final value of the interpolation to a new value. The + /// current state is taken for the beginning from now. + ///////////////////////////////////////////////////////////////////// + + public void reset_target(double end, double duration) { + this.end = end; + this.duration = duration; + this.start = this.val; + + if (duration == 0.0) { + this.val = end; + this.state = 1.0; + } else { + this.state = 0.0; + } + } + + ///////////////////////////////////////////////////////////////////// + /// Updates the interpolated value according to it's type. + ///////////////////////////////////////////////////////////////////// + + public void update(double time) { + this.state += time/this.duration; + + if (this.state < 1) { + + switch (this.type) { + case Type.LINEAR: + this.val = update_linear(); + break; + case Type.CUBIC: + switch (this.direction) { + case Direction.IN: + this.val = update_ease_in(); + return; + case Direction.OUT: + this.val = update_ease_out(); + return; + case Direction.IN_OUT: + this.val = update_ease_in_out(); + return; + case Direction.OUT_IN: + this.val = update_ease_out_in(); + return; + } + break; + } + + } else if (this.val != this.end) { + this.val = this.end; + } + } + + ///////////////////////////////////////////////////////////////////// + /// The following equations are based on Robert Penner's easing + /// equations. See (http://www.robertpenner.com/easing/) and their + /// adaption by Zeh Fernando, Nate Chatellier and Arthur Debert for + /// the Tweener class. See (http://code.google.com/p/tweener/). + ///////////////////////////////////////////////////////////////////// + + private double update_linear(double t = this.state, double s = this.start, double e = this.end) { + return (s + t*(e - s)); + } + + private double update_ease_in(double t = this.state, double s = this.start, double e = this.end) { + return (s + (t*t*((multiplier+1)*t-multiplier))*(e - s)); + } + + private double update_ease_out(double t = this.state, double s = this.start, double e = this.end) { + return (s + ((t-1) * (t-1) * ((multiplier+1)*(t-1)+multiplier) + 1) * (e - s)); + } + + private double update_ease_in_out(double t = this.state, double s = this.start, double e = this.end) { + if (this.state < 0.5) return update_ease_in(t*2, s, e - (e-s)*0.5); + else return update_ease_out(t*2-1, s + (e-s)*0.5, e); + } + + private double update_ease_out_in(double t = this.state, double s = this.start, double e = this.end) { + if (this.state < 0.5) return update_ease_out(t*2, s, e - (e-s)*0.5); + else return update_ease_in(t*2-1, s + (e-s)*0.5, e); + } +} + +} diff --git a/src/utilities/archiveReader.vala b/src/utilities/archiveReader.vala new file mode 100644 index 0000000..16e4541 --- /dev/null +++ b/src/utilities/archiveReader.vala @@ -0,0 +1,123 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// This class can be used to unpack an archive to a directory. +///////////////////////////////////////////////////////////////////////// + +public class ArchiveReader : GLib.Object { + + private Archive.Read archive; + private Archive.WriteDisk writer; + + ///////////////////////////////////////////////////////////////////// + /// Constructs a new ArchiveReader + ///////////////////////////////////////////////////////////////////// + + public ArchiveReader() { + this.archive = new Archive.Read(); + this.archive.support_format_all(); + this.archive.support_filter_all(); + + this.writer = new Archive.WriteDisk(); + this.writer.set_options( + Archive.ExtractFlags.TIME | + Archive.ExtractFlags.PERM | + Archive.ExtractFlags.ACL | + Archive.ExtractFlags.FFLAGS + ); + this.writer.set_standard_lookup(); + } + + ///////////////////////////////////////////////////////////////////// + /// Call this once after you created the ArchiveReader. Pass the + /// path to the target archive location. + ///////////////////////////////////////////////////////////////////// + + public bool open(string path) { + return this.archive.open_filename(path, 10240) == Archive.Result.OK; + } + + ///////////////////////////////////////////////////////////////////// + /// Extracts all files from the previously opened archive. + ///////////////////////////////////////////////////////////////////// + + public bool extract_to(string directory) { + while (true) { + unowned Archive.Entry entry; + var r = this.archive.next_header(out entry); + + if (r == Archive.Result.EOF) { + break; + } + + if (r != Archive.Result.OK) { + warning(this.archive.error_string()); + return false; + } + + entry.set_pathname(directory + "/" + entry.pathname()); + + r = this.writer.write_header(entry); + + if (r != Archive.Result.OK) { + warning(this.writer.error_string()); + return false; + } + + if (entry.size() > 0) { + while (true) { + size_t offset, size; + void *buff; + + r = this.archive.read_data_block(out buff, out size, out offset); + if (r == Archive.Result.EOF) { + break; + } + + if (r != Archive.Result.OK) { + warning(this.archive.error_string()); + return false; + } + + this.writer.write_data_block(buff, size, offset); + } + } + + r = this.writer.finish_entry(); + + if (r != Archive.Result.OK) { + warning(this.writer.error_string()); + return false; + } + } + return true; + } + + ///////////////////////////////////////////////////////////////////// + /// When all files have been added, close the directory again. + ///////////////////////////////////////////////////////////////////// + + public void close() { + this.archive.close(); + this.writer.close(); + } +} + +} diff --git a/src/utilities/archiveWriter.vala b/src/utilities/archiveWriter.vala new file mode 100644 index 0000000..92bd31b --- /dev/null +++ b/src/utilities/archiveWriter.vala @@ -0,0 +1,139 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// This class can be used to pack a directory of files recursively into +/// a *.tar.gz archive. +///////////////////////////////////////////////////////////////////////// + +public class ArchiveWriter : GLib.Object { + + private Archive.Write archive; + + ///////////////////////////////////////////////////////////////////// + /// Constructs a new ArchiveWriter + ///////////////////////////////////////////////////////////////////// + + public ArchiveWriter() { + this.archive = new Archive.Write(); + this.archive.add_filter_gzip(); + this.archive.set_format_pax_restricted(); + + } + + ///////////////////////////////////////////////////////////////////// + /// Call this once after you created the ArchiveWriter. Pass the + /// path to the target archive location. + ///////////////////////////////////////////////////////////////////// + + public bool open(string path) { + return this.archive.open_filename(path) == Archive.Result.OK; + } + + ///////////////////////////////////////////////////////////////////// + /// Adds all files of a given directory to the previously opened + /// archive. + ///////////////////////////////////////////////////////////////////// + + public bool add(string directory) { + return add_directory(directory, directory); + } + + ///////////////////////////////////////////////////////////////////// + /// When all files have been added, close the directory again. + ///////////////////////////////////////////////////////////////////// + + public void close() { + this.archive.close(); + } + + ///////////////////////////////////////////////////////////////////// + /// Private helper function which traveres a directory recursively. + ///////////////////////////////////////////////////////////////////// + + private bool add_directory(string directory, string relative_to) { + try { + var d = Dir.open(directory); + string name; + while ((name = d.read_name()) != null) { + string path = Path.build_filename(directory, name); + if (FileUtils.test(path, FileTest.IS_DIR)) { + if (!add_directory(path, relative_to)) { + return false; + } + + } else if (FileUtils.test(path, FileTest.IS_REGULAR)) { + if (!add_file(path, relative_to)) { + return false; + } + + } else { + warning("Packaging theme: Ignoring irregular file " + name); + } + } + } catch (Error e) { + warning (e.message); + return false; + } + + return true; + + } + + ///////////////////////////////////////////////////////////////////// + /// Private halper which adds a file to the archive. + ///////////////////////////////////////////////////////////////////// + + public bool add_file(string path, string relative_to) { + var entry = new Archive.Entry(); + entry.set_pathname(path.replace(relative_to, "")); + + Posix.Stat st; + Posix.stat(path, out st); + entry.copy_stat(st); + entry.set_size(st.st_size); + + if (this.archive.write_header(entry) == Archive.Result.OK) { + try { + var reader = File.new_for_path(path).read(); + uint8 buffer[4096]; + + var len = reader.read(buffer); + + while(len > 0) { + this.archive.write_data(buffer, len); + len = reader.read(buffer); + } + + this.archive.finish_entry(); + } catch (Error e) { + warning (e.message); + return false; + } + + } else { + warning("Failed to include file " + path + " into archive"); + return false; + } + + return true; + } +} + +} 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; + }); + } + } +} + +} diff --git a/src/utilities/color.vala b/src/utilities/color.vala new file mode 100644 index 0000000..a681e02 --- /dev/null +++ b/src/utilities/color.vala @@ -0,0 +1,327 @@ +///////////////////////////////////////////////////////////////////////// +// 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/>. +///////////////////////////////////////////////////////////////////////// + +using GLib.Math; + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A Color class with full rgb/hsv support +/// and some useful utility methods. +///////////////////////////////////////////////////////////////////////// + +public class Color: GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// Private members, storing the actual color information. + /// In range 0 .. 1 + ///////////////////////////////////////////////////////////////////// + + private float _r; + private float _g; + private float _b; + private float _a; + + ///////////////////////////////////////////////////////////////////// + /// Creates a white Color. + ///////////////////////////////////////////////////////////////////// + + public Color() { + Color.from_rgb(1.0f, 1.0f, 1.0f); + } + + ///////////////////////////////////////////////////////////////////// + /// Creates a solid color with the given RGB values. + ///////////////////////////////////////////////////////////////////// + + public Color.from_rgb(float red, float green, float blue) { + Color.from_rgba(red, green, blue, 1.0f); + } + + ///////////////////////////////////////////////////////////////////// + /// Creates a translucient color with the given RGBA values. + ///////////////////////////////////////////////////////////////////// + + public Color.from_rgba(float red, float green, float blue, float alpha) { + r = red; + g = green; + b = blue; + a = alpha; + } + + ///////////////////////////////////////////////////////////////////// + /// Creates a color from the given Gdk.Color + ///////////////////////////////////////////////////////////////////// + + public Color.from_gdk(Gdk.RGBA color) { + Color.from_rgba( + (float)color.red, + (float)color.green, + (float)color.blue, + (float)color.alpha + ); + } + + ///////////////////////////////////////////////////////////////////// + /// Creates a color from a given widget style + ///////////////////////////////////////////////////////////////////// + + public Color.from_widget_style(Gtk.Widget widget, string style_name) { + var ctx = widget.get_style_context(); + Gdk.RGBA color; + if (!ctx.lookup_color(style_name, out color)) { + warning("Failed to get style color for widget style \"" + style_name + "\"!"); + } + Color.from_gdk(color); + } + + ///////////////////////////////////////////////////////////////////// + /// Creates a color, parsed from a string, such as #22EE33 + ///////////////////////////////////////////////////////////////////// + + public Color.from_string(string hex_string) { + var color = Gdk.RGBA(); + color.parse(hex_string); + Color.from_gdk(color); + } + + ///////////////////////////////////////////////////////////////////// + /// Gets the main color from an Image. Code from Unity. + ///////////////////////////////////////////////////////////////////// + + public Color.from_icon(Image icon) { + unowned uchar[] data = icon.surface.get_data(); + + uint width = icon.surface.get_width(); + uint height = icon.surface.get_height(); + uint row_bytes = icon.surface.get_stride(); + + double total = 0.0; + double rtotal = 0.0; + double gtotal = 0.0; + double btotal = 0.0; + + for (uint i = 0; i < width; ++i) { + for (uint j = 0; j < height; ++j) { + uint pixel = j * row_bytes + i * 4; + double b = data[pixel + 0]/255.0; + double g = data[pixel + 1]/255.0; + double r = data[pixel + 2]/255.0; + double a = data[pixel + 3]/255.0; + + double saturation = (fmax (r, fmax (g, b)) - fmin (r, fmin (g, b))); + double relevance = 0.1 + 0.9 * a * saturation; + + rtotal += (r * relevance); + gtotal += (g * relevance); + btotal += (b * relevance); + + total += relevance; + } + } + + Color.from_rgb((float)(rtotal/total), (float)(gtotal/total), (float)(btotal/total)); + + if (s > 0.15f) s = 0.65f; + + v = 1.0f; + } + + ///////////////////////////////////////////////////////////////////// + /// Returns this color as its hex representation. + ///////////////////////////////////////////////////////////////////// + + public string to_hex_string() { + return "#%02X%02X%02X".printf((int)(_r*255), (int)(_g*255), (int)(_b*255)); + } + + ///////////////////////////////////////////////////////////////////// + /// The reddish part of the color. + ///////////////////////////////////////////////////////////////////// + + public float r { + get { + return _r; + } + set { + if (value > 1.0f) _r = 1.0f; + else if (value < 0.0f) _r = 0.0f; + else _r = value; + } + } + + ///////////////////////////////////////////////////////////////////// + /// The greenish part of the color. + ///////////////////////////////////////////////////////////////////// + + public float g { + get { + return _g; + } + set { + if (value > 1.0f) _g = 1.0f; + else if (value < 0.0f) _g = 0.0f; + else _g = value; + } + } + + ///////////////////////////////////////////////////////////////////// + /// The blueish part of the color. + ///////////////////////////////////////////////////////////////////// + + public float b { + get { + return _b; + } + set { + if (value > 1.0f) _b = 1.0f; + else if (value < 0.0f) _b = 0.0f; + else _b = value; + } + } + + ///////////////////////////////////////////////////////////////////// + /// The transparency of the color. + ///////////////////////////////////////////////////////////////////// + + public float a { + get { + return _a; + } + set { + if (value > 1.0f) _a = 1.0f; + else if (value < 0.0f) _a = 0.0f; + else _a = value; + } + } + + ///////////////////////////////////////////////////////////////////// + /// The hue of the color. + ///////////////////////////////////////////////////////////////////// + + public float h { + get { + if (s > 0.0f) { + float maxi = fmaxf(fmaxf(r, g), b); + float mini = fminf(fminf(r, g), b); + + if (maxi == r) + return fmodf(60.0f*((g-b)/(maxi-mini))+360.0f, 360.0f); + else if (maxi == g) + return fmodf(60.0f*(2.0f + (b-r)/(maxi-mini))+360.0f, 360.0f); + else + return fmodf(60.0f*(4.0f + (r-g)/(maxi-mini))+360.0f, 360.0f); + } + else return 0.0f; + } + set { + setHSV(value, s, v); + } + } + + ///////////////////////////////////////////////////////////////////// + /// The saturation of the color. + ///////////////////////////////////////////////////////////////////// + + public float s { + get { + if (v == 0.0f) return 0.0f; + else return ((v-fminf(fminf(r, g), b)) / v); + } + set { + if (value > 1.0f) setHSV(h, 1.0f, v); + else if (value < 0.0f) setHSV(h, 0.0f, v); + else setHSV(h, value, v); + } + } + + ///////////////////////////////////////////////////////////////////// + /// The value of the color. + ///////////////////////////////////////////////////////////////////// + + public float v { + get { + return fmaxf(fmaxf(r, g), b); + } + set { + if (value > 1) setHSV(h, s, 1.0f); + else if (value < 0) setHSV(h, s, 0.0f); + else setHSV(h, s, value); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Inverts the color. + ///////////////////////////////////////////////////////////////////// + + public void invert() { + h += 180.0f; + v = 1.0f - v; + } + + ///////////////////////////////////////////////////////////////////// + /// Private member, used to apply color changes. + ///////////////////////////////////////////////////////////////////// + + private void setHSV(float hue, float saturation, float val) { + if(saturation == 0) { + r = val; + g = val; + b = val; + return; + } + hue = fmodf(hue, 360); + hue /= 60; + int i = (int) floorf(hue); + float f = hue - i; + + switch(i) { + case 0: + r = val; + g = val * (1.0f - saturation * (1.0f - f)); + b = val * (1.0f - saturation); + break; + case 1: + r = val * (1.0f - saturation * f); + g = val; + b = val * (1.0f - saturation); + break; + case 2: + r = val * (1.0f - saturation); + g = val; + b = val * (1.0f - saturation * (1.0f - f)); + break; + case 3: + r = val * (1.0f - saturation); + g = val * (1.0f - saturation * f); + b = val; + break; + case 4: + r = val * (1.0f - saturation * (1.0f - f)); + g = val * (1.0f - saturation); + b = val; + break; + default: + r = val; + g = val * (1.0f - saturation); + b = val * (1.0f - saturation * f); + break; + } + } +} + +} diff --git a/src/utilities/config.vala b/src/utilities/config.vala new file mode 100644 index 0000000..74bbcbb --- /dev/null +++ b/src/utilities/config.vala @@ -0,0 +1,239 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// A singleton class for storing global settings. These settings can +/// be loaded from and saved to an XML file. +///////////////////////////////////////////////////////////////////////// + +public class Config : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// The singleton instance of this class. + ///////////////////////////////////////////////////////////////////// + + private static Config _instance = null; + + ///////////////////////////////////////////////////////////////////// + /// Returns the singleton instance. + ///////////////////////////////////////////////////////////////////// + + public static Config global { + get { + if (_instance == null) { + _instance = new Config(); + _instance.load(); + } + return _instance; + } + private set { + _instance = value; + } + } + + ///////////////////////////////////////////////////////////////////// + /// All settings variables. + ///////////////////////////////////////////////////////////////////// + + public Theme theme { get; set; } + public double refresh_rate { get; set; default = 60.0; } + public double global_scale { get; set; default = 1.0; } + public int activation_range { get; set; default = 200; } + public int max_visible_slices { get; set; default = 24; } + public bool show_indicator { get; set; default = true; } + public bool show_captions { get; set; default = false; } + public bool search_by_string { get; set; default = true; } + public bool auto_start { get; set; default = false; } + public int showed_news { get; set; default = 0; } + public Gee.ArrayList<Theme?> themes { get; private set; } + + ///////////////////////////////////////////////////////////////////// + /// Saves all above variables to a file. + ///////////////////////////////////////////////////////////////////// + + public void save() { + var writer = new Xml.TextWriter.filename(Paths.settings); + writer.start_document("1.0"); + writer.start_element("settings"); + writer.write_attribute("theme", theme.name); + writer.write_attribute("refresh_rate", refresh_rate.to_string()); + writer.write_attribute("global_scale", global_scale.to_string()); + writer.write_attribute("activation_range", activation_range.to_string()); + writer.write_attribute("max_visible_slices", max_visible_slices.to_string()); + writer.write_attribute("show_indicator", show_indicator ? "true" : "false"); + writer.write_attribute("show_captions", show_captions ? "true" : "false"); + writer.write_attribute("search_by_string", search_by_string ? "true" : "false"); + writer.write_attribute("showed_news", showed_news.to_string()); + writer.end_element(); + writer.end_document(); + } + + ///////////////////////////////////////////////////////////////////// + /// Loads all settings variables from a file. + ///////////////////////////////////////////////////////////////////// + + private void load() { + + // check for auto_start filename + this.auto_start = FileUtils.test(Paths.autostart, FileTest.EXISTS); + + // parse the settings file + Xml.Parser.init(); + Xml.Doc* settingsXML = Xml.Parser.parse_file(Paths.settings); + bool error_occrured = false; + string theme_name = ""; + + if (settingsXML != null) { + + Xml.Node* root = settingsXML->get_root_element(); + if (root != null) { + + for (Xml.Attr* attribute = root->properties; attribute != null; attribute = attribute->next) { + string attr_name = attribute->name.down(); + string attr_content = attribute->children->content; + + switch (attr_name) { + case "theme": + theme_name = attr_content; + break; + case "refresh_rate": + refresh_rate = double.parse(attr_content); + break; + case "global_scale": + global_scale = double.parse(attr_content); + global_scale.clamp(0.5, 2.0); + break; + case "activation_range": + activation_range = int.parse(attr_content); + activation_range.clamp(0, 2000); + break; + case "max_visible_slices": + max_visible_slices = int.parse(attr_content); + max_visible_slices.clamp(10, 2000); + break; + case "show_indicator": + show_indicator = bool.parse(attr_content); + break; + case "show_captions": + show_captions = bool.parse(attr_content); + break; + case "search_by_string": + search_by_string = bool.parse(attr_content); + break; + case "showed_news": + showed_news = int.parse(attr_content); + break; + default: + warning("Invalid setting \"" + attr_name + "\" in gnome-pie.conf!"); + break; + } + } + + Xml.Parser.cleanup(); + + } else { + warning("Error loading settings: gnome-pie.conf is empty! Using defaults..."); + error_occrured = true; + } + + delete settingsXML; + + } else { + warning("Error loading settings: gnome-pie.conf not found! Using defaults..."); + error_occrured = true; + } + + load_themes(theme_name); + + if (error_occrured) { + save(); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Registers all themes in the user's and in the global + /// theme directory. + ///////////////////////////////////////////////////////////////////// + + public void load_themes(string current) { + themes = new Gee.ArrayList<Theme?>(); + try { + string name; + + // load global themes + var d = Dir.open(Paths.global_themes); + while ((name = d.read_name()) != null) { + var new_theme = new Theme(Paths.global_themes + "/" + name); + + if (new_theme.load()) { + themes.add(new_theme); + } + } + + // load local themes + d = Dir.open(Paths.local_themes); + while ((name = d.read_name()) != null) { + var new_theme = new Theme(Paths.local_themes + "/" + name); + if (new_theme.load()) + themes.add(new_theme); + } + + } catch (Error e) { + warning (e.message); + } + + if (themes.size > 0) { + if (current == "") { + current = "Adwaita"; + warning("No theme specified! Using default..."); + } + foreach (var t in themes) { + if (t.name == current) { + theme = t; + break; + } + } + if (theme == null) { + theme = themes[0]; + warning("Theme \"" + current + "\" not found! Using fallback..."); + } + theme.load_images(); + } else { + error("No theme found!"); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Returns true if a loaded theme has the given name or is in a + /// directory with the given name. + ///////////////////////////////////////////////////////////////////// + + public bool has_theme(string name) { + + foreach (var theme in themes) { + if (theme.name == name || theme.directory.has_suffix(name)) { + return true; + } + } + + return false; + } +} + +} diff --git a/src/utilities/focusGrabber.vala b/src/utilities/focusGrabber.vala new file mode 100644 index 0000000..baa5fed --- /dev/null +++ b/src/utilities/focusGrabber.vala @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// Some helper methods which focus the input on a given Gtk.Window. +///////////////////////////////////////////////////////////////////////// + +public class FocusGrabber : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// Utilities for grabbing focus. + /// Code roughly from Gnome-Do/Synapse. + ///////////////////////////////////////////////////////////////////// + + public static void grab(Gdk.Window window, bool keyboard = true, bool pointer = true, bool owner_events = true) { + if (keyboard || pointer) { + window.raise(); + window.focus(Gdk.CURRENT_TIME); + + if (!try_grab_window(window, keyboard, pointer, owner_events)) { + int i = 0; + Timeout.add(100, () => { + if (++i >= 100) return false; + return !try_grab_window(window, keyboard, pointer, owner_events); + }); + } + } + } + + ///////////////////////////////////////////////////////////////////// + /// Code roughly from Gnome-Do/Synapse. + ///////////////////////////////////////////////////////////////////// + + public static void ungrab(bool keyboard = true, bool pointer = true) { + var display = Gdk.Display.get_default(); + var manager = display.get_device_manager(); + + GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER); + + foreach(var device in list) { + if ((device.input_source == Gdk.InputSource.KEYBOARD && keyboard) + || (device.input_source != Gdk.InputSource.KEYBOARD && pointer)) + + device.ungrab(Gdk.CURRENT_TIME); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Code roughly from Gnome-Do/Synapse. + ///////////////////////////////////////////////////////////////////// + + private static bool try_grab_window(Gdk.Window window, bool keyboard, bool pointer, bool owner_events) { + var display = Gdk.Display.get_default(); + var manager = display.get_device_manager(); + + bool grabbed_all = true; + + GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER); + + foreach(var device in list) { + if ((device.input_source == Gdk.InputSource.KEYBOARD && keyboard) + || (device.input_source != Gdk.InputSource.KEYBOARD && pointer)) { + + var status = device.grab(window, Gdk.GrabOwnership.APPLICATION, owner_events, + Gdk.EventMask.ALL_EVENTS_MASK, null, Gdk.CURRENT_TIME); + + if (status != Gdk.GrabStatus.SUCCESS) + grabbed_all = false; + } + } + + if (grabbed_all) + return true; + + ungrab(keyboard, pointer); + + return false; + } +} + +} diff --git a/src/utilities/key.vala b/src/utilities/key.vala new file mode 100644 index 0000000..486744d --- /dev/null +++ b/src/utilities/key.vala @@ -0,0 +1,161 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// A class which represents a key stroke. It can be used to "press" +/// the associated keys. +///////////////////////////////////////////////////////////////////////// + +public class Key : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// Some static members, which are often used by this class. + ///////////////////////////////////////////////////////////////////// + + private static X.Display display; + + private static int shift_code; + private static int ctrl_code; + private static int alt_code; + private static int super_code; + + ///////////////////////////////////////////////////////////////////// + /// A human readable form of the Key's accelerator. + ///////////////////////////////////////////////////////////////////// + + public string label { get; private set; } + + ///////////////////////////////////////////////////////////////////// + /// The accelerator of the Key. + ///////////////////////////////////////////////////////////////////// + + public string accelerator { get; private set; } + + ///////////////////////////////////////////////////////////////////// + /// Keycode and modifiers of this stroke. + ///////////////////////////////////////////////////////////////////// + + private int key_code; + private Gdk.ModifierType modifiers; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, initializes all members to defaults. + ///////////////////////////////////////////////////////////////////// + + public Key() { + this.accelerator = ""; + this.modifiers = 0; + this.key_code = 0; + this.label = _("Not bound"); + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, initializes all members. + ///////////////////////////////////////////////////////////////////// + + public Key.from_string(string stroke) { + this.accelerator = stroke; + + uint keysym; + Gtk.accelerator_parse(stroke, out keysym, out this.modifiers); + this.key_code = display.keysym_to_keycode(keysym); + this.label = Gtk.accelerator_get_label(keysym, this.modifiers); + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, initializes all members. + ///////////////////////////////////////////////////////////////////// + + public Key.from_values(uint keysym, Gdk.ModifierType modifiers) { + this.accelerator = Gtk.accelerator_name(keysym, modifiers); + this.label = Gtk.accelerator_get_label(keysym, modifiers); + this.key_code = display.keysym_to_keycode(keysym); + this.modifiers = modifiers; + } + + ///////////////////////////////////////////////////////////////////// + /// Initializes static members. + ///////////////////////////////////////////////////////////////////// + + static construct { + display = new X.Display(); + + shift_code = display.keysym_to_keycode(Gdk.keyval_from_name("Shift_L")); + ctrl_code = display.keysym_to_keycode(Gdk.keyval_from_name("Control_L")); + alt_code = display.keysym_to_keycode(Gdk.keyval_from_name("Alt_L")); + super_code = display.keysym_to_keycode(Gdk.keyval_from_name("Super_L")); + } + + ///////////////////////////////////////////////////////////////////// + /// Simulates the pressing of the Key . + ///////////////////////////////////////////////////////////////////// + + public void press() { + // store currently pressed modifier keys + Gdk.ModifierType current_modifiers = get_modifiers(); + + // release them and press the desired ones + press_modifiers(current_modifiers, false); + press_modifiers(this.modifiers, true); + + // send events to X + display.flush(); + + // press and release the actual key + XTest.fake_key_event(display, this.key_code, true, 0); + XTest.fake_key_event(display, this.key_code, false, 0); + + // release the pressed modifiers and re-press the keys hold down by the user + press_modifiers(this.modifiers, false); + press_modifiers(current_modifiers, true); + + // send events to X + display.flush(); + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method returning currently hold down modifier keys. + ///////////////////////////////////////////////////////////////////// + + private Gdk.ModifierType get_modifiers() { + Gdk.ModifierType modifiers; + Gtk.get_current_event_state(out modifiers); + return modifiers; + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method which 'presses' the desired modifier keys. + ///////////////////////////////////////////////////////////////////// + + private void press_modifiers(Gdk.ModifierType modifiers, bool down) { + if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0) + XTest.fake_key_event(display, ctrl_code, down, 0); + + if ((modifiers & Gdk.ModifierType.SHIFT_MASK) > 0) + XTest.fake_key_event(display, shift_code, down, 0); + + if ((modifiers & Gdk.ModifierType.MOD1_MASK) > 0) + XTest.fake_key_event(display, alt_code, down, 0); + + if ((modifiers & Gdk.ModifierType.SUPER_MASK) > 0) + XTest.fake_key_event(display, super_code, down, 0); + } +} + +} diff --git a/src/utilities/logger.vala b/src/utilities/logger.vala new file mode 100644 index 0000000..7c66615 --- /dev/null +++ b/src/utilities/logger.vala @@ -0,0 +1,270 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// A static class which beautifies the messages of the default logger. +/// Some of this code is inspired by plank's written by Robert Dyer. +/// Thanks a lot for this project! +///////////////////////////////////////////////////////////////////////// + +public class Logger { + + ///////////////////////////////////////////////////////////////////// + /// If these are set to false, the according messages are not shown + ///////////////////////////////////////////////////////////////////// + + private static const bool display_debug = true; + private static const bool display_warning = true; + private static const bool display_error = true; + private static const bool display_message = true; + + ///////////////////////////////////////////////////////////////////// + /// If these are set to false, the according messages are not logged + ///////////////////////////////////////////////////////////////////// + + private static const bool log_debug = false; + private static const bool log_warning = true; + private static const bool log_error = true; + private static const bool log_message = true; + + ///////////////////////////////////////////////////////////////////// + /// If true, a time stamp is shown in each message. + ///////////////////////////////////////////////////////////////////// + + private static const bool display_time = false; + private static const bool log_time = true; + + ///////////////////////////////////////////////////////////////////// + /// If true, the origin of the message is shown. In form file:line + ///////////////////////////////////////////////////////////////////// + + private static const bool display_file = false; + private static const bool log_file = false; + + ///////////////////////////////////////////////////////////////////// + /// A regex, used to format the standard message. + ///////////////////////////////////////////////////////////////////// + + private static Regex regex = null; + + ///////////////////////////////////////////////////////////////////// + /// Limit log and statistics size to roughly 1 MB. + ///////////////////////////////////////////////////////////////////// + + private static const int max_log_length = 1000000; + + private static int log_length; + + ///////////////////////////////////////////////////////////////////// + /// Possible terminal colors. + ///////////////////////////////////////////////////////////////////// + + private enum Color { + BLACK, + RED, + GREEN, + YELLOW, + BLUE, + PURPLE, + TURQUOISE, + WHITE + } + + ///////////////////////////////////////////////////////////////////// + /// Creates the regex and binds the handler. + ///////////////////////////////////////////////////////////////////// + + public static void init() { + log_length = -1; + + try { + regex = new Regex("""(.*)\.vala(:\d+): (.*)"""); + } catch {} + + GLib.Log.set_handler(null, GLib.LogLevelFlags.LEVEL_MASK, log_func); + } + + ///////////////////////////////////////////////////////////////////// + /// Appends a line to the log file + ///////////////////////////////////////////////////////////////////// + + private static void write_log_line(string line) { + var log = GLib.FileStream.open(Paths.log, "a"); + + if (log != null) { + if (log_length == -1) + log_length = (int)log.tell(); + + log.puts(line); + log_length += line.length; + } + + if (log_length > max_log_length) { + string content = ""; + + try { + GLib.FileUtils.get_contents(Paths.log, out content); + int split_index = content.index_of_char('\n', log_length - (int)(max_log_length*0.9)); + GLib.FileUtils.set_contents(Paths.log, content.substring(split_index+1)); + + log_length -= (split_index+1); + } catch (GLib.FileError e) {} + } + } + + ///////////////////////////////////////////////////////////////////// + /// Displays a message. + ///////////////////////////////////////////////////////////////////// + + private static void message(string message, string message_log) { + if (display_message) { + stdout.printf(set_color(Color.GREEN, false) + "[" + (display_time ? get_time() + " " : "") + "MESSAGE]" + message); + } + + if (log_message) { + write_log_line("[" + (log_time ? get_time() + " " : "") + "MESSAGE]" + message_log); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Displays a Debug message. + ///////////////////////////////////////////////////////////////////// + + private static void debug(string message, string message_log) { + if (display_debug) { + stdout.printf(set_color(Color.BLUE, false) + "[" + (display_time ? get_time() + " " : "") + " DEBUG ]" + message); + } + + if (log_debug) { + write_log_line("[" + (log_time ? get_time() + " " : "") + " DEBUG ]" + message_log); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Displays a Warning message. + ///////////////////////////////////////////////////////////////////// + + private static void warning(string message, string message_log) { + if (display_warning) { + stdout.printf(set_color(Color.YELLOW, false) + "[" + (display_time ? get_time() + " " : "") + "WARNING]" + message); + } + + if (log_warning) { + write_log_line("[" + (log_time ? get_time() + " " : "") + "WARNING]" + message_log); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Displays a Error message. + ///////////////////////////////////////////////////////////////////// + + private static void error(string message, string message_log) { + if (display_error) { + stdout.printf(set_color(Color.RED, false) + "[" + (display_time ? get_time() + " " : "") + " ERROR ]" + message); + } + + if (log_error) { + write_log_line("[" + (log_time ? get_time() + " " : "") + " ERROR ]" + message_log); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method which resets the terminal color. + ///////////////////////////////////////////////////////////////////// + + private static string reset_color() { + return "\x001b[0m"; + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method which sets the terminal color. + ///////////////////////////////////////////////////////////////////// + + private static string set_color(Color color, bool bold) { + if (bold) return "\x001b[1;%dm".printf((int)color + 30); + else return "\x001b[0;%dm".printf((int)color + 30); + } + + ///////////////////////////////////////////////////////////////////// + /// Returns the current time in hh:mm:ss:mmmmmm + ///////////////////////////////////////////////////////////////////// + + private static string get_time() { + var now = new DateTime.now_local(); + return "%.4d:%.2d:%.2d:%.2d:%.2d:%.2d:%.6d".printf(now.get_year(), now.get_month(), now.get_day_of_month(), now.get_hour(), now.get_minute(), now.get_second(), now.get_microsecond()); + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method to format the message. + ///////////////////////////////////////////////////////////////////// + + private static string create_message(string message) { + if (display_file && regex != null && regex.match(message)) { + var parts = regex.split(message); + return " [%s%s]%s %s\n".printf(parts[1], parts[2], reset_color(), parts[3]); + } else if (regex != null && regex.match(message)) { + var parts = regex.split(message); + return "%s %s\n".printf(reset_color(), parts[3]); + } else { + return reset_color() + " " + message + "\n"; + } + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method to format the message for logging. + ///////////////////////////////////////////////////////////////////// + + private static string create_log_message(string message) { + if (log_file && regex != null && regex.match(message)) { + var parts = regex.split(message); + return " [%s%s] %s\n".printf(parts[1], parts[2], parts[3]); + } else if (regex != null && regex.match(message)) { + var parts = regex.split(message); + return " %s\n".printf(parts[3]); + } else { + return " " + message + "\n"; + } + } + + ///////////////////////////////////////////////////////////////////// + /// The handler function. + ///////////////////////////////////////////////////////////////////// + + private static void log_func(string? d, LogLevelFlags flags, string text) { + switch (flags) { + case LogLevelFlags.LEVEL_ERROR: + case LogLevelFlags.LEVEL_CRITICAL: + error(create_message(text), create_log_message(text)); + break; + case LogLevelFlags.LEVEL_INFO: + case LogLevelFlags.LEVEL_MESSAGE: + message(create_message(text), create_log_message(text)); + break; + case LogLevelFlags.LEVEL_DEBUG: + debug(create_message(text), create_log_message(text)); + break; + case LogLevelFlags.LEVEL_WARNING: + default: + warning(create_message(text), create_log_message(text)); + break; + } + } +} + +} diff --git a/src/utilities/paths.vala b/src/utilities/paths.vala new file mode 100644 index 0000000..7bdd642 --- /dev/null +++ b/src/utilities/paths.vala @@ -0,0 +1,286 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// A static class which stores all relevant paths used by Gnome-Pie. +/// These depend upon the location from which the program was launched. +///////////////////////////////////////////////////////////////////////// + +public class Paths : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// The config directory, + /// usually ~/.config/gnome-pie/. + ///////////////////////////////////////////////////////////////////// + + public static string config_directory { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The log file, + /// usually ~/.config/gnome-pie/gnome-pie.log. + ///////////////////////////////////////////////////////////////////// + + public static string log { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The statistics file, + /// usually ~/.config/gnome-pie/gnome-pie.stats. + ///////////////////////////////////////////////////////////////////// + + public static string stats { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The settings file, + /// usually ~/.config/gnome-pie/gnome-pie.conf. + ///////////////////////////////////////////////////////////////////// + + public static string settings { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The pie configuration file + /// usually ~/.config/gnome-pie/pies.conf. + ///////////////////////////////////////////////////////////////////// + + public static string pie_config { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The directory containing themes installed by the user + /// usually ~/.config/gnome-pie/themes. + ///////////////////////////////////////////////////////////////////// + + public static string local_themes { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The directory containing pre-installed themes + /// usually /usr/share/gnome-pie/themes. + ///////////////////////////////////////////////////////////////////// + + public static string global_themes { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The directory containing locale files + /// usually /usr/share/locale. + ///////////////////////////////////////////////////////////////////// + + public static string locales { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The directory containing UI declaration files + /// usually /usr/share/gnome-pie/ui/. + ///////////////////////////////////////////////////////////////////// + + public static string ui_files { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The autostart file of gnome-pie_config + /// usually ~/.config/autostart/gnome-pie.desktop. + ///////////////////////////////////////////////////////////////////// + + public static string autostart { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The path where all pie-launchers are stored + /// usually ~/.config/gnome-pie/launchers. + ///////////////////////////////////////////////////////////////////// + + public static string launchers { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The path to the executable. + ///////////////////////////////////////////////////////////////////// + + public static string executable { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// Deletes a directory recursively from disk. Use with care :) + ///////////////////////////////////////////////////////////////////// + + public static void delete_directory(string directory) { + try { + var d = Dir.open(directory); + string name; + while ((name = d.read_name()) != null) { + string path = Path.build_filename(directory, name); + if (FileUtils.test(path, FileTest.IS_DIR)) { + delete_directory(path); + } else { + FileUtils.remove(path); + } + } + DirUtils.remove(directory); + } catch (Error e) { + warning (e.message); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Initializes all values above. + ///////////////////////////////////////////////////////////////////// + + public static void init() { + + // get path of executable + try { + executable = GLib.File.new_for_path(GLib.FileUtils.read_link("/proc/self/exe")).get_path(); + } catch (GLib.FileError e) { + warning("Failed to get path of executable!"); + } + + // append resources to icon search path to icon theme, if neccasary + var icon_dir = GLib.File.new_for_path(GLib.Path.get_dirname(executable)).get_child("resources"); + + if (icon_dir.query_exists()) { + string path = icon_dir.get_path(); + Gtk.IconTheme.get_default().append_search_path(path); + } + + Gtk.IconTheme.get_default().append_search_path("/usr/share/pixmaps/"); + Gtk.IconTheme.get_default().append_search_path("/usr/share/icons/hicolor/scalable/apps"); + Gtk.IconTheme.get_default().append_search_path("/usr/local/share/icons/hicolor/scalable/apps"); + + // get global paths + var default_dir = GLib.File.new_for_path("/usr/share/gnome-pie/"); + if(!default_dir.query_exists()) { + default_dir = GLib.File.new_for_path("/usr/local/share/gnome-pie/"); + + if(!default_dir.query_exists()) { + default_dir = GLib.File.new_for_path(GLib.Path.get_dirname( + executable)).get_child("resources"); + } + } + + global_themes = default_dir.get_path() + "/themes"; + ui_files = default_dir.get_path() + "/ui"; + + // get locales path + var locale_dir = GLib.File.new_for_path("/usr/share/locale/de/LC_MESSAGES/gnomepie.mo"); + if(locale_dir.query_exists()) { + locale_dir = GLib.File.new_for_path("/usr/share/locale"); + } else { + locale_dir = GLib.File.new_for_path("/usr/local/share/locale/de/LC_MESSAGES/gnomepie.mo"); + if(locale_dir.query_exists()) { + locale_dir = GLib.File.new_for_path("/usr/local/share/locale"); + } else { + locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname( + executable)).get_child("resources/locale/de/LC_MESSAGES/gnomepie.mo"); + + if(locale_dir.query_exists()) { + locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname( + executable)).get_child("resources/locale"); + } + } + } + + locales = locale_dir.get_path(); + + // get local paths + var config_dir = GLib.File.new_for_path( + GLib.Environment.get_user_config_dir()).get_child("gnome-pie"); + + // create config_dir if neccasary + if(!config_dir.query_exists()) { + try { + config_dir.make_directory(); + } catch (GLib.Error e) { + error(e.message); + } + } + + config_directory = config_dir.get_path(); + + // create local themes directory if neccasary + var themes_dir = config_dir.get_child("themes"); + if(!themes_dir.query_exists()) { + try { + themes_dir.make_directory(); + } catch (GLib.Error e) { + error(e.message); + } + } + + local_themes = themes_dir.get_path(); + + // create launchers directory if neccasary + var launchers_dir = config_dir.get_child("launchers"); + if(!launchers_dir.query_exists()) { + try { + launchers_dir.make_directory(); + } catch (GLib.Error e) { + error(e.message); + } + } + + launchers = launchers_dir.get_path(); + + // check for config file + var config_file = config_dir.get_child("pies.conf"); + + pie_config = config_file.get_path(); + settings = config_dir.get_path() + "/gnome-pie.conf"; + log = config_dir.get_path() + "/gnome-pie.log"; + stats = config_dir.get_path() + "/gnome-pie.stats"; + + if (!GLib.File.new_for_path(log).query_exists()) { + try { + FileUtils.set_contents(log, ""); + } catch (GLib.FileError e) { + error(e.message); + } + } + + if (!GLib.File.new_for_path(stats).query_exists()) { + try { + FileUtils.set_contents(stats, ""); + } catch (GLib.FileError e) { + error(e.message); + } + } + + // autostart file name + autostart = GLib.Path.build_filename(GLib.Environment.get_user_config_dir(), + "autostart", "gnome-pie.desktop", null); + + // print results + if (!GLib.File.new_for_path(pie_config).query_exists()) + warning("Failed to find pie configuration file \"pies.conf\"! (This should only happen when Gnome-Pie is started for the first time...)"); + + if (!GLib.File.new_for_path(settings).query_exists()) + warning("Failed to find settings file \"gnome-pie.conf\"! (This should only happen when Gnome-Pie is started for the first time...)"); + + if (!GLib.File.new_for_path(log).query_exists()) + warning("Failed to find log file \"gnome-pie.log\"!"); + + if (!GLib.File.new_for_path(stats).query_exists()) + warning("Failed to find statistics file \"gnome-pie.stats\"!"); + + if (!GLib.File.new_for_path(local_themes).query_exists()) + warning("Failed to find local themes directory!"); + + if (!GLib.File.new_for_path(launchers).query_exists()) + warning("Failed to find launchers directory!"); + + if (!GLib.File.new_for_path(global_themes).query_exists()) + warning("Failed to find global themes directory!"); + + if (!GLib.File.new_for_path(ui_files).query_exists()) + warning("Failed to find UI files directory!"); + } +} + +} diff --git a/src/utilities/trigger.vala b/src/utilities/trigger.vala new file mode 100644 index 0000000..5373b41 --- /dev/null +++ b/src/utilities/trigger.vala @@ -0,0 +1,357 @@ +///////////////////////////////////////////////////////////////////////// +// 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 { + +///////////////////////////////////////////////////////////////////////// +/// This class represents a hotkey, used to open pies. It supports any +/// combination of modifier keys with keyboard and mouse buttons. +///////////////////////////////////////////////////////////////////////// + +public class Trigger : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// Returns a human-readable version of this Trigger. + ///////////////////////////////////////////////////////////////////// + + public string label { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// Returns a human-readable version of this Trigger. Small + /// identifiers for turbo mode and delayed mode are added. + ///////////////////////////////////////////////////////////////////// + + public string label_with_specials { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The Trigger string. Like [delayed]<Control>button3 + ///////////////////////////////////////////////////////////////////// + + public string name { get; private set; default=""; } + + ///////////////////////////////////////////////////////////////////// + /// The key code of the hotkey or the button number of the mouse. + ///////////////////////////////////////////////////////////////////// + + public int key_code { get; private set; default=0; } + + ///////////////////////////////////////////////////////////////////// + /// The keysym of the hotkey or the button number of the mouse. + ///////////////////////////////////////////////////////////////////// + + public uint key_sym { get; private set; default=0; } + + ///////////////////////////////////////////////////////////////////// + /// Modifier keys pressed for this hotkey. + ///////////////////////////////////////////////////////////////////// + + public Gdk.ModifierType modifiers { get; private set; default=0; } + + ///////////////////////////////////////////////////////////////////// + /// True if this hotkey involves the mouse. + ///////////////////////////////////////////////////////////////////// + + public bool with_mouse { get; private set; default=false; } + + ///////////////////////////////////////////////////////////////////// + /// True if the pie closes when the trigger hotkey is released. + ///////////////////////////////////////////////////////////////////// + + public bool turbo { get; private set; default=false; } + + ///////////////////////////////////////////////////////////////////// + /// True if the trigger should wait a short delay before being + /// triggered. + ///////////////////////////////////////////////////////////////////// + + public bool delayed { get; private set; default=false; } + + ///////////////////////////////////////////////////////////////////// + /// True if the pie opens in the middle of the screen. + ///////////////////////////////////////////////////////////////////// + + public bool centered { get; private set; default=false; } + + ///////////////////////////////////////////////////////////////////// + /// True if the mouse pointer is warped to the pie's center. + ///////////////////////////////////////////////////////////////////// + + public bool warp { get; private set; default=false; } + + ///////////////////////////////////////////////////////////////////// + /// Returns the current selected "radio-button" shape: 0= automatic + /// 5= full pie; 1,3,7,8= quarters; 2,4,6,8=halves + /// 1 | 4 | 7 + /// 2 | 5 | 8 + /// 3 | 6 | 9 + ///////////////////////////////////////////////////////////////////// + + public int shape { get; private set; default=5; } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new, "unbound" Trigger. + ///////////////////////////////////////////////////////////////////// + + public Trigger() { + this.set_unbound(); + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new Trigger from a given Trigger string. This is + /// in this format: "[option(s)]<modifier(s)>button" where + /// "<modifier>" is something like "<Alt>" or "<Control>", "button" + /// something like "s", "F4" or "button0" and "[option]" is either + /// "[turbo]", "[centered]", "[warp]", "["delayed"]" or "["shape#"]" + ///////////////////////////////////////////////////////////////////// + + public Trigger.from_string(string trigger) { + this.parse_string(trigger); + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new Trigger from the key values. + ///////////////////////////////////////////////////////////////////// + + public Trigger.from_values(uint key_sym, Gdk.ModifierType modifiers, + bool with_mouse, bool turbo, bool delayed, + bool centered, bool warp, int shape ) { + + string trigger = (turbo ? "[turbo]" : "") + + (delayed ? "[delayed]" : "") + + (centered ? "[centered]" : "") + + (warp ? "[warp]" : "") + + (shape!=5 ? "[shape%d]".printf(shape) : ""); + + if (with_mouse) { + trigger += Gtk.accelerator_name(0, modifiers) + "button%u".printf(key_sym); + } else { + trigger += Gtk.accelerator_name(key_sym, modifiers); + } + + this.parse_string(trigger); + } + + ///////////////////////////////////////////////////////////////////// + /// Parses a Trigger string. This is + /// in this format: "[option(s)]<modifier(s)>button" where + /// "<modifier>" is something like "<Alt>" or "<Control>", "button" + /// something like "s", "F4" or "button0" and "[option]" is either + /// "[turbo]", "[centered]", "[warp]", "["delayed"]" or "["shape#"]" + ///////////////////////////////////////////////////////////////////// + + public void parse_string(string trigger) { + if (this.is_valid(trigger)) { + // copy string + string check_string = trigger; + + this.name = check_string; + + this.turbo = check_string.contains("[turbo]"); + this.delayed = check_string.contains("[delayed]"); + this.centered = check_string.contains("[centered]"); + this.warp = check_string.contains("[warp]"); + + this.shape= parse_shape( check_string ); + + // remove optional arguments + check_string = remove_optional(check_string); + + int button = this.get_mouse_button(check_string); + if (button > 0) { + this.with_mouse = true; + this.key_code = button; + this.key_sym = button; + + Gtk.accelerator_parse(check_string, null, out this._modifiers); + this.label = Gtk.accelerator_get_label(0, this.modifiers); + + string button_text = _("Button %i").printf(this.key_code); + + if (this.key_code == 1) + button_text = _("LeftButton"); + else if (this.key_code == 3) + button_text = _("RightButton"); + else if (this.key_code == 2) + button_text = _("MiddleButton"); + + this.label += button_text; + } else { + //empty triggers are ok now, they carry open options as well + if (check_string == "") { + this.label = _("Not bound"); + this.key_code = 0; + this.key_sym = 0; + this.modifiers = 0; + } else { + this.with_mouse = false; + + var display = new X.Display(); + + uint keysym = 0; + Gtk.accelerator_parse(check_string, out keysym, out this._modifiers); + this.key_code = display.keysym_to_keycode(keysym); + this.key_sym = keysym; + this.label = Gtk.accelerator_get_label(keysym, this.modifiers); + } + } + + this.label_with_specials = GLib.Markup.escape_text(this.label); + + string msg= ""; + if (this.turbo) { + msg= _("Turbo"); + } + if (this.delayed) { + if (msg == "") + msg= _("Delayed"); + else + msg += " | " + _("Delayed"); + } + if (this.centered) { + if (msg == "") + msg= _("Centered"); + else + msg += " | " + _("Centered"); + } + if (this.warp) { + if (msg == "") + msg= _("Warp"); + else + msg += " | " + _("Warp"); + } + if (this.shape == 0) { + if (msg == "") + msg= _("Auto-shaped"); + else + msg += " | " + _("Auto-shaped"); + } else if (this.shape == 1 || this.shape ==3 || this.shape == 7 || this.shape == 9) { + if (msg == "") + msg= _("Quarter pie"); + else + msg += " | " + _("Quarter pie"); + + } else if (this.shape == 2 || this.shape == 4 || this.shape == 6 || this.shape == 8) { + if (msg == "") + msg= _("Half pie"); + else + msg += " | " + _("Half pie"); + } + if (msg != "") + this.label_with_specials += (" [ " + msg + " ]"); + + } else { + this.set_unbound(); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Extract shape number from trigger string + /// "[0]".."[9]" 0:auto 5:full pie (default) + /// 1,3,7,9=quarters 2,4,6,8= halves + ///////////////////////////////////////////////////////////////////// + + private int parse_shape(string trigger) { + int rs; + for( rs= 0; rs < 10; rs++ ) + if (trigger.contains("[shape%d]".printf(rs) )) + return rs; + return 5; //default= full pie + } + + ///////////////////////////////////////////////////////////////////// + /// Resets all member variables to their defaults. + ///////////////////////////////////////////////////////////////////// + + private void set_unbound() { + this.label = _("Not bound"); + this.label_with_specials = _("Not bound"); + this.name = ""; + this.key_code = 0; + this.key_sym = 0; + this.modifiers = 0; + this.turbo = false; + this.delayed = false; + this.centered = false; + this.warp = false; + this.shape = 5; //full pie + this.with_mouse = false; + } + + ///////////////////////////////////////////////////////////////////// + /// Remove optional arguments from the given string + /// "[turbo]", "[delayed]", "[warp]" "[centered]" and "[shape#]" + ///////////////////////////////////////////////////////////////////// + + public static string remove_optional(string trigger) { + string trg= trigger; + trg = trg.replace("[turbo]", ""); + trg = trg.replace("[delayed]", ""); + trg = trg.replace("[centered]", ""); + trg = trg.replace("[warp]", ""); + for (int rs= 0; rs < 10; rs++) + trg = trg.replace("[shape%d]".printf(rs), ""); + return trg; + } + + ///////////////////////////////////////////////////////////////////// + /// Returns true, if the trigger string is in a valid format. + ///////////////////////////////////////////////////////////////////// + + private bool is_valid(string trigger) { + // remove optional arguments + string check_string = remove_optional(trigger); + + if (this.get_mouse_button(check_string) > 0) { + // it seems to be a valid mouse-trigger so replace button part, + // with something accepted by gtk, and check it with gtk + int button_index = check_string.index_of("button"); + check_string = check_string.slice(0, button_index) + "a"; + } + + //empty triggers are ok now, they carry open options as well + if (check_string == "") + return true; + + // now it shouls be a normal gtk accelerator + uint keysym = 0; + Gdk.ModifierType modifiers = 0; + Gtk.accelerator_parse(check_string, out keysym, out modifiers); + if (keysym == 0) + return false; + + return true; + } + + ///////////////////////////////////////////////////////////////////// + /// Returns the mouse button number of the given trigger string. + /// Returns -1 if it is not a mouse trigger. + ///////////////////////////////////////////////////////////////////// + + private int get_mouse_button(string trigger) { + if (trigger.contains("button")) { + // it seems to be a mouse-trigger so check the button part. + int button_index = trigger.index_of("button"); + int number = int.parse(trigger.slice(button_index + 6, trigger.length)); + if (number > 0) + return number; + } + + return -1; + } +} + +} |