1/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
2 *
3 * Copyright (C) 2011 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 * Authored by: Robert Ancell <robert.ancell@canonical.com>
18 */
19
20public const int grid_size = 40;
21
22public class SlickGreeter
23{
24    public static SlickGreeter singleton;
25
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 ();
30
31    public bool test_mode = false;
32
33    private string state_file;
34    private KeyFile state;
35
36    private Cairo.XlibSurface background_surface;
37
38    public bool orca_needs_kick;
39    private MainWindow main_window;
40
41    private LightDM.Greeter greeter;
42
43    private Canberra.Context canberra_context;
44
45    private static Timer log_timer;
46
47    private DialogDBusInterface dbus_object;
48
49    private SlickGreeter (bool test_mode_)
50    {
51        singleton = this;
52        test_mode = test_mode_;
53
54        /* Prepare to set the background */
55        debug ("Creating background surface");
56        background_surface = create_root_surface (Gdk.Screen.get_default ());
57
58        greeter = new LightDM.Greeter ();
59        greeter.show_message.connect ((text, type) => { show_message (text, type); });
60        greeter.show_prompt.connect ((text, type) => { show_prompt (text, type); });
61        greeter.autologin_timer_expired.connect (() => {
62		try {
63			greeter.authenticate_autologin ();
64		}
65		catch(Error e) {
66			warning("Unable to authenticate autologin: %s", e.message);
67		}
68	});
69
70        greeter.authentication_complete.connect (() => { authentication_complete (); });
71        var connected = false;
72        try
73        {
74            connected = greeter.connect_to_daemon_sync ();
75        }
76        catch (Error e)
77        {
78            warning ("Failed to connect to LightDM daemon: %s", e.message);
79        }
80        if (!connected && !test_mode)
81            Posix.exit (Posix.EXIT_FAILURE);
82
83        var state_dir = Path.build_filename (Environment.get_user_cache_dir (), "slick-greeter");
84        DirUtils.create_with_parents (state_dir, 0775);
85
86        var xdg_seat = GLib.Environment.get_variable("XDG_SEAT");
87        var state_file_name = xdg_seat != null && xdg_seat != "seat0" ? xdg_seat + "-state" : "state";
88
89        state_file = Path.build_filename (state_dir, state_file_name);
90        state = new KeyFile ();
91        try
92        {
93            state.load_from_file (state_file, KeyFileFlags.NONE);
94        }
95        catch (Error e)
96        {
97            if (!(e is FileError.NOENT))
98                warning ("Failed to load state from %s: %s\n", state_file, e.message);
99        }
100
101        main_window = new MainWindow ();
102        main_window.destroy.connect(() => { kill_fake_wm (); });
103        main_window.delete_event.connect(() =>
104        {
105            Gtk.main_quit();
106            return false;
107        });
108
109        Bus.own_name (BusType.SESSION, "x.dm.SlickGreeter", BusNameOwnerFlags.NONE);
110
111        dbus_object = new DialogDBusInterface ();
112        dbus_object.open_dialog.connect ((type) =>
113        {
114            ShutdownDialogType dialog_type;
115            switch (type)
116            {
117            default:
118            case 1:
119                dialog_type = ShutdownDialogType.LOGOUT;
120                break;
121            case 2:
122                dialog_type = ShutdownDialogType.RESTART;
123                break;
124            }
125            main_window.show_shutdown_dialog (dialog_type);
126        });
127        dbus_object.close_dialog.connect ((type) => { main_window.close_shutdown_dialog (); });
128
129        start_fake_wm ();
130        Gdk.threads_add_idle (ready_cb);
131    }
132
133    public string? get_state (string key)
134    {
135        try
136        {
137            return state.get_value ("greeter", key);
138        }
139        catch (Error e)
140        {
141            return null;
142        }
143    }
144
145    public void set_state (string key, string value)
146    {
147        state.set_value ("greeter", key, value);
148        var data = state.to_data ();
149        try
150        {
151            FileUtils.set_contents (state_file, data);
152        }
153        catch (Error e)
154        {
155            debug ("Failed to write state: %s", e.message);
156        }
157    }
158
159    public void push_list (GreeterList widget)
160    {
161        main_window.push_list (widget);
162    }
163
164    public void pop_list ()
165    {
166        main_window.pop_list ();
167    }
168
169    public static void add_style_class (Gtk.Widget widget)
170    {
171        /* Add style context class lightdm-user-list */
172        var ctx = widget.get_style_context ();
173        ctx.add_class ("lightdm");
174    }
175
176    public static string? get_default_session ()
177    {
178        var sessions = new List<string> ();
179        sessions.append ("cinnamon");
180        sessions.append ("mate");
181        sessions.append ("xfce");
182        sessions.append ("plasma");
183        sessions.append ("kde-plasma");
184        sessions.append ("kde");
185        sessions.append ("budgie-desktop");
186        sessions.append ("gnome");
187        sessions.append ("LXDE");
188        sessions.append ("lxqt");
189        sessions.append ("pekwm");
190        sessions.append ("pantheon");
191        sessions.append ("i3");
192        sessions.append ("enlightenment");
193        sessions.append ("deepin");
194        sessions.append ("openbox");
195        sessions.append ("awesome");
196        sessions.append ("gnome-xorg");
197        sessions.append ("ubuntu-xorg");
198        sessions.append ("fynedesk");
199
200        foreach (string session in sessions) {
201            var path = Path.build_filename  ("/usr/local/share/xsessions/", session.concat(".desktop"), null);
202            if (FileUtils.test (path, FileTest.EXISTS)) {
203                return session;
204            }
205        }
206
207        warning ("Could not find a default session.");
208        return null;
209    }
210
211    public static string validate_session (string? session)
212    {
213        /* Make sure the given session actually exists. Return it if it does.
214        otherwise, return the default session. */
215        if (session != null) {
216            var path = Path.build_filename  ("/usr/local/share/xsessions/", session.concat(".desktop"), null);
217            if (!FileUtils.test (path, FileTest.EXISTS) ) {
218                debug ("Invalid session: '%s'", session);
219                session = null;
220            }
221        }
222
223        if (session == null) {
224            var default_session = SlickGreeter.get_default_session ();
225            debug ("Using default session: '%s'", default_session);
226            return default_session;
227        }
228
229        return session;
230    }
231
232    public bool start_session (string? session, Background bg)
233    {
234        /* Explicitly set the right scale before closing window */
235        var display = Gdk.Display.get_default();
236        var monitor = display.get_primary_monitor();
237        var scale = monitor.get_scale_factor ();
238        background_surface.set_device_scale (scale, scale);
239
240        /* Paint our background onto the root window before we close our own window */
241        // var c = new Cairo.Context (background_surface);
242        // bg.draw_full (c, Background.DrawFlags.NONE);
243        // c = null;
244        // refresh_background (screen, background_surface);
245
246        main_window.before_session_start();
247
248        if (test_mode)
249        {
250            debug ("Successfully logged in! Quitting...");
251            Gtk.main_quit ();
252            return true;
253        }
254
255        if (!session_is_valid (session))
256        {
257            debug ("Session %s is not available, using system default %s instead", session, greeter.default_session_hint);
258            session = greeter.default_session_hint;
259        }
260
261        var result = false;
262        try
263        {
264            result = LightDM.greeter_start_session_sync (greeter, session);
265        }
266        catch (Error e)
267        {
268            warning ("Failed to start session: %s", e.message);
269        }
270
271        if (result)
272            starting_session ();
273
274        return result;
275    }
276
277    private bool session_is_valid (string? session)
278    {
279        if (session == null)
280            return true;
281
282        foreach (var s in LightDM.get_sessions ())
283            if (s.key == session)
284                return true;
285
286        return false;
287    }
288
289    private bool ready_cb ()
290    {
291        debug ("starting system-ready sound");
292
293        /* Launch canberra */
294        Canberra.Context.create (out canberra_context);
295        var sound_file = UGSettings.get_string (UGSettings.KEY_PLAY_READY_SOUND);
296        if (sound_file != "")
297            canberra_context.play (0, Canberra.PROP_MEDIA_FILENAME, sound_file);
298        return false;
299    }
300
301    public void show ()
302    {
303        debug ("Showing main window");
304        main_window.realize ();
305        main_window.setup_window();
306        main_window.show ();
307        main_window.get_window ().focus (Gdk.CURRENT_TIME);
308        main_window.set_keyboard_state ();
309    }
310
311    public bool is_authenticated ()
312    {
313        return greeter.is_authenticated;
314    }
315
316    public void authenticate (string? userid = null)
317    {
318	try {
319		greeter.authenticate (userid);
320	}
321	catch(Error e) {
322		warning ("Unable to authenticate greeter for %s, %s", userid, e.message);
323	}
324    }
325
326    public void authenticate_as_guest ()
327    {
328	try {
329		greeter.authenticate_as_guest ();
330	}
331	catch(Error e) {
332		warning ("Unable to authenticate greeter for guest: %s", e.message);
333	}
334    }
335
336    public void authenticate_remote (string session, string? userid)
337    {
338	try {
339		SlickGreeter.singleton.greeter.authenticate_remote (session, userid);
340	}
341	catch (Error e) {
342		warning("Unable to authenticate session for user %s, %s", userid, e.message);
343	}
344    }
345
346    public void cancel_authentication ()
347    {
348	try {
349		greeter.cancel_authentication ();
350	}
351	catch(Error e) {
352		warning ("Unable to cancel authentication: %s", e.message);
353	}
354    }
355
356
357    public void respond (string response)
358    {
359	try {
360		greeter.respond (response);
361	}
362	catch(Error e) {
363		warning ("Greeter unable to respond: %s", e.message);
364	}
365    }
366
367    public string authentication_user ()
368    {
369        return greeter.authentication_user;
370    }
371
372    public string default_session_hint ()
373    {
374        return greeter.default_session_hint;
375    }
376
377    public string select_user_hint ()
378    {
379        return greeter.select_user_hint;
380    }
381
382    public bool show_manual_login_hint ()
383    {
384        return greeter.show_manual_login_hint;
385    }
386
387    public bool show_remote_login_hint ()
388    {
389        return greeter.show_remote_login_hint;
390    }
391
392    public bool hide_users_hint ()
393    {
394        return greeter.hide_users_hint;
395    }
396
397    public bool has_guest_account_hint ()
398    {
399        return greeter.has_guest_account_hint;
400    }
401
402    private Gdk.FilterReturn focus_upon_map (Gdk.XEvent gxevent, Gdk.Event event)
403    {
404        var xevent = (X.Event*)gxevent;
405        if (xevent.type == X.EventType.MapNotify)
406        {
407            var display = Gdk.X11.Display.lookup_for_xdisplay (xevent.xmap.display);
408            var xwin = xevent.xmap.window;
409            var win = new Gdk.X11.Window.foreign_for_display (display, xwin);
410            if (win != null && !xevent.xmap.override_redirect)
411            {
412                /* Check to see if this window is our onboard window, since we don't want to focus it. */
413                X.Window keyboard_xid = 0;
414                if (main_window.menubar.keyboard_window != null)
415                    keyboard_xid = (main_window.menubar.keyboard_window.get_window () as Gdk.X11.Window).get_xid ();
416
417                if (xwin != keyboard_xid && win.get_type_hint() != Gdk.WindowTypeHint.NOTIFICATION)
418                {
419                    win.focus (Gdk.CURRENT_TIME);
420
421                    /* Make sure to keep keyboard above */
422                    if (main_window.menubar.keyboard_window != null)
423                        main_window.menubar.keyboard_window.get_window ().raise ();
424                }
425            }
426        }
427        else if (xevent.type == X.EventType.UnmapNotify)
428        {
429            // Since we aren't keeping track of focus (for example, we don't
430            // track the Z stack of windows) like a normal WM would, when we
431            // decide here where to return focus after another window unmaps,
432            // we don't have much to go on.  X will tell us if we should take
433            // focus back.  (I could not find an obvious way to determine this,
434            // but checking if the X input focus is RevertTo.None seems
435            // reliable.)
436
437            X.Window xwin;
438            int revert_to;
439            xevent.xunmap.display.get_input_focus (out xwin, out revert_to);
440
441            if (revert_to == X.RevertTo.None)
442            {
443                main_window.get_window ().focus (Gdk.CURRENT_TIME);
444
445                /* Make sure to keep keyboard above */
446                if (main_window.menubar.keyboard_window != null)
447                    main_window.menubar.keyboard_window.get_window ().raise ();
448            }
449        }
450        return Gdk.FilterReturn.CONTINUE;
451    }
452
453    private void start_fake_wm ()
454    {
455        /* We want new windows (e.g. the shutdown dialog) to gain focus.
456           We don't really need anything more than that (don't need alt-tab
457           since any dialog should be "modal" or at least dealt with before
458           continuing even if not actually marked as modal) */
459        var root = Gdk.get_default_root_window ();
460        root.set_events (root.get_events () | Gdk.EventMask.SUBSTRUCTURE_MASK);
461        root.add_filter (focus_upon_map);
462    }
463
464    private void kill_fake_wm ()
465    {
466        var root = Gdk.get_default_root_window ();
467        root.remove_filter (focus_upon_map);
468    }
469
470    private static Cairo.XlibSurface? create_root_surface (Gdk.Screen screen)
471    {
472        var visual = screen.get_system_visual ();
473
474        unowned X.Display display = (screen.get_display () as Gdk.X11.Display).get_xdisplay ();
475        unowned X.Screen xscreen = (screen as Gdk.X11.Screen).get_xscreen ();
476
477        var pixmap = X.CreatePixmap (display,
478                                     (screen.get_root_window () as Gdk.X11.Window).get_xid (),
479                                     xscreen.width_of_screen (),
480                                     xscreen.height_of_screen (),
481                                     visual.get_depth ());
482
483        /* Convert into a Cairo surface */
484        var surface = new Cairo.XlibSurface (display,
485                                             pixmap,
486                                             (visual as Gdk.X11.Visual).get_xvisual (),
487                                             xscreen.width_of_screen (), xscreen.height_of_screen ());
488
489        return surface;
490    }
491
492    // private static void refresh_background (Gdk.Screen screen, Cairo.XlibSurface surface)
493    // {
494    //     Gdk.flush ();
495
496    //     unowned X.Display display = (screen.get_display () as Gdk.X11.Display).get_xdisplay ();
497
498    //     // Ensure Cairo has actually finished its drawing
499    //     surface.flush ();
500    //     // Use this pixmap for the background
501    //     X.SetWindowBackgroundPixmap (display,
502    //                                  (screen.get_root_window () as Gdk.X11.Window).get_xid (),
503    //                                  surface.get_drawable ());
504
505    //     X.ClearWindow (display, (screen.get_root_window () as Gdk.X11.Window).get_xid ());
506    // }
507
508    private static void log_cb (string? log_domain, LogLevelFlags log_level, string message)
509    {
510        string prefix;
511        switch (log_level & LogLevelFlags.LEVEL_MASK)
512        {
513        case LogLevelFlags.LEVEL_ERROR:
514            prefix = "ERROR:";
515            break;
516        case LogLevelFlags.LEVEL_CRITICAL:
517            prefix = "CRITICAL:";
518            break;
519        case LogLevelFlags.LEVEL_WARNING:
520            prefix = "WARNING:";
521            break;
522        case LogLevelFlags.LEVEL_MESSAGE:
523            prefix = "MESSAGE:";
524            break;
525        case LogLevelFlags.LEVEL_INFO:
526            prefix = "INFO:";
527            break;
528        case LogLevelFlags.LEVEL_DEBUG:
529            prefix = "DEBUG:";
530            break;
531        default:
532            prefix = "LOG:";
533            break;
534        }
535
536        stderr.printf ("[%+.2fs] %s %s\n", log_timer.elapsed (), prefix, message);
537    }
538
539    private static void check_hidpi ()
540    {
541        try {
542            string output;
543            Process.spawn_command_line_sync("/usr/local/bin/slick-greeter-check-hidpi", out output, null, null);
544            output = output.strip();
545            if (output == "2") {
546                debug ("Activating HiDPI (2x scale ratio)");
547                GLib.Environment.set_variable ("GDK_SCALE", "2", true);
548            }
549        }
550        catch (Error e){
551            warning ("Error while setting HiDPI support: %s", e.message);
552        }
553    }
554
555    private static void set_keyboard_layout ()
556    {
557        /* Avoid expensive Python execution where possible */
558        if (!FileUtils.test("/usr/local/etc/default/keyboard", FileTest.EXISTS)) {
559            return;
560        }
561
562        try {
563            Process.spawn_command_line_sync("/usr/local/bin/slick-greeter-set-keyboard-layout", null, null, null);
564        }
565        catch (Error e){
566            warning ("Error while setting the keyboard layout: %s", e.message);
567        }
568    }
569
570    private static void activate_numlock ()
571    {
572        try {
573            Process.spawn_command_line_sync("/usr/local/bin/numlockx on", null, null, null);
574        }
575        catch (Error e){
576            warning ("Error while activating numlock: %s", e.message);
577        }
578    }
579
580    public static int main (string[] args)
581    {
582        /* Protect memory from being paged to disk, as we deal with passwords
583
584           According to systemd-dev,
585
586           "mlockall() is generally a bad idea and certainly has no place in a graphical program.
587           A program like this uses lots of memory and it is crucial that this memory can be paged
588           out to relieve memory pressure."
589
590           With systemd version 239 the ulimit for RLIMIT_MEMLOCK was set to 16 MiB
591           and therefore the mlockall call would fail. This is lucky becasue the subsequent mmap would not fail.
592
593           With systemd version 240 the RLIMIT_MEMLOCK is now set to 64 MiB
594           and now the mlockall no longer fails. However, it not possible to mmap in all
595           the memory and because that would still exceed the MEMLOCK limit.
596           "
597           See https://bugzilla.redhat.com/show_bug.cgi?id=1662857 &
598           https://github.com/CanonicalLtd/lightdm/issues/55
599
600           RLIMIT_MEMLOCK = 64 MiB means, slick-greeter will most likely fail with 64 bit and
601           will always fail on 32 bit systems.
602
603           Hence we better disable it. */
604
605        /*Posix.mlockall (Posix.MCL_CURRENT | Posix.MCL_FUTURE);*/
606
607        /* Disable global menubar */
608        Environment.unset_variable ("UBUNTU_MENUPROXY");
609
610        /* Initialize i18n */
611        Intl.setlocale (LocaleCategory.ALL, "");
612        Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
613        Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
614        Intl.textdomain (Config.GETTEXT_PACKAGE);
615
616        /* Set up the accessibility stack, in case the user needs it for screen reading etc. */
617        Environment.set_variable ("GTK_MODULES", "atk-bridge", false);
618
619        /* Fix for https://bugs.launchpad.net/ubuntu/+source/unity-greeter/+bug/1024482
620           Slick-greeter sets the mouse cursor on the root window.
621           Without GKD_CORE_DEVICE_EVENTS set, the DE is unable to apply its own cursor theme and size.
622        */
623        GLib.Environment.set_variable ("GDK_CORE_DEVICE_EVENTS", "1", true);
624
625        log_timer = new Timer ();
626        Log.set_default_handler (log_cb);
627
628        /* Override dconf settings with /etc settings */
629        UGSettings.apply_conf_settings ();
630
631        var hidpi = UGSettings.get_string (UGSettings.KEY_ENABLE_HIDPI);
632        debug ("HiDPI support: %s", hidpi);
633        if (hidpi == "auto") {
634            check_hidpi ();
635        }
636        else if (hidpi == "on") {
637            GLib.Environment.set_variable ("GDK_SCALE", "2", true);
638        }
639
640        /* Set the keyboard layout */
641        set_keyboard_layout ();
642
643        /* Set the numlock state */
644        if (UGSettings.get_boolean (UGSettings.KEY_ACTIVATE_NUMLOCK)) {
645            debug ("Activating numlock");
646            activate_numlock ();
647        }
648
649        Gtk.init (ref args);
650
651        debug ("Starting slick-greeter %s UID=%d LANG=%s", Config.VERSION, (int) Posix.getuid (), Environment.get_variable ("LANG"));
652
653        /* Set the cursor to not be the crap default */
654        debug ("Setting cursor");
655        Gdk.get_default_root_window ().set_cursor (new Gdk.Cursor.for_display (Gdk.Display.get_default (), Gdk.CursorType.LEFT_PTR));
656
657        bool do_show_version = false;
658        bool do_test_mode = false;
659        OptionEntry versionOption = { "version", 'v', 0, OptionArg.NONE, ref do_show_version,
660                /* Help string for command line --version flag */
661                N_("Show release version"), null };
662        OptionEntry testOption =  { "test-mode", 0, 0, OptionArg.NONE, ref do_test_mode,
663                /* Help string for command line --test-mode flag */
664                N_("Run in test mode"), null };
665        OptionEntry nullOption = { null };
666        OptionEntry[] options = { versionOption, testOption, nullOption };
667
668        debug ("Loading command line options");
669        var c = new OptionContext ("- Slick Greeter");
670        c.add_main_entries (options, Config.GETTEXT_PACKAGE);
671        c.add_group (Gtk.get_option_group (true));
672        try
673        {
674            c.parse (ref args);
675        }
676        catch (Error e)
677        {
678            stderr.printf ("%s\n", e.message);
679            stderr.printf (/* Text printed out when an unknown command-line argument provided */
680                           _("Run '%s --help' to see a full list of available command line options."), args[0]);
681            stderr.printf ("\n");
682            return Posix.EXIT_FAILURE;
683        }
684        if (do_show_version)
685        {
686            /* Note, not translated so can be easily parsed */
687            stderr.printf ("slick-greeter %s\n", Config.VERSION);
688            return Posix.EXIT_SUCCESS;
689        }
690
691        if (do_test_mode)
692            debug ("Running in test mode");
693
694        /* Set GTK+ settings */
695        debug ("Setting GTK+ settings");
696        var settings = Gtk.Settings.get_default ();
697        var value = UGSettings.get_string (UGSettings.KEY_THEME_NAME);
698        if (value != "")
699            settings.set ("gtk-theme-name", value, null);
700        value = UGSettings.get_string (UGSettings.KEY_ICON_THEME_NAME);
701        if (value != "")
702            settings.set ("gtk-icon-theme-name", value, null);
703        value = UGSettings.get_string (UGSettings.KEY_FONT_NAME);
704        if (value != "")
705            settings.set ("gtk-font-name", value, null);
706        var double_value = UGSettings.get_double (UGSettings.KEY_XFT_DPI);
707        if (double_value != 0.0)
708            settings.set ("gtk-xft-dpi", (int) (1024 * double_value), null);
709        var boolean_value = UGSettings.get_boolean (UGSettings.KEY_XFT_ANTIALIAS);
710        settings.set ("gtk-xft-antialias", boolean_value, null);
711        value = UGSettings.get_string (UGSettings.KEY_XFT_HINTSTYLE);
712        if (value != "")
713            settings.set ("gtk-xft-hintstyle", value, null);
714        value = UGSettings.get_string (UGSettings.KEY_XFT_RGBA);
715        if (value != "")
716            settings.set ("gtk-xft-rgba", value, null);
717
718        debug ("Creating Slick Greeter");
719        var greeter = new SlickGreeter (do_test_mode);
720
721        debug ("Showing greeter");
722        greeter.show ();
723
724        /* Setup a handler for TERM so we quit cleanly */
725        GLib.Unix.signal_add(GLib.ProcessSignal.TERM, () => {
726            debug("Got a SIGTERM");
727            Gtk.main_quit();
728            return true;
729        });
730
731        debug ("Starting main loop");
732        Gtk.main ();
733
734        debug ("Cleaning up");
735
736        var screen = Gdk.Screen.get_default ();
737        unowned X.Display xdisplay = (screen.get_display () as Gdk.X11.Display).get_xdisplay ();
738
739        var window = xdisplay.default_root_window();
740        var atom = xdisplay.intern_atom ("AT_SPI_BUS", true);
741
742        if (atom != X.None) {
743            xdisplay.delete_property (window, atom);
744            Gdk.flush();
745        }
746
747        debug ("Exiting");
748
749        return Posix.EXIT_SUCCESS;
750    }
751}
752
753[DBus (name="org.gnome.SessionManager.EndSessionDialog")]
754public class DialogDBusInterface : Object
755{
756    public signal void open_dialog (uint32 type);
757    public signal void close_dialog ();
758
759    public void open (uint32 type, uint32 timestamp, uint32 seconds_to_stay_open, ObjectPath[] inhibitor_object_paths) throws GLib.DBusError, GLib.IOError
760    {
761        open_dialog (type);
762    }
763
764    public void close () throws GLib.DBusError, GLib.IOError
765    {
766        close_dialog ();
767    }
768}
769