1
/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
3
* Copyright (C) 2011 Canonical Ltd
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.
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.
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/>.
17
* Authored by: Robert Ancell <robert.ancell@canonical.com>
20
public const int grid_size = 40;
22
public class UnityGreeter
24
public static UnityGreeter singleton;
26
public signal void show_message (string text, LightDM.MessageType type);
27
public signal void show_prompt (string text, LightDM.PromptType type);
28
public signal void authentication_complete ();
29
public signal void starting_session ();
31
public bool test_mode = false;
33
private string state_file;
34
private KeyFile state;
36
private Cairo.XlibSurface background_surface;
38
private SettingsDaemon settings_daemon;
40
private MainWindow main_window;
42
private LightDM.Greeter greeter;
44
private Canberra.Context canberra_context;
46
private static Timer log_timer;
48
private UnityGreeter (bool test_mode_)
51
test_mode = test_mode_;
53
/* Prepare to set the background */
54
debug ("Creating background surface");
55
background_surface = create_root_surface (Gdk.Screen.get_default ());
57
greeter = new LightDM.Greeter ();
58
greeter.show_message.connect ((text, type) => { show_message (text, type); });
59
greeter.show_prompt.connect ((text, type) => { show_prompt (text, type); });
60
greeter.autologin_timer_expired.connect (() => { greeter.authenticate_autologin (); });
61
greeter.authentication_complete.connect (() => { authentication_complete (); });
62
var connected = false;
65
connected = greeter.connect_sync ();
69
warning ("Failed to connect to LightDM daemon");
71
if (!connected && !test_mode)
72
Posix.exit (Posix.EXIT_FAILURE);
76
settings_daemon = new SettingsDaemon ();
77
settings_daemon.start.begin ();
80
var state_dir = Path.build_filename (Environment.get_user_cache_dir (), "unity-greeter");
81
DirUtils.create_with_parents (state_dir, 0775);
83
state_file = Path.build_filename (state_dir, "state");
84
state = new KeyFile ();
87
state.load_from_file (state_file, KeyFileFlags.NONE);
91
if (!(e is FileError.NOENT))
92
warning ("Failed to load state from %s: %s\n", state_file, e.message);
95
main_window = new MainWindow ();
98
Gdk.threads_add_idle (ready_cb);
101
public string? get_state (string key)
105
return state.get_value ("greeter", key);
113
public void set_state (string key, string value)
115
state.set_value ("greeter", key, value);
116
var data = state.to_data ();
119
FileUtils.set_contents (state_file, data);
123
debug ("Failed to write state: %s", e.message);
127
public void push_list (GreeterList widget)
129
main_window.push_list (widget);
132
public void pop_list ()
134
main_window.pop_list ();
137
public static void add_style_class (Gtk.Widget widget)
139
/* Add style context class lightdm-user-list */
140
var ctx = widget.get_style_context ();
141
ctx.add_class ("lightdm");
144
public static LightDM.Layout? get_layout_by_name (string name)
146
foreach (var layout in LightDM.get_layouts ())
148
if (layout.name == name)
154
public void start_session (string? session, Background bg)
156
/* Paint our background onto the root window before we close our own window */
157
var c = new Cairo.Context (background_surface);
158
bg.draw_full (c, Background.DrawFlags.NONE);
160
refresh_background (Gdk.Screen.get_default (), background_surface);
164
debug ("Successfully logged in! Quitting...");
172
greeter.start_session_sync (session);
176
warning ("Failed to start session: %s", e.message);
181
private bool ready_cb ()
183
debug ("starting system-ready sound");
185
/* Launch canberra */
186
Canberra.Context.create (out canberra_context);
188
if (UGSettings.get_boolean (UGSettings.KEY_PLAY_READY_SOUND))
189
canberra_context.play (0,
190
Canberra.PROP_CANBERRA_XDG_THEME_NAME,
192
Canberra.PROP_EVENT_ID,
200
debug ("Showing main window");
202
main_window.get_window ().focus (Gdk.CURRENT_TIME);
203
main_window.set_keyboard_state ();
206
public bool is_authenticated ()
208
return greeter.is_authenticated;
211
public void authenticate (string? userid = null)
213
greeter.authenticate (userid);
216
public void authenticate_as_guest ()
218
greeter.authenticate_as_guest ();
221
public void authenticate_remote (string? session, string? userid)
223
UnityGreeter.singleton.greeter.authenticate_remote (session, userid);
226
public void cancel_authentication ()
228
greeter.cancel_authentication ();
231
public void respond (string response)
233
greeter.respond (response);
236
public string authentication_user ()
238
return greeter.authentication_user;
241
public string default_session_hint ()
243
return greeter.default_session_hint;
246
public string select_user_hint ()
248
return greeter.select_user_hint;
251
public bool show_manual_login_hint ()
253
return greeter.show_manual_login_hint;
256
public bool show_remote_login_hint ()
258
return greeter.show_remote_login_hint;
261
public bool hide_users_hint ()
263
return greeter.hide_users_hint;
266
public bool has_guest_account_hint ()
268
return greeter.has_guest_account_hint;
271
private Gdk.FilterReturn focus_upon_map (Gdk.XEvent gxevent, Gdk.Event event)
273
var xevent = (X.Event*)gxevent;
274
if (xevent.type == X.EventType.MapNotify)
276
var display = Gdk.x11_lookup_xdisplay (xevent.xmap.display);
277
var xwin = xevent.xmap.window;
278
var win = Gdk.X11Window.foreign_new_for_display (display, xwin);
280
/* Check to see if this window is our onboard window, since we don't want to focus it. */
281
X.Window keyboard_xid = 0;
282
if (main_window.menubar.keyboard_window != null)
283
keyboard_xid = Gdk.X11Window.get_xid (main_window.menubar.keyboard_window.get_window ());
285
if (xwin != keyboard_xid && win.get_type_hint() != Gdk.WindowTypeHint.NOTIFICATION)
287
win.focus (Gdk.CURRENT_TIME);
289
/* Make sure to keep keyboard above */
290
if (main_window.menubar.keyboard_window != null)
291
main_window.menubar.keyboard_window.get_window ().raise ();
294
else if (xevent.type == X.EventType.UnmapNotify)
296
// Since we aren't keeping track of focus (for example, we don't
297
// track the Z stack of windows) like a normal WM would, when we
298
// decide here where to return focus after another window unmaps,
299
// we don't have much to go on. X will tell us if we should take
300
// focus back. (I could not find an obvious way to determine this,
301
// but checking if the X input focus is RevertTo.None seems
306
xevent.xunmap.display.get_input_focus (out xwin, out revert_to);
308
if (revert_to == X.RevertTo.None)
310
main_window.get_window ().focus (Gdk.CURRENT_TIME);
312
/* Make sure to keep keyboard above */
313
if (main_window.menubar.keyboard_window != null)
314
main_window.menubar.keyboard_window.get_window ().raise ();
317
return Gdk.FilterReturn.CONTINUE;
320
private void start_fake_wm ()
322
/* We want new windows (e.g. the shutdown dialog) to gain focus.
323
We don't really need anything more than that (don't need alt-tab
324
since any dialog should be "modal" or at least dealt with before
325
continuing even if not actually marked as modal) */
326
var root = Gdk.get_default_root_window ();
327
root.set_events (root.get_events () | Gdk.EventMask.SUBSTRUCTURE_MASK);
328
root.add_filter (focus_upon_map);
331
private static Cairo.XlibSurface? create_root_surface (Gdk.Screen screen)
333
var visual = screen.get_system_visual ();
335
unowned X.Display display = Gdk.X11Display.get_xdisplay (screen.get_display ());
337
var pixmap = X.CreatePixmap (display,
338
Gdk.X11Window.get_xid (screen.get_root_window ()),
340
screen.get_height (),
341
visual.get_depth ());
343
/* Convert into a Cairo surface */
344
var surface = new Cairo.XlibSurface (display,
346
Gdk.X11Visual.get_xvisual (visual),
347
screen.get_width (), screen.get_height ());
352
private static void refresh_background (Gdk.Screen screen, Cairo.XlibSurface surface)
356
unowned X.Display display = Gdk.X11Display.get_xdisplay (screen.get_display ());
358
/* Ensure Cairo has actually finished its drawing */
360
/* Use this pixmap for the background */
361
X.SetWindowBackgroundPixmap (display,
362
Gdk.X11Window.get_xid (screen.get_root_window ()),
363
surface.get_drawable ());
365
X.ClearWindow (display, Gdk.X11Window.get_xid (screen.get_root_window ()));
368
private static void log_cb (string? log_domain, LogLevelFlags log_level, string message)
371
switch (log_level & LogLevelFlags.LEVEL_MASK)
373
case LogLevelFlags.LEVEL_ERROR:
376
case LogLevelFlags.LEVEL_CRITICAL:
377
prefix = "CRITICAL:";
379
case LogLevelFlags.LEVEL_WARNING:
382
case LogLevelFlags.LEVEL_MESSAGE:
385
case LogLevelFlags.LEVEL_INFO:
388
case LogLevelFlags.LEVEL_DEBUG:
396
stderr.printf ("[%+.2fs] %s %s\n", log_timer.elapsed (), prefix, message);
399
public static int main (string[] args)
401
/* Protect memory from being paged to disk, as we deal with passwords */
402
Posix.mlockall (Posix.MCL_CURRENT | Posix.MCL_FUTURE);
404
/* Disable the stupid global menubar */
405
Environment.unset_variable ("UBUNTU_MENUPROXY");
407
/* Initialize i18n */
408
Intl.setlocale (LocaleCategory.ALL, "");
409
Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
410
Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
411
Intl.textdomain (Config.GETTEXT_PACKAGE);
413
/* Set up the accessibility stack, in case the user needs it for screen reading etc. */
414
Environment.set_variable ("GTK_MODULES", "atk-bridge", false);
422
Shell.parse_argv ("/usr/lib/at-spi2-core/at-spi-bus-launcher --launch-immediately", out argv);
423
Process.spawn_async (null,
426
SpawnFlags.SEARCH_PATH,
432
warning ("Error starting the at-spi registry: %s", e.message);
437
log_timer = new Timer ();
438
Log.set_default_handler (log_cb);
440
debug ("Starting unity-greeter %s UID=%d LANG=%s", Config.VERSION, (int) Posix.getuid (), Environment.get_variable ("LANG"));
442
/* Set the cursor to not be the crap default */
443
debug ("Setting cursor");
444
Gdk.get_default_root_window ().set_cursor (new Gdk.Cursor (Gdk.CursorType.LEFT_PTR));
446
bool do_show_version = false;
447
bool do_test_mode = false;
448
OptionEntry versionOption = { "version", 'v', 0, OptionArg.NONE, ref do_show_version,
449
/* Help string for command line --version flag */
450
N_("Show release version"), null };
451
OptionEntry testOption = { "test-mode", 0, 0, OptionArg.NONE, ref do_test_mode,
452
/* Help string for command line --test-mode flag */
453
N_("Run in test mode"), null };
454
OptionEntry nullOption = { null };
455
OptionEntry[] options = { versionOption, testOption, nullOption };
457
debug ("Loading command line options");
458
var c = new OptionContext (/* Arguments and description for --help text */
459
_("- Unity Greeter"));
460
c.add_main_entries (options, Config.GETTEXT_PACKAGE);
461
c.add_group (Gtk.get_option_group (true));
468
stderr.printf ("%s\n", e.message);
469
stderr.printf (/* Text printed out when an unknown command-line argument provided */
470
_("Run '%s --help' to see a full list of available command line options."), args[0]);
471
stderr.printf ("\n");
472
return Posix.EXIT_FAILURE;
476
/* Note, not translated so can be easily parsed */
477
stderr.printf ("unity-greeter %s\n", Config.VERSION);
478
return Posix.EXIT_SUCCESS;
482
debug ("Running in test mode");
486
/* Make nm-applet hide items the user does not have permissions to interact with */
487
Environment.set_variable ("NM_APPLET_HIDE_POLICY_ITEMS", "1", true);
491
Process.spawn_command_line_async ("nm-applet");
495
warning ("Error starting nm-applet: %s", e.message);
499
/* Set GTK+ settings */
500
debug ("Setting GTK+ settings");
501
var settings = Gtk.Settings.get_default ();
502
var value = UGSettings.get_string (UGSettings.KEY_THEME_NAME);
504
settings.set ("gtk-theme-name", value, null);
505
value = UGSettings.get_string (UGSettings.KEY_ICON_THEME_NAME);
507
settings.set ("gtk-icon-theme-name", value, null);
508
value = UGSettings.get_string (UGSettings.KEY_FONT_NAME);
510
settings.set ("gtk-font-name", value, null);
511
var double_value = UGSettings.get_double (UGSettings.KEY_XFT_DPI);
512
if (double_value != 0.0)
513
settings.set ("gtk-xft-dpi", (int) (1024 * double_value), null);
514
var boolean_value = UGSettings.get_boolean (UGSettings.KEY_XFT_ANTIALIAS);
515
settings.set ("gtk-xft-antialias", boolean_value, null);
516
value = UGSettings.get_string (UGSettings.KEY_XFT_HINTSTYLE);
518
settings.set ("gtk-xft-hintstyle", value, null);
519
value = UGSettings.get_string (UGSettings.KEY_XFT_RGBA);
521
settings.set ("gtk-xft-rgba", value, null);
523
debug ("Creating Unity Greeter");
524
var greeter = new UnityGreeter (do_test_mode);
526
debug ("Showing greeter");
529
debug ("Starting main loop");
534
Posix.kill (atspi_pid, Posix.SIGKILL);
536
Posix.waitpid (atspi_pid, out status, 0);
540
return Posix.EXIT_SUCCESS;