summaryrefslogtreecommitdiff
path: root/src/utilities
diff options
context:
space:
mode:
Diffstat (limited to 'src/utilities')
-rw-r--r--src/utilities/animatedValue.vala197
-rw-r--r--src/utilities/archiveReader.vala123
-rw-r--r--src/utilities/archiveWriter.vala139
-rw-r--r--src/utilities/bindingManager.vala428
-rw-r--r--src/utilities/color.vala327
-rw-r--r--src/utilities/config.vala239
-rw-r--r--src/utilities/focusGrabber.vala97
-rw-r--r--src/utilities/key.vala161
-rw-r--r--src/utilities/logger.vala270
-rw-r--r--src/utilities/paths.vala286
-rw-r--r--src/utilities/trigger.vala357
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;
+ }
+}
+
+}