Merge lp:~unity-team/unity/3v1n0-quick-alt+tab-fixes into lp:unity

Proposed by Marco Trevisan (Treviño)
Status: Merged
Approved by: Tim Penhey
Approved revision: 1822
Merged at revision: 2282
Proposed branch: lp:~unity-team/unity/3v1n0-quick-alt+tab-fixes
Merge into: lp:unity
Diff against target: 815 lines (+336/-97)
16 files modified
manual-tests/Switcher.txt (+1/-1)
plugins/unityshell/src/BamfLauncherIcon.cpp (+45/-30)
plugins/unityshell/src/Launcher.cpp (+3/-1)
plugins/unityshell/src/PluginAdapter.cpp (+91/-47)
plugins/unityshell/src/PluginAdapter.h (+4/-1)
plugins/unityshell/src/SwitcherController.cpp (+1/-0)
plugins/unityshell/src/UScreen.cpp (+9/-3)
plugins/unityshell/src/UScreen.h (+1/-0)
plugins/unityshell/src/WindowManager.cpp (+11/-1)
plugins/unityshell/src/WindowManager.h (+5/-2)
plugins/unityshell/src/unityshell.cpp (+8/-5)
tests/autopilot/autopilot/emulators/bamf.py (+11/-2)
tests/autopilot/autopilot/emulators/unity/switcher.py (+3/-0)
tests/autopilot/autopilot/tests/__init__.py (+12/-0)
tests/autopilot/autopilot/tests/test_launcher.py (+65/-0)
tests/autopilot/autopilot/tests/test_switcher.py (+66/-4)
To merge this branch: bzr merge lp:~unity-team/unity/3v1n0-quick-alt+tab-fixes
Reviewer Review Type Date Requested Status
Thomi Richards (community) quality Approve
Tim Penhey (community) Approve
Sam Spilsbury Pending
Alex Launi quality Pending
Review via email: mp+102028@code.launchpad.net

This proposal supersedes a proposal from 2012-04-05.

Commit message

Fix the behaviour of alt-tab and clicking on the launcher icon to just raise the most recently used window, not all for that app.

BamfLauncherIcon's activation related code has been updated to be more multimonitor aware (for free I've fixed also some bugs that caused the windows not to be put in spread mode in multi-monitor), and to support both the "Launcher only on Primary Monitor" and "Launcher on all monitors" options.

PluginAdapter's FocusWindowGroup method has been updated to optionally only unminimize / raise and activate only the top window. This code would have been more optimized using a reverse iterator to fetch the top_window, but not to change the whole logic and to allow to keep the previous behavior (that initially we wanted for "long alt+tab") without duplicating code, I've just hacked that.
Implemented also GetWindowMonitor to workaround the mismatch we had with the compiz' window->outputDevice() and the UScreen values that now we use in the whole unity.

Screencast of the fixed version: http://ubuntuone.com/7YaWciQnaZHfzr35asSz0N

Description of the change

== Problem ==
Launcher, Alt-Tab - clicking on launcher item or selecting a app in Alt-Tab raises all app windows, not just most recently focused.

== Fix ==
BamfLauncherIcon's activation related code has been updated to be more multimonitor aware (for free I've fixed also some bugs that caused the windows not to be put in spread mode in multi-monitor), and to support both the "Launcher only on Primary Monitor" and "Launcher on all monitors" options.

PluginAdapter's FocusWindowGroup method has been updated to optionally only unminimize / raise and activate only the top window. This code would have been more optimized using a reverse iterator to fetch the top_window, but not to change the whole logic and to allow to keep the previous behavior (that initially we wanted for "long alt+tab") without duplicating code, I've just hacked that.
Implemented also GetWindowMonitor to workaround the mismatch we had with the compiz' window->outputDevice() and the UScreen values that now we use in the whole unity.

Screencast of the fixed version: http://ubuntuone.com/7YaWciQnaZHfzr35asSz0N

== Test ==
There are autopilot test for both bugs.

Thanks to Brandon for taking care of merging the old lp:~3v1n0/unity/quick-alt+tab-fixes with trunk and for the AP tests that I used as my base.

UNBLOCK

To post a comment you must log in.
Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote : Posted in a previous version of this proposal

30 - if ((mapped && wm->IsWindowMapped(xid)) || !mapped)
31 + if ((mapped && wm->IsWindowMapped(xid) && !bamf_window_get_transient(BAMF_WINDOW(view))) || !mapped)
32 {

About this, I think I included that change mostly for testing purposes, I'm not sure it's actually wanted so move it out.

I didn't work too much on my branch lately since I didn't know if that was still the wanted desig, but if now it is, I'll be happy to get this merged :)

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote : Posted in a previous version of this proposal

I ran all the autopilot test that I thought these changes would effect.

Launcher Autopilot tests:
Ran 43 tests in 296.560s
OK

Switcher Autopilot tests:
Ran 21 tests in 187.666s
OK

Dash Autopilot tests:
Ran 32 tests in 225.549s
OK

Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

Tests OK, no iconic window regressions (which is what I was a little concerned about)

review: Approve
Revision history for this message
Tim Penhey (thumper) wrote : Posted in a previous version of this proposal

Clicking on the launcher to reveal only the top most one works.

However the quick alt-tab one does not work.

If I have two terminal windows and one gedit. Focus gedit and quick alt-tab, it raises both terminals.

review: Needs Fixing
Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote : Posted in a previous version of this proposal

Probably it is caused by the fact that now we use a very small delay for the Alt+Tab.
A workaround could be to use another time reference to consider an Alt+Tab quick, more than the only window visibility.

I mean, even if the window is shown for 100ms or less, the Alt+Tab is surely a quick one (as there's no time to look to the content of the window).

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote : Posted in a previous version of this proposal

I just ended up making a timer called quick_tab_timer_, which will go for 200ms if you hold on to alt for any longer it makes quick_tab = false. Seems like a nice and customizable way.

Revision history for this message
Alex Launi (alexlauni) wrote : Posted in a previous version of this proposal

It shouldn't be too difficult to automate the manual tests. Please do not merge this with the manual tests.

review: Needs Fixing (quality)
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote : Posted in a previous version of this proposal

@Alex

Sorry about having the manual test in there. I was trying to get this branch in 5.10 and was rushing to get it done. Never a good sign when it comes to quality. There are now autopilot test for each bug!

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote : Posted in a previous version of this proposal

So I guess diff continutes to complain about there not being a new line...I even took the trunk version of Switcher.txt and replaced it and it still gives that problem...

Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote : Posted in a previous version of this proposal

I've just checked this code again, there's a problem with the minimized windows when using the launcher icon.
Don't worry about that Brandon, I'll take care of that ;)

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote : Posted in a previous version of this proposal

Yeah, I just saw that. At lease we know what design wants now :). Thank you!

1817. By Marco Trevisan (Treviño)

PluginAdapter: avoid to raise and unminimize top_window if already done

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

Awesome work! Confirmed everything I could with only 1 monitor, bot AP test pass as well as doing a lot of manual testing :)

1818. By Tim Penhey

Tweaks to the test.

1819. By Tim Penhey

More AP test tweaks.

1820. By Tim Penhey

Assert that the visible window stack is what we expect.

1821. By Tim Penhey

Add window stack assertions to the switcher tests.

1822. By Tim Penhey

Test the alt-tab appears on the monitor that has window focus.

Revision history for this message
Tim Penhey (thumper) wrote :

The code looks fine, and I've tested the behaviour and tweaked the AP tests. Thomi will review the tests.

review: Approve
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

+1

review: Approve (quality)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'manual-tests/Switcher.txt'
2--- manual-tests/Switcher.txt 2012-03-23 15:14:42 +0000
3+++ manual-tests/Switcher.txt 2012-04-16 01:06:19 +0000
4@@ -37,4 +37,4 @@
5 When you press Alt+Tab to switch window, the application title in the top left
6 of the panel should change inline with changes in alt-tab focus.
7 Also the menus shouldn't ever show. Even if the mouse has been left over
8- the panel when starting the Alt+Tab.
9\ No newline at end of file
10+ the panel when starting the Alt+Tab.
11
12=== modified file 'plugins/unityshell/src/BamfLauncherIcon.cpp'
13--- plugins/unityshell/src/BamfLauncherIcon.cpp 2012-04-10 03:57:39 +0000
14+++ plugins/unityshell/src/BamfLauncherIcon.cpp 2012-04-16 01:06:19 +0000
15@@ -182,15 +182,15 @@
16 void BamfLauncherIcon::ActivateLauncherIcon(ActionArg arg)
17 {
18 SimpleLauncherIcon::ActivateLauncherIcon(arg);
19- bool scaleWasActive = WindowManager::Default()->IsScaleActive();
20- GList *l;
21+ WindowManager* wm = WindowManager::Default();
22+ bool scaleWasActive = wm->IsScaleActive();
23
24 bool active = IsActive();
25 bool user_visible = IsRunning();
26
27 if (arg.target && OwnsWindow(arg.target))
28 {
29- WindowManager::Default()->Activate(arg.target);
30+ wm->Activate(arg.target);
31 return;
32 }
33
34@@ -204,29 +204,48 @@
35 user_visible = bamf_view_user_visible(bamf_view);
36
37 bool any_visible = false;
38- GList *children = bamf_view_get_children(bamf_view);
39+ bool any_mapped = false;
40+ bool any_on_monitor = (arg.monitor < 0);
41+ int active_monitor = arg.monitor;
42+ GList* children = bamf_view_get_children(bamf_view);
43
44- for (l = children; l; l = l->next)
45+ for (GList* l = children; l; l = l->next)
46 {
47 if (!BAMF_IS_WINDOW(l->data))
48 continue;
49
50- Window xid = bamf_window_get_xid(static_cast<BamfWindow*>(l->data));
51+ auto view = static_cast<BamfView*>(l->data);
52+ auto win = static_cast<BamfWindow*>(l->data);
53+ Window xid = bamf_window_get_xid(win);
54
55- if (!any_visible && WindowManager::Default()->IsWindowOnCurrentDesktop(xid))
56+ if (!any_visible && wm->IsWindowOnCurrentDesktop(xid))
57 {
58 any_visible = true;
59 }
60
61- if (active && !WindowManager::Default()->IsWindowMapped(xid))
62- {
63- active = false;
64+ if (!any_mapped && wm->IsWindowMapped(xid))
65+ {
66+ any_mapped = true;
67+ }
68+
69+ if (!any_on_monitor && bamf_window_get_monitor(win) == arg.monitor &&
70+ wm->IsWindowMapped(xid) && wm->IsWindowVisible(xid))
71+ {
72+ any_on_monitor = true;
73+ }
74+
75+ if (bamf_view_is_active(view))
76+ {
77+ active_monitor = bamf_window_get_monitor(win);
78 }
79 }
80
81 g_list_free(children);
82
83- if (!any_visible)
84+ if (!any_visible || !any_mapped)
85+ active = false;
86+
87+ if (any_on_monitor && arg.monitor >= 0 && active_monitor != arg.monitor)
88 active = false;
89 }
90
91@@ -245,7 +264,7 @@
92
93 if (scaleWasActive)
94 {
95- WindowManager::Default()->TerminateScale();
96+ wm->TerminateScale();
97 }
98
99 SetQuirk(QUIRK_STARTING, true);
100@@ -257,7 +276,7 @@
101 {
102 if (scaleWasActive) // #5 above
103 {
104- WindowManager::Default()->TerminateScale();
105+ wm->TerminateScale();
106 Focus(arg);
107 }
108 else // #2 above
109@@ -272,7 +291,7 @@
110 {
111 if (scaleWasActive) // #4 above
112 {
113- WindowManager::Default()->TerminateScale();
114+ wm->TerminateScale();
115 Focus(arg);
116 if (arg.source != ActionArg::SWITCHER)
117 Spread(true, 0, false);
118@@ -290,9 +309,9 @@
119
120 std::vector<Window> BamfLauncherIcon::GetWindows(WindowFilterMask filter, int monitor)
121 {
122+ WindowManager* wm = WindowManager::Default();
123 std::vector<Window> results;
124 GList* children, *l;
125- WindowManager *wm = WindowManager::Default();
126
127 monitor = (filter & WindowFilter::ON_ALL_MONITORS) ? -1 : monitor;
128 bool mapped = (filter & WindowFilter::MAPPED);
129@@ -546,17 +565,16 @@
130
131 void BamfLauncherIcon::Focus(ActionArg arg)
132 {
133- GList* children, *l;
134 bool any_urgent = false;
135 bool any_visible = false;
136 bool any_user_visible = false;
137-
138- children = bamf_view_get_children(BAMF_VIEW(_bamf_app.RawPtr()));
139+ WindowManager* wm = WindowManager::Default();
140
141 std::vector<Window> windows;
142+ GList* children = bamf_view_get_children(BAMF_VIEW(_bamf_app.RawPtr()));
143
144 /* get the list of windows */
145- for (l = children; l; l = l->next)
146+ for (GList* l = children; l; l = l->next)
147 {
148 if (!BAMF_IS_WINDOW(l->data))
149 continue;
150@@ -590,32 +608,29 @@
151 windows.push_back(xid);
152 }
153
154- if (WindowManager::Default()->IsWindowOnCurrentDesktop(xid) &&
155- WindowManager::Default()->IsWindowVisible(xid))
156+ if (wm->IsWindowOnCurrentDesktop(xid) && wm->IsWindowVisible(xid))
157 {
158 any_visible = true;
159 }
160 }
161+
162 g_list_free(children);
163
164+ auto visibility = WindowManager::FocusVisibility::OnlyVisible;
165+
166 if (arg.source != ActionArg::SWITCHER)
167 {
168 if (any_visible)
169 {
170- WindowManager::Default()->FocusWindowGroup(windows,
171- WindowManager::FocusVisibility::ForceUnminimizeInvisible, arg.monitor);
172+ visibility = WindowManager::FocusVisibility::ForceUnminimizeInvisible;
173 }
174 else
175 {
176- WindowManager::Default()->FocusWindowGroup(windows,
177- WindowManager::FocusVisibility::ForceUnminimizeOnCurrentDesktop, arg.monitor);
178+ visibility = WindowManager::FocusVisibility::ForceUnminimizeOnCurrentDesktop;
179 }
180 }
181- else
182- {
183- WindowManager::Default()->FocusWindowGroup(windows,
184- WindowManager::FocusVisibility::OnlyVisible, arg.monitor);
185- }
186+
187+ wm->FocusWindowGroup(windows, visibility, arg.monitor);
188 }
189
190 bool BamfLauncherIcon::Spread(bool current_desktop, int state, bool force)
191
192=== modified file 'plugins/unityshell/src/Launcher.cpp'
193--- plugins/unityshell/src/Launcher.cpp 2012-04-10 01:41:16 +0000
194+++ plugins/unityshell/src/Launcher.cpp 2012-04-16 01:06:19 +0000
195@@ -2553,7 +2553,9 @@
196
197 if (GetActionState() == ACTION_NONE)
198 {
199- _icon_mouse_down->mouse_click.emit(nux::GetEventButton(button_flags), monitor);
200+ /* This will inform the icon if the action is valid for all the monitors */
201+ int action_monitor = options()->show_for_all ? -1 : monitor;
202+ _icon_mouse_down->mouse_click.emit(nux::GetEventButton(button_flags), action_monitor);
203 }
204 }
205
206
207=== modified file 'plugins/unityshell/src/PluginAdapter.cpp'
208--- plugins/unityshell/src/PluginAdapter.cpp 2012-04-02 10:45:47 +0000
209+++ plugins/unityshell/src/PluginAdapter.cpp 2012-04-16 01:06:19 +0000
210@@ -20,6 +20,7 @@
211 #include <glib.h>
212 #include <sstream>
213 #include "PluginAdapter.h"
214+#include "UScreen.h"
215
216 #include <NuxCore/Logger.h>
217
218@@ -392,6 +393,12 @@
219 }
220
221 // WindowManager implementation
222+guint32
223+PluginAdapter::GetActiveWindow()
224+{
225+ return m_Screen->activeWindow();
226+}
227+
228 bool
229 PluginAdapter::IsWindowMaximized(guint xid)
230 {
231@@ -618,11 +625,12 @@
232 }
233
234 void
235-PluginAdapter::FocusWindowGroup(std::vector<Window> window_ids, FocusVisibility focus_visibility, int monitor)
236+PluginAdapter::FocusWindowGroup(std::vector<Window> window_ids, FocusVisibility focus_visibility, int monitor, bool only_top_win)
237 {
238 CompPoint target_vp = m_Screen->vp();
239- CompWindow* top_window = NULL;
240- CompWindow* top_window_on_monitor = NULL;
241+ CompWindow* top_window = nullptr;
242+ CompWindow* top_monitor_win = nullptr;
243+
244 bool any_on_current = false;
245 bool any_mapped = false;
246 bool any_mapped_on_current = false;
247@@ -661,11 +669,12 @@
248
249 if (!any_on_current)
250 {
251- for (auto it = windows.rbegin(); it != windows.rend(); it++)
252+ for (auto it = windows.rbegin(); it != windows.rend(); ++it)
253 {
254- if ((any_mapped && !(*it)->minimized()) || !any_mapped)
255+ CompWindow* win = *it;
256+ if ((any_mapped && !win->minimized()) || !any_mapped)
257 {
258- target_vp = (*it)->defaultViewport();
259+ target_vp = win->defaultViewport();
260 break;
261 }
262 }
263@@ -675,46 +684,64 @@
264 {
265 if (win->defaultViewport() == target_vp)
266 {
267- /* Any window which is actually unmapped is
268- * not going to be accessible by either switcher
269- * or scale, so unconditionally unminimize those
270- * windows when the launcher icon is activated */
271- if ((focus_visibility == WindowManager::FocusVisibility::ForceUnminimizeOnCurrentDesktop &&
272- target_vp == m_Screen->vp()) ||
273- (focus_visibility == WindowManager::FocusVisibility::ForceUnminimizeInvisible &&
274- win->mapNum () == 0))
275- {
276- bool is_mapped = win->mapNum () != 0;
277- top_window = win;
278- if (monitor >= 0 && win->outputDevice() == monitor)
279- top_window_on_monitor = win;
280- win->unminimize ();
281-
282- forced_unminimize = true;
283-
284- /* Initially minimized windows dont get raised */
285- if (!is_mapped)
286- win->raise ();
287- }
288- else if ((any_mapped_on_current && !win->minimized()) || !any_mapped_on_current)
289- {
290- if (!forced_unminimize || target_vp == m_Screen->vp())
291- {
292- win->raise();
293- top_window = win;
294- if (monitor >= 0 && win->outputDevice() == monitor)
295- top_window_on_monitor = win;
296- }
297- }
298- }
299- }
300-
301- if (monitor > 0 && top_window_on_monitor)
302- {
303- top_window_on_monitor->activate();
304- }
305- else if (top_window)
306- {
307+ int win_monitor = GetWindowMonitor(win->id());
308+
309+ /* Any window which is actually unmapped is
310+ * not going to be accessible by either switcher
311+ * or scale, so unconditionally unminimize those
312+ * windows when the launcher icon is activated */
313+ if ((focus_visibility == WindowManager::FocusVisibility::ForceUnminimizeOnCurrentDesktop &&
314+ target_vp == m_Screen->vp()) ||
315+ (focus_visibility == WindowManager::FocusVisibility::ForceUnminimizeInvisible &&
316+ win->mapNum() == 0))
317+ {
318+ top_window = win;
319+ forced_unminimize = true;
320+
321+ if (monitor >= 0 && win_monitor == monitor)
322+ top_monitor_win = win;
323+
324+ if (!only_top_win)
325+ {
326+ bool is_mapped = (win->mapNum() != 0);
327+ win->unminimize();
328+
329+ /* Initially minimized windows dont get raised */
330+ if (!is_mapped)
331+ win->raise();
332+ }
333+ }
334+ else if ((any_mapped_on_current && !win->minimized()) || !any_mapped_on_current)
335+ {
336+ if (!forced_unminimize || target_vp == m_Screen->vp())
337+ {
338+ top_window = win;
339+
340+ if (monitor >= 0 && win_monitor == monitor)
341+ top_monitor_win = win;
342+
343+ if (!only_top_win)
344+ win->raise();
345+ }
346+ }
347+ }
348+ }
349+
350+ if (monitor >= 0 && top_monitor_win)
351+ top_window = top_monitor_win;
352+
353+ if (top_window)
354+ {
355+ if (only_top_win)
356+ {
357+ if (forced_unminimize)
358+ {
359+ top_window->unminimize();
360+ }
361+
362+ top_window->raise();
363+ }
364+
365 top_window->activate();
366 }
367 }
368@@ -768,12 +795,29 @@
369 _in_show_desktop = false;
370 }
371
372+int
373+PluginAdapter::GetWindowMonitor(guint32 xid) const
374+{
375+ // FIXME, we should use window->outputDevice() but this is not UScreen friendly
376+ nux::Geometry const& geo = GetWindowGeometry(xid);
377+
378+ if (!geo.IsNull())
379+ {
380+ int x = geo.x + geo.width/2;
381+ int y = geo.y + geo.height/2;
382+
383+ return UScreen::GetDefault()->GetMonitorAtPosition(x, y);
384+ }
385+
386+ return -1;
387+}
388+
389 nux::Geometry
390 PluginAdapter::GetWindowGeometry(guint32 xid) const
391 {
392 Window win = xid;
393 CompWindow* window;
394- nux::Geometry geo(0, 0, 1, 1);
395+ nux::Geometry geo;
396
397 window = m_Screen->findWindow(win);
398 if (window)
399
400=== modified file 'plugins/unityshell/src/PluginAdapter.h'
401--- plugins/unityshell/src/PluginAdapter.h 2012-04-02 10:45:47 +0000
402+++ plugins/unityshell/src/PluginAdapter.h 2012-04-16 01:06:19 +0000
403@@ -109,6 +109,8 @@
404 void NotifyCompizEvent(const char* plugin, const char* event, CompOption::Vector& option);
405 void NotifyNewDecorationState(guint32 xid);
406
407+ guint32 GetActiveWindow();
408+
409 void Decorate(guint32 xid);
410 void Undecorate(guint32 xid);
411
412@@ -132,7 +134,7 @@
413
414 void SetWindowIconGeometry(Window window, nux::Geometry const& geo);
415
416- void FocusWindowGroup(std::vector<Window> windows, FocusVisibility, int monitor = -1);
417+ void FocusWindowGroup(std::vector<Window> windows, FocusVisibility, int monitor = -1, bool only_top_win = true);
418 bool ScaleWindowGroup(std::vector<Window> windows, int state, bool force);
419
420 bool IsScreenGrabbed();
421@@ -142,6 +144,7 @@
422
423 bool MaximizeIfBigEnough(CompWindow* window);
424
425+ int GetWindowMonitor(guint32 xid) const;
426 nux::Geometry GetWindowGeometry(guint32 xid) const;
427 nux::Geometry GetWindowSavedGeometry(guint32 xid) const;
428 nux::Geometry GetScreenGeometry() const;
429
430=== modified file 'plugins/unityshell/src/SwitcherController.cpp'
431--- plugins/unityshell/src/SwitcherController.cpp 2012-04-04 00:00:05 +0000
432+++ plugins/unityshell/src/SwitcherController.cpp 2012-04-16 01:06:19 +0000
433@@ -507,6 +507,7 @@
434 .add("initial-detail-timeout-length", initial_detail_timeout_length())
435 .add("detail-timeout-length", detail_timeout_length())
436 .add("visible", visible_)
437+ .add("monitor", monitor_)
438 .add("detail-mode", detail_mode_);
439 }
440
441
442=== modified file 'plugins/unityshell/src/UScreen.cpp'
443--- plugins/unityshell/src/UScreen.cpp 2012-03-29 20:07:45 +0000
444+++ plugins/unityshell/src/UScreen.cpp 2012-04-16 01:06:19 +0000
445@@ -59,19 +59,17 @@
446 int
447 UScreen::GetMonitorWithMouse()
448 {
449- GdkScreen* screen;
450 GdkDevice* device;
451 GdkDisplay *display;
452 int x;
453 int y;
454
455- screen = gdk_screen_get_default();
456 display = gdk_display_get_default();
457 device = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(display));
458
459 gdk_device_get_position(device, NULL, &x, &y);
460
461- return gdk_screen_get_monitor_at_point(screen, x, y);
462+ return GetMonitorAtPosition(x, y);
463 }
464
465 int
466@@ -80,6 +78,14 @@
467 return primary_;
468 }
469
470+int
471+UScreen::GetMonitorAtPosition(int x, int y)
472+{
473+ GdkScreen* screen = gdk_screen_get_default();
474+
475+ return gdk_screen_get_monitor_at_point(screen, x, y);
476+}
477+
478 nux::Geometry&
479 UScreen::GetMonitorGeometry(int monitor)
480 {
481
482=== modified file 'plugins/unityshell/src/UScreen.h'
483--- plugins/unityshell/src/UScreen.h 2012-03-29 20:07:45 +0000
484+++ plugins/unityshell/src/UScreen.h 2012-04-16 01:06:19 +0000
485@@ -36,6 +36,7 @@
486
487 int GetPrimaryMonitor();
488 int GetMonitorWithMouse();
489+ int GetMonitorAtPosition(int x, int y);
490 nux::Geometry& GetMonitorGeometry(int monitor);
491
492 std::vector<nux::Geometry>& GetMonitors();
493
494=== modified file 'plugins/unityshell/src/WindowManager.cpp'
495--- plugins/unityshell/src/WindowManager.cpp 2012-04-02 10:45:47 +0000
496+++ plugins/unityshell/src/WindowManager.cpp 2012-04-16 01:06:19 +0000
497@@ -23,6 +23,11 @@
498
499 class WindowManagerDummy : public WindowManager
500 {
501+ guint32 GetActiveWindow()
502+ {
503+ return 0;
504+ }
505+
506 unsigned long long GetWindowActiveNumber (guint32 xid)
507 {
508 return 0;
509@@ -113,7 +118,7 @@
510 g_debug("%s", G_STRFUNC);
511 }
512
513- void FocusWindowGroup(std::vector<Window> windows, FocusVisibility, int monitor)
514+ void FocusWindowGroup(std::vector<Window> windows, FocusVisibility, int monitor, bool only_top_win)
515 {
516 g_debug("%s", G_STRFUNC);
517 }
518@@ -124,6 +129,11 @@
519 return false;
520 }
521
522+ int GetWindowMonitor(guint32 xid) const
523+ {
524+ return -1;
525+ }
526+
527 nux::Geometry GetWindowGeometry(guint xid) const
528 {
529 int width = (guint32)xid >> 16;
530
531=== modified file 'plugins/unityshell/src/WindowManager.h'
532--- plugins/unityshell/src/WindowManager.h 2012-04-02 10:45:47 +0000
533+++ plugins/unityshell/src/WindowManager.h 2012-04-16 01:06:19 +0000
534@@ -53,7 +53,9 @@
535 };
536
537 static WindowManager* Default();
538- static void SetDefault(WindowManager* manager);
539+ static void SetDefault(WindowManager* manager);
540+
541+ virtual guint32 GetActiveWindow() = 0;
542
543 virtual bool IsWindowMaximized(guint32 xid) = 0;
544 virtual bool IsWindowDecorated(guint32 xid) = 0;
545@@ -81,7 +83,7 @@
546 virtual void InitiateExpo() = 0;
547 virtual bool IsExpoActive() = 0;
548
549- virtual void FocusWindowGroup(std::vector<Window> windows, FocusVisibility, int monitor = -1) = 0;
550+ virtual void FocusWindowGroup(std::vector<Window> windows, FocusVisibility, int monitor = -1, bool only_top_win = true) = 0;
551 virtual bool ScaleWindowGroup(std::vector<Window> windows, int state, bool force) = 0;
552
553 virtual void Decorate(guint32 xid) {};
554@@ -93,6 +95,7 @@
555 virtual void MoveResizeWindow(guint32 xid, nux::Geometry geometry) = 0;
556 void StartMove(guint32 xid, int, int);
557
558+ virtual int GetWindowMonitor(guint32 xid) const = 0;
559 virtual nux::Geometry GetWindowGeometry(guint32 xid) const = 0;
560 virtual nux::Geometry GetWindowSavedGeometry(guint32 xid) const = 0;
561 virtual nux::Geometry GetScreenGeometry() const = 0;
562
563=== modified file 'plugins/unityshell/src/unityshell.cpp'
564--- plugins/unityshell/src/unityshell.cpp 2012-04-11 16:29:31 +0000
565+++ plugins/unityshell/src/unityshell.cpp 2012-04-16 01:06:19 +0000
566@@ -1711,11 +1711,14 @@
567
568 // maybe check launcher position/hide state?
569
570- int device = screen->outputDeviceForPoint (pointerX, pointerY);
571- switcher_controller_->SetWorkspace(nux::Geometry(screen->outputDevs()[device].x1() + 100,
572- screen->outputDevs()[device].y1() + 100,
573- screen->outputDevs()[device].width() - 200,
574- screen->outputDevs()[device].height() - 200), device);
575+ WindowManager *wm = WindowManager::Default();
576+ int monitor = wm->GetWindowMonitor(wm->GetActiveWindow());
577+ nux::Geometry monitor_geo = UScreen::GetDefault()->GetMonitorGeometry(monitor);
578+ monitor_geo.x += 100;
579+ monitor_geo.y += 100;
580+ monitor_geo.width -= 200;
581+ monitor_geo.height -= 200;
582+ switcher_controller_->SetWorkspace(monitor_geo, monitor);
583
584 if (!optionGetAltTabBiasViewport())
585 {
586
587=== modified file 'tests/autopilot/autopilot/emulators/bamf.py'
588--- tests/autopilot/autopilot/emulators/bamf.py 2012-04-05 03:41:38 +0000
589+++ tests/autopilot/autopilot/emulators/bamf.py 2012-04-16 01:06:19 +0000
590@@ -81,12 +81,21 @@
591 If user_visible_only is True (the default), only applications
592 visible to the user in the switcher will be returned.
593
594+ The result is sorted to be in stacking order.
595+
596 """
597
598+ # Get the stacking order from the root window.
599+ root_win = _X_DISPLAY.screen().root
600+ prop = root_win.get_full_property(
601+ _X_DISPLAY.get_atom('_NET_CLIENT_LIST_STACKING'), X.AnyPropertyType)
602+ stack = prop.value.tolist()
603+
604 windows = [BamfWindow(w) for w in self.matcher_interface.WindowPaths()]
605 if user_visible_only:
606- return filter(_filter_user_visible, windows)
607- return windows
608+ windows = filter(_filter_user_visible, windows)
609+ # Now sort on stacking order.
610+ return sorted(windows, key=lambda w: stack.index(w.x_id), reverse=True)
611
612 def wait_until_application_is_running(self, desktop_file, timeout):
613 """Wait until a given application is running.
614
615=== modified file 'tests/autopilot/autopilot/emulators/unity/switcher.py'
616--- tests/autopilot/autopilot/emulators/unity/switcher.py 2012-04-02 20:18:58 +0000
617+++ tests/autopilot/autopilot/emulators/unity/switcher.py 2012-04-16 01:06:19 +0000
618@@ -157,6 +157,9 @@
619 def get_is_visible(self):
620 return bool(self.__get_controller()['visible'])
621
622+ def get_monitor(self):
623+ return int(self.__get_controller()['monitor'])
624+
625 def get_is_in_details_mode(self):
626 """Return True if the SwitcherView is in details mode."""
627 return bool(self.__get_model()['detail-selection'])
628
629=== modified file 'tests/autopilot/autopilot/tests/__init__.py'
630--- tests/autopilot/autopilot/tests/__init__.py 2012-04-11 06:32:50 +0000
631+++ tests/autopilot/autopilot/tests/__init__.py 2012-04-16 01:06:19 +0000
632@@ -335,3 +335,15 @@
633 controllers = PanelController.get_all_instances()
634 self.assertThat(len(controllers), Equals(1))
635 return controllers[0]
636+
637+ def assertVisibleWindowStack(self, stack_start):
638+ """Check that the visible window stack starts with the windows passed in.
639+
640+ The start_stack is an iterable of BamfWindow objects.
641+ Minimised windows are skipped.
642+
643+ """
644+ stack = [win for win in self.bamf.get_open_windows() if not win.is_hidden]
645+ for pos, win in enumerate(stack_start):
646+ self.assertThat(stack[pos].x_id, Equals(win.x_id),
647+ "%r at %d does not equal %r" % (stack[pos], pos, win))
648
649=== modified file 'tests/autopilot/autopilot/tests/test_launcher.py'
650--- tests/autopilot/autopilot/tests/test_launcher.py 2012-04-06 07:31:45 +0000
651+++ tests/autopilot/autopilot/tests/test_launcher.py 2012-04-16 01:06:19 +0000
652@@ -403,6 +403,71 @@
653 self.assertThat(self.launcher.key_nav_is_active, Equals(False))
654
655
656+class LauncherIconsBehaviorTests(LauncherTestCase):
657+ """Test the launcher icons interactions"""
658+
659+ def test_launcher_activate_last_focused_window(self):
660+ """This tests shows that when you activate a launcher icon only the last
661+ focused instance of that application is rasied.
662+
663+ This is tested by opening 2 Mahjongg and a Calculator.
664+ Then we activate the Calculator launcher icon.
665+ Then we actiavte the Mahjongg launcher icon.
666+ Then we minimize the focused applications.
667+ This should give focus to the next window on the stack.
668+ Then we activate the Mahjongg launcher icon
669+ This should bring to focus the non-minimized window.
670+ If only 1 instance is raised then the Calculator gets the focus.
671+ If ALL the instances are raised then the second Mahjongg gets the focus.
672+
673+ """
674+ mahj = self.start_app("Mahjongg")
675+ [mah_win1] = mahj.get_windows()
676+ self.assertTrue(mah_win1.is_focused)
677+
678+ calc = self.start_app("Calculator")
679+ [calc_win] = calc.get_windows()
680+ self.assertTrue(calc_win.is_focused)
681+
682+ self.start_app("Mahjongg")
683+ # Sleeping due to the start_app only waiting for the bamf model to be
684+ # updated with the application. Since the app has already started,
685+ # and we are just waiting on a second window, however a defined sleep
686+ # here is likely to be problematic.
687+ # TODO: fix bamf emulator to enable waiting for new windows.
688+ sleep(1)
689+ [mah_win2] = [w for w in mahj.get_windows() if w.x_id != mah_win1.x_id]
690+ self.assertTrue(mah_win2.is_focused)
691+
692+ self.assertVisibleWindowStack([mah_win2, calc_win, mah_win1])
693+
694+ mahj_icon = self.launcher.model.get_icon_by_desktop_id(mahj.desktop_file)
695+ calc_icon = self.launcher.model.get_icon_by_desktop_id(calc.desktop_file)
696+
697+ self.launcher_instance.click_launcher_icon(calc_icon)
698+ sleep(1)
699+ self.assertTrue(calc_win.is_focused)
700+ self.assertVisibleWindowStack([calc_win, mah_win2, mah_win1])
701+
702+ self.launcher_instance.click_launcher_icon(mahj_icon)
703+ sleep(1)
704+ self.assertTrue(mah_win2.is_focused)
705+ self.assertVisibleWindowStack([mah_win2, calc_win, mah_win1])
706+
707+ self.keybinding("window/minimize")
708+ sleep(1)
709+
710+ self.assertTrue(mah_win2.is_hidden)
711+ self.assertTrue(calc_win.is_focused)
712+ self.assertVisibleWindowStack([calc_win, mah_win1])
713+
714+ self.launcher_instance.click_launcher_icon(mahj_icon)
715+ sleep(1)
716+ self.assertTrue(mah_win1.is_focused)
717+ self.assertTrue(mah_win2.is_hidden)
718+ self.assertVisibleWindowStack([mah_win1, calc_win])
719+
720+
721 class LauncherRevealTests(LauncherTestCase):
722 """Test the launcher reveal bahavior when in autohide mode."""
723
724
725=== modified file 'tests/autopilot/autopilot/tests/test_switcher.py'
726--- tests/autopilot/autopilot/tests/test_switcher.py 2012-04-03 13:48:39 +0000
727+++ tests/autopilot/autopilot/tests/test_switcher.py 2012-04-16 01:06:19 +0000
728@@ -21,9 +21,9 @@
729 def setUp(self):
730 super(SwitcherTests, self).setUp()
731
732- self.start_app('Character Map')
733- self.start_app('Calculator')
734- self.start_app('Mahjongg')
735+ self.char_map = self.start_app('Character Map')
736+ self.calc = self.start_app('Calculator')
737+ self.mahjongg = self.start_app('Mahjongg')
738
739 def tearDown(self):
740 super(SwitcherTests, self).tearDown()
741@@ -197,6 +197,69 @@
742
743 self.assertThat(self.switcher.get_is_visible(), Equals(False))
744
745+ def test_switcher_appears_on_monitor_with_focused_window(self):
746+ num_monitors = self.screen_geo.get_num_monitors()
747+ if num_monitors == 1:
748+ self.skip("No point testing this on one monitor")
749+
750+ [calc_win] = self.calc.get_windows()
751+ for monitor in range(num_monitors):
752+ self.screen_geo.drag_window_to_monitor(calc_win, monitor)
753+ self.switcher.initiate()
754+ sleep(1)
755+ self.assertThat(self.switcher.get_monitor(), Equals(monitor))
756+ self.switcher.terminate()
757+
758+
759+class SwitcherWindowsManagementTests(AutopilotTestCase):
760+ """Test the switcher window management."""
761+
762+ def test_switcher_raises_only_last_focused_window(self):
763+ """Tests that when we do an alt+tab only the previously focused window
764+ is raised.
765+ This is tests by opening 2 Calculators and a Mahjongg.
766+ Then we do a quick alt+tab twice.
767+ Then we close the currently focused window.
768+ """
769+ self.close_all_app("Mahjongg")
770+ self.close_all_app("Calculator")
771+
772+ mahj = self.start_app("Mahjongg")
773+ [mah_win1] = mahj.get_windows()
774+ self.assertTrue(mah_win1.is_focused)
775+
776+ calc = self.start_app("Calculator")
777+ [calc_win] = calc.get_windows()
778+ self.assertTrue(calc_win.is_focused)
779+
780+ self.start_app("Mahjongg")
781+ # Sleeping due to the start_app only waiting for the bamf model to be
782+ # updated with the application. Since the app has already started,
783+ # and we are just waiting on a second window, however a defined sleep
784+ # here is likely to be problematic.
785+ # TODO: fix bamf emulator to enable waiting for new windows.
786+ sleep(1)
787+ [mah_win2] = [w for w in mahj.get_windows() if w.x_id != mah_win1.x_id]
788+ self.assertTrue(mah_win2.is_focused)
789+
790+ self.assertVisibleWindowStack([mah_win2, calc_win, mah_win1])
791+
792+ self.keybinding("switcher/reveal_normal")
793+ sleep(1)
794+ self.assertTrue(calc_win.is_focused)
795+ self.assertVisibleWindowStack([calc_win, mah_win2, mah_win1])
796+
797+ self.keybinding("switcher/reveal_normal")
798+ sleep(1)
799+ self.assertTrue(mah_win2.is_focused)
800+ self.assertVisibleWindowStack([mah_win2, calc_win, mah_win1])
801+
802+ self.keybinding("window/close")
803+ sleep(1)
804+
805+ self.assertTrue(calc_win.is_focused)
806+ self.assertVisibleWindowStack([calc_win, mah_win1])
807+
808
809 class SwitcherDetailsTests(AutopilotTestCase):
810 """Test the details mode for the switcher."""
811@@ -364,4 +427,3 @@
812 # current workspace and ask that one if it is hidden.
813 self.assertFalse(wins[0].is_hidden)
814 self.assertFalse(wins[1].is_hidden)
815-