aboutsummaryrefslogtreecommitdiff
path: root/src/arctica-greeter.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/arctica-greeter.vala')
-rw-r--r--src/arctica-greeter.vala660
1 files changed, 660 insertions, 0 deletions
diff --git a/src/arctica-greeter.vala b/src/arctica-greeter.vala
new file mode 100644
index 0000000..217e23c
--- /dev/null
+++ b/src/arctica-greeter.vala
@@ -0,0 +1,660 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Robert Ancell <robert.ancell@canonical.com>
+ */
+
+public const int grid_size = 40;
+
+public class ArcticaGreeter
+{
+ public static ArcticaGreeter singleton;
+
+ public signal void show_message (string text, LightDM.MessageType type);
+ public signal void show_prompt (string text, LightDM.PromptType type);
+ public signal void authentication_complete ();
+ public signal void starting_session ();
+
+ public bool test_mode = false;
+
+ private string state_file;
+ private KeyFile state;
+
+ private Cairo.XlibSurface background_surface;
+
+ private SettingsDaemon settings_daemon;
+
+ public bool orca_needs_kick;
+ private MainWindow main_window;
+
+ private LightDM.Greeter greeter;
+
+ private Canberra.Context canberra_context;
+
+ private static Timer log_timer;
+
+ private DialogDBusInterface dbus_object;
+
+ private ArcticaGreeter (bool test_mode_)
+ {
+ singleton = this;
+ test_mode = test_mode_;
+
+ /* Prepare to set the background */
+ debug ("Creating background surface");
+ background_surface = create_root_surface (Gdk.Screen.get_default ());
+
+ greeter = new LightDM.Greeter ();
+ greeter.show_message.connect ((text, type) => { show_message (text, type); });
+ greeter.show_prompt.connect ((text, type) => { show_prompt (text, type); });
+ greeter.autologin_timer_expired.connect (() => { greeter.authenticate_autologin (); });
+ greeter.authentication_complete.connect (() => { authentication_complete (); });
+ var connected = false;
+ try
+ {
+ connected = greeter.connect_sync ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to connect to LightDM daemon");
+ }
+ if (!connected && !test_mode)
+ Posix.exit (Posix.EXIT_FAILURE);
+
+ if (!test_mode)
+ {
+ settings_daemon = new SettingsDaemon ();
+ settings_daemon.start ();
+ }
+
+ var state_dir = Path.build_filename (Environment.get_user_cache_dir (), "arctica-greeter");
+ DirUtils.create_with_parents (state_dir, 0775);
+
+ var xdg_seat = GLib.Environment.get_variable("XDG_SEAT");
+ var state_file_name = xdg_seat != null && xdg_seat != "seat0" ? xdg_seat + "-state" : "state";
+
+ state_file = Path.build_filename (state_dir, state_file_name);
+ state = new KeyFile ();
+ try
+ {
+ state.load_from_file (state_file, KeyFileFlags.NONE);
+ }
+ catch (Error e)
+ {
+ if (!(e is FileError.NOENT))
+ warning ("Failed to load state from %s: %s\n", state_file, e.message);
+ }
+
+ main_window = new MainWindow ();
+
+ Bus.own_name (BusType.SESSION, "com.canonical.ArcticaGreeter", BusNameOwnerFlags.NONE);
+
+ dbus_object = new DialogDBusInterface ();
+ dbus_object.open_dialog.connect ((type) =>
+ {
+ ShutdownDialogType dialog_type;
+ switch (type)
+ {
+ default:
+ case 1:
+ dialog_type = ShutdownDialogType.LOGOUT;
+ break;
+ case 2:
+ dialog_type = ShutdownDialogType.RESTART;
+ break;
+ }
+ main_window.show_shutdown_dialog (dialog_type);
+ });
+ dbus_object.close_dialog.connect ((type) => { main_window.close_shutdown_dialog (); });
+ Bus.own_name (BusType.SESSION, "com.canonical.Unity", BusNameOwnerFlags.NONE,
+ (c) =>
+ {
+ try
+ {
+ c.register_object ("/org/gnome/SessionManager/EndSessionDialog", dbus_object);
+ }
+ catch (Error e)
+ {
+ warning ("Failed to register /org/gnome/SessionManager/EndSessionDialog: %s", e.message);
+ }
+ },
+ null,
+ () => debug ("Failed to acquire name com.canonical.Unity"));
+
+ start_fake_wm ();
+ Gdk.threads_add_idle (ready_cb);
+ }
+
+ public string? get_state (string key)
+ {
+ try
+ {
+ return state.get_value ("greeter", key);
+ }
+ catch (Error e)
+ {
+ return null;
+ }
+ }
+
+ public void set_state (string key, string value)
+ {
+ state.set_value ("greeter", key, value);
+ var data = state.to_data ();
+ try
+ {
+ FileUtils.set_contents (state_file, data);
+ }
+ catch (Error e)
+ {
+ debug ("Failed to write state: %s", e.message);
+ }
+ }
+
+ public void push_list (GreeterList widget)
+ {
+ main_window.push_list (widget);
+ }
+
+ public void pop_list ()
+ {
+ main_window.pop_list ();
+ }
+
+ public static void add_style_class (Gtk.Widget widget)
+ {
+ /* Add style context class lightdm-user-list */
+ var ctx = widget.get_style_context ();
+ ctx.add_class ("lightdm");
+ }
+
+ public bool start_session (string? session, Background bg)
+ {
+ /* Paint our background onto the root window before we close our own window */
+ var c = new Cairo.Context (background_surface);
+ bg.draw_full (c, Background.DrawFlags.NONE);
+ c = null;
+ refresh_background (Gdk.Screen.get_default (), background_surface);
+
+ if (test_mode)
+ {
+ debug ("Successfully logged in! Quitting...");
+ Gtk.main_quit ();
+ return true;
+ }
+
+ if (!session_is_valid (session))
+ {
+ debug ("Session %s is not available, using system default %s instead", session, greeter.default_session_hint);
+ session = greeter.default_session_hint;
+ }
+
+ var result = false;
+ try
+ {
+ result = LightDM.greeter_start_session_sync (greeter, session);
+ }
+ catch (Error e)
+ {
+ warning ("Failed to start session: %s", e.message);
+ }
+
+ if (result)
+ starting_session ();
+
+ return result;
+ }
+
+ private bool session_is_valid (string? session)
+ {
+ if (session == null)
+ return true;
+
+ foreach (var s in LightDM.get_sessions ())
+ if (s.key == session)
+ return true;
+
+ return false;
+ }
+
+ private bool ready_cb ()
+ {
+ debug ("starting system-ready sound");
+
+ /* Launch canberra */
+ Canberra.Context.create (out canberra_context);
+
+ if (UGSettings.get_boolean (UGSettings.KEY_PLAY_READY_SOUND))
+ canberra_context.play (0,
+ Canberra.PROP_CANBERRA_XDG_THEME_NAME,
+ "ubuntu",
+ Canberra.PROP_EVENT_ID,
+ "system-ready");
+
+ return false;
+ }
+
+ public void show ()
+ {
+ debug ("Showing main window");
+ main_window.show ();
+ main_window.get_window ().focus (Gdk.CURRENT_TIME);
+ main_window.set_keyboard_state ();
+ }
+
+ public bool is_authenticated ()
+ {
+ return greeter.is_authenticated;
+ }
+
+ public void authenticate (string? userid = null)
+ {
+ greeter.authenticate (userid);
+ }
+
+ public void authenticate_as_guest ()
+ {
+ greeter.authenticate_as_guest ();
+ }
+
+ public void authenticate_remote (string? session, string? userid)
+ {
+ ArcticaGreeter.singleton.greeter.authenticate_remote (session, userid);
+ }
+
+ public void cancel_authentication ()
+ {
+ greeter.cancel_authentication ();
+ }
+
+ public void respond (string response)
+ {
+ greeter.respond (response);
+ }
+
+ public string authentication_user ()
+ {
+ return greeter.authentication_user;
+ }
+
+ public string default_session_hint ()
+ {
+ return greeter.default_session_hint;
+ }
+
+ public string select_user_hint ()
+ {
+ return greeter.select_user_hint;
+ }
+
+ public bool show_manual_login_hint ()
+ {
+ return greeter.show_manual_login_hint;
+ }
+
+ public bool show_remote_login_hint ()
+ {
+ return greeter.show_remote_login_hint;
+ }
+
+ public bool hide_users_hint ()
+ {
+ return greeter.hide_users_hint;
+ }
+
+ public bool has_guest_account_hint ()
+ {
+ return greeter.has_guest_account_hint;
+ }
+
+ private Gdk.FilterReturn focus_upon_map (Gdk.XEvent gxevent, Gdk.Event event)
+ {
+ var xevent = (X.Event*)gxevent;
+ if (xevent.type == X.EventType.MapNotify)
+ {
+ var display = Gdk.x11_lookup_xdisplay (xevent.xmap.display);
+ var xwin = xevent.xmap.window;
+ var win = Gdk.X11Window.foreign_new_for_display (display, xwin);
+ if (win != null && !xevent.xmap.override_redirect)
+ {
+ /* Check to see if this window is our onboard window, since we don't want to focus it. */
+ X.Window keyboard_xid = 0;
+ if (main_window.menubar.keyboard_window != null)
+ keyboard_xid = Gdk.X11Window.get_xid (main_window.menubar.keyboard_window.get_window ());
+
+ if (xwin != keyboard_xid && win.get_type_hint() != Gdk.WindowTypeHint.NOTIFICATION)
+ {
+ win.focus (Gdk.CURRENT_TIME);
+
+ /* Make sure to keep keyboard above */
+ if (main_window.menubar.keyboard_window != null)
+ main_window.menubar.keyboard_window.get_window ().raise ();
+ }
+ }
+ }
+ else if (xevent.type == X.EventType.UnmapNotify)
+ {
+ // Since we aren't keeping track of focus (for example, we don't
+ // track the Z stack of windows) like a normal WM would, when we
+ // decide here where to return focus after another window unmaps,
+ // we don't have much to go on. X will tell us if we should take
+ // focus back. (I could not find an obvious way to determine this,
+ // but checking if the X input focus is RevertTo.None seems
+ // reliable.)
+
+ X.Window xwin;
+ int revert_to;
+ xevent.xunmap.display.get_input_focus (out xwin, out revert_to);
+
+ if (revert_to == X.RevertTo.None)
+ {
+ main_window.get_window ().focus (Gdk.CURRENT_TIME);
+
+ /* Make sure to keep keyboard above */
+ if (main_window.menubar.keyboard_window != null)
+ main_window.menubar.keyboard_window.get_window ().raise ();
+ }
+ }
+ return Gdk.FilterReturn.CONTINUE;
+ }
+
+ private void start_fake_wm ()
+ {
+ /* We want new windows (e.g. the shutdown dialog) to gain focus.
+ We don't really need anything more than that (don't need alt-tab
+ since any dialog should be "modal" or at least dealt with before
+ continuing even if not actually marked as modal) */
+ var root = Gdk.get_default_root_window ();
+ root.set_events (root.get_events () | Gdk.EventMask.SUBSTRUCTURE_MASK);
+ root.add_filter (focus_upon_map);
+ }
+
+ private static Cairo.XlibSurface? create_root_surface (Gdk.Screen screen)
+ {
+ var visual = screen.get_system_visual ();
+
+ unowned X.Display display = Gdk.X11Display.get_xdisplay (screen.get_display ());
+
+ var pixmap = X.CreatePixmap (display,
+ Gdk.X11Window.get_xid (screen.get_root_window ()),
+ screen.get_width (),
+ screen.get_height (),
+ visual.get_depth ());
+
+ /* Convert into a Cairo surface */
+ var surface = new Cairo.XlibSurface (display,
+ pixmap,
+ Gdk.X11Visual.get_xvisual (visual),
+ screen.get_width (), screen.get_height ());
+
+ return surface;
+ }
+
+ private static void refresh_background (Gdk.Screen screen, Cairo.XlibSurface surface)
+ {
+ Gdk.flush ();
+
+ unowned X.Display display = Gdk.X11Display.get_xdisplay (screen.get_display ());
+
+ /* Ensure Cairo has actually finished its drawing */
+ surface.flush ();
+ /* Use this pixmap for the background */
+ X.SetWindowBackgroundPixmap (display,
+ Gdk.X11Window.get_xid (screen.get_root_window ()),
+ surface.get_drawable ());
+
+ X.ClearWindow (display, Gdk.X11Window.get_xid (screen.get_root_window ()));
+ }
+
+ private static void log_cb (string? log_domain, LogLevelFlags log_level, string message)
+ {
+ string prefix;
+ switch (log_level & LogLevelFlags.LEVEL_MASK)
+ {
+ case LogLevelFlags.LEVEL_ERROR:
+ prefix = "ERROR:";
+ break;
+ case LogLevelFlags.LEVEL_CRITICAL:
+ prefix = "CRITICAL:";
+ break;
+ case LogLevelFlags.LEVEL_WARNING:
+ prefix = "WARNING:";
+ break;
+ case LogLevelFlags.LEVEL_MESSAGE:
+ prefix = "MESSAGE:";
+ break;
+ case LogLevelFlags.LEVEL_INFO:
+ prefix = "INFO:";
+ break;
+ case LogLevelFlags.LEVEL_DEBUG:
+ prefix = "DEBUG:";
+ break;
+ default:
+ prefix = "LOG:";
+ break;
+ }
+
+ stderr.printf ("[%+.2fs] %s %s\n", log_timer.elapsed (), prefix, message);
+ }
+
+ public static int main (string[] args)
+ {
+ /* Protect memory from being paged to disk, as we deal with passwords */
+ Posix.mlockall (Posix.MCL_CURRENT | Posix.MCL_FUTURE);
+
+ /* Disable the stupid global menubar */
+ Environment.unset_variable ("UBUNTU_MENUPROXY");
+
+ /* Initialize i18n */
+ Intl.setlocale (LocaleCategory.ALL, "");
+ Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+ Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+ Intl.textdomain (Config.GETTEXT_PACKAGE);
+
+ /* Set up the accessibility stack, in case the user needs it for screen reading etc. */
+ Environment.set_variable ("GTK_MODULES", "atk-bridge", false);
+
+ Pid atspi_pid = 0;
+ Pid upstart_pid = 0;
+
+ try
+ {
+ string[] argv;
+
+ Shell.parse_argv ("/usr/lib/at-spi2-core/at-spi-bus-launcher --launch-immediately", out argv);
+ Process.spawn_async (null,
+ argv,
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out atspi_pid);
+ }
+ catch (Error e)
+ {
+ warning ("Error starting the at-spi registry: %s", e.message);
+ }
+
+ Gtk.init (ref args);
+ Ido.init ();
+
+ log_timer = new Timer ();
+ Log.set_default_handler (log_cb);
+
+ debug ("Starting arctica-greeter %s UID=%d LANG=%s", Config.VERSION, (int) Posix.getuid (), Environment.get_variable ("LANG"));
+
+ /* Set the cursor to not be the crap default */
+ debug ("Setting cursor");
+ Gdk.get_default_root_window ().set_cursor (new Gdk.Cursor (Gdk.CursorType.LEFT_PTR));
+
+ bool do_show_version = false;
+ bool do_test_mode = false;
+ OptionEntry versionOption = { "version", 'v', 0, OptionArg.NONE, ref do_show_version,
+ /* Help string for command line --version flag */
+ N_("Show release version"), null };
+ OptionEntry testOption = { "test-mode", 0, 0, OptionArg.NONE, ref do_test_mode,
+ /* Help string for command line --test-mode flag */
+ N_("Run in test mode"), null };
+ OptionEntry nullOption = { null };
+ OptionEntry[] options = { versionOption, testOption, nullOption };
+
+ debug ("Loading command line options");
+ var c = new OptionContext (/* Arguments and description for --help text */
+ _("- Arctica Greeter"));
+ c.add_main_entries (options, Config.GETTEXT_PACKAGE);
+ c.add_group (Gtk.get_option_group (true));
+ try
+ {
+ c.parse (ref args);
+ }
+ catch (Error e)
+ {
+ stderr.printf ("%s\n", e.message);
+ stderr.printf (/* Text printed out when an unknown command-line argument provided */
+ _("Run '%s --help' to see a full list of available command line options."), args[0]);
+ stderr.printf ("\n");
+ return Posix.EXIT_FAILURE;
+ }
+ if (do_show_version)
+ {
+ /* Note, not translated so can be easily parsed */
+ stderr.printf ("arctica-greeter %s\n", Config.VERSION);
+ return Posix.EXIT_SUCCESS;
+ }
+
+ if (do_test_mode)
+ debug ("Running in test mode");
+
+ /* Set GTK+ settings */
+ debug ("Setting GTK+ settings");
+ var settings = Gtk.Settings.get_default ();
+ var value = UGSettings.get_string (UGSettings.KEY_THEME_NAME);
+ if (value != "")
+ settings.set ("gtk-theme-name", value, null);
+ value = UGSettings.get_string (UGSettings.KEY_ICON_THEME_NAME);
+ if (value != "")
+ settings.set ("gtk-icon-theme-name", value, null);
+ value = UGSettings.get_string (UGSettings.KEY_FONT_NAME);
+ if (value != "")
+ settings.set ("gtk-font-name", value, null);
+ var double_value = UGSettings.get_double (UGSettings.KEY_XFT_DPI);
+ if (double_value != 0.0)
+ settings.set ("gtk-xft-dpi", (int) (1024 * double_value), null);
+ var boolean_value = UGSettings.get_boolean (UGSettings.KEY_XFT_ANTIALIAS);
+ settings.set ("gtk-xft-antialias", boolean_value, null);
+ value = UGSettings.get_string (UGSettings.KEY_XFT_HINTSTYLE);
+ if (value != "")
+ settings.set ("gtk-xft-hintstyle", value, null);
+ value = UGSettings.get_string (UGSettings.KEY_XFT_RGBA);
+ if (value != "")
+ settings.set ("gtk-xft-rgba", value, null);
+
+ debug ("Creating Arctica Greeter");
+ var greeter = new ArcticaGreeter (do_test_mode);
+
+ debug ("Showing greeter");
+ greeter.show ();
+
+ if (!do_test_mode)
+ {
+ /* Start the indicator services */
+ try
+ {
+ string[] argv;
+
+ Shell.parse_argv ("init --user --startup-event indicator-services-start", out argv);
+ Process.spawn_async (null,
+ argv,
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out upstart_pid);
+ }
+ catch (Error e)
+ {
+ warning ("Error starting Upstart for indicators: %s", e.message);
+ }
+
+ /* Make nm-applet hide items the user does not have permissions to interact with */
+ Environment.set_variable ("NM_APPLET_HIDE_POLICY_ITEMS", "1", true);
+
+ try
+ {
+ Process.spawn_command_line_async ("nm-applet");
+ }
+ catch (Error e)
+ {
+ warning ("Error starting nm-applet: %s", e.message);
+ }
+ }
+
+ /* Setup a handler for TERM so we quit cleanly */
+ GLib.Unix.signal_add(GLib.ProcessSignal.TERM, () => {
+ debug("Got a SIGTERM");
+ Gtk.main_quit();
+ return true;
+ });
+
+ debug ("Starting main loop");
+ Gtk.main ();
+
+ debug ("Cleaning up");
+
+ if (upstart_pid != 0)
+ {
+ Posix.kill (upstart_pid, Posix.SIGTERM);
+ int status;
+ Posix.waitpid (upstart_pid, out status, 0);
+ if (Process.if_exited (status))
+ debug ("Upstart exited with return value %d", Process.exit_status (status));
+ else
+ debug ("Upstart terminated with signal %d", Process.term_sig (status));
+ upstart_pid = 0;
+ }
+
+ if (atspi_pid != 0)
+ {
+ Posix.kill (atspi_pid, Posix.SIGKILL);
+ int status;
+ Posix.waitpid (atspi_pid, out status, 0);
+ if (Process.if_exited (status))
+ debug ("AT-SPI exited with return value %d", Process.exit_status (status));
+ else
+ debug ("AT-SPI terminated with signal %d", Process.term_sig (status));
+ atspi_pid = 0;
+ }
+
+ debug ("Exiting");
+
+ return Posix.EXIT_SUCCESS;
+ }
+}
+
+[DBus (name="org.gnome.SessionManager.EndSessionDialog")]
+public class DialogDBusInterface : Object
+{
+ public signal void open_dialog (uint32 type);
+ public signal void close_dialog ();
+
+ public void open (uint32 type, uint32 timestamp, uint32 seconds_to_stay_open, ObjectPath[] inhibitor_object_paths)
+ {
+ open_dialog (type);
+ }
+
+ public void close ()
+ {
+ close_dialog ();
+ }
+}