1 // Copyright (c) 2012 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 <gtk/gtk.h> 6 #include <math.h> 7 8 #include "base/compiler_specific.h" 9 #include "base/logging.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "remoting/base/string_resources.h" 13 #include "remoting/host/client_session_control.h" 14 #include "remoting/host/host_window.h" 15 #include "ui/base/glib/glib_signal.h" 16 #include "ui/base/l10n/l10n_util.h" 17 18 namespace remoting { 19 20 namespace { 21 22 class DisconnectWindowGtk : public HostWindow { 23 public: 24 DisconnectWindowGtk(); 25 virtual ~DisconnectWindowGtk(); 26 27 // HostWindow overrides. 28 virtual void Start( 29 const base::WeakPtr<ClientSessionControl>& client_session_control) 30 OVERRIDE; 31 32 private: 33 CHROMEG_CALLBACK_1(DisconnectWindowGtk, gboolean, OnDelete, 34 GtkWidget*, GdkEvent*); 35 CHROMEG_CALLBACK_0(DisconnectWindowGtk, void, OnClicked, GtkButton*); 36 CHROMEG_CALLBACK_1(DisconnectWindowGtk, gboolean, OnConfigure, 37 GtkWidget*, GdkEventConfigure*); 38 CHROMEG_CALLBACK_1(DisconnectWindowGtk, gboolean, OnButtonPress, 39 GtkWidget*, GdkEventButton*); 40 41 // Used to disconnect the client session. 42 base::WeakPtr<ClientSessionControl> client_session_control_; 43 44 GtkWidget* disconnect_window_; 45 GtkWidget* message_; 46 GtkWidget* button_; 47 48 // Used to distinguish resize events from other types of "configure-event" 49 // notifications. 50 int current_width_; 51 int current_height_; 52 53 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowGtk); 54 }; 55 56 // Helper function for creating a rectangular path with rounded corners, as 57 // Cairo doesn't have this facility. |radius| is the arc-radius of each 58 // corner. The bounding rectangle extends from (0, 0) to (width, height). 59 void AddRoundRectPath(cairo_t* cairo_context, int width, int height, 60 int radius) { 61 cairo_new_sub_path(cairo_context); 62 cairo_arc(cairo_context, width - radius, radius, radius, -M_PI_2, 0); 63 cairo_arc(cairo_context, width - radius, height - radius, radius, 0, M_PI_2); 64 cairo_arc(cairo_context, radius, height - radius, radius, M_PI_2, 2 * M_PI_2); 65 cairo_arc(cairo_context, radius, radius, radius, 2 * M_PI_2, 3 * M_PI_2); 66 cairo_close_path(cairo_context); 67 } 68 69 DisconnectWindowGtk::DisconnectWindowGtk() 70 : disconnect_window_(NULL), 71 current_width_(0), 72 current_height_(0) { 73 } 74 75 DisconnectWindowGtk::~DisconnectWindowGtk() { 76 DCHECK(CalledOnValidThread()); 77 78 if (disconnect_window_) { 79 gtk_widget_destroy(disconnect_window_); 80 disconnect_window_ = NULL; 81 } 82 } 83 84 void DisconnectWindowGtk::Start( 85 const base::WeakPtr<ClientSessionControl>& client_session_control) { 86 DCHECK(CalledOnValidThread()); 87 DCHECK(!client_session_control_.get()); 88 DCHECK(client_session_control.get()); 89 DCHECK(!disconnect_window_); 90 91 client_session_control_ = client_session_control; 92 93 // Create the window. 94 disconnect_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); 95 GtkWindow* window = GTK_WINDOW(disconnect_window_); 96 97 g_signal_connect(disconnect_window_, "delete-event", 98 G_CALLBACK(OnDeleteThunk), this); 99 gtk_window_set_title(window, 100 l10n_util::GetStringUTF8(IDS_PRODUCT_NAME).c_str()); 101 gtk_window_set_resizable(window, FALSE); 102 103 // Try to keep the window always visible. 104 gtk_window_stick(window); 105 gtk_window_set_keep_above(window, TRUE); 106 107 // Remove window titlebar. 108 gtk_window_set_decorated(window, FALSE); 109 110 // In case the titlebar is still there, try to remove some of the buttons. 111 // Utility windows have no minimize button or taskbar presence. 112 gtk_window_set_type_hint(window, GDK_WINDOW_TYPE_HINT_UTILITY); 113 gtk_window_set_deletable(window, FALSE); 114 115 // Allow custom rendering of the background pixmap. 116 gtk_widget_set_app_paintable(disconnect_window_, TRUE); 117 118 // Handle window resizing, to regenerate the background pixmap and window 119 // shape bitmap. The stored width & height need to be initialized here 120 // in case the window is created a second time (the size of the previous 121 // window would be remembered, preventing the generation of bitmaps for the 122 // new window). 123 current_height_ = current_width_ = 0; 124 g_signal_connect(disconnect_window_, "configure-event", 125 G_CALLBACK(OnConfigureThunk), this); 126 127 // Handle mouse events to allow the user to drag the window around. 128 gtk_widget_set_events(disconnect_window_, GDK_BUTTON_PRESS_MASK); 129 g_signal_connect(disconnect_window_, "button-press-event", 130 G_CALLBACK(OnButtonPressThunk), this); 131 132 // All magic numbers taken from screen shots provided by UX. 133 // The alignment sets narrow margins at the top and bottom, compared with 134 // left and right. The left margin is made larger to accommodate the 135 // window movement gripper. 136 GtkWidget* align = gtk_alignment_new(0, 0, 1, 1); 137 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 24, 12); 138 gtk_container_add(GTK_CONTAINER(window), align); 139 140 GtkWidget* button_row = gtk_hbox_new(FALSE, 12); 141 gtk_container_add(GTK_CONTAINER(align), button_row); 142 143 button_ = gtk_button_new_with_label( 144 l10n_util::GetStringUTF8(IDS_STOP_SHARING_BUTTON).c_str()); 145 gtk_box_pack_end(GTK_BOX(button_row), button_, FALSE, FALSE, 0); 146 147 g_signal_connect(button_, "clicked", G_CALLBACK(OnClickedThunk), this); 148 149 message_ = gtk_label_new(NULL); 150 gtk_box_pack_end(GTK_BOX(button_row), message_, FALSE, FALSE, 0); 151 152 // Override any theme setting for the text color, so that the text is 153 // readable against the window's background pixmap. 154 PangoAttrList* attributes = pango_attr_list_new(); 155 PangoAttribute* text_color = pango_attr_foreground_new(0, 0, 0); 156 pango_attr_list_insert(attributes, text_color); 157 gtk_label_set_attributes(GTK_LABEL(message_), attributes); 158 pango_attr_list_unref(attributes); 159 160 gtk_widget_show_all(disconnect_window_); 161 162 // Extract the user name from the JID. 163 std::string client_jid = client_session_control_->client_jid(); 164 base::string16 username = 165 base::UTF8ToUTF16(client_jid.substr(0, client_jid.find('/'))); 166 gtk_label_set_text( 167 GTK_LABEL(message_), 168 l10n_util::GetStringFUTF8(IDS_MESSAGE_SHARED, username).c_str()); 169 gtk_window_present(window); 170 } 171 172 void DisconnectWindowGtk::OnClicked(GtkButton* button) { 173 DCHECK(CalledOnValidThread()); 174 175 if (client_session_control_.get()) 176 client_session_control_->DisconnectSession(); 177 } 178 179 gboolean DisconnectWindowGtk::OnDelete(GtkWidget* window, 180 GdkEvent* event) { 181 DCHECK(CalledOnValidThread()); 182 183 if (client_session_control_.get()) 184 client_session_control_->DisconnectSession(); 185 return TRUE; 186 } 187 188 gboolean DisconnectWindowGtk::OnConfigure(GtkWidget* widget, 189 GdkEventConfigure* event) { 190 DCHECK(CalledOnValidThread()); 191 192 // Only generate bitmaps if the size has actually changed. 193 if (event->width == current_width_ && event->height == current_height_) 194 return FALSE; 195 196 current_width_ = event->width; 197 current_height_ = event->height; 198 199 // Create the depth 1 pixmap for the window shape. 200 GdkPixmap* shape_mask = gdk_pixmap_new(NULL, current_width_, current_height_, 201 1); 202 cairo_t* cairo_context = gdk_cairo_create(shape_mask); 203 204 // Set the arc radius for the corners. 205 const int kCornerRadius = 6; 206 207 // Initialize the whole bitmap to be transparent. 208 cairo_set_source_rgba(cairo_context, 0, 0, 0, 0); 209 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); 210 cairo_paint(cairo_context); 211 212 // Paint an opaque round rect covering the whole area (leaving the extreme 213 // corners transparent). 214 cairo_set_source_rgba(cairo_context, 1, 1, 1, 1); 215 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); 216 AddRoundRectPath(cairo_context, current_width_, current_height_, 217 kCornerRadius); 218 cairo_fill(cairo_context); 219 220 cairo_destroy(cairo_context); 221 gdk_window_shape_combine_mask(widget->window, shape_mask, 0, 0); 222 g_object_unref(shape_mask); 223 224 // Create a full-color pixmap for the window background image. 225 GdkPixmap* background = gdk_pixmap_new(NULL, current_width_, current_height_, 226 24); 227 cairo_context = gdk_cairo_create(background); 228 229 // Paint the whole bitmap one color. 230 cairo_set_source_rgb(cairo_context, 0.91, 0.91, 0.91); 231 cairo_paint(cairo_context); 232 233 // Paint the round-rectangle edge. 234 cairo_set_source_rgb(cairo_context, 0.13, 0.69, 0.11); 235 cairo_set_line_width(cairo_context, 6); 236 AddRoundRectPath(cairo_context, current_width_, current_height_, 237 kCornerRadius); 238 cairo_stroke(cairo_context); 239 240 // Render the window-gripper. In order for a straight line to light up 241 // single pixels, Cairo requires the coordinates to have fractional 242 // components of 0.5 (so the "/ 2" is a deliberate integer division). 243 double gripper_top = current_height_ / 2 - 10.5; 244 double gripper_bottom = current_height_ / 2 + 10.5; 245 cairo_set_line_width(cairo_context, 1); 246 247 double x = 12.5; 248 cairo_set_source_rgb(cairo_context, 0.70, 0.70, 0.70); 249 cairo_move_to(cairo_context, x, gripper_top); 250 cairo_line_to(cairo_context, x, gripper_bottom); 251 cairo_stroke(cairo_context); 252 x += 3; 253 cairo_move_to(cairo_context, x, gripper_top); 254 cairo_line_to(cairo_context, x, gripper_bottom); 255 cairo_stroke(cairo_context); 256 257 x -= 2; 258 cairo_set_source_rgb(cairo_context, 0.97, 0.97, 0.97); 259 cairo_move_to(cairo_context, x, gripper_top); 260 cairo_line_to(cairo_context, x, gripper_bottom); 261 cairo_stroke(cairo_context); 262 x += 3; 263 cairo_move_to(cairo_context, x, gripper_top); 264 cairo_line_to(cairo_context, x, gripper_bottom); 265 cairo_stroke(cairo_context); 266 267 cairo_destroy(cairo_context); 268 269 gdk_window_set_back_pixmap(widget->window, background, FALSE); 270 g_object_unref(background); 271 gdk_window_invalidate_rect(widget->window, NULL, TRUE); 272 273 return FALSE; 274 } 275 276 gboolean DisconnectWindowGtk::OnButtonPress(GtkWidget* widget, 277 GdkEventButton* event) { 278 DCHECK(CalledOnValidThread()); 279 280 gtk_window_begin_move_drag(GTK_WINDOW(disconnect_window_), 281 event->button, 282 event->x_root, 283 event->y_root, 284 event->time); 285 return FALSE; 286 } 287 288 } // namespace 289 290 // static 291 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() { 292 return scoped_ptr<HostWindow>(new DisconnectWindowGtk()); 293 } 294 295 } // namespace remoting 296