Commit efb2a4e8 authored by erg@chromium.org's avatar erg@chromium.org

linux_aura: Use system configuration for middle clicking the titlebar.

The action that is taken when the user middle clicks on non client area
is configurable with gnome-tweak-tool. This allows users to disable any
action on middle click, lower windows, minimize windows, or toggle the
maximize state.

Also renames GConfTitlebarListener to GConfListener, since it's now
doing more than just checking maximize button order.

BUG=132061

Review URL: https://codereview.chromium.org/229783002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@262839 0039d316-1c4b-4281-b951-d872f2087c98
parent 2add5dd5
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Copyright 2014 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.
#include "chrome/browser/ui/libgtk2ui/gconf_titlebar_listener.h"
#include "chrome/browser/ui/libgtk2ui/gconf_listener.h"
#include <gtk/gtk.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/environment.h"
#include "base/memory/scoped_ptr.h"
#include "base/nix/xdg_util.h"
......@@ -19,11 +21,17 @@ namespace {
// The GConf key we read for the button placement string. Even through the key
// has "metacity" in it, it's shared between metacity and compiz.
const char* kButtonLayoutKey = "/apps/metacity/general/button_layout";
const char kButtonLayoutKey[] = "/apps/metacity/general/button_layout";
// The GConf key we read for what to do in case of middle clicks on non client
// area. Even through the key has "metacity" in it, it's shared between
// metacity and compiz.
const char kMiddleClickActionKey[] =
"/apps/metacity/general/action_middle_click_titlebar";
// GConf requires us to subscribe to a parent directory before we can subscribe
// to changes in an individual key in that directory.
const char* kMetacityGeneral = "/apps/metacity/general";
const char kMetacityGeneral[] = "/apps/metacity/general";
const char kDefaultButtonString[] = ":minimize,maximize,close";
......@@ -33,7 +41,7 @@ namespace libgtk2ui {
// Public interface:
GConfTitlebarListener::GConfTitlebarListener(Gtk2UI* delegate)
GConfListener::GConfListener(Gtk2UI* delegate)
: delegate_(delegate),
client_(NULL) {
scoped_ptr<base::Environment> env(base::Environment::Create());
......@@ -46,49 +54,64 @@ GConfTitlebarListener::GConfTitlebarListener(Gtk2UI* delegate)
// If we fail to get a context, that's OK, since we'll just fallback on
// not receiving gconf keys.
if (client_) {
// Get the initial value of the key.
GError* error = NULL;
GConfValue* gconf_value = gconf_client_get(client_, kButtonLayoutKey,
&error);
if (HandleGError(error, kButtonLayoutKey))
return;
ParseAndStoreValue(gconf_value);
if (gconf_value)
gconf_value_free(gconf_value);
// Register that we're interested in the values of this directory.
GError* error = NULL;
gconf_client_add_dir(client_, kMetacityGeneral,
GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
if (HandleGError(error, kMetacityGeneral))
return;
// Register to get notifies about changes to this key.
gconf_client_notify_add(
client_, kButtonLayoutKey,
reinterpret_cast<void (*)(GConfClient*, guint, GConfEntry*, void*)>(
OnChangeNotificationThunk),
this, NULL, &error);
if (HandleGError(error, kButtonLayoutKey))
return;
// Get the initial value of the keys we're interested in.
GetAndRegister(kButtonLayoutKey,
base::Bind(&GConfListener::ParseAndStoreButtonValue,
base::Unretained(this)));
GetAndRegister(kMiddleClickActionKey,
base::Bind(&GConfListener::ParseAndStoreMiddleClickValue,
base::Unretained(this)));
}
}
}
GConfTitlebarListener::~GConfTitlebarListener() {
GConfListener::~GConfListener() {
}
// Private:
void GConfTitlebarListener::OnChangeNotification(GConfClient* client,
void GConfListener::GetAndRegister(
const char* key_to_subscribe,
const base::Callback<void(GConfValue*)>& initial_setter) {
GError* error = NULL;
GConfValue* gconf_value = gconf_client_get(client_, key_to_subscribe,
&error);
if (HandleGError(error, key_to_subscribe))
return;
initial_setter.Run(gconf_value);
if (gconf_value)
gconf_value_free(gconf_value);
// Register to get notifies about changes to this key.
gconf_client_notify_add(
client_, key_to_subscribe,
reinterpret_cast<void (*)(GConfClient*, guint, GConfEntry*, void*)>(
OnChangeNotificationThunk),
this, NULL, &error);
if (HandleGError(error, key_to_subscribe))
return;
}
void GConfListener::OnChangeNotification(GConfClient* client,
guint cnxn_id,
GConfEntry* entry) {
if (strcmp(gconf_entry_get_key(entry), kButtonLayoutKey) == 0) {
GConfValue* gconf_value = gconf_entry_get_value(entry);
ParseAndStoreValue(gconf_value);
ParseAndStoreButtonValue(gconf_value);
} else if (strcmp(gconf_entry_get_key(entry), kMiddleClickActionKey) == 0) {
GConfValue* gconf_value = gconf_entry_get_value(entry);
ParseAndStoreMiddleClickValue(gconf_value);
}
}
bool GConfTitlebarListener::HandleGError(GError* error, const char* key) {
bool GConfListener::HandleGError(GError* error, const char* key) {
if (error != NULL) {
LOG(ERROR) << "Error with gconf key '" << key << "': " << error->message;
g_error_free(error);
......@@ -99,7 +122,7 @@ bool GConfTitlebarListener::HandleGError(GError* error, const char* key) {
return false;
}
void GConfTitlebarListener::ParseAndStoreValue(GConfValue* gconf_value) {
void GConfListener::ParseAndStoreButtonValue(GConfValue* gconf_value) {
std::string button_string;
if (gconf_value) {
const char* value = gconf_value_get_string(gconf_value);
......@@ -136,4 +159,29 @@ void GConfTitlebarListener::ParseAndStoreValue(GConfValue* gconf_value) {
delegate_->SetWindowButtonOrdering(leading_buttons, trailing_buttons);
}
void GConfListener::ParseAndStoreMiddleClickValue(GConfValue* gconf_value) {
Gtk2UI::NonClientMiddleClickAction action =
views::LinuxUI::MIDDLE_CLICK_ACTION_LOWER;
if (gconf_value) {
const char* value = gconf_value_get_string(gconf_value);
if (strcmp(value, "none") == 0) {
action = views::LinuxUI::MIDDLE_CLICK_ACTION_NONE;
} else if (strcmp(value, "lower") == 0) {
action = views::LinuxUI::MIDDLE_CLICK_ACTION_LOWER;
} else if (strcmp(value, "minimize") == 0) {
action = views::LinuxUI::MIDDLE_CLICK_ACTION_MINIMIZE;
} else if (strcmp(value, "toggle-maximize") == 0) {
action = views::LinuxUI::MIDDLE_CLICK_ACTION_TOGGLE_MAXIMIZE;
} else {
// While we want to have the default state be lower if there isn't a
// value, we want to default to no action if the user has explicitly
// chose an action that we don't implement.
action = views::LinuxUI::MIDDLE_CLICK_ACTION_NONE;
}
}
delegate_->SetNonClientMiddleClickAction(action);
}
} // namespace libgtk2ui
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Copyright 2014 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 CHROME_BROWSER_UI_LIBGTK2UI_GCONF_TITLEBAR_LISTENER_H_
#define CHROME_BROWSER_UI_LIBGTK2UI_GCONF_TITLEBAR_LISTENER_H_
#ifndef CHROME_BROWSER_UI_LIBGTK2UI_GCONF_LISTENER_H_
#define CHROME_BROWSER_UI_LIBGTK2UI_GCONF_LISTENER_H_
#include <gconf/gconf-client.h>
#include <gtk/gtk.h>
......@@ -12,6 +12,7 @@
#include <string>
#include "base/basictypes.h"
#include "base/callback_forward.h"
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
namespace libgtk2ui {
......@@ -20,24 +21,28 @@ class Gtk2UI;
// On GNOME desktops, subscribes to the gconf key which controlls button order.
// Everywhere else, SetTiltebarButtons() just calls back into BrowserTitlebar
// with the default ordering.
class GConfTitlebarListener {
class GConfListener {
public:
// Sends data to the Gtk2UI when available.
explicit GConfTitlebarListener(Gtk2UI* delegate);
~GConfTitlebarListener();
explicit GConfListener(Gtk2UI* delegate);
~GConfListener();
private:
// Called whenever the metacity key changes.
CHROMEG_CALLBACK_2(GConfTitlebarListener, void, OnChangeNotification,
CHROMEG_CALLBACK_2(GConfListener, void, OnChangeNotification,
GConfClient*, guint, GConfEntry*);
void GetAndRegister(const char* key_to_subscribe,
const base::Callback<void(GConfValue*)>& initial_setter);
// Checks |error|. On error, prints out a message and closes the connection
// to GConf and reverts to default mode.
bool HandleGError(GError* error, const char* key);
// Parses the return data structure from GConf, falling back to the default
// value on any error.
void ParseAndStoreValue(GConfValue* gconf_value);
void ParseAndStoreButtonValue(GConfValue* gconf_value);
void ParseAndStoreMiddleClickValue(GConfValue* gconf_value);
Gtk2UI* delegate_;
......@@ -45,9 +50,9 @@ class GConfTitlebarListener {
// gconf.
GConfClient* client_;
DISALLOW_COPY_AND_ASSIGN(GConfTitlebarListener);
DISALLOW_COPY_AND_ASSIGN(GConfListener);
};
} // namespace libgtk2ui
#endif // CHROME_BROWSER_UI_LIBGTK2UI_GCONF_TITLEBAR_LISTENER_H_
#endif // CHROME_BROWSER_UI_LIBGTK2UI_GCONF_LISTENER_H_
......@@ -46,7 +46,7 @@
#include "ui/views/linux_ui/window_button_order_observer.h"
#if defined(USE_GCONF)
#include "chrome/browser/ui/libgtk2ui/gconf_titlebar_listener.h"
#include "chrome/browser/ui/libgtk2ui/gconf_listener.h"
#endif
// A minimized port of GtkThemeService into something that can provide colors
......@@ -318,7 +318,7 @@ color_utils::HSL GetDefaultTint(int id) {
namespace libgtk2ui {
Gtk2UI::Gtk2UI() {
Gtk2UI::Gtk2UI() : middle_click_action_(MIDDLE_CLICK_ACTION_LOWER) {
GtkInitFromCommandLine(*CommandLine::ForCurrentProcess());
}
......@@ -350,7 +350,7 @@ void Gtk2UI::Initialize() {
#if defined(USE_GCONF)
// We must build this after GTK gets initialized.
titlebar_listener_.reset(new GConfTitlebarListener(this));
gconf_listener_.reset(new GConfListener(this));
#endif // defined(USE_GCONF)
indicators_count = 0;
......@@ -565,6 +565,10 @@ void Gtk2UI::SetWindowButtonOrdering(
trailing_buttons_));
}
void Gtk2UI::SetNonClientMiddleClickAction(NonClientMiddleClickAction action) {
middle_click_action_ = action;
}
scoped_ptr<ui::LinuxInputMethodContext> Gtk2UI::CreateInputMethodContext(
ui::LinuxInputMethodContextDelegate* delegate) const {
return scoped_ptr<ui::LinuxInputMethodContext>(
......@@ -674,6 +678,11 @@ bool Gtk2UI::UnityIsRunning() {
return unity::IsRunning();
}
views::LinuxUI::NonClientMiddleClickAction
Gtk2UI::GetNonClientMiddleClickAction() {
return middle_click_action_;
}
void Gtk2UI::NotifyWindowManagerStartupComplete() {
// TODO(port) Implement this using _NET_STARTUP_INFO_BEGIN/_NET_STARTUP_INFO
// from http://standards.freedesktop.org/startup-notification-spec/ instead.
......
......@@ -36,7 +36,7 @@ namespace libgtk2ui {
class Gtk2Border;
class Gtk2KeyBindingsHandler;
class Gtk2SignalRegistrar;
class GConfTitlebarListener;
class GConfListener;
// Interface to GTK2 desktop features.
//
......@@ -45,9 +45,11 @@ class Gtk2UI : public views::LinuxUI {
Gtk2UI();
virtual ~Gtk2UI();
// Setters used by GConfListener:
void SetWindowButtonOrdering(
const std::vector<views::FrameButton>& leading_buttons,
const std::vector<views::FrameButton>& trailing_buttons);
void SetNonClientMiddleClickAction(NonClientMiddleClickAction action);
// Draws the GTK button border for state |gtk_state| onto a bitmap.
SkBitmap DrawGtkButtonBorder(int gtk_state,
......@@ -110,6 +112,7 @@ class Gtk2UI : public views::LinuxUI {
virtual void RemoveNativeThemeChangeObserver(
views::NativeThemeChangeObserver* observer) OVERRIDE;
virtual bool UnityIsRunning() OVERRIDE;
virtual NonClientMiddleClickAction GetNonClientMiddleClickAction() OVERRIDE;
virtual void NotifyWindowManagerStartupComplete() OVERRIDE;
// ui::TextEditKeybindingDelegate:
......@@ -234,7 +237,7 @@ class Gtk2UI : public views::LinuxUI {
#if defined(USE_GCONF)
// Currently, the only source of window button configuration. This will
// change if we ever have to support XFCE's configuration system or KDE's.
scoped_ptr<GConfTitlebarListener> titlebar_listener_;
scoped_ptr<GConfListener> gconf_listener_;
#endif // defined(USE_GCONF)
// If either of these vectors are non-empty, they represent the current
......@@ -250,6 +253,10 @@ class Gtk2UI : public views::LinuxUI {
// Observers to notify when the theme state changes.
ObserverList<views::NativeThemeChangeObserver> theme_change_observers_;
// Whether we should lower the window on a middle click to the non client
// area.
NonClientMiddleClickAction middle_click_action_;
// Image cache of lazily created images.
mutable ImageCache gtk_images_;
......
......@@ -45,8 +45,8 @@
'chrome_gtk_menu_subclasses.h',
'g_object_destructor_filo.cc',
'g_object_destructor_filo.h',
'gconf_titlebar_listener.cc',
'gconf_titlebar_listener.h',
'gconf_listener.cc',
'gconf_listener.h',
'gtk2_border.cc',
'gtk2_border.h',
'gtk2_event_loop.cc',
......
......@@ -46,6 +46,15 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory,
public ui::LinuxShellDialog,
public ui::TextEditKeyBindingsDelegateAuraLinux {
public:
// Describes the window management actions that could be taken in response to
// a middle click in the non client area.
enum NonClientMiddleClickAction {
MIDDLE_CLICK_ACTION_NONE,
MIDDLE_CLICK_ACTION_LOWER,
MIDDLE_CLICK_ACTION_MINIMIZE,
MIDDLE_CLICK_ACTION_TOGGLE_MAXIMIZE
};
virtual ~LinuxUI() {}
// Sets the dynamically loaded singleton that draws the desktop native UI.
......@@ -127,6 +136,10 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory,
// Determines whether the user's window manager is Unity.
virtual bool UnityIsRunning() = 0;
// What action we should take when the user middle clicks on non-client
// area. The default is lowering the window.
virtual NonClientMiddleClickAction GetNonClientMiddleClickAction() = 0;
// Notifies the window manager that start up has completed.
// Normally Chromium opens a new window on startup and GTK does this
// automatically. In case Chromium does not open a new window on startup,
......
......@@ -17,6 +17,7 @@
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host.h"
#include "ui/views/widget/native_widget_aura.h"
......@@ -107,7 +108,27 @@ void X11WindowEventFilter::OnMouseEvent(ui::MouseEvent* event) {
return;
if (event->IsMiddleMouseButton() && (component == HTCAPTION)) {
LinuxUI::NonClientMiddleClickAction action =
LinuxUI::MIDDLE_CLICK_ACTION_LOWER;
LinuxUI* linux_ui = LinuxUI::instance();
if (linux_ui)
action = linux_ui->GetNonClientMiddleClickAction();
switch (action) {
case LinuxUI::MIDDLE_CLICK_ACTION_NONE:
break;
case LinuxUI::MIDDLE_CLICK_ACTION_LOWER:
XLowerWindow(xdisplay_, xwindow_);
break;
case LinuxUI::MIDDLE_CLICK_ACTION_MINIMIZE:
window_tree_host_->Minimize();
break;
case LinuxUI::MIDDLE_CLICK_ACTION_TOGGLE_MAXIMIZE:
if (target->GetProperty(aura::client::kCanMaximizeKey))
ToggleMaximizedState();
break;
}
event->SetHandled();
return;
}
......@@ -119,10 +140,7 @@ void X11WindowEventFilter::OnMouseEvent(ui::MouseEvent* event) {
// Our event is a double click in the caption area in a window that can be
// maximized. We are responsible for dispatching this as a minimize/
// maximize on X11 (Windows converts this to min/max events for us).
if (window_tree_host_->IsMaximized())
window_tree_host_->Restore();
else
window_tree_host_->Maximize();
ToggleMaximizedState();
event->SetHandled();
return;
}
......@@ -139,6 +157,13 @@ void X11WindowEventFilter::OnMouseEvent(ui::MouseEvent* event) {
}
}
void X11WindowEventFilter::ToggleMaximizedState() {
if (window_tree_host_->IsMaximized())
window_tree_host_->Restore();
else
window_tree_host_->Maximize();
}
bool X11WindowEventFilter::DispatchHostWindowDragMovement(
int hittest,
const gfx::Point& screen_location) {
......
......@@ -39,6 +39,8 @@ class VIEWS_EXPORT X11WindowEventFilter : public ui::EventHandler {
virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
private:
void ToggleMaximizedState();
// Dispatches a _NET_WM_MOVERESIZE message to the window manager to tell it
// to act as if a border or titlebar drag occurred.
bool DispatchHostWindowDragMovement(int hittest,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment