Merge lp:~3v1n0/unity/ups-menu-close-on-keybindings into lp:unity

Proposed by Marco Trevisan (Treviño) on 2013-10-03
Status: Merged
Approved by: Christopher Townsend on 2013-10-03
Approved revision: 3565
Merged at revision: 3551
Proposed branch: lp:~3v1n0/unity/ups-menu-close-on-keybindings
Merge into: lp:unity
Diff against target: 942 lines (+452/-128)
11 files modified
launcher/Launcher.cpp (+2/-1)
launcher/SwitcherController.cpp (+4/-4)
launcher/SwitcherView.h (+7/-7)
services/panel-service-private.h (+41/-0)
services/panel-service.c (+178/-102)
tests/autopilot/unity/emulators/window_manager.py (+8/-0)
tests/autopilot/unity/tests/test_panel.py (+17/-5)
tests/autopilot/unity/tests/test_spread.py (+3/-3)
tests/autopilot/unity/tests/test_wm_keybindings.py (+55/-2)
tests/data/external.gschema.xml (+6/-4)
tests/test_panel_service.cpp (+131/-0)
To merge this branch: bzr merge lp:~3v1n0/unity/ups-menu-close-on-keybindings
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve on 2013-10-03
Christopher Townsend Approve on 2013-10-03
Brandon Schaefer (community) 2013-10-03 Needs Fixing on 2013-10-03
Review via email: mp+188971@code.launchpad.net

Commit message

PanelService: close a menu and re-send the keyevent when handling a combination

Or when we try to open HUD/Dash. Also, close the active menu if a new application is opened
and focused.

Description of the change

Make the PanelService to ignore all the <Meta>+<key> keybindings and to reinject them to the X server root window so that the WindowManager can process them. Also monitor HUD and Dash keybindings and blacklist them.

We also now listen to the indicators signals about opening invalid entries as an hint to close our menus if the focused window has just been changed.

Added new AP tests.

To post a comment you must log in.
Brandon Schaefer (brandontschaefer) wrote :

Hmm my menus don't seem to want to close, but it is getting the super event. If I open the menu press super, the menu stays open then I click outside to close then the dash appears....

review: Needs Fixing
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Brandon Schaefer (brandontschaefer) wrote :

It working now! Forgot to start my compiled version of u-p-s :).

Everything looks good...but im also tired haha... would like a second set of eyes, or ill re-review it tomorrow.

3563. By Marco Trevisan (Treviño) on 2013-10-03

Tests: add new monitored keys to external gschemas

3564. By Marco Trevisan (Treviño) on 2013-10-03

PanelService: add private header, add testable parse_string_keybinding

3565. By Marco Trevisan (Treviño) on 2013-10-03

TestPanelServiceCompizShortcutParsing: verify the parsing of the compiz shortcuts

Christopher Townsend (townsend) wrote :

This looks all good. Very nice!

review: Approve
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'launcher/Launcher.cpp'
2--- launcher/Launcher.cpp 2013-10-01 23:51:59 +0000
3+++ launcher/Launcher.cpp 2013-10-03 15:16:29 +0000
4@@ -1188,8 +1188,9 @@
5
6 if (icon_under_mouse_)
7 icon_under_mouse_->HideTooltip();
8+
9+ QueueDraw();
10 }
11- QueueDraw();
12 }
13
14 void Launcher::OnOverlayHidden(GVariant* data)
15
16=== modified file 'launcher/SwitcherController.cpp'
17--- launcher/SwitcherController.cpp 2013-09-30 16:21:14 +0000
18+++ launcher/SwitcherController.cpp 2013-10-03 15:16:29 +0000
19@@ -471,10 +471,10 @@
20 ResetDetailTimer(obj_->detail_timeout_length);
21 });
22
23- view_->switcher_next.connect(sigc::mem_fun(this, &Controller::Impl::Next));
24- view_->switcher_prev.connect(sigc::mem_fun(this, &Controller::Impl::Prev));
25- view_->switcher_start_detail.connect(sigc::mem_fun(this, &Controller::Impl::StartDetailMode));
26- view_->switcher_stop_detail.connect(sigc::mem_fun(this, &Controller::Impl::StopDetailMode));
27+ view_->switcher_next.connect(sigc::mem_fun(this, &Impl::Next));
28+ view_->switcher_prev.connect(sigc::mem_fun(this, &Impl::Prev));
29+ view_->switcher_start_detail.connect(sigc::mem_fun(this, &Impl::StartDetailMode));
30+ view_->switcher_stop_detail.connect(sigc::mem_fun(this, &Impl::StopDetailMode));
31
32 ConstructWindow();
33 main_layout_->AddView(view_.GetPointer(), 1);
34
35=== modified file 'launcher/SwitcherView.h'
36--- launcher/SwitcherView.h 2013-09-30 16:21:14 +0000
37+++ launcher/SwitcherView.h 2013-10-03 15:16:29 +0000
38@@ -76,17 +76,17 @@
39 int DetailIconIdexAt(int x, int y) const;
40
41 /* void; int icon_index, int button*/
42- sigc::signal<void, int, int> switcher_mouse_down;
43- sigc::signal<void, int, int> switcher_mouse_up;
44+ sigc::signal<void, int, int> switcher_mouse_down;
45+ sigc::signal<void, int, int> switcher_mouse_up;
46
47 /* void; int icon_index */
48- sigc::signal<void, int> switcher_mouse_move;
49+ sigc::signal<void, int> switcher_mouse_move;
50
51 /* void; */
52- sigc::signal<void> switcher_next;
53- sigc::signal<void> switcher_prev;
54- sigc::signal<void> switcher_start_detail;
55- sigc::signal<void> switcher_stop_detail;
56+ sigc::signal<void> switcher_next;
57+ sigc::signal<void> switcher_prev;
58+ sigc::signal<void> switcher_start_detail;
59+ sigc::signal<void> switcher_stop_detail;
60
61 /* void; bool visible */
62 sigc::signal<void, bool> hide_request;
63
64=== added file 'services/panel-service-private.h'
65--- services/panel-service-private.h 1970-01-01 00:00:00 +0000
66+++ services/panel-service-private.h 2013-10-03 15:16:29 +0000
67@@ -0,0 +1,41 @@
68+/*
69+ * Copyright (C) 2013 Canonical Ltd
70+ *
71+ * This program is free software: you can redistribute it and/or modify
72+ * it under the terms of the GNU General Public License version 3 as
73+ * published by the Free Software Foundation.
74+ *
75+ * This program is distributed in the hope that it will be useful,
76+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
77+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
78+ * GNU General Public License for more details.
79+ *
80+ * You should have received a copy of the GNU General Public License
81+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
82+ *
83+ * Authored by: Marco Trevisan <marco.trevisan@canonical.com>
84+ */
85+
86+#ifndef _PANEL_SERVICE_PRIVATE_H_
87+#define _PANEL_SERVICE_PRIVATE_H_
88+
89+#include <X11/Xlib.h>
90+#include <X11/keysym.h>
91+
92+G_BEGIN_DECLS
93+
94+typedef struct _KeyBinding
95+{
96+ KeySym key;
97+ KeySym fallback;
98+ guint32 modifiers;
99+} KeyBinding;
100+
101+#define AltMask Mod1Mask
102+#define SuperMask Mod4Mask
103+
104+void parse_string_keybinding (const char *, KeyBinding *);
105+
106+G_END_DECLS
107+
108+#endif /* _PANEL_SERVICE_PRIVATE_H_ */
109
110=== modified file 'services/panel-service.c'
111--- services/panel-service.c 2013-09-24 00:27:02 +0000
112+++ services/panel-service.c 2013-10-03 15:16:29 +0000
113@@ -21,6 +21,7 @@
114
115 #include "config.h"
116 #include "panel-service.h"
117+#include "panel-service-private.h"
118
119 #include <stdlib.h>
120 #include <string.h>
121@@ -29,8 +30,8 @@
122 #include <glib/gi18n-lib.h>
123 #include <libindicator/indicator-ng.h>
124
125+#include <X11/XKBlib.h>
126 #include <X11/extensions/XInput2.h>
127-#include <X11/XKBlib.h>
128
129 #include <upstart.h>
130 #include <nih/alloc.h>
131@@ -47,6 +48,8 @@
132 #define COMPIZ_OPTION_SCHEMA "org.compiz.unityshell"
133 #define COMPIZ_OPTION_PATH "/org/compiz/profiles/unity/plugins/"
134 #define MENU_TOGGLE_KEYBINDING_KEY "panel-first-menu"
135+#define SHOW_DASH_KEY "show-launcher"
136+#define SHOW_HUD_KEY "show-hud"
137
138 static PanelService *static_service = NULL;
139
140@@ -62,8 +65,6 @@
141 GtkWidget *menubar;
142 GtkWidget *offscreen_window;
143 GtkMenu *last_menu;
144- guint32 last_menu_id;
145- guint32 last_menu_move_id;
146 gint32 last_x;
147 gint32 last_y;
148 gint last_left;
149@@ -72,9 +73,10 @@
150 gint last_bottom;
151 guint32 last_menu_button;
152
153- KeyCode toggle_key;
154- guint32 toggle_modifiers;
155 GSettings *gsettings;
156+ KeyBinding menu_toggle;
157+ KeyBinding show_dash;
158+ KeyBinding show_hud;
159
160 IndicatorObjectEntry *pressed_entry;
161 gboolean use_event;
162@@ -125,22 +127,13 @@
163 };
164
165 /* Forwards */
166-static void load_indicator (PanelService *self,
167- IndicatorObject *object,
168- const gchar *_name);
169-static void load_indicators (PanelService *self);
170-static void load_indicators_from_indicator_files (PanelService *self);
171-static void sort_indicators (PanelService *self);
172-
173+static void load_indicator (PanelService *, IndicatorObject *, const gchar *);
174+static void load_indicators (PanelService *);
175+static void load_indicators_from_indicator_files (PanelService *);
176+static void sort_indicators (PanelService *);
177 static void notify_object (IndicatorObject *object);
178-
179-static GdkFilterReturn event_filter (GdkXEvent *ev,
180- GdkEvent *gev,
181- PanelService *self);
182-
183-static void on_keybinding_changed (GSettings *settings,
184- gchar *key,
185- gpointer data);
186+static void update_keybinding (GSettings *, const gchar *, gpointer);
187+static GdkFilterReturn event_filter (GdkXEvent *, GdkEvent *, PanelService *);
188
189 /*
190 * GObject stuff
191@@ -184,7 +177,8 @@
192 if (GTK_IS_WIDGET (priv->last_menu) &&
193 gtk_widget_get_realized (GTK_WIDGET (priv->last_menu)))
194 {
195- g_object_unref (priv->last_menu);
196+ gtk_menu_popdown (GTK_MENU (priv->last_menu));
197+ g_signal_handlers_disconnect_by_data (priv->last_menu, self);
198 priv->last_menu = NULL;
199 }
200
201@@ -199,7 +193,8 @@
202
203 if (G_IS_OBJECT (priv->gsettings))
204 {
205- g_signal_handlers_disconnect_by_func (priv->gsettings, on_keybinding_changed, self);
206+ g_signal_handlers_disconnect_matched (priv->gsettings, G_SIGNAL_MATCH_FUNC,
207+ 0, 0, NULL, update_keybinding, NULL);
208 g_object_unref (priv->gsettings);
209 priv->gsettings = NULL;
210 }
211@@ -216,6 +211,8 @@
212 g_hash_table_destroy (priv->panel2entries_hash);
213
214 static_service = NULL;
215+
216+ G_OBJECT_CLASS (panel_service_parent_class)->finalize (object);
217 }
218
219 static void
220@@ -357,6 +354,42 @@
221 return entry;
222 }
223
224+static void
225+reinject_key_event_to_root_window (XIDeviceEvent *ev)
226+{
227+ XKeyEvent kev;
228+ kev.display = ev->display;
229+ kev.window = ev->root;
230+ kev.root = ev->root;
231+ kev.subwindow = None;
232+ kev.time = ev->time;
233+ kev.x = ev->event_x;
234+ kev.y = ev->event_x;
235+ kev.x_root = ev->root_x;
236+ kev.y_root = ev->root_y;
237+ kev.same_screen = True;
238+ kev.keycode = ev->detail;
239+ kev.state = ev->mods.base;
240+ kev.type = ev->evtype;
241+
242+ XSendEvent (kev.display, kev.root, True, KeyPressMask, (XEvent*) &kev);
243+ XFlush (kev.display);
244+}
245+
246+static gboolean
247+event_matches_keybinding (guint32 modifiers, KeySym key, KeyBinding *kb)
248+{
249+ if (modifiers == kb->modifiers && key != NoSymbol)
250+ {
251+ if (key == kb->key || key == kb->fallback)
252+ {
253+ return TRUE;
254+ }
255+ }
256+
257+ return FALSE;
258+}
259+
260 static GdkFilterReturn
261 event_filter (GdkXEvent *ev, GdkEvent *gev, PanelService *self)
262 {
263@@ -365,41 +398,65 @@
264 GdkFilterReturn ret = GDK_FILTER_CONTINUE;
265
266 if (!PANEL_IS_SERVICE (self))
267- {
268- g_warning ("%s: Invalid PanelService instance", G_STRLOC);
269- return ret;
270- }
271+ {
272+ g_warning ("%s: Invalid PanelService instance", G_STRLOC);
273+ return ret;
274+ }
275
276 if (!GTK_IS_WIDGET (self->priv->last_menu))
277 return ret;
278
279 /* Use XI2 to read the event data */
280 XGenericEventCookie *cookie = &e->xcookie;
281- if (cookie->type == GenericEvent)
282+ if (cookie->type != GenericEvent)
283+ return ret;
284+
285+ XIDeviceEvent *event = cookie->data;
286+ if (!event)
287+ return ret;
288+
289+ switch (event->evtype)
290 {
291- XIDeviceEvent *event = cookie->data;
292- if (!event)
293- return ret;
294-
295- if (event->evtype == XI_KeyPress)
296+ case XI_KeyPress:
297 {
298- if (event->mods.base == priv->toggle_modifiers && event->detail == priv->toggle_key)
299- {
300- if (GTK_IS_MENU (priv->last_menu))
301- gtk_menu_popdown (GTK_MENU (priv->last_menu));
302- }
303+ KeySym keysym = XkbKeycodeToKeysym (event->display, event->detail, 0, 0);
304+
305+ if (event_matches_keybinding (event->mods.base, keysym, &priv->menu_toggle) ||
306+ event_matches_keybinding (event->mods.base, keysym, &priv->show_dash) ||
307+ event_matches_keybinding (event->mods.base, keysym, &priv->show_hud))
308+ {
309+ if (GTK_IS_MENU (priv->last_menu))
310+ gtk_menu_popdown (GTK_MENU (priv->last_menu));
311+
312+ ret = GDK_FILTER_REMOVE;
313+ }
314+ else if (event->mods.base != GDK_CONTROL_MASK)
315+ {
316+ if (!IsModifierKey (keysym) && (event->mods.base != 0 || keysym == XK_Print))
317+ {
318+ if (GTK_IS_MENU (priv->last_menu))
319+ gtk_menu_popdown (GTK_MENU (priv->last_menu));
320+
321+ reinject_key_event_to_root_window (event);
322+ ret = GDK_FILTER_REMOVE;
323+ }
324+ }
325+
326+ break;
327 }
328
329- if (event->evtype == XI_ButtonPress)
330+ case XI_ButtonPress:
331 {
332 priv->pressed_entry = get_entry_at (self, event->root_x, event->root_y);
333 priv->use_event = (priv->pressed_entry == NULL);
334
335 if (priv->pressed_entry)
336 ret = GDK_FILTER_REMOVE;
337+
338+ break;
339 }
340
341- if (event->evtype == XI_ButtonRelease)
342+ case XI_ButtonRelease:
343 {
344 IndicatorObjectEntry *entry;
345 gboolean event_is_a_click = FALSE;
346@@ -469,6 +526,8 @@
347 ret = GDK_FILTER_REMOVE;
348 g_free (entry_id);
349 }
350+
351+ break;
352 }
353 }
354
355@@ -497,20 +556,28 @@
356 }
357
358 static void
359-panel_service_update_menu_keybinding (PanelService *self)
360-{
361- gchar *binding = g_settings_get_string (self->priv->gsettings, MENU_TOGGLE_KEYBINDING_KEY);
362-
363- KeyCode keycode = 0;
364- KeySym keysym = NoSymbol;
365- guint32 modifiers = 0;
366-
367+update_keybinding (GSettings *settings, const gchar *key, gpointer data)
368+{
369+ KeyBinding *kb = data;
370+ gchar *binding = g_settings_get_string (settings, key);
371+ parse_string_keybinding (binding, kb);
372+ g_free (binding);
373+}
374+
375+void
376+parse_string_keybinding (const char *str, KeyBinding *kb)
377+{
378+ kb->key = NoSymbol;
379+ kb->fallback = NoSymbol;
380+ kb->modifiers = 0;
381+
382+ gchar *binding = g_strdup (str);
383 gchar *keystart = (binding) ? strrchr (binding, '>') : NULL;
384
385 if (!keystart)
386 keystart = binding;
387
388- while (keystart && !g_ascii_isalnum (*keystart))
389+ while (keystart && *keystart != '\0' && !g_ascii_isalnum (*keystart))
390 keystart++;
391
392 gchar *keyend = keystart;
393@@ -519,51 +586,63 @@
394 keyend++;
395
396 if (keystart != keyend)
397- {
398- gchar *keystr = g_strndup (keystart, keyend-keystart);
399- keysym = XStringToKeysym (keystr);
400- g_free (keystr);
401- }
402-
403- if (keysym != NoSymbol)
404- {
405- Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
406- keycode = XKeysymToKeycode(dpy, keysym);
407-
408+ {
409+ gchar *keystr = g_strndup (keystart, keyend-keystart);
410+ kb->key = XStringToKeysym (keystr);
411+ g_free (keystr);
412+ }
413+ else
414+ {
415+ /* Parsing the case where we only have meta-keys */
416+ keyend = (binding) ? strrchr (binding, '>') : NULL;
417+ keyend = keyend ? keyend - 1 : NULL;
418+ keystart = keyend;
419+
420+ while (keystart && keystart > binding && g_ascii_isalnum (*keystart))
421+ keystart--;
422+
423+ if (keystart != keyend)
424+ {
425+ gchar *keystr = g_strndup (keystart+1, keyend-keystart);
426+ gchar *left = g_strconcat (keystr, "_L", NULL);
427+ kb->key = XStringToKeysym (left);
428+
429+ gchar *right = g_strconcat (keystr, "_R", NULL);
430+ kb->fallback = XStringToKeysym (right);
431+ g_free (left);
432+ g_free (right);
433+ g_free (keystr);
434+
435+ keystr = g_strndup (binding, keystart-binding);
436+ g_free (binding);
437+ binding = keystr;
438+ }
439+ }
440+
441+ if (kb->key != NoSymbol)
442+ {
443 if (g_strrstr (binding, "<Shift>"))
444 {
445- modifiers |= GDK_SHIFT_MASK;
446+ kb->modifiers |= ShiftMask;
447 }
448 if (g_strrstr (binding, "<Control>") || g_strrstr (binding, "<Primary>"))
449 {
450- modifiers |= GDK_CONTROL_MASK;
451+ kb->modifiers |= ControlMask;
452 }
453 if (g_strrstr (binding, "<Alt>") || g_strrstr (binding, "<Mod1>"))
454 {
455- modifiers |= GDK_MOD1_MASK;
456+ kb->modifiers |= AltMask;
457 }
458 if (g_strrstr (binding, "<Super>"))
459 {
460- modifiers |= GDK_SUPER_MASK;
461+ kb->modifiers |= SuperMask;
462 }
463 }
464
465- self->priv->toggle_key = keycode;
466- self->priv->toggle_modifiers = modifiers;
467-
468 g_free (binding);
469 }
470
471 static void
472-on_keybinding_changed (GSettings *settings, gchar *key, gpointer data)
473-{
474- g_return_if_fail (PANEL_IS_SERVICE (data));
475- PanelService *self = data;
476-
477- panel_service_update_menu_keybinding (self);
478-}
479-
480-static void
481 panel_service_init (PanelService *self)
482 {
483 PanelServicePrivate *priv;
484@@ -582,15 +661,20 @@
485
486 priv->gsettings = g_settings_new_with_path (COMPIZ_OPTION_SCHEMA, COMPIZ_OPTION_PATH);
487 g_signal_connect (priv->gsettings, "changed::"MENU_TOGGLE_KEYBINDING_KEY,
488- G_CALLBACK (on_keybinding_changed), self);
489-
490- panel_service_update_menu_keybinding (self);
491-
492- const gchar * upstartsession = g_getenv ("UPSTART_SESSION");
493+ G_CALLBACK (update_keybinding), &priv->menu_toggle);
494+ g_signal_connect (priv->gsettings, "changed::"SHOW_DASH_KEY,
495+ G_CALLBACK (update_keybinding), &priv->show_dash);
496+ g_signal_connect (priv->gsettings, "changed::"SHOW_HUD_KEY,
497+ G_CALLBACK (update_keybinding), &priv->show_hud);
498+
499+ update_keybinding (priv->gsettings, MENU_TOGGLE_KEYBINDING_KEY, &priv->menu_toggle);
500+ update_keybinding (priv->gsettings, SHOW_DASH_KEY, &priv->show_dash);
501+ update_keybinding (priv->gsettings, SHOW_HUD_KEY, &priv->show_hud);
502+
503+ const gchar *upstartsession = g_getenv ("UPSTART_SESSION");
504 if (upstartsession != NULL)
505 {
506- DBusConnection * conn = NULL;
507- conn = dbus_connection_open (upstartsession, NULL);
508+ DBusConnection *conn = dbus_connection_open (upstartsession, NULL);
509 if (conn != NULL)
510 {
511 priv->upstart = nih_dbus_proxy_new (NULL, conn,
512@@ -603,7 +687,6 @@
513
514 if (priv->upstart != NULL)
515 priv->upstart->auto_start = FALSE;
516-
517 }
518
519 static gboolean
520@@ -1040,11 +1123,13 @@
521 PanelService *self)
522 {
523 gchar *entry_id;
524-
525 g_return_if_fail (PANEL_IS_SERVICE (self));
526- if (entry == NULL)
527+
528+ if (!entry)
529 {
530- g_warning ("on_indicator_menu_show() called with a NULL entry");
531+ if (GTK_IS_MENU (self->priv->last_menu))
532+ gtk_menu_popdown (GTK_MENU (self->priv->last_menu));
533+
534 return;
535 }
536
537@@ -1060,11 +1145,11 @@
538 PanelService *self)
539 {
540 gchar *entry_id;
541-
542 g_return_if_fail (PANEL_IS_SERVICE (self));
543- if (entry == NULL)
544+
545+ if (!entry)
546 {
547- g_warning ("on_indicator_menu_show_now_changed() called with a NULL entry");
548+ g_warning ("%s called with a NULL entry", G_STRFUNC);
549 return;
550 }
551
552@@ -1443,12 +1528,7 @@
553 priv->last_y = 0;
554 priv->last_menu_button = 0;
555
556- g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
557- g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
558-
559 priv->last_menu = NULL;
560- priv->last_menu_id = 0;
561- priv->last_menu_move_id = 0;
562 priv->last_entry = NULL;
563 priv->last_left = 0;
564 priv->last_right = 0;
565@@ -1857,13 +1937,10 @@
566 priv->last_x = 0;
567 priv->last_y = 0;
568
569- g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
570- g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
571+ g_signal_handlers_disconnect_by_data (priv->last_menu, self);
572
573 priv->last_entry = NULL;
574 priv->last_menu = NULL;
575- priv->last_menu_id = 0;
576- priv->last_menu_move_id = 0;
577 priv->last_menu_button = 0;
578 }
579
580@@ -1905,10 +1982,9 @@
581 priv->last_x = x;
582 priv->last_y = y;
583 priv->last_menu_button = button;
584- priv->last_menu_id = g_signal_connect (priv->last_menu, "hide",
585- G_CALLBACK (on_active_menu_hidden), self);
586- priv->last_menu_move_id = g_signal_connect_after (priv->last_menu, "move-current",
587- G_CALLBACK (on_active_menu_move_current), self);
588+ g_signal_connect (priv->last_menu, "hide", G_CALLBACK (on_active_menu_hidden), self);
589+ g_signal_connect_after (priv->last_menu, "move-current",
590+ G_CALLBACK (on_active_menu_move_current), self);
591
592 gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, 0, CurrentTime);
593 gtk_menu_reposition (priv->last_menu);
594
595=== modified file 'tests/autopilot/unity/emulators/window_manager.py'
596--- tests/autopilot/unity/emulators/window_manager.py 2012-07-04 02:37:23 +0000
597+++ tests/autopilot/unity/emulators/window_manager.py 2013-10-03 15:16:29 +0000
598@@ -25,6 +25,14 @@
599 """Returns a tuple of (x,y,w,h) for the screen."""
600 return (self.x, self.y, self.width, self.height)
601
602+ def initiate_spread(self):
603+ self.keybinding("spread/start")
604+ self.scale_active.wait_for(True)
605+
606+ def terminate_spread(self):
607+ self.keybinding("spread/cancel")
608+ self.scale_active.wait_for(False)
609+
610 def enter_show_desktop(self):
611 if not self.showdesktop_active:
612 logger.info("Entering show desktop mode.")
613
614=== modified file 'tests/autopilot/unity/tests/test_panel.py'
615--- tests/autopilot/unity/tests/test_panel.py 2013-05-10 05:16:07 +0000
616+++ tests/autopilot/unity/tests/test_panel.py 2013-10-03 15:16:29 +0000
617@@ -919,7 +919,6 @@
618
619 self.assertThat(self.panel.menus_shown, Eventually(Equals(False)))
620
621-
622 def test_menus_dont_show_with_hud(self):
623 """Tests that menus are not showing when opening the HUD."""
624 self.open_new_application_window("Text Editor", maximized=True)
625@@ -982,6 +981,17 @@
626 self.assertThat(menu_entry.menu_x, Eventually(Equals(0)))
627 self.assertThat(menu_entry.menu_y, Eventually(Equals(0)))
628
629+ def test_menu_closes_on_new_focused_application(self):
630+ """Clicking outside an open menu must close it."""
631+ menu_entry = self.open_app_and_get_menu_entry()
632+ self.mouse_open_indicator(menu_entry)
633+
634+ # This assert is for timing purposes only:
635+ self.assertThat(menu_entry.active, Eventually(Equals(True)))
636+
637+ self.open_new_application_window("Text Editor")
638+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(Equals(None)))
639+
640 def test_indicator_opens_when_dash_is_open(self):
641 """When the dash is open and a click is on an indicator the dash
642 must close and the indicator must open.
643@@ -1011,9 +1021,10 @@
644 def test_panel_first_menu_show_works(self):
645 """Pressing the open-menus keybinding must open the first indicator."""
646 self.open_new_application_window("Calculator")
647- sleep(1)
648+ refresh_fn = lambda: len(self.panel.menus.get_entries())
649+ self.assertThat(refresh_fn, Eventually(GreaterThan(0)))
650+ self.addCleanup(self.keyboard.press_and_release, "Escape")
651 self.keybinding("panel/open_first_menu")
652- self.addCleanup(self.keyboard.press_and_release, "Escape")
653
654 open_indicator = self.get_active_indicator()
655 expected_indicator = self.panel.get_indicator_entries(include_hidden_menus=True)[0]
656@@ -1025,9 +1036,10 @@
657 def test_panel_menu_accelerators_work(self):
658 """Pressing a valid menu accelerator must open the correct menu item."""
659 self.open_new_application_window("Text Editor")
660- sleep(1)
661+ refresh_fn = lambda: len(self.panel.menus.get_entries())
662+ self.assertThat(refresh_fn, Eventually(GreaterThan(0)))
663+ self.addCleanup(self.keyboard.press_and_release, "Escape")
664 self.keyboard.press_and_release("Alt+f")
665- self.addCleanup(self.keyboard.press_and_release, "Escape")
666
667 open_indicator = self.get_active_indicator()
668 self.assertThat(open_indicator.label, Eventually(Equals("_File")))
669
670=== modified file 'tests/autopilot/unity/tests/test_spread.py'
671--- tests/autopilot/unity/tests/test_spread.py 2013-05-01 00:18:11 +0000
672+++ tests/autopilot/unity/tests/test_spread.py 2013-10-03 15:16:29 +0000
673@@ -36,8 +36,8 @@
674
675 def initiate_spread_for_screen(self):
676 """Initiate the Spread for all windows"""
677- self.addCleanup(self.keybinding, "spread/cancel")
678- self.keybinding("spread/start")
679+ self.addCleanup(self.unity.window_manager.terminate_spread)
680+ self.unity.window_manager.initiate_spread()
681 self.assertThat(self.unity.window_manager.scale_active, Eventually(Equals(True)))
682
683 def initiate_spread_for_application(self, desktop_id):
684@@ -46,7 +46,7 @@
685 self.assertThat(icon, NotEquals(None))
686 launcher = self.unity.launcher.get_launcher_for_monitor(self.display.get_primary_screen())
687
688- self.addCleanup(self.keybinding, "spread/cancel")
689+ self.addCleanup(self.unity.window_manager.terminate_spread)
690 launcher.click_launcher_icon(icon)
691 self.assertThat(self.unity.window_manager.scale_active_for_group, Eventually(Equals(True)))
692
693
694=== modified file 'tests/autopilot/unity/tests/test_wm_keybindings.py'
695--- tests/autopilot/unity/tests/test_wm_keybindings.py 2013-09-16 13:59:22 +0000
696+++ tests/autopilot/unity/tests/test_wm_keybindings.py 2013-10-03 15:16:29 +0000
697@@ -9,17 +9,70 @@
698 from __future__ import absolute_import
699
700 from autopilot.matchers import Eventually
701-from testtools.matchers import Equals, NotEquals
702+from testtools.matchers import Equals, NotEquals, GreaterThan
703 from unity.tests import UnityTestCase
704+from unity.emulators import switcher
705
706 class WindowManagerKeybindings(UnityTestCase):
707 """Window Manager keybindings tests"""
708
709+ def setUp(self):
710+ super(WindowManagerKeybindings, self).setUp()
711+
712+ def open_panel_menu(self):
713+ panel = self.unity.panels.get_panel_for_monitor(0)
714+ self.assertThat(lambda: len(panel.menus.get_entries()), Eventually(GreaterThan(0)))
715+ self.addCleanup(self.keyboard.press_and_release, "Escape")
716+ self.keybinding("panel/open_first_menu")
717+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(NotEquals(None)))
718+
719+ def test_dash_shows_on_menus_opened(self):
720+ self.process_manager.start_app_window("Calculator")
721+ self.open_panel_menu()
722+ self.addCleanup(self.unity.dash.ensure_hidden)
723+ self.unity.dash.ensure_visible()
724+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(Equals(None)))
725+
726+ def test_hud_shows_on_menus_opened(self):
727+ self.process_manager.start_app_window("Calculator")
728+ self.open_panel_menu()
729+ self.addCleanup(self.unity.hud.ensure_hidden)
730+ self.unity.hud.ensure_visible()
731+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(Equals(None)))
732+
733+ def test_switcher_shows_on_menus_opened(self):
734+ self.process_manager.start_app_window("Calculator")
735+ self.open_panel_menu()
736+ self.addCleanup(self.unity.switcher.terminate)
737+ self.unity.switcher.initiate()
738+ self.assertProperty(self.unity.switcher, mode=switcher.SwitcherMode.NORMAL)
739+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(Equals(None)))
740+
741+ def test_shortcut_hints_shows_on_menus_opened(self):
742+ self.process_manager.start_app_window("Calculator")
743+ self.open_panel_menu()
744+ self.addCleanup(self.unity.shortcut_hint.ensure_hidden)
745+ self.unity.shortcut_hint.show()
746+ self.assertThat(self.unity.shortcut_hint.visible, Eventually(Equals(True)))
747+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(Equals(None)))
748+
749+ def test_spread_shows_on_menus_opened(self):
750+ self.process_manager.start_app_window("Calculator")
751+ self.open_panel_menu()
752+ self.addCleanup(self.unity.window_manager.terminate_spread)
753+ self.unity.window_manager.initiate_spread()
754+ self.assertThat(self.unity.window_manager.scale_active, Eventually(Equals(True)))
755+ self.assertThat(self.unity.panels.get_active_indicator, Eventually(Equals(None)))
756+
757+
758+class WindowManagerKeybindingsForWindowHandling(WindowManagerKeybindings):
759+ """Window Manager keybindings tests for handling a window"""
760+
761 scenarios = [('Restored Window', {'start_restored': True}),
762 ('Maximized Window', {'start_restored': False})]
763
764 def setUp(self):
765- super(WindowManagerKeybindings, self).setUp()
766+ super(WindowManagerKeybindingsForWindowHandling, self).setUp()
767 self.start_test_window()
768
769 def keybinding_if_not_minimized(self, keybinding):
770
771=== modified file 'tests/data/external.gschema.xml'
772--- tests/data/external.gschema.xml 2013-07-12 23:24:53 +0000
773+++ tests/data/external.gschema.xml 2013-10-03 15:16:29 +0000
774@@ -5,15 +5,17 @@
775 <schema path="/apps/indicator-session/" id="com.canonical.indicator.session">
776 <key type="b" name="suppress-logout-restart-shutdown">
777 <default>false</default>
778- <summary>Suppress the dialog to confirm logout, restart and shutdown action</summary>
779- <description>Whether or not to show confirmation dialogs for logout, restart and shutdown actions.</description>
780 </key>
781 </schema>
782 <schema id="org.compiz.unityshell" gettext-domain="compiz">
783 <key type="s" name="panel-first-menu">
784 <default>'&lt;Alt&gt;F10'</default>
785- <summary>Key to open the first panel menu</summary>
786- <description>Opens the first indicator menu of the Panel, allowing keyboard navigation thereafter.</description>
787+ </key>
788+ <key type="s" name="show-launcher">
789+ <default>'&lt;Super&gt;'</default>
790+ </key>
791+ <key type="s" name="show-hud">
792+ <default>'&lt;Alt&gt;'</default>
793 </key>
794 </schema>
795 </schemalist>
796
797=== modified file 'tests/test_panel_service.cpp'
798--- tests/test_panel_service.cpp 2013-07-25 11:07:06 +0000
799+++ tests/test_panel_service.cpp 2013-10-03 15:16:29 +0000
800@@ -22,6 +22,7 @@
801 #include <UnityCore/GLibWrapper.h>
802 #include <UnityCore/Variant.h>
803 #include "panel-service.h"
804+#include "panel-service-private.h"
805 #include "mock_indicator_object.h"
806
807 using namespace testing;
808@@ -418,4 +419,134 @@
809 EXPECT_TRUE(called);
810 }
811
812+TEST(TestPanelServiceCompizShortcutParsing, Null)
813+{
814+ KeyBinding kb;
815+ parse_string_keybinding(NULL, &kb);
816+
817+ EXPECT_EQ(NoSymbol, kb.key);
818+ EXPECT_EQ(NoSymbol, kb.fallback);
819+ EXPECT_EQ(0, kb.modifiers);
820+}
821+
822+TEST(TestPanelServiceCompizShortcutParsing, Empty)
823+{
824+ KeyBinding kb;
825+ parse_string_keybinding("", &kb);
826+
827+ EXPECT_EQ(NoSymbol, kb.key);
828+ EXPECT_EQ(NoSymbol, kb.fallback);
829+ EXPECT_EQ(0, kb.modifiers);
830+}
831+
832+TEST(TestPanelServiceCompizShortcutParsing, SimpleKey)
833+{
834+ KeyBinding kb;
835+ parse_string_keybinding("U", &kb);
836+
837+ EXPECT_EQ(XK_U, kb.key);
838+ EXPECT_EQ(NoSymbol, kb.fallback);
839+ EXPECT_EQ(0, kb.modifiers);
840+}
841+
842+TEST(TestPanelServiceCompizShortcutParsing, ControlCombo)
843+{
844+ KeyBinding kb;
845+ parse_string_keybinding("<Control>F1", &kb);
846+
847+ EXPECT_EQ(XK_F1, kb.key);
848+ EXPECT_EQ(NoSymbol, kb.fallback);
849+ EXPECT_EQ(ControlMask, kb.modifiers);
850+}
851+
852+TEST(TestPanelServiceCompizShortcutParsing, AltCombo)
853+{
854+ KeyBinding kb;
855+ parse_string_keybinding("<Alt>F2", &kb);
856+
857+ EXPECT_EQ(XK_F2, kb.key);
858+ EXPECT_EQ(NoSymbol, kb.fallback);
859+ EXPECT_EQ(AltMask, kb.modifiers);
860+}
861+
862+TEST(TestPanelServiceCompizShortcutParsing, ShiftCombo)
863+{
864+ KeyBinding kb;
865+ parse_string_keybinding("<Shift>F3", &kb);
866+
867+ EXPECT_EQ(XK_F3, kb.key);
868+ EXPECT_EQ(NoSymbol, kb.fallback);
869+ EXPECT_EQ(ShiftMask, kb.modifiers);
870+}
871+
872+TEST(TestPanelServiceCompizShortcutParsing, SuperCombo)
873+{
874+ KeyBinding kb;
875+ parse_string_keybinding("<Super>F4", &kb);
876+
877+ EXPECT_EQ(XK_F4, kb.key);
878+ EXPECT_EQ(NoSymbol, kb.fallback);
879+ EXPECT_EQ(SuperMask, kb.modifiers);
880+}
881+
882+TEST(TestPanelServiceCompizShortcutParsing, FullCombo)
883+{
884+ KeyBinding kb;
885+ parse_string_keybinding("<Control><Alt><Shift><Super>Escape", &kb);
886+
887+ EXPECT_EQ(XK_Escape, kb.key);
888+ EXPECT_EQ(NoSymbol, kb.fallback);
889+ EXPECT_EQ(ControlMask|AltMask|ShiftMask|SuperMask, kb.modifiers);
890+}
891+
892+TEST(TestPanelServiceCompizShortcutParsing, MetaKeyControl)
893+{
894+ KeyBinding kb;
895+ parse_string_keybinding("<Control>", &kb);
896+
897+ EXPECT_EQ(XK_Control_L, kb.key);
898+ EXPECT_EQ(XK_Control_R, kb.fallback);
899+ EXPECT_EQ(0, kb.modifiers);
900+}
901+
902+TEST(TestPanelServiceCompizShortcutParsing, MetaKeyAlt)
903+{
904+ KeyBinding kb;
905+ parse_string_keybinding("<Alt>", &kb);
906+
907+ EXPECT_EQ(XK_Alt_L, kb.key);
908+ EXPECT_EQ(XK_Alt_R, kb.fallback);
909+ EXPECT_EQ(0, kb.modifiers);
910+}
911+
912+TEST(TestPanelServiceCompizShortcutParsing, MetaKeyShift)
913+{
914+ KeyBinding kb;
915+ parse_string_keybinding("<Shift>", &kb);
916+
917+ EXPECT_EQ(XK_Shift_L, kb.key);
918+ EXPECT_EQ(XK_Shift_R, kb.fallback);
919+ EXPECT_EQ(0, kb.modifiers);
920+}
921+
922+TEST(TestPanelServiceCompizShortcutParsing, MetaKeySuper)
923+{
924+ KeyBinding kb;
925+ parse_string_keybinding("<Super>", &kb);
926+
927+ EXPECT_EQ(XK_Super_L, kb.key);
928+ EXPECT_EQ(XK_Super_R, kb.fallback);
929+ EXPECT_EQ(0, kb.modifiers);
930+}
931+
932+TEST(TestPanelServiceCompizShortcutParsing, MetaKeyMix)
933+{
934+ KeyBinding kb;
935+ parse_string_keybinding("<Control><Alt><Super>", &kb);
936+
937+ EXPECT_EQ(XK_Super_L, kb.key);
938+ EXPECT_EQ(XK_Super_R, kb.fallback);
939+ EXPECT_EQ(ControlMask|AltMask, kb.modifiers);
940+}
941+
942 } // anonymous namespace