1/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 * 3 * Copyright (C) 2011,2012 Canonical Ltd 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 3 as 7 * published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 * Authors: Robert Ancell <robert.ancell@canonical.com> 18 * Michael Terry <michael.terry@canonical.com> 19 */ 20 21public class MainWindow : Gtk.Window 22{ 23 public MenuBar menubar; 24 25 private List<Monitor> monitors; 26 private Monitor? primary_monitor; 27 private Monitor active_monitor; 28 private string only_on_monitor; 29 private bool monitor_setting_ok; 30 private Background background; 31 private Gtk.Box login_box; 32 private Gtk.Box hbox; 33 private Gtk.Button back_button; 34 private ShutdownDialog? shutdown_dialog = null; 35 private bool do_resize; 36 37 public ListStack stack; 38 39 // Menubar is smaller, but with shadow, we reserve more space 40 public const int MENUBAR_HEIGHT = 32; 41 42 construct 43 { 44 events |= Gdk.EventMask.POINTER_MOTION_MASK; 45 46 var accel_group = new Gtk.AccelGroup (); 47 add_accel_group (accel_group); 48 49 var bg_color = Gdk.RGBA (); 50 bg_color.parse (UGSettings.get_string (UGSettings.KEY_BACKGROUND_COLOR)); 51 override_background_color (Gtk.StateFlags.NORMAL, bg_color); 52 get_accessible ().set_name (_("Login Screen")); 53 has_resize_grip = false; 54 SlickGreeter.add_style_class (this); 55 56 background = new Background (); 57 add (background); 58 SlickGreeter.add_style_class (background); 59 60 login_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 61 login_box.show (); 62 background.add (login_box); 63 64 /* Box for menubar shadow */ 65 var menubox = new Gtk.EventBox (); 66 var menualign = new Gtk.Alignment (0.0f, 0.0f, 1.0f, 0.0f); 67 var shadow_path = Path.build_filename (Config.PKGDATADIR, 68 "shadow.png", null); 69 var shadow_style = ""; 70 if (FileUtils.test (shadow_path, FileTest.EXISTS)) 71 { 72 shadow_style = "background-image: url('%s');background-repeat: repeat;".printf(shadow_path); 73 } 74 try 75 { 76 var style = new Gtk.CssProvider (); 77 style.load_from_data ("* {background-color: transparent;%s}".printf(shadow_style), -1); 78 var context = menubox.get_style_context (); 79 context.add_provider (style, 80 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); 81 } 82 catch (Error e) 83 { 84 debug ("Internal error loading menubox style: %s", e.message); 85 } 86 menubox.set_size_request (-1, MENUBAR_HEIGHT); 87 menubox.show (); 88 menualign.show (); 89 menubox.add (menualign); 90 login_box.add (menubox); 91 SlickGreeter.add_style_class (menualign); 92 SlickGreeter.add_style_class (menubox); 93 94 menubar = new MenuBar (background, accel_group, this); 95 menubar.show (); 96 menualign.add (menubar); 97 SlickGreeter.add_style_class (menubar); 98 99 hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 100 hbox.expand = true; 101 hbox.show (); 102 login_box.add (hbox); 103 104 var align = new Gtk.Alignment (0.5f, 0.5f, 0.0f, 0.0f); 105 // Hack to avoid gtk 3.20's new allocate logic, which messes us up. 106 align.resize_mode = Gtk.ResizeMode.QUEUE; 107 align.set_size_request (grid_size, -1); 108 align.margin_bottom = MENUBAR_HEIGHT; /* offset for menubar at top */ 109 align.show (); 110 hbox.add (align); 111 112 back_button = new FlatButton (); 113 back_button.get_accessible ().set_name (_("Back")); 114 Gtk.button_set_focus_on_click (back_button, false); 115 var image = new Gtk.Image.from_file (Path.build_filename (Config.PKGDATADIR, "arrow_left.png", null)); 116 image.show (); 117 back_button.set_size_request (grid_size - GreeterList.BORDER * 2, grid_size - GreeterList.BORDER * 2); 118 back_button.add (image); 119 back_button.clicked.connect (pop_list); 120 align.add (back_button); 121 122 align = new Gtk.Alignment (0.0f, 0.5f, 0.0f, 1.0f); 123 align.show (); 124 hbox.add (align); 125 126 stack = new ListStack (); 127 stack.show (); 128 align.add (stack); 129 130 add_user_list (); 131 132 primary_monitor = null; 133 do_resize = false; 134 135 only_on_monitor = UGSettings.get_string(UGSettings.KEY_ONLY_ON_MONITOR); 136 monitor_setting_ok = only_on_monitor == "auto"; 137 138 if (SlickGreeter.singleton.test_mode) 139 { 140 /* Simulate an 800x600 monitor to the left of a 640x480 monitor */ 141 monitors = new List<Monitor> (); 142 monitors.append (new Monitor (0, 0, 800, 600)); 143 monitors.append (new Monitor (800, 120, 640, 480)); 144 background.set_monitors (monitors); 145 move_to_monitor (monitors.nth_data (0)); 146 resize (background.width, background.height); 147 } 148 else 149 { 150 var screen = get_screen (); 151 screen.monitors_changed.connect (monitors_changed_cb); 152 monitors_changed_cb (screen); 153 } 154 155 /* Force a call on login_box.show()... 156 This fixes the following issue: 157 When the greeter starts, the login box looks too small, its entry isn't visible and 158 its session button isn't sensitive/clickable. 159 Pressing Escape fixes the box but not the session button.. 160 Scrolling up/down fixes both.. 161 */ 162 if (login_box.sensitive) { 163 login_box.show(); 164 } 165 } 166 167 public void push_list (GreeterList widget) 168 { 169 stack.push (widget); 170 171 if (stack.num_children > 1) 172 back_button.show (); 173 } 174 175 public void pop_list () 176 { 177 if (stack.num_children <= 2) 178 back_button.hide (); 179 180 stack.pop (); 181 } 182 183 public override void size_allocate (Gtk.Allocation allocation) 184 { 185 base.size_allocate (allocation); 186 187 if (hbox != null) 188 { 189 hbox.margin_left = get_grid_offset (get_allocated_width ()) + grid_size; 190 hbox.margin_right = get_grid_offset (get_allocated_width ()); 191 hbox.margin_top = get_grid_offset (get_allocated_height ()); 192 hbox.margin_bottom = get_grid_offset (get_allocated_height ()); 193 } 194 } 195 196 public override void realize () 197 { 198 base.realize (); 199 background.set_surface (Gdk.cairo_create (get_window ()).get_target ()); 200 } 201 202 public void before_session_start() 203 { 204 debug ("Killing orca and onboard"); 205 menubar.cleanup(); 206 } 207 208 /* Setup the size and position of the window */ 209 public void setup_window () 210 { 211 resize (background.width, background.height); 212 move (0, 0); 213 move_to_monitor (primary_monitor); 214 } 215 216 private void monitors_changed_cb (Gdk.Screen screen) 217 { 218 Gdk.Display display = screen.get_display(); 219 Gdk.Monitor primary = display.get_primary_monitor(); 220 Gdk.Rectangle geometry; 221 222 monitors = new List<Monitor> (); 223 primary_monitor = null; 224 225 for (var i = 0; i < display.get_n_monitors (); i++) 226 { 227 Gdk.Monitor monitor = display.get_monitor(i); 228 geometry = monitor.get_geometry (); 229 debug ("Monitor %d is %dx%d pixels at %d,%d", i, geometry.width, geometry.height, geometry.x, geometry.y); 230 231 if (monitor_is_unique_position (display, i)) 232 { 233 var greeter_monitor = new Monitor (geometry.x, geometry.y, geometry.width, geometry.height); 234 var plug_name = monitor.get_model(); 235 monitors.append (greeter_monitor); 236 237 if (plug_name == only_on_monitor) 238 monitor_setting_ok = true; 239 240 if (plug_name == only_on_monitor || primary_monitor == null || primary == monitor) 241 primary_monitor = greeter_monitor; 242 } 243 } 244 245 debug ("MainWindow is %dx%d pixels", background.width, background.height); 246 247 background.set_monitors (monitors); 248 249 if(do_resize) 250 { 251 setup_window (); 252 } 253 else 254 { 255 do_resize = true; 256 } 257 } 258 259 /* Check if a monitor has a unique position */ 260 private bool monitor_is_unique_position (Gdk.Display display, int n) 261 { 262 Gdk.Rectangle g0; 263 Gdk.Monitor mon0; 264 mon0 = display.get_monitor(n); 265 g0 = mon0.get_geometry (); 266 267 for (var i = n + 1; i < display.get_n_monitors (); i++) 268 { 269 Gdk.Rectangle g1; 270 Gdk.Monitor mon1; 271 mon1 = display.get_monitor(i); 272 g1 = mon1.get_geometry(); 273 274 if (g0.x == g1.x && g0.y == g1.y) 275 return false; 276 } 277 278 return true; 279 } 280 281 public override bool motion_notify_event (Gdk.EventMotion event) 282 { 283 if (!monitor_setting_ok || only_on_monitor == "auto") 284 { 285 var x = (int) (event.x + 0.5); 286 var y = (int) (event.y + 0.5); 287 288 /* Get motion event relative to this widget */ 289 if (event.window != get_window ()) 290 { 291 int w_x, w_y; 292 get_window ().get_origin (out w_x, out w_y); 293 x -= w_x; 294 y -= w_y; 295 event.window.get_origin (out w_x, out w_y); 296 x += w_x; 297 y += w_y; 298 } 299 300 foreach (var m in monitors) 301 { 302 if (x >= m.x && x <= m.x + m.width && y >= m.y && y <= m.y + m.height) 303 { 304 move_to_monitor (m); 305 break; 306 } 307 } 308 } 309 310 return false; 311 } 312 313 private void move_to_monitor (Monitor monitor) 314 { 315 active_monitor = monitor; 316 login_box.set_size_request (monitor.width, monitor.height); 317 background.set_active_monitor (monitor); 318 background.move (login_box, monitor.x, monitor.y); 319 320 if (shutdown_dialog != null) 321 { 322 shutdown_dialog.set_active_monitor (monitor); 323 background.move (shutdown_dialog, monitor.x, monitor.y); 324 } 325 } 326 327 private void add_user_list () 328 { 329 GreeterList greeter_list; 330 greeter_list = new UserList (background, menubar); 331 greeter_list.show (); 332 SlickGreeter.add_style_class (greeter_list); 333 push_list (greeter_list); 334 } 335 336 public override bool key_press_event (Gdk.EventKey event) 337 { 338 var top = stack.top (); 339 340 if (stack.top () is UserList) 341 { 342 var user_list = stack.top () as UserList; 343 if (!user_list.show_hidden_users) 344 { 345 var shift_mask = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK; 346 var control_mask = Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.MOD1_MASK; 347 var alt_mask = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK; 348 if (((event.keyval == Gdk.Key.Shift_L || event.keyval == Gdk.Key.Shift_R) && (event.state & shift_mask) == shift_mask) || 349 ((event.keyval == Gdk.Key.Control_L || event.keyval == Gdk.Key.Control_R) && (event.state & control_mask) == control_mask) || 350 ((event.keyval == Gdk.Key.Alt_L || event.keyval == Gdk.Key.Alt_R) && (event.state & alt_mask) == alt_mask)) 351 { 352 debug ("Hidden user key combination detected"); 353 user_list.show_hidden_users = true; 354 return true; 355 } 356 } 357 } 358 359 switch (event.keyval) 360 { 361 case Gdk.Key.Escape: 362 if (login_box.sensitive) 363 top.cancel_authentication (); 364 if (shutdown_dialog != null) 365 shutdown_dialog.cancel (); 366 return true; 367 case Gdk.Key.Page_Up: 368 case Gdk.Key.KP_Page_Up: 369 if (login_box.sensitive) 370 top.scroll (GreeterList.ScrollTarget.START); 371 return true; 372 case Gdk.Key.Page_Down: 373 case Gdk.Key.KP_Page_Down: 374 if (login_box.sensitive) 375 top.scroll (GreeterList.ScrollTarget.END); 376 return true; 377 case Gdk.Key.Up: 378 case Gdk.Key.KP_Up: 379 if (login_box.sensitive) 380 top.scroll (GreeterList.ScrollTarget.UP); 381 return true; 382 case Gdk.Key.Down: 383 case Gdk.Key.KP_Down: 384 if (login_box.sensitive) 385 top.scroll (GreeterList.ScrollTarget.DOWN); 386 return true; 387 case Gdk.Key.Left: 388 case Gdk.Key.KP_Left: 389 if (shutdown_dialog != null) 390 shutdown_dialog.focus_prev (); 391 return true; 392 case Gdk.Key.Right: 393 case Gdk.Key.KP_Right: 394 if (shutdown_dialog != null) 395 shutdown_dialog.focus_next (); 396 return true; 397 case Gdk.Key.F10: 398 if (login_box.sensitive) 399 menubar.select_first (false); 400 return true; 401 case Gdk.Key.PowerOff: 402 show_shutdown_dialog (ShutdownDialogType.SHUTDOWN); 403 return true; 404 case Gdk.Key.Print: 405 debug ("Taking screenshot"); 406 var root = Gdk.get_default_root_window (); 407 var screenshot = Gdk.pixbuf_get_from_window (root, 0, 0, root.get_width (), root.get_height ()); 408 try 409 { 410 screenshot.save ("Screenshot.png", "png", null); 411 } 412 catch (Error e) 413 { 414 warning ("Failed to save screenshot: %s", e.message); 415 } 416 return true; 417 case Gdk.Key.z: 418 if (SlickGreeter.singleton.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0) 419 { 420 show_shutdown_dialog (ShutdownDialogType.SHUTDOWN); 421 return true; 422 } 423 break; 424 case Gdk.Key.Z: 425 if (SlickGreeter.singleton.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0) 426 { 427 show_shutdown_dialog (ShutdownDialogType.RESTART); 428 return true; 429 } 430 break; 431 } 432 433 return base.key_press_event (event); 434 } 435 436 public void set_keyboard_state () 437 { 438 menubar.set_keyboard_state (); 439 } 440 441 public void show_shutdown_dialog (ShutdownDialogType type) 442 { 443 if (shutdown_dialog != null) 444 shutdown_dialog.destroy (); 445 446 /* Stop input to login box */ 447 login_box.sensitive = false; 448 449 shutdown_dialog = new ShutdownDialog (type, background); 450 shutdown_dialog.closed.connect (close_shutdown_dialog); 451 background.add (shutdown_dialog); 452 move_to_monitor (active_monitor); 453 shutdown_dialog.visible = true; 454 } 455 456 public void close_shutdown_dialog () 457 { 458 if (shutdown_dialog == null) 459 return; 460 461 shutdown_dialog.destroy (); 462 shutdown_dialog = null; 463 464 login_box.sensitive = true; 465 } 466} 467