1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/libgtk2ui/gconf_listener.h" 6 7#include <gtk/gtk.h> 8 9#include "base/bind.h" 10#include "base/callback.h" 11#include "base/environment.h" 12#include "base/memory/scoped_ptr.h" 13#include "base/nix/xdg_util.h" 14#include "base/strings/string_piece.h" 15#include "base/strings/string_tokenizer.h" 16#include "chrome/browser/ui/libgtk2ui/gtk2_ui.h" 17#include "ui/base/x/x11_util.h" 18#include "ui/views/window/frame_buttons.h" 19 20namespace { 21 22// The GConf key we read for the button placement string. Even through the key 23// has "metacity" in it, it's shared between metacity and compiz. 24const char kButtonLayoutKey[] = "/apps/metacity/general/button_layout"; 25 26// The GConf key we read for what to do in case of middle clicks on non client 27// area. Even through the key has "metacity" in it, it's shared between 28// metacity and compiz. 29const char kMiddleClickActionKey[] = 30 "/apps/metacity/general/action_middle_click_titlebar"; 31 32// GConf requires us to subscribe to a parent directory before we can subscribe 33// to changes in an individual key in that directory. 34const char kMetacityGeneral[] = "/apps/metacity/general"; 35 36const char kDefaultButtonString[] = ":minimize,maximize,close"; 37 38} // namespace 39 40namespace libgtk2ui { 41 42// Public interface: 43 44GConfListener::GConfListener(Gtk2UI* delegate) 45 : delegate_(delegate), 46 client_(NULL) { 47 scoped_ptr<base::Environment> env(base::Environment::Create()); 48 base::nix::DesktopEnvironment de = 49 base::nix::GetDesktopEnvironment(env.get()); 50 if (de == base::nix::DESKTOP_ENVIRONMENT_GNOME || 51 de == base::nix::DESKTOP_ENVIRONMENT_UNITY || 52 ui::GuessWindowManager() == ui::WM_METACITY) { 53 client_ = gconf_client_get_default(); 54 // If we fail to get a context, that's OK, since we'll just fallback on 55 // not receiving gconf keys. 56 if (client_) { 57 // Register that we're interested in the values of this directory. 58 GError* error = NULL; 59 gconf_client_add_dir(client_, kMetacityGeneral, 60 GCONF_CLIENT_PRELOAD_ONELEVEL, &error); 61 if (HandleGError(error, kMetacityGeneral)) 62 return; 63 64 // Get the initial value of the keys we're interested in. 65 GetAndRegister(kButtonLayoutKey, 66 base::Bind(&GConfListener::ParseAndStoreButtonValue, 67 base::Unretained(this))); 68 GetAndRegister(kMiddleClickActionKey, 69 base::Bind(&GConfListener::ParseAndStoreMiddleClickValue, 70 base::Unretained(this))); 71 } 72 } 73} 74 75GConfListener::~GConfListener() { 76} 77 78// Private: 79 80void GConfListener::GetAndRegister( 81 const char* key_to_subscribe, 82 const base::Callback<void(GConfValue*)>& initial_setter) { 83 GError* error = NULL; 84 GConfValue* gconf_value = gconf_client_get(client_, key_to_subscribe, 85 &error); 86 if (HandleGError(error, key_to_subscribe)) 87 return; 88 initial_setter.Run(gconf_value); 89 if (gconf_value) 90 gconf_value_free(gconf_value); 91 92 // Register to get notifies about changes to this key. 93 gconf_client_notify_add( 94 client_, key_to_subscribe, 95 reinterpret_cast<void (*)(GConfClient*, guint, GConfEntry*, void*)>( 96 OnChangeNotificationThunk), 97 this, NULL, &error); 98 if (HandleGError(error, key_to_subscribe)) 99 return; 100} 101 102void GConfListener::OnChangeNotification(GConfClient* client, 103 guint cnxn_id, 104 GConfEntry* entry) { 105 if (strcmp(gconf_entry_get_key(entry), kButtonLayoutKey) == 0) { 106 GConfValue* gconf_value = gconf_entry_get_value(entry); 107 ParseAndStoreButtonValue(gconf_value); 108 } else if (strcmp(gconf_entry_get_key(entry), kMiddleClickActionKey) == 0) { 109 GConfValue* gconf_value = gconf_entry_get_value(entry); 110 ParseAndStoreMiddleClickValue(gconf_value); 111 } 112} 113 114bool GConfListener::HandleGError(GError* error, const char* key) { 115 if (error != NULL) { 116 LOG(ERROR) << "Error with gconf key '" << key << "': " << error->message; 117 g_error_free(error); 118 g_object_unref(client_); 119 client_ = NULL; 120 return true; 121 } 122 return false; 123} 124 125void GConfListener::ParseAndStoreButtonValue(GConfValue* gconf_value) { 126 std::string button_string; 127 if (gconf_value) { 128 const char* value = gconf_value_get_string(gconf_value); 129 button_string = value ? value : kDefaultButtonString; 130 } else { 131 button_string = kDefaultButtonString; 132 } 133 134 // Parse the button_layout string. 135 std::vector<views::FrameButton> leading_buttons; 136 std::vector<views::FrameButton> trailing_buttons; 137 bool left_side = true; 138 base::StringTokenizer tokenizer(button_string, ":,"); 139 tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS); 140 while (tokenizer.GetNext()) { 141 if (tokenizer.token_is_delim()) { 142 if (*tokenizer.token_begin() == ':') 143 left_side = false; 144 } else { 145 base::StringPiece token = tokenizer.token_piece(); 146 if (token == "minimize") { 147 (left_side ? leading_buttons : trailing_buttons).push_back( 148 views::FRAME_BUTTON_MINIMIZE); 149 } else if (token == "maximize") { 150 (left_side ? leading_buttons : trailing_buttons).push_back( 151 views::FRAME_BUTTON_MAXIMIZE); 152 } else if (token == "close") { 153 (left_side ? leading_buttons : trailing_buttons).push_back( 154 views::FRAME_BUTTON_CLOSE); 155 } 156 } 157 } 158 159 delegate_->SetWindowButtonOrdering(leading_buttons, trailing_buttons); 160} 161 162void GConfListener::ParseAndStoreMiddleClickValue(GConfValue* gconf_value) { 163 Gtk2UI::NonClientMiddleClickAction action = 164 views::LinuxUI::MIDDLE_CLICK_ACTION_LOWER; 165 if (gconf_value) { 166 const char* value = gconf_value_get_string(gconf_value); 167 168 if (strcmp(value, "none") == 0) { 169 action = views::LinuxUI::MIDDLE_CLICK_ACTION_NONE; 170 } else if (strcmp(value, "lower") == 0) { 171 action = views::LinuxUI::MIDDLE_CLICK_ACTION_LOWER; 172 } else if (strcmp(value, "minimize") == 0) { 173 action = views::LinuxUI::MIDDLE_CLICK_ACTION_MINIMIZE; 174 } else if (strcmp(value, "toggle-maximize") == 0) { 175 action = views::LinuxUI::MIDDLE_CLICK_ACTION_TOGGLE_MAXIMIZE; 176 } else { 177 // While we want to have the default state be lower if there isn't a 178 // value, we want to default to no action if the user has explicitly 179 // chose an action that we don't implement. 180 action = views::LinuxUI::MIDDLE_CLICK_ACTION_NONE; 181 } 182 } 183 184 delegate_->SetNonClientMiddleClickAction(action); 185} 186 187} // namespace libgtk2ui 188