Linux: Reorder browser list on workspace switch
Opening a link from a 3rd party app picks the most recently
used profile/browser to open the new tab in. Whenever a user
changes workspaces, reorder the browser list such that the
browsers in the current workspace are moved to the top of the
list. This way, when a user opens a link, it will open in a
browser on the current workspace (if there are any browsers).
BUG=619673
Review-Url: https://codereview.chromium.org/2108933003
Cr-Commit-Position: refs/heads/master@{#404544}
diff --git a/chrome/browser/ui/browser_list.cc b/chrome/browser/ui/browser_list.cc
index 4c39d62..a7b8870 100644
--- a/chrome/browser/ui/browser_list.cc
+++ b/chrome/browser/ui/browser_list.cc
@@ -186,6 +186,36 @@
}
// static
+void BrowserList::MoveBrowsersInWorkspaceToFront(
+ const std::string& new_workspace) {
+ DCHECK(!new_workspace.empty());
+
+ BrowserList* instance = GetInstance();
+
+ Browser* old_last_active = instance->GetLastActive();
+ BrowserVector& last_active_browsers = instance->last_active_browsers_;
+
+ // Perform a stable partition on the browsers in the list so that the browsers
+ // in the new workspace appear after the browsers in the other workspaces.
+ //
+ // For example, if we have a list of browser-workspace pairs
+ // [{b1, 0}, {b2, 1}, {b3, 0}, {b4, 1}]
+ // and we switch to workspace 1, we want the resulting browser list to look
+ // like [{b1, 0}, {b3, 0}, {b2, 1}, {b4, 1}].
+ std::stable_partition(
+ last_active_browsers.begin(), last_active_browsers.end(),
+ [&new_workspace](Browser* browser) {
+ return browser->window()->GetWorkspace() != new_workspace;
+ });
+
+ Browser* new_last_active = instance->GetLastActive();
+ if (old_last_active != new_last_active) {
+ FOR_EACH_OBSERVER(chrome::BrowserListObserver, observers_.Get(),
+ OnBrowserSetLastActive(new_last_active));
+ }
+}
+
+// static
void BrowserList::SetLastActive(Browser* browser) {
content::RecordAction(UserMetricsAction("ActiveBrowserChanged"));
diff --git a/chrome/browser/ui/browser_list.h b/chrome/browser/ui/browser_list.h
index 33fdb59f4..4270d45 100644
--- a/chrome/browser/ui/browser_list.h
+++ b/chrome/browser/ui/browser_list.h
@@ -69,6 +69,10 @@
static void AddObserver(chrome::BrowserListObserver* observer);
static void RemoveObserver(chrome::BrowserListObserver* observer);
+ // Moves all the browsers that show on workspace |new_workspace| to the end of
+ // the browser list (i.e. the browsers that were "activated" most recently).
+ static void MoveBrowsersInWorkspaceToFront(const std::string& new_workspace);
+
// Called by Browser objects when their window is activated (focused). This
// allows us to determine what the last active Browser was on each desktop.
static void SetLastActive(Browser* browser);
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc
index 8355677..ecd4f8ec 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc
@@ -10,6 +10,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/libgtk2ui/gtk2_ui.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
@@ -18,6 +19,7 @@
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
+#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/base/ime/input_method_initializer.h"
#include "ui/base/l10n/l10n_util.h"
@@ -28,6 +30,7 @@
#include "ui/native_theme/native_theme_dark_aura.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/widget/desktop_aura/desktop_screen.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
#include "ui/views/widget/native_widget_aura.h"
namespace {
@@ -68,7 +71,11 @@
}
ChromeBrowserMainExtraPartsViewsLinux::
- ~ChromeBrowserMainExtraPartsViewsLinux() {}
+ ~ChromeBrowserMainExtraPartsViewsLinux() {
+ // X11DesktopHandler is destructed at this point, so we don't need to remove
+ // ourselves as an X11DesktopHandlerObserver
+ DCHECK(!aura::Env::GetInstanceDontCreate());
+}
void ChromeBrowserMainExtraPartsViewsLinux::PreEarlyInitialization() {
// TODO(erg): Refactor this into a dlopen call when we add a GTK3 port.
@@ -89,6 +96,8 @@
views::LinuxUI::instance()->MaterialDesignControllerReady();
views::LinuxUI::instance()->UpdateDeviceScaleFactor(
display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor());
+
+ views::X11DesktopHandler::get()->AddObserver(this);
}
void ChromeBrowserMainExtraPartsViewsLinux::PreProfileInit() {
@@ -117,3 +126,8 @@
exit(EXIT_FAILURE);
}
+
+void ChromeBrowserMainExtraPartsViewsLinux::OnWorkspaceChanged(
+ const std::string& new_workspace) {
+ BrowserList::MoveBrowsersInWorkspaceToFront(new_workspace);
+}
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h
index 0b542ed..ed3a86bd 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h
@@ -11,9 +11,11 @@
#include "base/macros.h"
#include "build/build_config.h"
#include "chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler_observer.h"
class ChromeBrowserMainExtraPartsViewsLinux
- : public ChromeBrowserMainExtraPartsViews {
+ : public ChromeBrowserMainExtraPartsViews,
+ public views::X11DesktopHandlerObserver {
public:
ChromeBrowserMainExtraPartsViewsLinux();
~ChromeBrowserMainExtraPartsViewsLinux() override;
@@ -24,6 +26,9 @@
void PreCreateThreads() override;
void PreProfileInit() override;
+ // Overridden from views::X11DesktopHandlerObserver.
+ void OnWorkspaceChanged(const std::string& new_workspace) override;
+
private:
DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainExtraPartsViewsLinux);
};
diff --git a/chrome/browser/ui/views/frame/browser_frame.cc b/chrome/browser/ui/views/frame/browser_frame.cc
index d1c767c..37bcfbb 100644
--- a/chrome/browser/ui/views/frame/browser_frame.cc
+++ b/chrome/browser/ui/views/frame/browser_frame.cc
@@ -41,6 +41,10 @@
#include "chrome/browser/ui/views/frame/browser_command_handler_linux.h"
#endif
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
+#endif
+
#if defined(OS_WIN)
#include "ui/native_theme/native_theme_dark_win.h"
#endif
@@ -221,6 +225,10 @@
void BrowserFrame::OnNativeWidgetWorkspaceChanged() {
chrome::SaveWindowWorkspace(browser_view_->browser(), GetWorkspace());
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ BrowserList::MoveBrowsersInWorkspaceToFront(
+ views::X11DesktopHandler::get()->GetWorkspace());
+#endif
Widget::OnNativeWidgetWorkspaceChanged();
}
diff --git a/ui/base/x/x11_util.h b/ui/base/x/x11_util.h
index 0709114..3965a50 100644
--- a/ui/base/x/x11_util.h
+++ b/ui/base/x/x11_util.h
@@ -204,7 +204,7 @@
static const int kAllDesktops = -1;
// Queries the desktop |window| is on, kAllDesktops if sticky. Returns false if
// property not found.
-bool GetWindowDesktop(XID window, int* desktop);
+UI_BASE_X_EXPORT bool GetWindowDesktop(XID window, int* desktop);
// Translates an X11 error code into a printable string.
UI_BASE_X_EXPORT std::string GetX11ErrorString(XDisplay* display, int err);
diff --git a/ui/views/views.gyp b/ui/views/views.gyp
index 29964e6..5dd8411 100644
--- a/ui/views/views.gyp
+++ b/ui/views/views.gyp
@@ -463,6 +463,7 @@
'widget/desktop_aura/desktop_window_tree_host_x11.h',
'widget/desktop_aura/x11_desktop_handler.cc',
'widget/desktop_aura/x11_desktop_handler.h',
+ 'widget/desktop_aura/x11_desktop_handler_observer.h',
'widget/desktop_aura/x11_desktop_window_move_client.cc',
'widget/desktop_aura/x11_desktop_window_move_client.h',
'widget/desktop_aura/x11_move_loop.h',
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 82ba30c..f1cc3f6 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -568,10 +568,20 @@
}
std::string DesktopWindowTreeHostX11::GetWorkspace() const {
- int workspace_id;
- if (ui::GetIntProperty(xwindow_, "_NET_WM_DESKTOP", &workspace_id))
- return base::IntToString(workspace_id);
- return std::string();
+ if (workspace_.empty())
+ const_cast<DesktopWindowTreeHostX11*>(this)->UpdateWorkspace();
+ return workspace_;
+}
+
+bool DesktopWindowTreeHostX11::UpdateWorkspace() {
+ int workspace_int;
+ if (!ui::GetWindowDesktop(xwindow_, &workspace_int))
+ return false;
+ std::string workspace_str = base::IntToString(workspace_int);
+ if (workspace_ == workspace_str)
+ return false;
+ workspace_ = workspace_str;
+ return true;
}
gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInScreen() const {
@@ -1230,6 +1240,7 @@
if (is_always_on_top_)
state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_ABOVE"));
+ workspace_.clear();
if (params.visible_on_all_workspaces) {
state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_STICKY"));
ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllDesktops);
@@ -1996,12 +2007,14 @@
}
case PropertyNotify: {
::Atom changed_atom = xev->xproperty.atom;
- if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE"))
+ if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE")) {
OnWMStateUpdated();
- else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS"))
+ } else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS")) {
OnFrameExtentsUpdated();
- else if (changed_atom == atom_cache_.GetAtom("_NET_WM_DESKTOP"))
- OnHostWorkspaceChanged();
+ } else if (changed_atom == atom_cache_.GetAtom("_NET_WM_DESKTOP")) {
+ if (UpdateWorkspace())
+ OnHostWorkspaceChanged();
+ }
break;
}
case SelectionNotify: {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
index fa262f13..074d7776 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
@@ -185,6 +185,10 @@
// Called when |xwindow_|'s _NET_FRAME_EXTENTS property is updated.
void OnFrameExtentsUpdated();
+ // Makes a round trip to the X server to get the enclosing workspace for this
+ // window. Returns true iff |workspace_| was changed.
+ bool UpdateWorkspace();
+
// Updates |xwindow_|'s minimum and maximum size.
void UpdateMinAndMaxSize();
@@ -284,6 +288,9 @@
// |xwindow_|'s maximum size.
gfx::Size max_size_in_pixels_;
+ // The workspace containing |xwindow_|.
+ std::string workspace_;
+
// The window manager state bits.
std::set< ::Atom> window_properties_;
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler.cc b/ui/views/widget/desktop_aura/x11_desktop_handler.cc
index 8ec4b1e..aa3c234 100644
--- a/ui/views/widget/desktop_aura/x11_desktop_handler.cc
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler.cc
@@ -8,6 +8,7 @@
#include <X11/Xlib.h>
#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
#include "ui/aura/env.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/x/x11_foreign_window_manager.h"
@@ -19,9 +20,10 @@
namespace {
-const char* kAtomsToCache[] = {
+const char* const kAtomsToCache[] = {
"_NET_ACTIVE_WINDOW",
- NULL
+ "_NET_CURRENT_DESKTOP",
+ nullptr
};
// Our global instance. Deleted when our Env() is deleted.
@@ -124,6 +126,29 @@
}
}
+void X11DesktopHandler::AddObserver(X11DesktopHandlerObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void X11DesktopHandler::RemoveObserver(X11DesktopHandlerObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+std::string X11DesktopHandler::GetWorkspace() {
+ if (workspace_.empty())
+ UpdateWorkspace();
+ return workspace_;
+}
+
+bool X11DesktopHandler::UpdateWorkspace() {
+ int desktop;
+ if (ui::GetCurrentDesktop(&desktop)) {
+ workspace_ = base::IntToString(desktop);
+ return true;
+ }
+ return false;
+}
+
void X11DesktopHandler::set_wm_user_time_ms(Time time_ms) {
if (time_ms != CurrentTime) {
int64_t event_time_64 = time_ms;
@@ -184,10 +209,7 @@
uint32_t X11DesktopHandler::DispatchEvent(const ui::PlatformEvent& event) {
switch (event->type) {
case PropertyNotify: {
- // Check for a change to the active window.
- CHECK_EQ(x_root_window_, event->xproperty.window);
- ::Atom active_window_atom = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW");
- if (event->xproperty.atom == active_window_atom) {
+ if (event->xproperty.atom == atom_cache_.GetAtom("_NET_ACTIVE_WINDOW")) {
::Window window;
if (ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &window) &&
window) {
@@ -196,10 +218,15 @@
} else {
x_active_window_ = None;
}
+ } else if (event->xproperty.atom ==
+ atom_cache_.GetAtom("_NET_CURRENT_DESKTOP")) {
+ if (UpdateWorkspace()) {
+ FOR_EACH_OBSERVER(views::X11DesktopHandlerObserver, observers_,
+ OnWorkspaceChanged(workspace_));
+ }
}
break;
}
-
case CreateNotify:
OnWindowCreatedOrDestroyed(event->type, event->xcreatewindow.window);
break;
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler.h b/ui/views/widget/desktop_aura/x11_desktop_handler.h
index 47a1ff1..b9c9060 100644
--- a/ui/views/widget/desktop_aura/x11_desktop_handler.h
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler.h
@@ -13,12 +13,14 @@
#include <vector>
#include "base/macros.h"
+#include "base/observer_list.h"
#include "ui/aura/env_observer.h"
#include "ui/events/platform/platform_event_dispatcher.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/views_export.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler_observer.h"
namespace base {
template <typename T> struct DefaultSingletonTraits;
@@ -35,6 +37,13 @@
// Returns the singleton handler.
static X11DesktopHandler* get();
+ // Adds/removes X11DesktopHandlerObservers.
+ void AddObserver(X11DesktopHandlerObserver* observer);
+ void RemoveObserver(X11DesktopHandlerObserver* observer);
+
+ // Gets the current workspace ID.
+ std::string GetWorkspace();
+
// Gets/sets the X11 server time of the most recent mouse click, touch or
// key press on a Chrome window.
int wm_user_time_ms() const { return wm_user_time_ms_; }
@@ -81,6 +90,9 @@
// managed by Chrome.
void OnWindowCreatedOrDestroyed(int event_type, XID window);
+ // Makes a round trip to the X server to get the current workspace.
+ bool UpdateWorkspace();
+
// The display and the native X window hosting the root window.
XDisplay* xdisplay_;
@@ -105,6 +117,10 @@
bool wm_supports_active_window_;
+ base::ObserverList<X11DesktopHandlerObserver> observers_;
+
+ std::string workspace_;
+
DISALLOW_COPY_AND_ASSIGN(X11DesktopHandler);
};
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler_observer.h b/ui/views/widget/desktop_aura/x11_desktop_handler_observer.h
new file mode 100644
index 0000000..252ca21
--- /dev/null
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler_observer.h
@@ -0,0 +1,26 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_WIDGET_DESKTOP_AURA_X11_DESKTOP_HANDLER_OBSERVER_H_
+#define UI_VIEWS_WIDGET_DESKTOP_AURA_X11_DESKTOP_HANDLER_OBSERVER_H_
+
+#include <string>
+
+#include "ui/views/views_export.h"
+
+namespace views {
+
+class VIEWS_EXPORT X11DesktopHandlerObserver {
+ public:
+ // Called when the (platform-specific) workspace ID changes to
+ // |new_workspace|.
+ virtual void OnWorkspaceChanged(const std::string& new_workspace) = 0;
+
+ protected:
+ virtual ~X11DesktopHandlerObserver() {}
+};
+
+} // namespace views
+
+#endif // UI_DISPLAY_DESKTOP_OBSERVER_H_