/* * Copyright (C) 2008 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * 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 . * * Authored by Neil Jagdish Patel * */ #include #include #include #include #include #include #define WNCK_I_KNOW_THIS_IS_UNSTABLE #include #include #include #include #include #include #include #include #include "maximus-bind.h" #include "tomboykeybinder.h" #include "eggaccelerators.h" #define KEY_RELEASE_TIMEOUT 300 #define STATE_CHANGED_SLEEP 0.5 /* GSettings schemas and keys */ #define BIND_SCHEMA "org.mate.maximus" #define BIND_EXCLUDE_CLASS "binding" #define SYSRULESDIR SYSCONFDIR"/maximus" #ifdef __GNUC__ #define UNUSED_VARIABLE __attribute__ ((unused)) #else #define UNUSED_VARIABLE #endif struct _MaximusBindPrivate { FakeKey *fk; WnckScreen *screen; GSettings *settings; gchar *binding; GList *rules; }; typedef struct { gchar *wm_class; gchar *fullscreen; gchar *unfullscreen; } MaximusRule; G_DEFINE_TYPE_WITH_PRIVATE (MaximusBind, maximus_bind, G_TYPE_OBJECT); static const gchar * get_fullscreen_keystroke (GList *rules, WnckWindow *window) { WnckClassGroup *group; const gchar *class_name; GList *r; group = wnck_window_get_class_group (window); class_name = wnck_class_group_get_name (group); g_debug ("Searching rules for %s:\n", wnck_window_get_name (window)); for (r = rules; r; r = r->next) { MaximusRule *rule = r->data; g_debug ("\t%s ?= %s", class_name, rule->wm_class); if (class_name && rule->wm_class && strstr (class_name, rule->wm_class)) { g_debug ("\tYES!\n"); return rule->fullscreen; } g_debug ("\tNO!\n"); } return NULL; } static const gchar * get_unfullscreen_keystroke (GList *rules, WnckWindow *window) { WnckClassGroup *group; const gchar *class_name; GList *r; group = wnck_window_get_class_group (window); class_name = wnck_class_group_get_name (group); for (r = rules; r; r = r->next) { MaximusRule *rule = r->data; if (class_name && rule->wm_class && strstr (class_name, rule->wm_class)) { return rule->unfullscreen; } } return NULL; } static gboolean real_fullscreen (MaximusBind *bind) { MaximusBindPrivate *priv; GdkDisplay UNUSED_VARIABLE *display; WnckWindow *active; const gchar *keystroke; priv = bind->priv; display = gdk_display_get_default (); active = wnck_screen_get_active_window (priv->screen); if (!WNCK_IS_WINDOW (active) || wnck_window_get_window_type (active) != WNCK_WINDOW_NORMAL) return FALSE; keystroke = get_fullscreen_keystroke (priv->rules, active); if (keystroke) { guint keysym = 0; EggVirtualModifierType modifiers = 0; if (egg_accelerator_parse_virtual (keystroke, &keysym, &modifiers)) { guint mods = 0; if (modifiers & EGG_VIRTUAL_SHIFT_MASK) mods |= FAKEKEYMOD_SHIFT; if (modifiers & EGG_VIRTUAL_CONTROL_MASK) mods |= FAKEKEYMOD_CONTROL; if (modifiers & EGG_VIRTUAL_ALT_MASK) mods |= FAKEKEYMOD_ALT; if (modifiers & EGG_VIRTUAL_META_MASK) mods |= FAKEKEYMOD_META; g_debug ("Sending fullscreen special event: %s = %d %d", keystroke, keysym, mods); fakekey_press_keysym (priv->fk, keysym, mods); fakekey_release (priv->fk); return FALSE; } } if (!wnck_window_is_fullscreen (active)) { g_debug ("Sending fullscreen F11 event"); fakekey_press_keysym (priv->fk, XK_F11, 0); fakekey_release (priv->fk); } sleep (STATE_CHANGED_SLEEP); if (!wnck_window_is_fullscreen (active)) { g_debug ("Forcing fullscreen wnck event"); wnck_window_set_fullscreen (active, TRUE); } return FALSE; } static void fullscreen (MaximusBind *bind, WnckWindow *window) { MaximusBindPrivate UNUSED_VARIABLE *priv; priv = bind->priv; g_timeout_add (KEY_RELEASE_TIMEOUT, (GSourceFunc)real_fullscreen, bind); } static gboolean real_unfullscreen (MaximusBind *bind) { MaximusBindPrivate *priv; GdkDisplay UNUSED_VARIABLE *display; WnckWindow *active; const gchar *keystroke; priv = bind->priv; display = gdk_display_get_default (); active = wnck_screen_get_active_window (priv->screen); if (!WNCK_IS_WINDOW (active) || wnck_window_get_window_type (active) != WNCK_WINDOW_NORMAL) return FALSE; keystroke = get_unfullscreen_keystroke (priv->rules, active); if (keystroke) { guint keysym = 0; EggVirtualModifierType modifiers = 0; if (egg_accelerator_parse_virtual (keystroke, &keysym, &modifiers)) { guint mods = 0; if (modifiers & EGG_VIRTUAL_SHIFT_MASK) mods |= FAKEKEYMOD_SHIFT; if (modifiers & EGG_VIRTUAL_CONTROL_MASK) mods |= FAKEKEYMOD_CONTROL; if (modifiers & EGG_VIRTUAL_ALT_MASK) mods |= FAKEKEYMOD_ALT; if (modifiers & EGG_VIRTUAL_META_MASK) mods |= FAKEKEYMOD_META; g_debug ("Sending fullscreen special event: %s = %d %d", keystroke, keysym, mods); fakekey_press_keysym (priv->fk, keysym, mods); fakekey_release (priv->fk); return FALSE; } } if (wnck_window_is_fullscreen (active)) { g_debug ("Sending un-fullscreen F11 event"); fakekey_press_keysym (priv->fk, XK_F11, 0); fakekey_release (priv->fk); } sleep (STATE_CHANGED_SLEEP); if (wnck_window_is_fullscreen (active)) { g_debug ("Forcing un-fullscreen wnck event"); wnck_window_set_fullscreen (active, FALSE); } return FALSE; } static void unfullscreen (MaximusBind *bind, WnckWindow *window) { MaximusBindPrivate UNUSED_VARIABLE *priv; priv = bind->priv; g_timeout_add (KEY_RELEASE_TIMEOUT, (GSourceFunc)real_unfullscreen, bind); } static void on_binding_activated (gchar *keystring, MaximusBind *bind) { MaximusBindPrivate *priv; WnckWindow *active; g_return_if_fail (MAXIMUS_IS_BIND (bind)); priv = bind->priv; active = wnck_screen_get_active_window (priv->screen); if (wnck_window_get_window_type (active) != WNCK_WINDOW_NORMAL) return; if (wnck_window_is_fullscreen (active)) { unfullscreen (bind, active); } else { fullscreen (bind, active); } } /* Callbacks */ static gboolean binding_is_valid (const gchar *binding) { gboolean retval = TRUE; if (!binding || strlen (binding) < 1 || strcmp (binding, "disabled") == 0) retval = FALSE; return retval; } static void on_binding_changed (GSettings *settings, gchar *key, MaximusBind *bind) { MaximusBindPrivate *priv; g_return_if_fail (MAXIMUS_IS_BIND (bind)); priv = bind->priv; if (binding_is_valid (priv->binding)) tomboy_keybinder_unbind (priv->binding, (TomboyBindkeyHandler)on_binding_changed); g_free (priv->binding); priv->binding = g_settings_get_string (settings, BIND_EXCLUDE_CLASS); if (binding_is_valid (priv->binding)) tomboy_keybinder_bind (priv->binding, (TomboyBindkeyHandler)on_binding_activated, bind); g_print ("Binding changed: %s\n", priv->binding); } /* GObject stuff */ static void create_rule (MaximusBind *bind, const gchar *filename) { #define RULE_GROUP "Fullscreening" #define RULE_WMCLASS "WMClass" #define RULE_FULLSCREEN "Fullscreen" #define RULE_UNFULLSCREEN "Unfullscreen" MaximusBindPrivate *priv; GKeyFile *file; GError *error = NULL; MaximusRule *rule; priv = bind->priv; file = g_key_file_new (); g_key_file_load_from_file (file, filename, 0, &error); if (error) { g_warning ("Unable to load %s: %s\n", filename, error->message); g_error_free (error); g_key_file_free (file); return; } rule = g_slice_new0 (MaximusRule); rule->wm_class = g_key_file_get_string (file, RULE_GROUP, RULE_WMCLASS, NULL); rule->fullscreen = g_key_file_get_string (file, RULE_GROUP, RULE_FULLSCREEN, NULL); rule->unfullscreen = g_key_file_get_string (file, RULE_GROUP, RULE_UNFULLSCREEN, NULL); if (!rule->wm_class || !rule->fullscreen || !rule->unfullscreen) { g_free (rule->wm_class); g_free (rule->fullscreen); g_free (rule->unfullscreen); g_slice_free (MaximusRule, rule); g_warning ("Unable to load %s, missing strings", filename); } else priv->rules = g_list_append (priv->rules, rule); g_key_file_free (file); } static void load_rules (MaximusBind *bind, const gchar *path) { MaximusBindPrivate UNUSED_VARIABLE *priv; GDir *dir; const gchar *name; priv = bind->priv; dir = g_dir_open (path, 0, NULL); if (!dir) return; while ((name = g_dir_read_name (dir))) { gchar *filename; filename= g_build_filename (path, name, NULL); create_rule (bind, filename); g_free (filename); } g_dir_close (dir); } static void maximus_bind_finalize (GObject *obj) { MaximusBind *bind = MAXIMUS_BIND (obj); MaximusBindPrivate *priv; GList *r; g_return_if_fail (MAXIMUS_IS_BIND (bind)); priv = bind->priv; for (r = priv->rules; r; r = r->next) { MaximusRule *rule = r->data; g_free (rule->wm_class); g_free (rule->fullscreen); g_free (rule->unfullscreen); g_slice_free (MaximusRule, rule); } g_free (priv->binding); G_OBJECT_CLASS (maximus_bind_parent_class)->finalize (obj); } static void maximus_bind_class_init (MaximusBindClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS (klass); obj_class->finalize = maximus_bind_finalize; } static void maximus_bind_init (MaximusBind *bind) { MaximusBindPrivate *priv; GdkDisplay *display = gdk_display_get_default (); WnckScreen *screen; priv = bind->priv = maximus_bind_get_instance_private (bind); priv->fk = fakekey_init (GDK_DISPLAY_XDISPLAY (display)); priv->screen = screen = wnck_screen_get_default (); priv->rules = NULL; priv->settings = g_settings_new (BIND_SCHEMA); tomboy_keybinder_init (); g_signal_connect (priv->settings, "changed::" BIND_EXCLUDE_CLASS, G_CALLBACK (on_binding_changed), bind); priv->binding = g_settings_get_string (priv->settings, BIND_EXCLUDE_CLASS); if (binding_is_valid (priv->binding)) tomboy_keybinder_bind (priv->binding, (TomboyBindkeyHandler)on_binding_activated, bind); load_rules (bind, SYSRULESDIR); } MaximusBind * maximus_bind_get_default (void) { static MaximusBind *bind = NULL; if (!bind) bind = g_object_new (MAXIMUS_TYPE_BIND, NULL); return bind; }