aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am76
-rw-r--r--src/animate-timer.vala154
-rw-r--r--src/background.vala705
-rw-r--r--src/cached-image.vala59
-rw-r--r--src/cairo-utils.vala238
-rw-r--r--src/config.vapi12
-rw-r--r--src/dash-box.vala230
-rw-r--r--src/dash-button.vala89
-rw-r--r--src/dash-entry.vala319
-rw-r--r--src/email-autocompleter.vala79
-rw-r--r--src/fadable-box.vala46
-rw-r--r--src/fadable.vala99
-rw-r--r--src/fading-label.vala85
-rw-r--r--src/fixes.vapi57
-rw-r--r--src/flat-button.vala66
-rw-r--r--src/greeter-list.vala949
-rw-r--r--src/indicator.vapi165
-rw-r--r--src/list-stack.vala92
-rw-r--r--src/logo-generator.vala46
-rw-r--r--src/main-window.vala398
-rw-r--r--src/menu.vala47
-rw-r--r--src/menubar.vala559
-rw-r--r--src/prompt-box.vala663
-rw-r--r--src/remote-login-service.vala54
-rw-r--r--src/session-list.vala158
-rw-r--r--src/settings-daemon.vala234
-rw-r--r--src/settings.vala101
-rw-r--r--src/shutdown-dialog.vala622
-rw-r--r--src/toggle-box.vala131
-rw-r--r--src/unity-greeter.vala660
-rw-r--r--src/user-list.vala1603
-rw-r--r--src/user-prompt-box.vala36
32 files changed, 8832 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..b890ee0
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,76 @@
+# -*- Mode: Automake; indent-tabs-mode: t; tab-width: 4 -*-
+
+sbin_PROGRAMS = unity-greeter
+noinst_PROGRAMS = logo-generator
+
+unity_greeter_SOURCES = \
+ config.vapi \
+ fixes.vapi \
+ indicator.vapi \
+ animate-timer.vala \
+ background.vala \
+ cached-image.vala \
+ cairo-utils.vala \
+ email-autocompleter.vala \
+ dash-box.vala \
+ dash-button.vala \
+ dash-entry.vala \
+ fadable.vala \
+ fadable-box.vala \
+ fading-label.vala \
+ flat-button.vala \
+ greeter-list.vala \
+ list-stack.vala \
+ main-window.vala \
+ menu.vala \
+ menubar.vala \
+ prompt-box.vala \
+ session-list.vala \
+ remote-login-service.vala \
+ settings.vala \
+ settings-daemon.vala \
+ shutdown-dialog.vala \
+ toggle-box.vala \
+ unity-greeter.vala \
+ user-list.vala \
+ user-prompt-box.vala
+
+logo_generator_SOURCES = logo-generator.vala
+
+unity_greeter_CFLAGS = \
+ $(UNITY_GREETER_CFLAGS) \
+ -w \
+ -DGNOME_DESKTOP_USE_UNSTABLE_API \
+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
+ -DLOCALEDIR=\""$(localedir)"\" \
+ -DVERSION=\"$(VERSION)\" \
+ -DCONFIG_FILE=\""$(sysconfdir)/lightdm/unity-greeter.conf"\" \
+ -DPKGDATADIR=\""$(pkgdatadir)"\" \
+ -DINDICATORDIR=\""$(INDICATORDIR)"\"
+
+logo_generator_CFLAGS = $(unity_greeter_CFLAGS)
+
+unity_greeter_VALAFLAGS = \
+ --pkg posix \
+ --pkg gtk+-3.0 \
+ --pkg gdk-x11-3.0 \
+ --pkg gio-unix-2.0 \
+ --pkg x11 \
+ --pkg liblightdm-gobject-1 \
+ --pkg libcanberra \
+ --pkg gio-2.0 \
+ --pkg pixman-1 \
+ --target-glib 2.32
+
+logo_generator_VALAFLAGS = $(unity_greeter_VALAFLAGS)
+
+unity_greeter_LDADD = \
+ $(UNITY_GREETER_LIBS) \
+ -lm
+
+logo_generator_LDADD = $(unity_greeter_LDADD)
+
+unity_greeter_vala.stamp: $(top_srcdir)/config.h
+
+DISTCLEANFILES = \
+ Makefile.in
diff --git a/src/animate-timer.vala b/src/animate-timer.vala
new file mode 100644
index 0000000..d5aaf71
--- /dev/null
+++ b/src/animate-timer.vala
@@ -0,0 +1,154 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+private class AnimateTimer : Object
+{
+ /* x and y are 0.0 to 1.0 */
+ public delegate double EasingFunc (double x);
+
+ /* The following are the same intervals that Unity uses */
+ public static const int INSTANT = 150; /* Good for animations that don't convey any information */
+ public static const int FAST = 250; /* Good for animations that convey duplicated information */
+ public static const int NORMAL = 500;
+ public static const int SLOW = 1000; /* Good for animations that convey information that is only presented in the animation */
+
+ /* speed is in milliseconds */
+ public unowned EasingFunc easing_func { get; private set; }
+ public int speed { get; set; }
+ public bool is_running { get { return timeout != 0; } }
+ public double progress { get; private set; }
+
+ /* progress is from 0.0 to 1.0 */
+ public signal void animate (double progress);
+
+ /* AnimateTimer requires two things: an easing function and a speed.
+
+ The speed is just the duration of the animation in milliseconds.
+
+ The easing function describes how fast the animation occurs at different
+ parts of the duration.
+
+ See http://hosted.zeh.com.br/tweener/docs/en-us/misc/transitions.html
+ for examples of various easing functions.
+
+ A few are provided with this class, notably ease_in_out and
+ ease_out_quint.
+ */
+ /* speed is in milliseconds */
+ public AnimateTimer (EasingFunc func, int speed)
+ {
+ Object (speed: speed);
+ this.easing_func = func;
+ }
+
+ ~AnimateTimer ()
+ {
+ stop ();
+ }
+
+ /* temp_speed is in milliseconds */
+ public void reset (int temp_speed = -1)
+ {
+ stop ();
+
+ timeout = Timeout.add (16, animate_cb);
+ progress = 0;
+ start_time = 0;
+ extra_time = 0;
+ extra_progress = 0;
+
+ if (temp_speed == -1)
+ temp_speed = speed;
+
+ length = temp_speed * TimeSpan.MILLISECOND;
+ }
+
+ public void stop ()
+ {
+ if (timeout != 0)
+ Source.remove (timeout);
+ timeout = 0;
+ }
+
+ private uint timeout = 0;
+ private TimeSpan start_time = 0;
+ private TimeSpan length = 0;
+ private TimeSpan extra_time = 0;
+ private double extra_progress = 0.0;
+
+ private bool animate_cb ()
+ {
+ if (start_time == 0)
+ start_time = GLib.get_monotonic_time ();
+
+ var time_progress = normalize_time (GLib.get_monotonic_time ());
+ progress = calculate_progress (time_progress);
+ animate (progress);
+
+ if (time_progress >= 1.0)
+ {
+ timeout = 0;
+ return false;
+ }
+ else
+ return true;
+ }
+
+ /* Returns 0.0 to 1.0 where 1.0 is at or past end_time */
+ private double normalize_time (TimeSpan now)
+ {
+ if (length == 0)
+ return 1.0f;
+
+ return (((double)(now - start_time)) / length).clamp (0.0, 1.0);
+ }
+
+ /* Returns 0.0 to 1.0 where 1.0 is done.
+ time is not normalized yet! */
+ private double calculate_progress (double time_progress)
+ {
+ var y = easing_func (time_progress);
+ return y.clamp (0.0, 1.0);
+ }
+
+ public static double ease_in_out (double x)
+ {
+ return (1 - Math.cos (Math.PI * x)) / 2;
+ }
+
+ /*public static double ease_in_quad (double x)
+ {
+ return Math.pow (x, 2);
+ }*/
+ /*public static double ease_out_quad (double x)
+ {
+ return -1 * Math.pow (x - 1, 2) + 1;
+ }*/
+
+ /*public static double ease_in_quint (double x)
+ {
+ return Math.pow (x, 5);
+ }*/
+ public static double ease_out_quint (double x)
+ {
+ return Math.pow (x - 1, 5) + 1;
+ }
+}
+
diff --git a/src/background.vala b/src/background.vala
new file mode 100644
index 0000000..a1c28e9
--- /dev/null
+++ b/src/background.vala
@@ -0,0 +1,705 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+class BackgroundLoader : Object
+{
+ public string filename { get; private set; }
+ public Cairo.Surface logo { get; set; }
+
+ public int[] widths;
+ public int[] heights;
+ public Cairo.Pattern[] patterns;
+ public Gdk.RGBA average_color;
+
+ private Cairo.Surface target_surface;
+ private bool draw_grid;
+ private Thread<void*> thread;
+ private Gdk.Pixbuf[] images;
+ private bool finished;
+ private uint ready_id;
+
+ public signal void loaded ();
+
+ public BackgroundLoader (Cairo.Surface target_surface, string filename, int[] widths, int[] heights, bool draw_grid)
+ {
+ this.target_surface = target_surface;
+ this.filename = filename;
+ this.widths = widths;
+ this.heights = heights;
+ patterns = new Cairo.Pattern[widths.length];
+ images = new Gdk.Pixbuf[widths.length];
+ this.draw_grid = draw_grid;
+ }
+
+ public bool load ()
+ {
+ /* Already loaded */
+ if (finished)
+ return true;
+
+ /* Currently loading */
+ if (thread != null)
+ return false;
+
+ /* No monitor data */
+ if (widths.length == 0)
+ return false;
+
+ var text = "Making background %s at %dx%d".printf (filename, widths[0], heights[0]);
+ for (var i = 1; i < widths.length; i++)
+ text += ",%dx%d".printf (widths[i], heights[i]);
+ debug (text);
+
+ var color = Gdk.RGBA ();
+ if (color.parse (filename))
+ {
+ var pattern = new Cairo.Pattern.rgba (color.red, color.green, color.blue, color.alpha);
+ for (var i = 0; i < widths.length; i++)
+ patterns[i] = pattern;
+
+ average_color = color;
+ finished = true;
+ debug ("Render of background %s complete", filename);
+ return true;
+ }
+ else
+ {
+ try
+ {
+ this.ref ();
+ thread = new Thread<void*>.try ("background-loader", load_and_scale);
+ }
+ catch (Error e)
+ {
+ this.unref ();
+ finished = true;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public Cairo.Pattern? get_pattern (int width, int height)
+ {
+ for (var i = 0; i < widths.length; i++)
+ {
+ if (widths[i] == width && heights[i] == height)
+ return patterns[i];
+ }
+ return null;
+ }
+
+ ~BackgroundLoader ()
+ {
+ if (ready_id > 0)
+ Source.remove (ready_id);
+ ready_id = 0;
+ }
+
+ private bool ready_cb ()
+ {
+ ready_id = 0;
+
+ debug ("Render of background %s complete", filename);
+
+ thread.join ();
+ thread = null;
+ finished = true;
+
+ for (var i = 0; i < widths.length; i++)
+ {
+ if (images[i] != null)
+ {
+ patterns[i] = create_pattern (images[i]);
+ if (i == 0)
+ pixbuf_average_value (images[i], out average_color);
+ images[i] = null;
+ }
+ else
+ {
+ debug ("images[%d] was null for %s", i, filename);
+ patterns[i] = null;
+ }
+ }
+
+ loaded ();
+
+ this.unref ();
+ return false;
+ }
+
+ private void* load_and_scale ()
+ {
+ try
+ {
+ var image = new Gdk.Pixbuf.from_file (filename);
+ for (var i = 0; i < widths.length; i++)
+ images[i] = scale (image, widths[i], heights[i]);
+ }
+ catch (Error e)
+ {
+ debug ("Error loading background: %s", e.message);
+ }
+
+ ready_id = Gdk.threads_add_idle (ready_cb);
+
+ return null;
+ }
+
+ private Gdk.Pixbuf? scale (Gdk.Pixbuf? image, int width, int height)
+ {
+ var target_aspect = (double) width / height;
+ var aspect = (double) image.width / image.height;
+ double scale, offset_x = 0, offset_y = 0;
+ if (aspect > target_aspect)
+ {
+ /* Fit height and trim sides */
+ scale = (double) height / image.height;
+ offset_x = (image.width * scale - width) / 2;
+ }
+ else
+ {
+ /* Fit width and trim top and bottom */
+ scale = (double) width / image.width;
+ offset_y = (image.height * scale - height) / 2;
+ }
+
+ var scaled_image = new Gdk.Pixbuf (image.colorspace, image.has_alpha, image.bits_per_sample, width, height);
+ image.scale (scaled_image, 0, 0, width, height, -offset_x, -offset_y, scale, scale, Gdk.InterpType.BILINEAR);
+
+ return scaled_image;
+ }
+
+ private Cairo.Pattern? create_pattern (Gdk.Pixbuf image)
+ {
+ var grid_x_offset = get_grid_offset (image.width);
+ var grid_y_offset = get_grid_offset (image.height);
+
+ /* Create background */
+ var surface = new Cairo.Surface.similar (target_surface, Cairo.Content.COLOR, image.width, image.height);
+ var bc = new Cairo.Context (surface);
+ Gdk.cairo_set_source_pixbuf (bc, image, 0, 0);
+
+ bc.paint ();
+
+ /* Draw logo */
+ if (logo != null)
+ {
+ bc.save ();
+ var y = (int) (image.height / grid_size - 2) * grid_size + grid_y_offset;
+ bc.translate (grid_x_offset, y);
+ bc.set_source_surface (logo, 0, 0);
+ bc.paint_with_alpha (0.5);
+ bc.restore ();
+ }
+
+ var pattern = new Cairo.Pattern.for_surface (surface);
+ pattern.set_extend (Cairo.Extend.REPEAT);
+
+ return pattern;
+ }
+
+ /* The following color averaging algorithm was originally written for
+ Unity in C++, then patched into gnome-desktop3 in C. I've taken it
+ and put it here in Vala. It would be nice if we could get
+ gnome-desktop3 to expose this for our use instead of copying the
+ code... */
+
+ static const int QUAD_MAX_LEVEL_OF_RECURSION = 16;
+ static const int QUAD_MIN_LEVEL_OF_RECURSION = 2;
+ static const int QUAD_CORNER_WEIGHT_NW = 3;
+ static const int QUAD_CORNER_WEIGHT_NE = 1;
+ static const int QUAD_CORNER_WEIGHT_SE = 1;
+ static const int QUAD_CORNER_WEIGHT_SW = 3;
+ static const int QUAD_CORNER_WEIGHT_CENTER = 2;
+ static const int QUAD_CORNER_WEIGHT_TOTAL = (QUAD_CORNER_WEIGHT_NW + QUAD_CORNER_WEIGHT_NE + QUAD_CORNER_WEIGHT_SE + QUAD_CORNER_WEIGHT_SW + QUAD_CORNER_WEIGHT_CENTER);
+
+ /* Pixbuf utilities */
+ private Gdk.RGBA get_pixbuf_sample (uint8[] pixels,
+ int rowstride,
+ int channels,
+ int x,
+ int y)
+ {
+ var sample = Gdk.RGBA ();
+ double dd = 0xFF;
+ int offset = ((y * rowstride) + (x * channels));
+
+ sample.red = pixels[offset++] / dd;
+ sample.green = pixels[offset++] / dd;
+ sample.blue = pixels[offset++] / dd;
+ sample.alpha = 1.0f;
+
+ return sample;
+ }
+
+ private bool is_color_different (Gdk.RGBA color_a,
+ Gdk.RGBA color_b)
+ {
+ var diff = Gdk.RGBA ();
+
+ diff.red = color_a.red - color_b.red;
+ diff.green = color_a.green - color_b.green;
+ diff.blue = color_a.blue - color_b.blue;
+ diff.alpha = 1.0f;
+
+ if (GLib.Math.fabs (diff.red) > 0.15 ||
+ GLib.Math.fabs (diff.green) > 0.15 ||
+ GLib.Math.fabs (diff.blue) > 0.15)
+ return true;
+
+ return false;
+ }
+
+ private Gdk.RGBA get_quad_average (int x,
+ int y,
+ int width,
+ int height,
+ int level_of_recursion,
+ uint8[] pixels,
+ int rowstride,
+ int channels)
+ {
+ // samples four corners
+ // c1-----c2
+ // | |
+ // c3-----c4
+
+ var average = Gdk.RGBA ();
+ var corner1 = get_pixbuf_sample (pixels, rowstride, channels, x , y );
+ var corner2 = get_pixbuf_sample (pixels, rowstride, channels, x + width, y );
+ var corner3 = get_pixbuf_sample (pixels, rowstride, channels, x , y + height);
+ var corner4 = get_pixbuf_sample (pixels, rowstride, channels, x + width, y + height);
+ var centre = get_pixbuf_sample (pixels, rowstride, channels, x + (width / 2), y + (height / 2));
+
+ /* If we're over the max we want to just take the average and be happy
+ with that value */
+ if (level_of_recursion < QUAD_MAX_LEVEL_OF_RECURSION) {
+ /* Otherwise we want to look at each value and check it's distance
+ from the center color and take the average if they're far apart. */
+
+ /* corner 1 */
+ if (level_of_recursion < QUAD_MIN_LEVEL_OF_RECURSION ||
+ is_color_different(corner1, centre)) {
+ corner1 = get_quad_average (x, y, width/2, height/2, level_of_recursion + 1, pixels, rowstride, channels);
+ }
+
+ /* corner 2 */
+ if (level_of_recursion < QUAD_MIN_LEVEL_OF_RECURSION ||
+ is_color_different(corner2, centre)) {
+ corner2 = get_quad_average (x + width/2, y, width/2, height/2, level_of_recursion + 1, pixels, rowstride, channels);
+ }
+
+ /* corner 3 */
+ if (level_of_recursion < QUAD_MIN_LEVEL_OF_RECURSION ||
+ is_color_different(corner3, centre)) {
+ corner3 = get_quad_average (x, y + height/2, width/2, height/2, level_of_recursion + 1, pixels, rowstride, channels);
+ }
+
+ /* corner 4 */
+ if (level_of_recursion < QUAD_MIN_LEVEL_OF_RECURSION ||
+ is_color_different(corner4, centre)) {
+ corner4 = get_quad_average (x + width/2, y + height/2, width/2, height/2, level_of_recursion + 1, pixels, rowstride, channels);
+ }
+ }
+
+ average.red = ((corner1.red * QUAD_CORNER_WEIGHT_NW) +
+ (corner3.red * QUAD_CORNER_WEIGHT_SW) +
+ (centre.red * QUAD_CORNER_WEIGHT_CENTER) +
+ (corner2.red * QUAD_CORNER_WEIGHT_NE) +
+ (corner4.red * QUAD_CORNER_WEIGHT_SE))
+ / QUAD_CORNER_WEIGHT_TOTAL;
+ average.green = ((corner1.green * QUAD_CORNER_WEIGHT_NW) +
+ (corner3.green * QUAD_CORNER_WEIGHT_SW) +
+ (centre.green * QUAD_CORNER_WEIGHT_CENTER) +
+ (corner2.green * QUAD_CORNER_WEIGHT_NE) +
+ (corner4.green * QUAD_CORNER_WEIGHT_SE))
+ / QUAD_CORNER_WEIGHT_TOTAL;
+ average.blue = ((corner1.blue * QUAD_CORNER_WEIGHT_NW) +
+ (corner3.blue * QUAD_CORNER_WEIGHT_SW) +
+ (centre.blue * QUAD_CORNER_WEIGHT_CENTER) +
+ (corner2.blue * QUAD_CORNER_WEIGHT_NE) +
+ (corner4.blue * QUAD_CORNER_WEIGHT_SE))
+ / QUAD_CORNER_WEIGHT_TOTAL;
+ average.alpha = 1.0f;
+
+ return average;
+ }
+
+ private void pixbuf_average_value (Gdk.Pixbuf pixbuf,
+ out Gdk.RGBA result)
+ {
+ var average = get_quad_average (0, 0,
+ pixbuf.get_width () - 1, pixbuf.get_height () - 1,
+ 1,
+ pixbuf.get_pixels (),
+ pixbuf.get_rowstride (),
+ pixbuf.get_n_channels ());
+
+ result = Gdk.RGBA ();
+ result.red = average.red;
+ result.green = average.green;
+ result.blue = average.blue;
+ result.alpha = average.alpha;
+ }
+}
+
+public class Monitor
+{
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+
+ public Monitor (int x, int y, int width, int height)
+ {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ public bool equals (Monitor? other)
+ {
+ if (other != null)
+ return (x == other.x && y == other.y && width == other.width && height == other.height);
+
+ return false;
+ }
+}
+
+public class Background : Gtk.Fixed
+{
+ public enum DrawFlags
+ {
+ NONE,
+ GRID,
+ }
+
+ public string default_background { get; set; default = UGSettings.get_string (UGSettings.KEY_BACKGROUND_COLOR); }
+ public string? current_background { get; set; default = null; }
+ public bool draw_grid { get; set; default = true; }
+ public double alpha { get; private set; default = 1.0; }
+ public Gdk.RGBA average_color { get { return current.average_color; } }
+
+ private Cairo.Surface target_surface;
+
+ private List<Monitor> monitors = null;
+ private Monitor? active_monitor = null;
+
+ private AnimateTimer timer;
+
+ private BackgroundLoader current;
+ private BackgroundLoader old;
+
+ private HashTable<string, BackgroundLoader> loaders;
+
+ private Cairo.Surface? version_logo_surface = null;
+ private int version_logo_width;
+ private int version_logo_height;
+ private Cairo.Surface? background_logo_surface = null;
+ private int background_logo_width;
+ private int background_logo_height;
+
+ public Background (Cairo.Surface target_surface)
+ {
+ this.target_surface = target_surface;
+ timer = new AnimateTimer (AnimateTimer.ease_in_out, 700);
+ timer.animate.connect (animate_cb);
+
+ loaders = new HashTable<string?, BackgroundLoader> (str_hash, str_equal);
+
+ notify["current-background"].connect (() => { reload (); });
+ }
+
+ public void set_logo (string version_logo, string background_logo)
+ {
+ version_logo_surface = load_image (version_logo, out version_logo_width, out version_logo_height);
+ background_logo_surface = load_image (background_logo, out background_logo_width, out background_logo_height);
+ }
+
+ private Cairo.Surface? load_image (string filename, out int width, out int height)
+ {
+ width = height = 0;
+ try
+ {
+ var image = new Gdk.Pixbuf.from_file (filename);
+ width = image.width;
+ height = image.height;
+ var surface = new Cairo.Surface.similar (target_surface, Cairo.Content.COLOR_ALPHA, image.width, image.height);
+ var c = new Cairo.Context (surface);
+ Gdk.cairo_set_source_pixbuf (c, image, 0, 0);
+ c.paint ();
+ return surface;
+ }
+ catch (Error e)
+ {
+ debug ("Failed to load background component %s: %s", filename, e.message);
+ }
+
+ return null;
+ }
+
+ public void set_monitors (List<Monitor> monitors)
+ {
+ this.monitors = new List<Monitor> ();
+ foreach (var m in monitors)
+ this.monitors.append (m);
+ queue_draw ();
+ }
+
+ public void set_active_monitor (Monitor? monitor)
+ {
+ active_monitor = monitor;
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ var resized = allocation.height != get_allocated_height () || allocation.width != get_allocated_width ();
+
+ base.size_allocate (allocation);
+
+ /* Regenerate backgrounds */
+ if (resized)
+ {
+ debug ("Regenerating backgrounds");
+ loaders.remove_all ();
+ load_background (null);
+ reload ();
+ }
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ var flags = DrawFlags.NONE;
+ if (draw_grid)
+ flags |= DrawFlags.GRID;
+ draw_full (c, flags);
+ return base.draw (c);
+ }
+
+ public void draw_full (Cairo.Context c, DrawFlags flags)
+ {
+ c.save ();
+
+ /* Test whether we ran into an error loading this background */
+ if (current == null || (current.load () && current.patterns[0] == null))
+ {
+ /* We couldn't load it, so swap it out for the default background
+ and remember that choice */
+ var new_background = load_background (null);
+ if (current != null)
+ loaders.insert (current.filename, new_background);
+ if (old == current)
+ old = new_background;
+ current = new_background;
+ publish_average_color ();
+ }
+
+ /* Fade to this background when loaded */
+ if (current.load () && current != old && !timer.is_running)
+ {
+ alpha = 0.0;
+ timer.reset ();
+ }
+
+ c.set_source_rgba (0.0, 0.0, 0.0, 0.0);
+ var old_painted = false;
+
+ /* Draw old background */
+ if (old != null && old.load () && (alpha < 1.0 || !current.load ()))
+ {
+ draw_background (c, old, 1.0);
+ old_painted = true;
+ }
+
+ /* Draw new background */
+ if (current.load () && alpha > 0.0)
+ draw_background (c, current, old_painted ? alpha : 1.0);
+
+ c.restore ();
+
+ if ((flags & DrawFlags.GRID) != 0)
+ overlay_grid (c);
+ }
+
+ private void draw_background (Cairo.Context c, BackgroundLoader background, double alpha)
+ {
+ foreach (var monitor in monitors)
+ {
+ var pattern = background.get_pattern (monitor.width, monitor.height);
+ if (pattern == null)
+ continue;
+
+ c.save ();
+ pattern = background.get_pattern (monitor.width, monitor.height);
+ var matrix = Cairo.Matrix.identity ();
+ matrix.translate (-monitor.x, -monitor.y);
+ pattern.set_matrix (matrix);
+ c.set_source (pattern);
+ c.rectangle (monitor.x, monitor.y, monitor.width, monitor.height);
+ c.clip ();
+ c.paint_with_alpha (alpha);
+ c.restore ();
+
+ if (monitor != active_monitor && background_logo_surface != null)
+ {
+ var width = background_logo_width;
+ var height = background_logo_height;
+
+ c.save ();
+ pattern = new Cairo.Pattern.for_surface (background_logo_surface);
+ matrix = Cairo.Matrix.identity ();
+ var x = monitor.x + (monitor.width - width) / 2;
+ var y = monitor.y + (monitor.height - height) / 2;
+ matrix.translate (-x, -y);
+ pattern.set_matrix (matrix);
+ c.set_source (pattern);
+ c.rectangle (x, y, width, height);
+ c.clip ();
+ c.paint_with_alpha (alpha);
+ c.restore ();
+ }
+ }
+ }
+
+ private void animate_cb (double progress)
+ {
+ alpha = progress;
+ queue_draw ();
+
+ /* Stop when we get there */
+ if (alpha >= 1.0)
+ old = current;
+ }
+
+ private void reload ()
+ {
+ if (get_realized ())
+ {
+ var new_background = load_background (current_background);
+
+ if (current != new_background)
+ {
+ old = current;
+ current = new_background;
+ alpha = 1.0; /* if the timer isn't going, we should always be at 1.0 */
+ timer.stop ();
+ }
+
+ queue_draw ();
+ publish_average_color ();
+ }
+ }
+
+ private BackgroundLoader load_background (string? filename)
+ {
+ if (filename == null)
+ filename = default_background;
+
+ var b = loaders.lookup (filename);
+ if (b == null)
+ {
+ /* Load required sizes to draw background */
+ var widths = new int[monitors.length ()];
+ var heights = new int[monitors.length ()];
+ var n_sizes = 0;
+ foreach (var monitor in monitors)
+ {
+ if (monitor_is_unique_size (monitor))
+ {
+ widths[n_sizes] = monitor.width;
+ heights[n_sizes] = monitor.height;
+ n_sizes++;
+ }
+ }
+ widths.resize (n_sizes);
+ heights.resize (n_sizes);
+
+ b = new BackgroundLoader (target_surface, filename, widths, heights, draw_grid);
+ b.logo = version_logo_surface;
+ b.loaded.connect (() => { reload (); });
+ b.load ();
+ loaders.insert (filename, b);
+ }
+
+ return b;
+ }
+
+ /* Check if a monitor has a unique size */
+ private bool monitor_is_unique_size (Monitor monitor)
+ {
+ foreach (var m in monitors)
+ {
+ if (m == monitor)
+ break;
+ else if (m.width == monitor.width && m.height == monitor.height)
+ return false;
+ }
+
+ return true;
+ }
+
+ private void overlay_grid (Cairo.Context c)
+ {
+ var width = get_allocated_width ();
+ var height = get_allocated_height ();
+ var grid_x_offset = get_grid_offset (width);
+ var grid_y_offset = get_grid_offset (height);
+
+ /* Overlay grid */
+ var overlay_surface = new Cairo.Surface.similar (target_surface, Cairo.Content.COLOR_ALPHA, grid_size, grid_size);
+ var oc = new Cairo.Context (overlay_surface);
+ oc.rectangle (0, 0, 1, 1);
+ oc.rectangle (grid_size - 1, 0, 1, 1);
+ oc.rectangle (0, grid_size - 1, 1, 1);
+ oc.rectangle (grid_size - 1, grid_size - 1, 1, 1);
+ oc.set_source_rgba (1.0, 1.0, 1.0, 0.25);
+ oc.fill ();
+ var overlay = new Cairo.Pattern.for_surface (overlay_surface);
+ var matrix = Cairo.Matrix.identity ();
+ matrix.translate (-grid_x_offset, -grid_y_offset);
+ overlay.set_matrix (matrix);
+ overlay.set_extend (Cairo.Extend.REPEAT);
+
+ /* Draw overlay */
+ c.save ();
+ c.set_source (overlay);
+ c.rectangle (0, 0, width, height);
+ c.fill ();
+ c.restore ();
+ }
+
+ void publish_average_color ()
+ {
+ notify_property ("average-color");
+ var rgba = current.average_color.to_string ();
+ var root = get_screen ().get_root_window ();
+
+ Gdk.property_change (root,
+ Gdk.Atom.intern_static_string ("_GNOME_BACKGROUND_REPRESENTATIVE_COLORS"),
+ Gdk.Atom.intern_static_string ("STRING"),
+ 8,
+ Gdk.PropMode.REPLACE,
+ rgba.data,
+ rgba.data.length);
+ }
+}
diff --git a/src/cached-image.vala b/src/cached-image.vala
new file mode 100644
index 0000000..56157a3
--- /dev/null
+++ b/src/cached-image.vala
@@ -0,0 +1,59 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class CachedImage : Gtk.Image
+{
+ private static HashTable<Gdk.Pixbuf, Cairo.Surface> surface_table;
+
+ public static Cairo.Surface? get_cached_surface (Cairo.Context c, Gdk.Pixbuf pixbuf)
+ {
+ if (surface_table == null)
+ surface_table = new HashTable<Gdk.Pixbuf, Cairo.Surface> (direct_hash, direct_equal);
+
+ var surface = surface_table.lookup (pixbuf);
+ if (surface == null)
+ {
+ surface = new Cairo.Surface.similar (c.get_target (), Cairo.Content.COLOR_ALPHA, pixbuf.width, pixbuf.height);
+ var new_c = new Cairo.Context (surface);
+ Gdk.cairo_set_source_pixbuf (new_c, pixbuf, 0, 0);
+ new_c.paint ();
+ surface_table.insert (pixbuf, surface);
+ }
+ return surface;
+ }
+
+ public CachedImage (Gdk.Pixbuf? pixbuf)
+ {
+ Object (pixbuf: pixbuf);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (pixbuf != null)
+ {
+ var cached_surface = get_cached_surface (c, pixbuf);
+ if (cached_surface != null)
+ {
+ c.set_source_surface (cached_surface, 0, 0);
+ c.paint ();
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/cairo-utils.vala b/src/cairo-utils.vala
new file mode 100644
index 0000000..a30a580
--- /dev/null
+++ b/src/cairo-utils.vala
@@ -0,0 +1,238 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2013 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/>.
+ *
+ * Authors: Marco Trevisan <marco.trevisan@canonical.com>
+ * Mirco "MacSlow" Mueller <mirco.mueller@canonical.com>
+ */
+
+namespace CairoUtils
+{
+
+public void rounded_rectangle (Cairo.Context c, double x, double y,
+ double width, double height, double radius)
+{
+ var w = width - radius * 2;
+ var h = height - radius * 2;
+ var kappa = 0.5522847498 * radius;
+ c.move_to (x + radius, y);
+ c.rel_line_to (w, 0);
+ c.rel_curve_to (kappa, 0, radius, radius - kappa, radius, radius);
+ c.rel_line_to (0, h);
+ c.rel_curve_to (0, kappa, kappa - radius, radius, -radius, radius);
+ c.rel_line_to (-w, 0);
+ c.rel_curve_to (-kappa, 0, -radius, kappa - radius, -radius, -radius);
+ c.rel_line_to (0, -h);
+ c.rel_curve_to (0, -kappa, radius - kappa, -radius, radius, -radius);
+}
+
+class GaussianBlur
+{
+ /* Gaussian Blur, based on Mirco Mueller work on notify-osd */
+
+ public static void surface (Cairo.ImageSurface surface, uint radius, double sigma = 0.0f)
+ {
+ if (surface.get_format () != Cairo.Format.ARGB32)
+ {
+ warning ("Impossible to blur a non ARGB32-formatted ImageSurface");
+ return;
+ }
+
+ surface.flush ();
+
+ double radiusf = Math.fabs (radius) + 1.0f;
+
+ if (sigma == 0.0f)
+ sigma = Math.sqrt (-(radiusf * radiusf) / (2.0f * Math.log (1.0f / 255.0f)));
+
+ int w = surface.get_width ();
+ int h = surface.get_height ();
+ int s = surface.get_stride ();
+
+ // create pixman image for cairo image surface
+ unowned uchar[] p = surface.get_data ();
+ var src = new Pixman.Image.bits (Pixman.Format.A8R8G8B8, w, h, p, s);
+
+ // attach gaussian kernel to pixman image
+ var params = create_gaussian_blur_kernel ((int) radius, sigma);
+ src.set_filter (Pixman.Filter.CONVOLUTION, params);
+
+ // render blured image to new pixman image
+ Pixman.Image.composite (Pixman.Operation.SRC, src, null, src,
+ 0, 0, 0, 0, 0, 0, (uint16) w, (uint16) h);
+
+ surface.mark_dirty ();
+ }
+
+ private static Pixman.Fixed[] create_gaussian_blur_kernel (int radius, double sigma)
+ {
+ double scale2 = 2.0f * sigma * sigma;
+ double scale1 = 1.0f / (Math.PI * scale2);
+ int size = 2 * radius + 1;
+ int n_params = size * size;
+ double sum = 0;
+
+ var tmp = new double[n_params];
+
+ // caluclate gaussian kernel in floating point format
+ for (int i = 0, x = -radius; x <= radius; ++x)
+ {
+ for (int y = -radius; y <= radius; ++y, ++i)
+ {
+ double u = x * x;
+ double v = y * y;
+
+ tmp[i] = scale1 * Math.exp (-(u+v)/scale2);
+
+ sum += tmp[i];
+ }
+ }
+
+ // normalize gaussian kernel and convert to fixed point format
+ var params = new Pixman.Fixed[n_params + 2];
+
+ params[0] = Pixman.Fixed.int (size);
+ params[1] = Pixman.Fixed.int (size);
+
+ for (int i = 2; i < params.length; ++i)
+ params[i] = Pixman.Fixed.double (tmp[i] / sum);
+
+ return params;
+ }
+}
+
+class ExponentialBlur
+{
+ /* Exponential Blur, based on the Nux version */
+
+ const int APREC = 16;
+ const int ZPREC = 7;
+
+ public static void surface (Cairo.ImageSurface surface, int radius)
+ {
+ if (radius < 1)
+ return;
+
+ // before we mess with the surface execute any pending drawing
+ surface.flush ();
+
+ unowned uchar[] pixels = surface.get_data ();
+ var width = surface.get_width ();
+ var height = surface.get_height ();
+ var format = surface.get_format ();
+
+ switch (format)
+ {
+ case Cairo.Format.ARGB32:
+ blur (pixels, width, height, 4, radius);
+ break;
+
+ case Cairo.Format.RGB24:
+ blur (pixels, width, height, 3, radius);
+ break;
+
+ case Cairo.Format.A8:
+ blur (pixels, width, height, 1, radius);
+ break;
+
+ default :
+ // do nothing
+ break;
+ }
+
+ // inform cairo we altered the surfaces contents
+ surface.mark_dirty ();
+ }
+
+ static void blur (uchar[] pixels, int width, int height, int channels, int radius)
+ {
+ // calculate the alpha such that 90% of
+ // the kernel is within the radius.
+ // (Kernel extends to infinity)
+
+ int alpha = (int) ((1 << APREC) * (1.0f - Math.expf(-2.3f / (radius + 1.0f))));
+
+ for (int row = 0; row < height; ++row)
+ blurrow (pixels, width, height, channels, row, alpha);
+
+ for (int col = 0; col < width; ++col)
+ blurcol (pixels, width, height, channels, col, alpha);
+ }
+
+ static void blurrow (uchar[] pixels, int width, int height, int channels, int line, int alpha)
+ {
+ var scanline = &(pixels[line * width * channels]);
+
+ int zR = *scanline << ZPREC;
+ int zG = *(scanline + 1) << ZPREC;
+ int zB = *(scanline + 2) << ZPREC;
+ int zA = *(scanline + 3) << ZPREC;
+
+ for (int index = 0; index < width; ++index)
+ {
+ blurinner (&scanline[index * channels], alpha, ref zR, ref zG, ref zB, ref zA);
+ }
+
+ for (int index = width - 2; index >= 0; --index)
+ {
+ blurinner (&scanline[index * channels], alpha, ref zR, ref zG, ref zB, ref zA);
+ }
+ }
+
+ static void blurcol (uchar[] pixels, int width, int height, int channels, int x, int alpha)
+ {
+ var ptr = &(pixels[x * channels]);
+
+ int zR = *ptr << ZPREC;
+ int zG = *(ptr + 1) << ZPREC;
+ int zB = *(ptr + 2) << ZPREC;
+ int zA = *(ptr + 3) << ZPREC;
+
+ for (int index = width; index < (height - 1) * width; index += width)
+ {
+ blurinner (&ptr[index * channels], alpha, ref zR, ref zG, ref zB, ref zA);
+ }
+
+ for (int index = (height - 2) * width; index >= 0; index -= width)
+ {
+ blurinner (&ptr[index * channels], alpha, ref zR, ref zG, ref zB, ref zA);
+ }
+ }
+
+ static void blurinner (uchar *pixel, int alpha, ref int zR, ref int zG, ref int zB, ref int zA)
+ {
+ int R;
+ int G;
+ int B;
+ uchar A;
+
+ R = *pixel;
+ G = *(pixel + 1);
+ B = *(pixel + 2);
+ A = *(pixel + 3);
+
+ zR += (alpha * ((R << ZPREC) - zR)) >> APREC;
+ zG += (alpha * ((G << ZPREC) - zG)) >> APREC;
+ zB += (alpha * ((B << ZPREC) - zB)) >> APREC;
+ zA += (alpha * ((A << ZPREC) - zA)) >> APREC;
+
+ *pixel = zR >> ZPREC;
+ *(pixel + 1) = zG >> ZPREC;
+ *(pixel + 2) = zB >> ZPREC;
+ *(pixel + 3) = zA >> ZPREC;
+ }
+}
+
+}
diff --git a/src/config.vapi b/src/config.vapi
new file mode 100644
index 0000000..8fe2441
--- /dev/null
+++ b/src/config.vapi
@@ -0,0 +1,12 @@
+[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
+namespace Config
+{
+ public const string GETTEXT_PACKAGE;
+ public const string LOCALEDIR;
+ public const string VERSION;
+ public const string CONFIG_FILE;
+ public const string INDICATOR_FILE_DIR;
+ public const string PKGDATADIR;
+ public const string INDICATORDIR;
+ public const string USD_BINARY;
+}
diff --git a/src/dash-box.vala b/src/dash-box.vala
new file mode 100644
index 0000000..889ba41
--- /dev/null
+++ b/src/dash-box.vala
@@ -0,0 +1,230 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class DashBox : Gtk.Box
+{
+ public Background? background { get; construct; default = null; }
+
+ public bool has_base { get; private set; default = false; }
+ public double base_alpha { get; private set; default = 1.0; }
+
+ private enum Mode
+ {
+ NORMAL,
+ PUSH_FADE_OUT,
+ PUSH_FADE_IN,
+ POP_FADE_OUT,
+ POP_FADE_IN,
+ }
+
+ private GreeterList pushed;
+ private Gtk.Widget orig = null;
+ private FadeTracker orig_tracker;
+ private int orig_height = -1;
+ private Mode mode;
+
+ public DashBox (Background bg)
+ {
+ Object (background: bg);
+ }
+
+ construct
+ {
+ mode = Mode.NORMAL;
+ }
+
+ /* Does not actually add w to this widget, as doing so would potentially mess with w's placement. */
+ public void set_base (Gtk.Widget? w)
+ {
+ return_if_fail (pushed == null);
+ return_if_fail (mode == Mode.NORMAL);
+
+ if (orig != null)
+ orig.size_allocate.disconnect (base_size_allocate_cb);
+ orig = w;
+
+ if (orig != null)
+ {
+ orig.size_allocate.connect (base_size_allocate_cb);
+ orig_tracker = new FadeTracker (orig);
+ orig_tracker.notify["alpha"].connect (() =>
+ {
+ base_alpha = orig_tracker.alpha;
+ queue_draw ();
+ });
+ orig_tracker.done.connect (fade_done_cb);
+ base_alpha = orig_tracker.alpha;
+ has_base = true;
+ }
+ else
+ {
+ orig_height = -1;
+ get_preferred_height (null, out orig_height); /* save height */
+
+ orig_tracker = null;
+ base_alpha = 1.0;
+ has_base = false;
+ }
+
+ queue_resize ();
+ }
+
+ public void push (GreeterList l)
+ {
+ /* This isn't designed to push more than one widget at a time yet */
+ return_if_fail (pushed == null);
+ return_if_fail (orig != null);
+ return_if_fail (mode == Mode.NORMAL);
+
+ get_preferred_height (null, out orig_height);
+ pushed = l;
+ pushed.fade_done.connect (fade_done_cb);
+ mode = Mode.PUSH_FADE_OUT;
+ orig_tracker.reset (FadeTracker.Mode.FADE_OUT);
+ queue_resize ();
+ }
+
+ public void pop ()
+ {
+ return_if_fail (pushed != null);
+ return_if_fail (orig != null);
+ return_if_fail (mode == Mode.NORMAL);
+
+ mode = Mode.POP_FADE_OUT;
+ pushed.fade_out ();
+ }
+
+ private void fade_done_cb ()
+ {
+ switch (mode)
+ {
+ case Mode.PUSH_FADE_OUT:
+ mode = Mode.PUSH_FADE_IN;
+ orig.hide ();
+ pushed.fade_in ();
+ break;
+ case Mode.PUSH_FADE_IN:
+ mode = Mode.NORMAL;
+ pushed.grab_focus ();
+ break;
+ case Mode.POP_FADE_OUT:
+ mode = Mode.POP_FADE_IN;
+ orig_tracker.reset (FadeTracker.Mode.FADE_IN);
+ orig.show ();
+ break;
+ case Mode.POP_FADE_IN:
+ mode = Mode.NORMAL;
+ pushed.fade_done.disconnect (fade_done_cb);
+ pushed.destroy ();
+ pushed = null;
+ queue_resize ();
+ orig.grab_focus ();
+ break;
+ }
+ }
+
+ private void base_size_allocate_cb ()
+ {
+ queue_resize ();
+ }
+
+ public override void get_preferred_height (out int min, out int nat)
+ {
+ if (orig == null)
+ {
+ /* Return cached height if we have it. This makes transitions between two base widgets smoother. */
+ if (orig_height >= 0)
+ {
+ min = orig_height;
+ nat = orig_height;
+ }
+ else
+ {
+ min = grid_size * GreeterList.DEFAULT_BOX_HEIGHT - GreeterList.BORDER * 2;
+ nat = grid_size * GreeterList.DEFAULT_BOX_HEIGHT - GreeterList.BORDER * 2;
+ }
+ }
+ else
+ {
+ if (pushed == null)
+ orig.get_preferred_height (out min, out nat);
+ else
+ {
+ pushed.selected_entry.get_preferred_height (out min, out nat);
+ min = int.max (orig_height, min);
+ nat = int.max (orig_height, nat);
+ }
+ }
+ }
+
+ public override void get_preferred_width (out int min, out int nat)
+ {
+ min = grid_size * GreeterList.BOX_WIDTH - GreeterList.BORDER * 2;
+ nat = grid_size * GreeterList.BOX_WIDTH - GreeterList.BORDER * 2;
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (background != null)
+ {
+ int x, y;
+ background.translate_coordinates (this, 0, 0, out x, out y);
+ c.save ();
+ c.translate (x, y);
+ background.draw_full (c, Background.DrawFlags.NONE);
+ c.restore ();
+ }
+
+ /* Draw darker background with a rounded border */
+ var box_r = 0.3 * grid_size;
+ int box_y = 0;
+ int box_w;
+ int box_h;
+ get_preferred_width (null, out box_w);
+ get_preferred_height (null, out box_h);
+
+ if (mode == Mode.PUSH_FADE_OUT)
+ {
+ /* Grow dark bg to fit new pushed object */
+ var new_box_h = box_h - (int) ((box_h - orig_height) * base_alpha);
+ box_h = new_box_h;
+ }
+ else if (mode == Mode.POP_FADE_IN)
+ {
+ /* Shrink dark bg to fit orig */
+ var new_box_h = box_h - (int) ((box_h - orig_height) * base_alpha);
+ box_h = new_box_h;
+ }
+
+ c.save ();
+
+ CairoUtils.rounded_rectangle (c, 0, box_y, box_w, box_h, box_r);
+
+ c.set_source_rgba (0.1, 0.1, 0.1, 0.4);
+ c.fill_preserve ();
+
+ c.set_source_rgba (0.4, 0.4, 0.4, 0.4);
+ c.set_line_width (1);
+ c.stroke ();
+
+ c.restore ();
+
+ return base.draw (c);
+ }
+}
diff --git a/src/dash-button.vala b/src/dash-button.vala
new file mode 100644
index 0000000..c562a28
--- /dev/null
+++ b/src/dash-button.vala
@@ -0,0 +1,89 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class DashButton : FlatButton, Fadable
+{
+ protected FadeTracker fade_tracker { get; protected set; }
+ private Gtk.Label text_label;
+
+ private string _text = "";
+ public string text
+ {
+ get { return _text; }
+ set
+ {
+ _text = value;
+ text_label.set_markup ("<span font=\"Ubuntu 13\">%s</span>".printf (value));
+ }
+ }
+
+ public DashButton (string text)
+ {
+ fade_tracker = new FadeTracker (this);
+
+ var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+
+ /* Add text */
+ text_label = new Gtk.Label ("");
+ text_label.use_markup = true;
+ text_label.hexpand = true;
+ text_label.halign = Gtk.Align.START;
+ hbox.add (text_label);
+ this.text = text;
+
+ /* Add chevron */
+ var path = Path.build_filename (Config.PKGDATADIR, "arrow_right.png", null);
+ try
+ {
+ var pixbuf = new Gdk.Pixbuf.from_file (path);
+ var image = new CachedImage (pixbuf);
+ image.valign = Gtk.Align.CENTER;
+ hbox.add (image);
+ }
+ catch (Error e)
+ {
+ debug ("Error loading image %s: %s", path, e.message);
+ }
+
+ hbox.show_all ();
+ add (hbox);
+
+ try
+ {
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {padding: 6px 8px 6px 8px;
+ -GtkWidget-focus-line-width: 0px;
+ }", -1);
+ this.get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading session chooser style: %s", e.message);
+ }
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ c.push_group ();
+ base.draw (c);
+ c.pop_group_to_source ();
+ c.paint_with_alpha (fade_tracker.alpha);
+ return false;
+ }
+}
diff --git a/src/dash-entry.vala b/src/dash-entry.vala
new file mode 100644
index 0000000..9528705
--- /dev/null
+++ b/src/dash-entry.vala
@@ -0,0 +1,319 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+/* Vala's vapi for gtk3 is broken for lookup_color (it forgets the out keyword) */
+[CCode (cheader_filename = "gtk/gtk.h")]
+extern bool gtk_style_context_lookup_color (Gtk.StyleContext ctx, string color_name, out Gdk.RGBA color);
+
+public class DashEntry : Gtk.Entry, Fadable
+{
+ public static string font = "Ubuntu 14";
+ public signal void respond ();
+
+ public string constant_placeholder_text { get; set; }
+ public bool can_respond { get; set; default = true; }
+
+ private bool _did_respond;
+ public bool did_respond
+ {
+ get
+ {
+ return _did_respond;
+ }
+ set
+ {
+ _did_respond = value;
+ if (value)
+ set_state_flags (Gtk.StateFlags.ACTIVE, false);
+ else
+ unset_state_flags (Gtk.StateFlags.ACTIVE);
+ queue_draw ();
+ }
+ }
+
+ private static const string NO_BORDER_CLASS = "unity-greeter-no-border";
+
+ protected FadeTracker fade_tracker { get; protected set; }
+ private Gdk.Window arrow_win;
+ private static Gdk.Pixbuf arrow_pixbuf;
+
+ construct
+ {
+ fade_tracker = new FadeTracker (this);
+
+ notify["can-respond"].connect (queue_draw);
+ button_press_event.connect (button_press_event_cb);
+
+ if (arrow_pixbuf == null)
+ {
+ var filename = Path.build_filename (Config.PKGDATADIR, "arrow_right.png");
+ try
+ {
+ arrow_pixbuf = new Gdk.Pixbuf.from_file (filename);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading arrow icon: %s", e.message);
+ }
+ }
+
+ override_font (Pango.FontDescription.from_string (font));
+
+ var style_ctx = get_style_context ();
+
+ try
+ {
+ var padding_provider = new Gtk.CssProvider ();
+ var css = "* {padding-right: %dpx;}".printf (get_arrow_size ());
+ padding_provider.load_from_data (css, -1);
+ style_ctx.add_provider (padding_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading padding style: %s", e.message);
+ }
+
+ // We add the styles and classes we need for normal operation of the
+ // spinner animation. These are always "on" and we just turn them off
+ // right before drawing our parent class's draw function. This is done
+ // opt-out like that rather than just turning the styles on when we
+ // need to draw the spinner because the animation doesn't work right
+ // otherwise. See the draw() function for how we turn it off.
+ var no_border_provider = new Gtk.CssProvider ();
+ try
+ {
+ var css = ".%s {border: 0px;}".printf (NO_BORDER_CLASS);
+ no_border_provider.load_from_data (css, -1);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading spinner style: %s", e.message);
+ }
+ style_ctx.add_provider (no_border_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ style_ctx.add_class (NO_BORDER_CLASS);
+ style_ctx.add_class (Gtk.STYLE_CLASS_SPINNER);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ var style_ctx = get_style_context ();
+
+ // See construct method for explanation of why we remove classes
+ style_ctx.save ();
+ style_ctx.remove_class (Gtk.STYLE_CLASS_SPINNER);
+ style_ctx.remove_class (NO_BORDER_CLASS);
+ c.save ();
+ c.push_group ();
+ base.draw (c);
+ c.pop_group_to_source ();
+ c.paint_with_alpha (fade_tracker.alpha);
+ c.restore ();
+ style_ctx.restore ();
+
+ /* Now draw the prompt text */
+ if (get_text_length () == 0 && constant_placeholder_text.length > 0)
+ draw_prompt_text (c);
+
+ /* Draw activity spinner if we need to */
+ if (did_respond)
+ draw_spinner (c);
+ else if (can_respond && get_text_length () > 0)
+ draw_arrow (c);
+
+ return false;
+ }
+
+ private void draw_spinner (Cairo.Context c)
+ {
+ c.save ();
+
+ var style_ctx = get_style_context ();
+ var arrow_size = get_arrow_size ();
+ Gtk.cairo_transform_to_window (c, this, arrow_win);
+ style_ctx.render_activity (c, 0, 0, arrow_size, arrow_size);
+
+ c.restore ();
+ }
+
+ private void draw_arrow (Cairo.Context c)
+ {
+ if (arrow_pixbuf == null)
+ return;
+
+ c.save ();
+
+ var arrow_size = get_arrow_size ();
+ Gtk.cairo_transform_to_window (c, this, arrow_win);
+ c.translate (arrow_size - arrow_pixbuf.get_width () - 1, 0); // right align
+ Gdk.cairo_set_source_pixbuf (c, arrow_pixbuf, 0, 0);
+
+ c.paint ();
+ c.restore ();
+ }
+
+ private void draw_prompt_text (Cairo.Context c)
+ {
+ c.save ();
+
+ /* Position text */
+ int x, y;
+ get_layout_offsets (out x, out y);
+ c.move_to (x, y);
+
+ /* Set foreground color */
+ var fg = Gdk.RGBA ();
+ var context = get_style_context ();
+ if (!gtk_style_context_lookup_color (context, "placeholder_text_color", out fg))
+ fg.parse ("#888");
+ c.set_source_rgba (fg.red, fg.green, fg.blue, fg.alpha);
+
+ /* Draw text */
+ var layout = create_pango_layout (constant_placeholder_text);
+ layout.set_font_description (Pango.FontDescription.from_string ("Ubuntu 13"));
+ Pango.cairo_show_layout (c, layout);
+
+ c.restore ();
+ }
+
+ public override void activate ()
+ {
+ base.activate ();
+ if (can_respond)
+ {
+ did_respond = true;
+ respond ();
+ }
+ else
+ {
+ get_toplevel ().child_focus (Gtk.DirectionType.TAB_FORWARD);
+ }
+ }
+
+ public bool button_press_event_cb (Gdk.EventButton event)
+ {
+ if (event.window == arrow_win && get_text_length () > 0)
+ {
+ activate ();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ private int get_arrow_size ()
+ {
+ // height is larger than width for the arrow, so we measure using that
+ if (arrow_pixbuf != null)
+ return arrow_pixbuf.get_height ();
+ else
+ return 20; // Shouldn't happen
+ }
+
+ private void get_arrow_location (out int x, out int y)
+ {
+ var arrow_size = get_arrow_size ();
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ // height is larger than width for the arrow, so we measure using that
+ var margin = (allocation.height - arrow_size) / 2;
+
+ x = allocation.x + allocation.width - margin - arrow_size;
+ y = allocation.y + margin;
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+
+ if (arrow_win == null)
+ return;
+
+ int arrow_x, arrow_y;
+ get_arrow_location (out arrow_x, out arrow_y);
+ var arrow_size = get_arrow_size ();
+
+ arrow_win.move_resize (arrow_x, arrow_y, arrow_size, arrow_size);
+ }
+
+ public override void realize ()
+ {
+ base.realize ();
+
+ var cursor = new Gdk.Cursor (Gdk.CursorType.LEFT_PTR);
+ var attrs = Gdk.WindowAttr ();
+ attrs.x = 0;
+ attrs.y = 0;
+ attrs.width = 1;
+ attrs.height = 1;
+ attrs.cursor = cursor;
+ attrs.wclass = Gdk.WindowWindowClass.INPUT_ONLY;
+ attrs.window_type = Gdk.WindowType.CHILD;
+ attrs.event_mask = get_events () |
+ Gdk.EventMask.BUTTON_PRESS_MASK;
+
+ arrow_win = new Gdk.Window (get_window (), attrs,
+ Gdk.WindowAttributesType.X |
+ Gdk.WindowAttributesType.Y |
+ Gdk.WindowAttributesType.CURSOR);
+ arrow_win.ref ();
+ arrow_win.set_user_data (this);
+ }
+
+ public override void unrealize ()
+ {
+ if (arrow_win != null)
+ {
+ arrow_win.destroy ();
+ arrow_win = null;
+ }
+ base.unrealize ();
+ }
+
+ public override void map ()
+ {
+ base.map ();
+ if (arrow_win != null)
+ arrow_win.show ();
+ }
+
+ public override void unmap ()
+ {
+ if (arrow_win != null)
+ arrow_win.hide ();
+ base.unmap ();
+ }
+
+ public override bool key_press_event (Gdk.EventKey event)
+ {
+ // This is a workaroud for bug https://launchpad.net/bugs/944159
+ // The problem is that orca seems to not notice that it's in a password
+ // field on startup. We just need to kick orca in the pants.
+ if (UnityGreeter.singleton.orca_needs_kick)
+ {
+ Signal.emit_by_name (get_accessible (), "focus-event", true);
+ UnityGreeter.singleton.orca_needs_kick = false;
+ }
+
+ return base.key_press_event (event);
+ }
+}
diff --git a/src/email-autocompleter.vala b/src/email-autocompleter.vala
new file mode 100644
index 0000000..d11fb30
--- /dev/null
+++ b/src/email-autocompleter.vala
@@ -0,0 +1,79 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ */
+
+public class EmailAutocompleter
+{
+ private Gtk.Entry entry;
+ private string[] domains;
+ private string prevText = "";
+
+ private void entry_changed ()
+ {
+ if (entry.text.length < prevText.length)
+ {
+ /* Do nothing on erases of text */
+ prevText = entry.text;
+ return;
+ }
+
+ prevText = entry.text;
+
+ int first_at = entry.text.index_of ("@");
+ if (first_at != -1)
+ {
+ int second_at = entry.text.index_of ("@", first_at + 1);
+ if (second_at == -1)
+ {
+ /* We have exactly one @ */
+ string text_after_at = entry.text.slice (first_at + 1, entry.text.length);
+
+ /* Find first prefix match */
+ int match = -1;
+ for (int i = 0; match == -1 && i < domains.length; ++i)
+ {
+ if (domains[i].has_prefix (text_after_at))
+ match = i;
+ }
+
+ if (match != -1)
+ {
+ /* Calculate the suffix part we need to add */
+ var best_match = domains[match];
+ var text_to_add = best_match.slice (text_after_at.length, best_match.length);
+ if (text_to_add.length > 0)
+ {
+ entry.text = entry.text + text_to_add;
+ /* TODO This is quite ugly/hacky :-/ */
+ Timeout.add (0, () =>
+ {
+ entry.select_region (entry.text.length - text_to_add.length, entry.text.length);
+ return false;
+ });
+ }
+ }
+ }
+ }
+ }
+
+ public EmailAutocompleter (Gtk.Entry e, string[] email_domains)
+ {
+ entry = e;
+ domains = email_domains;
+ entry.changed.connect (entry_changed);
+ }
+}
diff --git a/src/fadable-box.vala b/src/fadable-box.vala
new file mode 100644
index 0000000..c287d21
--- /dev/null
+++ b/src/fadable-box.vala
@@ -0,0 +1,46 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class FadableBox : Gtk.EventBox, Fadable
+{
+ public signal void fade_done ();
+
+ protected FadeTracker fade_tracker { get; protected set; }
+
+ construct
+ {
+ visible_window = false;
+ fade_tracker = new FadeTracker (this);
+ fade_tracker.done.connect (() => { fade_done (); });
+ }
+
+ protected virtual void draw_full_alpha (Cairo.Context c)
+ {
+ base.draw (c);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ c.push_group ();
+ draw_full_alpha (c);
+ c.pop_group_to_source ();
+ c.paint_with_alpha (fade_tracker.alpha);
+ return false;
+ }
+}
diff --git a/src/fadable.vala b/src/fadable.vala
new file mode 100644
index 0000000..a67ad63
--- /dev/null
+++ b/src/fadable.vala
@@ -0,0 +1,99 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class FadeTracker : Object
+{
+ public signal void done ();
+
+ public double alpha { get; set; default = 1.0; }
+ public Gtk.Widget widget { get; construct; }
+
+ public enum Mode
+ {
+ FADE_IN,
+ FADE_OUT,
+ }
+
+ public FadeTracker (Gtk.Widget widget)
+ {
+ Object (widget: widget);
+ }
+
+ public void reset (Mode mode)
+ {
+ this.mode = mode;
+ animate_cb (0.0);
+ widget.show ();
+ timer.reset ();
+ }
+
+ private AnimateTimer timer;
+ private Mode mode;
+
+ construct
+ {
+ timer = new AnimateTimer (AnimateTimer.ease_out_quint, AnimateTimer.INSTANT);
+ timer.animate.connect (animate_cb);
+ }
+
+ private void animate_cb (double progress)
+ {
+ if (mode == Mode.FADE_IN)
+ {
+ alpha = progress;
+ if (progress == 1.0)
+ {
+ done ();
+ }
+ }
+ else
+ {
+ alpha = 1.0 - progress;
+ if (progress == 1.0)
+ {
+ widget.hide (); /* finish the job */
+ done ();
+ }
+ }
+
+ widget.queue_draw ();
+ }
+}
+
+public interface Fadable : Gtk.Widget
+{
+ protected abstract FadeTracker fade_tracker { get; protected set; }
+
+ public void fade_in ()
+ {
+ fade_tracker.reset (FadeTracker.Mode.FADE_IN);
+ }
+
+ public void fade_out ()
+ {
+ fade_tracker.reset (FadeTracker.Mode.FADE_OUT);
+ }
+
+ /* In case you want to control fade manually */
+ public void set_alpha (double alpha)
+ {
+ fade_tracker.alpha = alpha;
+ queue_draw ();
+ }
+}
diff --git a/src/fading-label.vala b/src/fading-label.vala
new file mode 100644
index 0000000..80977e6
--- /dev/null
+++ b/src/fading-label.vala
@@ -0,0 +1,85 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class FadingLabel : Gtk.Label
+{
+ private Cairo.Surface cached_surface;
+
+ public FadingLabel (string text)
+ {
+ Object (label: text);
+ }
+
+ public override void get_preferred_width (out int minimum, out int natural)
+ {
+ base.get_preferred_width (out minimum, out natural);
+ minimum = 0;
+ }
+
+ public override void get_preferred_width_for_height (int height, out int minimum, out int natural)
+ {
+ base.get_preferred_width_for_height (height, out minimum, out natural);
+ minimum = 0;
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+ cached_surface = null;
+ }
+
+ private Cairo.Surface make_surface (Cairo.Context orig_c)
+ {
+ int w, h;
+ get_layout ().get_pixel_size (out w, out h);
+
+ var bw = get_allocated_width ();
+ var bh = get_allocated_height ();
+
+ var surface = new Cairo.Surface.similar (orig_c.get_target (), Cairo.Content.COLOR_ALPHA, bw, bh);
+ var c = new Cairo.Context (surface);
+
+ if (w > bw)
+ {
+ c.push_group ();
+ base.draw (c);
+ c.pop_group_to_source ();
+
+ var mask = new Cairo.Pattern.linear (0, 0, bw, 0);
+ mask.add_color_stop_rgba (1.0 - 27.0 / bw, 1.0, 1.0, 1.0, 1.0);
+ mask.add_color_stop_rgba (1.0 - 21.6 / bw, 1.0, 1.0, 1.0, 0.5);
+ mask.add_color_stop_rgba (1.0, 1.0, 1.0, 1.0, 0.0);
+
+ c.mask (mask);
+ }
+ else
+ base.draw (c);
+
+ return surface;
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (cached_surface == null)
+ cached_surface = make_surface (c);
+ c.set_source_surface (cached_surface, 0, 0);
+ c.paint ();
+ return false;
+ }
+}
diff --git a/src/fixes.vapi b/src/fixes.vapi
new file mode 100644
index 0000000..8d08370
--- /dev/null
+++ b/src/fixes.vapi
@@ -0,0 +1,57 @@
+#if !VALA_0_22
+namespace Posix
+{
+ [CCode (cheader_filename = "sys/mman.h")]
+ public const int MCL_CURRENT;
+ [CCode (cheader_filename = "sys/mman.h")]
+ public const int MCL_FUTURE;
+ [CCode (cheader_filename = "sys/mman.h")]
+ public int mlockall (int flags);
+ [CCode (cheader_filename = "sys/mman.h")]
+ public int munlockall ();
+}
+#endif
+
+// See https://bugzilla.gnome.org/show_bug.cgi?id=727113
+[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "X11/Xlib.h")]
+namespace X
+{
+ [CCode (cname = "XCreatePixmap")]
+ public int CreatePixmap (X.Display display, X.Drawable d, uint width, uint height, uint depth);
+ [CCode (cname = "XSetWindowBackgroundPixmap")]
+ public int SetWindowBackgroundPixmap (X.Display display, X.Window w, int Pixmap);
+ [CCode (cname = "XClearWindow")]
+ public int ClearWindow (X.Display display, X.Window w);
+ public const int RetainPermanent;
+}
+
+namespace Gtk
+{
+ namespace RGB
+ {
+ // Fixed in Vala 0.24
+ public void to_hsv (double r, double g, double b, out double h, out double s, out double v);
+ }
+}
+
+namespace Gnome
+{
+ [CCode (cheader_filename = "libgnome-desktop/gnome-idle-monitor.h")]
+ public class IdleMonitor : GLib.Object
+ {
+ public IdleMonitor ();
+ public IdleMonitor.for_device (Gdk.Device device);
+ public uint add_idle_watch (uint64 interval_msec, IdleMonitorWatchFunc callback, GLib.DestroyNotify? notify = null);
+ public uint add_user_active_watch (IdleMonitorWatchFunc callback, GLib.DestroyNotify? notify = null);
+ public void remove_watch (uint id);
+ public int64 get_idletime ();
+ }
+
+ public delegate void IdleMonitorWatchFunc (IdleMonitor monitor, uint id);
+}
+
+// Note, fixed in 1.10.0
+namespace LightDM
+{
+ bool greeter_start_session_sync (LightDM.Greeter greeter, string session) throws GLib.Error;
+}
diff --git a/src/flat-button.vala b/src/flat-button.vala
new file mode 100644
index 0000000..3a718d2
--- /dev/null
+++ b/src/flat-button.vala
@@ -0,0 +1,66 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class FlatButton : Gtk.Button
+{
+ private bool did_press;
+
+ construct
+ {
+ UnityGreeter.add_style_class (this);
+ try
+ {
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {-GtkButton-child-displacement-x: 0px;
+ -GtkButton-child-displacement-y: 0px;
+ -GtkWidget-focus-line-width: 1px;
+ }", -1);
+ get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading session chooser style: %s", e.message);
+ }
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ // Make sure we don't react to mouse hovers
+ unset_state_flags (Gtk.StateFlags.PRELIGHT);
+ return base.draw (c);
+ }
+
+ public override void pressed ()
+ {
+ // Do nothing. The normal handler sets priv->button_down which
+ // internally causes draw() to draw a special border and background
+ // that we don't want.
+ did_press = true;
+ }
+
+ public override void released ()
+ {
+ if (did_press)
+ {
+ base.pressed (); // fake an insta-click
+ did_press = false;
+ }
+ base.released ();
+ }
+}
diff --git a/src/greeter-list.vala b/src/greeter-list.vala
new file mode 100644
index 0000000..dee584f
--- /dev/null
+++ b/src/greeter-list.vala
@@ -0,0 +1,949 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ * Scott Sweeny <scott.sweeny@canonical.com>
+ */
+
+private int get_grid_offset (int size)
+{
+ return (int) (size % grid_size) / 2;
+}
+
+[DBus (name="com.canonical.UnityGreeter.List")]
+public class ListDBusInterface : Object
+{
+ private GreeterList list;
+
+ public ListDBusInterface (GreeterList list)
+ {
+ this.list = list;
+ this.list.entry_selected.connect ((name) => {
+ entry_selected (name);
+ });
+ }
+
+ public string get_active_entry ()
+ {
+ string entry = "";
+
+ if (list.selected_entry != null && list.selected_entry.id != null)
+ entry = list.selected_entry.id;
+
+ return entry;
+ }
+
+ public void set_active_entry (string entry_name)
+ {
+ list.set_active_entry (entry_name);
+ }
+
+ public signal void entry_selected (string entry_name);
+}
+
+public abstract class GreeterList : FadableBox
+{
+ public Background background { get; construct; }
+ public MenuBar menubar { get; construct; }
+ public PromptBox? selected_entry { get; private set; default = null; }
+ public bool start_scrolling { get; set; default = true; }
+
+ protected string greeter_authenticating_user;
+
+ protected bool _always_show_manual = false;
+ public bool always_show_manual
+ {
+ get { return _always_show_manual; }
+ set
+ {
+ _always_show_manual = value;
+ if (value)
+ add_manual_entry ();
+ else if (have_entries ())
+ remove_entry ("*other");
+ }
+ }
+
+ protected List<PromptBox> entries = null;
+
+ private ListDBusInterface dbus_object;
+
+ private double scroll_target_location;
+ private double scroll_start_location;
+ private double scroll_location;
+ private double scroll_direction;
+
+ private AnimateTimer scroll_timer;
+
+ private Gtk.Fixed fixed;
+ public DashBox greeter_box;
+ private int cached_box_height = -1;
+
+ protected enum Mode
+ {
+ ENTRY,
+ SCROLLING,
+ }
+ protected Mode mode = Mode.ENTRY;
+
+ public static const int BORDER = 4;
+ public static const int BOX_WIDTH = 8; /* in grid_size blocks */
+ public static const int DEFAULT_BOX_HEIGHT = 3; /* in grid_size blocks */
+
+ private uint n_above = 4;
+ private uint n_below = 4;
+
+ private int box_x
+ {
+ get { return 0; }
+ }
+
+ private int box_y
+ {
+ get
+ {
+ /* First, get grid row number as if menubar weren't there */
+ var row = (MainWindow.MENUBAR_HEIGHT + get_allocated_height ()) / grid_size;
+ row = row - DEFAULT_BOX_HEIGHT; /* and no default dash box */
+ row = row / 2; /* and in the middle */
+ /* Now calculate y pixel spot keeping in mind menubar's allocation */
+ return row * grid_size - MainWindow.MENUBAR_HEIGHT;
+ }
+ }
+
+ public signal void entry_selected (string? name);
+ public signal void entry_displayed_start ();
+ public signal void entry_displayed_done ();
+
+ protected virtual string? get_selected_id ()
+ {
+ if (selected_entry == null)
+ return null;
+ return selected_entry.id;
+ }
+
+ private string? _manual_name = null;
+ public string? manual_name
+ {
+ get { return _manual_name; }
+ set
+ {
+ _manual_name = value;
+ if (find_entry ("*other") != null)
+ add_manual_entry ();
+ }
+ }
+
+ private PromptBox _scrolling_entry = null;
+ private PromptBox scrolling_entry
+ {
+ get { return _scrolling_entry; }
+ set
+ {
+ /* When we swap out a scrolling entry, make sure to hide its
+ * image button, else it will appear in the tab chain. */
+ if (_scrolling_entry != null)
+ _scrolling_entry.set_options_image (null);
+ _scrolling_entry = value;
+ }
+ }
+
+ public GreeterList (Background bg, MenuBar mb)
+ {
+ Object (background: bg, menubar: mb);
+ }
+
+ construct
+ {
+ can_focus = false;
+ visible_window = false;
+
+ fixed = new Gtk.Fixed ();
+ fixed.show ();
+ add (fixed);
+
+ greeter_box = new DashBox (background);
+ greeter_box.notify["base-alpha"].connect (() => { queue_draw (); });
+ greeter_box.show ();
+ greeter_box.size_allocate.connect (greeter_box_size_allocate_cb);
+ add_with_class (greeter_box);
+
+ scroll_timer = new AnimateTimer (AnimateTimer.ease_out_quint, AnimateTimer.FAST);
+ scroll_timer.animate.connect (animate_scrolling);
+
+ try
+ {
+ Bus.get.begin (BusType.SESSION, null, on_bus_acquired);
+ }
+ catch (IOError e)
+ {
+ debug ("Error getting session bus: %s", e.message);
+ }
+ }
+
+ private void on_bus_acquired (Object? obj, AsyncResult res)
+ {
+ try
+ {
+ var conn = Bus.get.end (res);
+ this.dbus_object = new ListDBusInterface (this);
+ conn.register_object ("/list", this.dbus_object);
+ }
+ catch (IOError e)
+ {
+ debug ("Error registering user list dbus object: %s", e.message);
+ }
+ }
+
+ public enum ScrollTarget
+ {
+ START,
+ END,
+ UP,
+ DOWN,
+ }
+
+ public override void get_preferred_width (out int min, out int nat)
+ {
+ min = BOX_WIDTH * grid_size;
+ nat = BOX_WIDTH * grid_size;
+ }
+
+ public override void get_preferred_height (out int min, out int nat)
+ {
+ base.get_preferred_height (out min, out nat);
+ min = 0;
+ }
+
+ public void cancel_authentication ()
+ {
+ UnityGreeter.singleton.cancel_authentication ();
+ entry_selected (selected_entry.id);
+ }
+
+ public void scroll (ScrollTarget target)
+ {
+ if (!sensitive)
+ return;
+
+ switch (target)
+ {
+ case ScrollTarget.START:
+ select_entry (entries.nth_data (0), -1.0);
+ break;
+ case ScrollTarget.END:
+ select_entry (entries.nth_data (entries.length () - 1), 1.0);
+ break;
+ case ScrollTarget.UP:
+ var index = entries.index (selected_entry) - 1;
+ if (index < 0)
+ index = 0;
+ select_entry (entries.nth_data (index), -1.0);
+ break;
+ case ScrollTarget.DOWN:
+ var index = entries.index (selected_entry) + 1;
+ if (index >= (int) entries.length ())
+ index = (int) entries.length () - 1;
+ select_entry (entries.nth_data (index), 1.0);
+ break;
+ }
+ }
+
+ protected void add_with_class (Gtk.Widget widget)
+ {
+ fixed.add (widget);
+ UnityGreeter.add_style_class (widget);
+ }
+
+ protected void redraw_greeter_box ()
+ {
+ Gtk.Allocation allocation;
+ greeter_box.get_allocation (out allocation);
+ queue_draw_area (allocation.x, allocation.y, allocation.width, allocation.height);
+ }
+
+ public void show_message (string text, bool is_error = false)
+ {
+ if (will_clear)
+ {
+ selected_entry.clear ();
+ will_clear = false;
+ }
+
+ selected_entry.add_message (text, is_error);
+ }
+
+ public DashEntry add_prompt (string text, bool secret = false)
+ {
+ if (will_clear)
+ {
+ selected_entry.clear ();
+ will_clear = false;
+ }
+
+ string accessible_text = null;
+ if (selected_entry != null && selected_entry.label != null)
+ accessible_text = _("Enter password for %s").printf (selected_entry.label);
+ var prompt = selected_entry.add_prompt (text, accessible_text, secret);
+
+ if (mode != Mode.SCROLLING)
+ selected_entry.show_prompts ();
+
+ focus_prompt ();
+ redraw_greeter_box ();
+
+ return prompt;
+ }
+
+ public Gtk.ComboBox add_combo (GenericArray<string> texts, bool read_only)
+ {
+ if (will_clear)
+ {
+ selected_entry.clear ();
+ will_clear = false;
+ }
+
+ var combo = selected_entry.add_combo (texts, read_only);
+
+ focus_prompt ();
+ redraw_greeter_box ();
+
+ return combo;
+ }
+
+ public override void grab_focus ()
+ {
+ focus_prompt ();
+ }
+
+ public virtual void focus_prompt ()
+ {
+ selected_entry.sensitive = true;
+ selected_entry.grab_focus ();
+ }
+
+ public abstract void show_authenticated (bool successful = true);
+
+ protected PromptBox? find_entry (string id)
+ {
+ foreach (var entry in entries)
+ {
+ if (entry.id == id)
+ return entry;
+ }
+
+ return null;
+ }
+
+ protected static int compare_entry (PromptBox a, PromptBox b)
+ {
+ if (a.id.has_prefix ("*") || b.id.has_prefix ("*"))
+ {
+ /* Special entries go after normal ones */
+ if (!a.id.has_prefix ("*"))
+ return -1;
+ if (!b.id.has_prefix ("*"))
+ return 1;
+
+ /* Manual comes before guest */
+ if (a.id == "*other")
+ return -1;
+ if (a.id == "*guest")
+ return 1;
+ }
+
+ /* Alphabetical by label */
+ return a.label.ascii_casecmp (b.label);
+ }
+
+ protected bool have_entries ()
+ {
+ foreach (var e in entries)
+ {
+ if (e.id != "*other")
+ return true;
+ }
+ return false;
+ }
+
+ protected virtual void insert_entry (PromptBox entry)
+ {
+ entries.insert_sorted (entry, compare_entry);
+ }
+
+ protected abstract void add_manual_entry ();
+
+ protected void add_entry (PromptBox entry)
+ {
+ entry.expand = true;
+ entry.set_size_request (grid_size * BOX_WIDTH - BORDER * 2, -1);
+ add_with_class (entry);
+
+ insert_entry (entry);
+
+ entry.name_clicked.connect (entry_clicked_cb);
+
+ if (selected_entry == null)
+ select_entry (entry, 1.0);
+ else
+ select_entry (selected_entry, 1.0);
+
+ move_names ();
+ }
+
+ public void set_active_entry (string ?name)
+ {
+ var e = find_entry (name);
+ if (e != null)
+ {
+ var direction = 1.0;
+ if (selected_entry != null &&
+ entries.index (selected_entry) > entries.index (e))
+ {
+ direction = -1.0;
+ }
+ select_entry (e, direction);
+ }
+ }
+
+ public void set_active_first_entry_with_prefix (string prefix)
+ {
+ foreach (var e in entries)
+ {
+ if (e.id.has_prefix (prefix))
+ {
+ select_entry (e, 1.0);
+ break;
+ }
+ }
+ }
+
+ public void remove_entry (string? name)
+ {
+ remove_entry_by_entry (find_entry (name));
+ }
+
+ public void remove_entries_with_prefix (string prefix)
+ {
+ int i = 0;
+ while (i < entries.length ())
+ {
+ PromptBox e = entries.nth_data (i);
+ if (e.id.has_prefix (prefix))
+ remove_entry_by_entry (e);
+ else
+ i++;
+ }
+ }
+
+ public void remove_entry_by_entry (PromptBox? entry)
+ {
+ if (entry == null)
+ return;
+
+ var index = entries.index (entry);
+ entry.destroy ();
+ entries.remove (entry);
+
+ /* Select another entry if the selected one was removed */
+ if (entry == selected_entry)
+ {
+ if (index >= entries.length () && index > 0)
+ index--;
+ else if (index < entries.length ())
+ index++;
+
+ if (entries.nth_data (index) != null)
+ select_entry (entries.nth_data (index), -1.0);
+ }
+
+ /* Show a manual login if no users and no remote login entry */
+ if (!have_entries () && !UnityGreeter.singleton.show_remote_login_hint ())
+ add_manual_entry ();
+
+ queue_draw ();
+ }
+
+ protected int get_greeter_box_height ()
+ {
+ int height;
+ greeter_box.get_preferred_height (null, out height);
+ return height;
+ }
+
+ protected int get_greeter_box_height_grids ()
+ {
+ int height = get_greeter_box_height ();
+ return height / grid_size + 1; /* +1 because we'll be slightly under due to BORDER */
+ }
+
+ protected int get_greeter_box_x ()
+ {
+ return box_x + BORDER;
+ }
+
+ protected int get_greeter_box_y ()
+ {
+ return box_y + BORDER;
+ }
+
+ protected virtual int get_position_y (double position)
+ {
+ // Most position heights are just the grid height. Except for the
+ // greeter box itself.
+ int box_height = get_greeter_box_height_grids () * grid_size;
+ double offset;
+
+ if (position < 0)
+ offset = position * grid_size;
+ else if (position < 1)
+ offset = position * box_height;
+ else
+ offset = (position - 1) * grid_size + box_height;
+
+ return box_y + (int)Math.round(offset);
+ }
+
+ private void move_entry (PromptBox entry, double position)
+ {
+ var alpha = 1.0;
+ if (position < 0)
+ alpha = 1.0 + position / (n_above + 1);
+ else
+ alpha = 1.0 - position / (n_below + 1);
+ entry.set_alpha (alpha);
+
+ /* Some entry types may care where they are (e.g. wifi prompt) */
+ entry.position = position;
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ var child_allocation = Gtk.Allocation ();
+ child_allocation.width = grid_size * BOX_WIDTH - BORDER * 2;
+ entry.get_preferred_height_for_width (child_allocation.width, null, out child_allocation.height);
+ child_allocation.x = allocation.x + get_greeter_box_x ();
+ child_allocation.y = allocation.y + get_position_y (position);
+ fixed.move (entry, child_allocation.x, child_allocation.y);
+ entry.size_allocate (child_allocation);
+ }
+
+ public void greeter_box_size_allocate_cb (Gtk.Allocation allocation)
+ {
+ /* If the greeter box allocation changes while not moving fix the entries position */
+ if (scrolling_entry == null && allocation.height != cached_box_height)
+ {
+ /* We run in idle because it's kind of a recursive loop and
+ * ends up positioning the entries in the wrong place if we try
+ * to do it during an existing allocation. */
+ Idle.add (() => { move_names (); return false; });
+ }
+ cached_box_height = allocation.height;
+ }
+
+ public void move_names ()
+ {
+ var index = 0;
+ foreach (var entry in entries)
+ {
+ var position = index - scroll_location;
+
+ /* Draw entries above, in and below the box */
+ if (position > -1 * (int)(n_above + 1) && position < n_below + 1)
+ {
+ move_entry (entry, position);
+ // Sometimes we will be overlayed by another widget like the
+ // session chooser. In such cases, don't try to show ourselves
+ var is_hidden = (position == 0 && greeter_box.has_base &&
+ greeter_box.base_alpha == 0.0);
+ if (!is_hidden)
+ entry.show ();
+ }
+ else
+ entry.hide ();
+
+ index++;
+ }
+ queue_draw ();
+ }
+
+ private void animate_scrolling (double progress)
+ {
+ /* Total height of list */
+ var h = entries.length ();
+
+ /* How far we have to go in total, either up or down with wrapping */
+ var distance = scroll_target_location - scroll_start_location;
+ if (scroll_direction * distance < 0)
+ distance += scroll_direction * h;
+
+ /* How far we've gone so far */
+ distance *= progress;
+
+ /* Go that far and wrap around */
+ scroll_location = scroll_start_location + distance;
+ if (scroll_location > h)
+ scroll_location -= h;
+ if (scroll_location < 0)
+ scroll_location += h;
+
+ move_names ();
+
+ if (progress >= 0.975 && !greeter_box.has_base)
+ {
+ setup_prompt_box ();
+ entry_displayed_start ();
+ }
+
+ /* Stop when we get there */
+ if (progress >= 1.0)
+ finished_scrolling ();
+ }
+
+ private void finished_scrolling ()
+ {
+ scrolling_entry = null;
+ selected_entry.show_prompts (); /* set prompts to be visible immediately */
+ focus_prompt ();
+ entry_displayed_done ();
+ mode = Mode.ENTRY;
+ }
+
+ protected void select_entry (PromptBox entry, double direction, bool do_scroll = true)
+ {
+ if (!get_realized ())
+ {
+ /* Just note it for the future if we haven't been realized yet */
+ selected_entry = entry;
+ return;
+ }
+
+ if (scroll_target_location != entries.index (entry))
+ {
+ var new_target = entries.index (entry);
+ var new_direction = direction;
+ var new_start = scroll_location;
+
+ if (scroll_location != new_target && do_scroll)
+ {
+ var new_distance = new_direction * (new_target - new_start);
+ /* Base rate is 350 (250 + 100). If we find ourselves going further, slow down animation */
+ scroll_timer.reset (250 + int.min ((int)(100 * (Math.fabs (new_distance))), 500));
+
+ mode = Mode.SCROLLING;
+ }
+
+ scrolling_entry = selected_entry;
+ scroll_target_location = new_target;
+ scroll_direction = new_direction;
+ scroll_start_location = new_start;
+ }
+
+ if (selected_entry != entry)
+ {
+ greeter_box.set_base (null);
+ if (selected_entry != null)
+ selected_entry.clear ();
+
+ selected_entry = entry;
+ entry_selected (selected_entry.id);
+
+ if (mode == Mode.ENTRY)
+ {
+ /* don't need to move, but make sure we trigger the same side effects */
+ setup_prompt_box ();
+ scroll_timer.reset (0);
+ }
+ }
+ }
+
+ protected virtual void setup_prompt_box (bool fade = true)
+ {
+ greeter_box.set_base (selected_entry);
+ selected_entry.add_static_prompts ();
+ if (fade)
+ selected_entry.fade_in_prompts ();
+ else
+ selected_entry.show_prompts ();
+ }
+
+ public override void realize ()
+ {
+ base.realize ();
+
+ /* NOTE: This is going to cause the entry_selected signal to be emitted even if selected_entry has not changed */
+ var saved_entry = selected_entry;
+ selected_entry = null;
+ select_entry (saved_entry, 1, start_scrolling);
+ move_names ();
+ }
+
+ private void allocate_greeter_box ()
+ {
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ var child_allocation = Gtk.Allocation ();
+ greeter_box.get_preferred_width (null, out child_allocation.width);
+ greeter_box.get_preferred_height (null, out child_allocation.height);
+ child_allocation.x = allocation.x + get_greeter_box_x ();
+ child_allocation.y = allocation.y + get_greeter_box_y ();
+ fixed.move (greeter_box, child_allocation.x, child_allocation.y);
+ greeter_box.size_allocate (child_allocation);
+
+ foreach (var entry in entries)
+ {
+ entry.set_zone (greeter_box);
+ }
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+
+ if (!get_realized ())
+ return;
+
+ allocate_greeter_box ();
+ move_names ();
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ c.push_group ();
+
+ c.save ();
+ fixed.propagate_draw (greeter_box, c); /* Always full alpha */
+ c.restore ();
+
+ if (greeter_box.base_alpha != 0.0)
+ {
+ c.save ();
+ c.push_group ();
+
+ c.rectangle (get_greeter_box_x (), get_greeter_box_y () - n_above * grid_size, grid_size * BOX_WIDTH - BORDER * 2, grid_size * (n_above + n_below + get_greeter_box_height_grids ()));
+ c.clip ();
+
+ foreach (var child in fixed.get_children ())
+ {
+ if (child != greeter_box)
+ fixed.propagate_draw (child, c);
+ }
+
+ c.pop_group_to_source ();
+ c.paint_with_alpha (greeter_box.base_alpha);
+ c.restore ();
+ }
+
+ c.pop_group_to_source ();
+ c.paint_with_alpha (fade_tracker.alpha);
+
+ return false;
+ }
+
+ private void entry_clicked_cb (PromptBox entry)
+ {
+ if (mode != Mode.ENTRY)
+ return;
+
+ var index = entries.index (entry);
+ var position = index - scroll_location;
+
+ if (position < 0.0)
+ select_entry (entry, -1.0);
+ else if (position >= 1.0)
+ select_entry (entry, 1.0);
+ }
+
+
+ /* Not all subclasses are going to be interested in talking to lightdm, but for those that are, make it easy. */
+
+ protected bool will_clear = false;
+ protected bool prompted = false;
+ protected bool unacknowledged_messages = false;
+
+ protected void connect_to_lightdm ()
+ {
+ UnityGreeter.singleton.show_message.connect (show_message_cb);
+ UnityGreeter.singleton.show_prompt.connect (show_prompt_cb);
+ UnityGreeter.singleton.authentication_complete.connect (authentication_complete_cb);
+ }
+
+ protected void show_message_cb (string text, LightDM.MessageType type)
+ {
+ unacknowledged_messages = true;
+ show_message (text, type == LightDM.MessageType.ERROR);
+ }
+
+ protected virtual void show_prompt_cb (string text, LightDM.PromptType type)
+ {
+ /* Notify the greeter on what user has been logged */
+ if (get_selected_id () == "*other" && manual_name == null)
+ {
+ if (UnityGreeter.singleton.test_mode)
+ manual_name = test_username;
+ else
+ manual_name = UnityGreeter.singleton.authentication_user();
+ }
+
+ prompted = true;
+ if (text == "Password: ")
+ text = _("Password:");
+ if (text == "login:")
+ text = _("Username:");
+ add_prompt (text, type == LightDM.PromptType.SECRET);
+ }
+
+ protected virtual void authentication_complete_cb ()
+ {
+ /* Not the best of the solutions but seems the asynchrony
+ * when talking to lightdm process means that you can
+ * go to the "Guest" account, start authenticating as guest
+ * keep moving down to some of the remote servers
+ * and the answer will come after that, and even calling
+ * greeter.cancel_authentication won't help
+ * so basically i'm just ignoring any authentication callback
+ * if we are not in the same place in the list as we were
+ * when we called greeter.authenticate* */
+ if (greeter_authenticating_user != selected_entry.id)
+ return;
+
+ bool is_authenticated;
+ if (UnityGreeter.singleton.test_mode)
+ is_authenticated = test_is_authenticated;
+ else
+ is_authenticated = UnityGreeter.singleton.is_authenticated();
+
+ if (is_authenticated)
+ {
+ /* Login immediately if prompted and user has acknowledged all messages */
+ if (prompted && !unacknowledged_messages)
+ {
+ login_complete ();
+ if (UnityGreeter.singleton.test_mode)
+ start_session ();
+ else
+ {
+ if (background.alpha == 1.0)
+ start_session ();
+ else
+ background.notify["alpha"].connect (background_loaded_cb);
+ }
+ }
+ else
+ {
+ prompted = true;
+ show_authenticated ();
+ }
+ }
+ else
+ {
+ if (prompted)
+ {
+ /* Show an error if one wasn't provided */
+ if (will_clear)
+ show_message (_("Invalid password, please try again"), true);
+
+ selected_entry.reset_spinners ();
+
+ /* Restart authentication */
+ start_authentication ();
+ }
+ else
+ {
+ /* Show an error if one wasn't provided */
+ if (!selected_entry.has_errors)
+ show_message (_("Failed to authenticate"), true);
+
+ /* Stop authentication */
+ show_authenticated (false);
+ }
+ }
+ }
+
+ protected virtual void start_authentication ()
+ {
+ prompted = false;
+ unacknowledged_messages = false;
+
+ /* Reset manual username */
+ manual_name = null;
+
+ will_clear = false;
+
+ greeter_authenticating_user = get_selected_id ();
+
+ if (UnityGreeter.singleton.test_mode)
+ test_start_authentication ();
+ else
+ {
+ if (get_selected_id () == "*other")
+ UnityGreeter.singleton.authenticate ();
+ else if (get_selected_id () == "*guest")
+ UnityGreeter.singleton.authenticate_as_guest ();
+ else
+ UnityGreeter.singleton.authenticate (get_selected_id ());
+ }
+ }
+
+ private void background_loaded_cb (ParamSpec pspec)
+ {
+ if (background.alpha == 1.0)
+ {
+ background.notify["alpha"].disconnect (background_loaded_cb);
+ start_session ();
+ }
+ }
+
+ private void start_session ()
+ {
+ if (!UnityGreeter.singleton.start_session (get_lightdm_session (), background))
+ {
+ show_message (_("Failed to start session"), true);
+ start_authentication ();
+ return;
+ }
+
+ /* Set the background */
+ background.draw_grid = false;
+ background.queue_draw ();
+ }
+
+ public void login_complete ()
+ {
+ sensitive = false;
+
+ selected_entry.clear ();
+ selected_entry.add_message (_("Logging in…"), false);
+
+ redraw_greeter_box ();
+ }
+
+ protected virtual string get_lightdm_session ()
+ {
+ return "ubuntu";
+ }
+
+ /* Testing code below this */
+
+ protected string? test_username = null;
+ protected bool test_is_authenticated = false;
+
+ protected virtual void test_start_authentication ()
+ {
+ }
+}
diff --git a/src/indicator.vapi b/src/indicator.vapi
new file mode 100644
index 0000000..9b28c72
--- /dev/null
+++ b/src/indicator.vapi
@@ -0,0 +1,165 @@
+[CCode (cprefix = "Indicator", lower_case_cprefix = "indicator_")]
+namespace Indicator {
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public class DesktopShortcuts : GLib.Object {
+ [CCode (has_construct_function = false)]
+ public DesktopShortcuts (string file, string identity);
+ public unowned string get_nicks ();
+ public bool nick_exec (string nick);
+ public unowned string nick_get_name (string nick);
+ public string desktop_file { construct; }
+ [NoAccessorMethod]
+ public string identity { owned get; construct; }
+ }
+ [CCode (cheader_filename = "libindicator/indicator-object.h")]
+ public class Object : GLib.Object {
+ [CCode (has_construct_function = false)]
+ protected Object ();
+ public bool check_environment (string env);
+ [NoWrapper]
+ public virtual void entry_activate (Indicator.ObjectEntry entry, uint timestamp);
+ [NoWrapper]
+ public virtual void entry_close (Indicator.ObjectEntry entry, uint timestamp);
+ [CCode (has_construct_function = false)]
+ public Object.from_file (string file);
+ [NoWrapper]
+ public virtual unowned string get_accessible_desc ();
+ public virtual GLib.List<unowned ObjectEntry> get_entries ();
+ public unowned string[] get_environment ();
+ [NoWrapper]
+ public virtual unowned Gtk.Image get_image ();
+ [NoWrapper]
+ public virtual unowned Gtk.Label get_label ();
+ public virtual uint get_location (Indicator.ObjectEntry entry);
+ [NoWrapper]
+ public virtual unowned Gtk.Menu get_menu ();
+ [NoWrapper]
+ public virtual unowned string get_name_hint ();
+ public virtual bool get_show_now (Indicator.ObjectEntry entry);
+ public virtual int get_position ();
+ [NoWrapper]
+ public virtual void reserved1 ();
+ [NoWrapper]
+ public virtual void reserved2 ();
+ [NoWrapper]
+ public virtual void reserved3 ();
+ [NoWrapper]
+ public virtual void reserved4 ();
+ [NoWrapper]
+ public virtual void reserved5 ();
+ public void set_environment (string[] env);
+ public virtual signal void accessible_desc_update (Indicator.ObjectEntry entry);
+ public virtual signal void entry_added (Indicator.ObjectEntry entry);
+ public virtual signal void entry_moved (Indicator.ObjectEntry entry, uint old_pos, uint new_pos);
+ public virtual signal void entry_removed (Indicator.ObjectEntry entry);
+ public virtual signal void entry_scrolled (Indicator.ObjectEntry entry, uint delta, Indicator.ScrollDirection direction);
+ public virtual signal void menu_show (Indicator.ObjectEntry entry, uint timestamp);
+ public virtual signal void show_now_changed (Indicator.ObjectEntry entry, bool show_now_state);
+ }
+ [CCode (cheader_filename = "libindicator/indicator-ng.h")]
+ public class Ng : Object {
+ [CCode (has_construct_function = false)]
+ public Ng.for_profile (string filename, string profile) throws GLib.Error;
+ }
+ [Compact]
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public class ObjectEntry {
+ public weak string accessible_desc;
+ public weak Gtk.Image image;
+ public weak Gtk.Label label;
+ public weak Gtk.Menu menu;
+ public weak string name_hint;
+ public weak GLib.Callback reserved1;
+ public weak GLib.Callback reserved2;
+ public weak GLib.Callback reserved3;
+ public weak GLib.Callback reserved4;
+ public static void activate (Indicator.Object io, Indicator.ObjectEntry entry, uint timestamp);
+ public static void close (Indicator.Object io, Indicator.ObjectEntry entry, uint timestamp);
+ }
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public class Service : GLib.Object {
+ [CCode (has_construct_function = false)]
+ public Service (string name);
+ [NoWrapper]
+ public virtual void indicator_service_reserved1 ();
+ [NoWrapper]
+ public virtual void indicator_service_reserved2 ();
+ [NoWrapper]
+ public virtual void indicator_service_reserved3 ();
+ [NoWrapper]
+ public virtual void indicator_service_reserved4 ();
+ [CCode (has_construct_function = false)]
+ public Service.version (string name, uint version);
+ [NoAccessorMethod]
+ public string name { owned get; set; }
+ public virtual signal void shutdown ();
+ }
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public class ServiceManager : GLib.Object {
+ [CCode (has_construct_function = false)]
+ public ServiceManager (string dbus_name);
+ public bool connected ();
+ [NoWrapper]
+ public virtual void indicator_service_manager_reserved1 ();
+ [NoWrapper]
+ public virtual void indicator_service_manager_reserved2 ();
+ [NoWrapper]
+ public virtual void indicator_service_manager_reserved3 ();
+ [NoWrapper]
+ public virtual void indicator_service_manager_reserved4 ();
+ public void set_refresh (uint time_in_ms);
+ [CCode (has_construct_function = false)]
+ public ServiceManager.version (string dbus_name, uint version);
+ [NoAccessorMethod]
+ public string name { owned get; set; }
+ public virtual signal void connection_change (bool connected);
+ }
+ [CCode (cprefix = "INDICATOR_OBJECT_SCROLL_", has_type_id = false, cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public enum ScrollDirection {
+ UP,
+ DOWN,
+ LEFT,
+ RIGHT
+ }
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h", has_target = false)]
+ public delegate GLib.Type get_type_t ();
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h", has_target = false)]
+ public delegate unowned string get_version_t ();
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string GET_TYPE_S;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string GET_VERSION_S;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_ENTRY_ADDED;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_ENTRY_MOVED;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_ENTRY_REMOVED;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_ENTRY_SCROLLED;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_MENU_SHOW;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string OBJECT_SIGNAL_SHOW_NOW_CHANGED;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string SERVICE_SIGNAL_SHUTDOWN;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const int SET_VERSION;
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public const string VERSION;
+ [CCode (cname = "get_version", cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public static unowned string get_version ();
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public static unowned Gtk.Image image_helper (string name);
+ [CCode (cheader_filename = "gtk/gtk.h,libindicator/indicator.h,libindicator/indicator-desktop-shortcuts.h,libindicator/indicator-image-helper.h,libindicator/indicator-object.h,libindicator/indicator-service.h,libindicator/indicator-service-manager.h")]
+ public static void image_helper_update (Gtk.Image image, string name);
+}
+
+[CCode (cheader_filename="libido/libido.h", lower_case_cprefix = "ido_")]
+namespace Ido {
+ public void init ();
+}
diff --git a/src/list-stack.vala b/src/list-stack.vala
new file mode 100644
index 0000000..cd9745d
--- /dev/null
+++ b/src/list-stack.vala
@@ -0,0 +1,92 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class ListStack : Gtk.Fixed
+{
+ public uint num_children
+ {
+ get
+ {
+ var children = get_children ();
+ return children.length ();
+ }
+ }
+
+ private int width;
+
+ construct
+ {
+ width = grid_size * GreeterList.BOX_WIDTH;
+ }
+
+ public GreeterList? top ()
+ {
+ var children = get_children ();
+ if (children == null)
+ return null;
+ else
+ return children.last ().data as GreeterList;
+ }
+
+ public void push (GreeterList pushed)
+ {
+ return_if_fail (pushed != null);
+
+ var children = get_children ();
+
+ pushed.start_scrolling = false;
+ pushed.set_size_request (width, -1);
+ add (pushed);
+
+ if (children != null)
+ {
+ var current = children.last ().data as GreeterList;
+ /* Clear any errors so when we come back, they will be gone. */
+ current.selected_entry.reset_state ();
+ current.greeter_box.push (pushed);
+ }
+ }
+
+ public void pop ()
+ {
+ var children = get_children ();
+
+ return_if_fail (children != null);
+
+ unowned List<Gtk.Widget> prev = children.last ().prev;
+ if (prev != null)
+ (prev.data as GreeterList).greeter_box.pop ();
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+ var children = get_children ();
+ foreach (var child in children)
+ {
+ child.size_allocate (allocation);
+ }
+ }
+
+ public override void get_preferred_width (out int min, out int nat)
+ {
+ min = width;
+ nat = width;
+ }
+}
diff --git a/src/logo-generator.vala b/src/logo-generator.vala
new file mode 100644
index 0000000..244c4b3
--- /dev/null
+++ b/src/logo-generator.vala
@@ -0,0 +1,46 @@
+public class Main : Object
+{
+
+ private static string? file = null;
+ private static string? text = null;
+ private static string? result = null;
+ private const OptionEntry[] options = {
+ {"logo", 0, 0, OptionArg.FILENAME, ref file, "Path to logo", "LOGO"},
+ {"text", 0, 0, OptionArg.STRING, ref text, "Sublogo text", "TEXT"},
+ {"output", 0, 0, OptionArg.FILENAME, ref result, "Path to rendered output", "OUTPUT"},
+ {null}
+ };
+
+ public static int main(string[] args) {
+ try {
+ var opt_context = new OptionContext ("- OptionContext example");
+ opt_context.set_help_enabled (true);
+ opt_context.add_main_entries (options, null);
+ opt_context.parse (ref args);
+ } catch (OptionError e) {
+ stdout.printf ("error: %s\n", e.message);
+ stdout.printf ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
+ return 0;
+ }
+ Cairo.ImageSurface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, 245, 43);
+ Cairo.Context context = new Cairo.Context (surface);
+ context.translate (42, 11);
+ Cairo.ImageSurface logo = new Cairo.ImageSurface.from_png (file);
+ context.set_source_surface (logo, 0, 0);
+ context.paint();
+
+ context.set_source_rgba (1, 1, 1, 1);
+ context.translate (logo.get_width() + 0.25*logo.get_height(), logo.get_height());
+
+ var font_description = new Pango.FontDescription();
+ font_description.set_family("Ubuntu");
+ font_description.set_size((int)(0.75*logo.get_height() * Pango.SCALE));
+ var layout = Pango.cairo_create_layout (context);
+ layout.set_font_description (font_description);
+ layout.set_text (text, -1);
+ Pango.cairo_show_layout_line(context, layout.get_line(0));
+
+ surface.write_to_png(result);
+ return 0;
+ }
+}
diff --git a/src/main-window.vala b/src/main-window.vala
new file mode 100644
index 0000000..7574719
--- /dev/null
+++ b/src/main-window.vala
@@ -0,0 +1,398 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+public class MainWindow : Gtk.Window
+{
+ public MenuBar menubar;
+
+ private List<Monitor> monitors;
+ private Monitor? primary_monitor;
+ private Monitor active_monitor;
+ private Background background;
+ private Gtk.Box login_box;
+ private Gtk.Box hbox;
+ private Gtk.Button back_button;
+ private ShutdownDialog? shutdown_dialog = null;
+
+ public ListStack stack;
+
+ // Menubar is smaller, but with shadow, we reserve more space
+ public static const int MENUBAR_HEIGHT = 32;
+
+ construct
+ {
+ events |= Gdk.EventMask.POINTER_MOTION_MASK;
+
+ var accel_group = new Gtk.AccelGroup ();
+ add_accel_group (accel_group);
+
+ var bg_color = Gdk.RGBA ();
+ bg_color.parse (UGSettings.get_string (UGSettings.KEY_BACKGROUND_COLOR));
+ override_background_color (Gtk.StateFlags.NORMAL, bg_color);
+ get_accessible ().set_name (_("Login Screen"));
+ has_resize_grip = false;
+ UnityGreeter.add_style_class (this);
+
+ realize ();
+ background = new Background (Gdk.cairo_create (get_window ()).get_target ());
+ background.draw_grid = UGSettings.get_boolean (UGSettings.KEY_DRAW_GRID);
+ background.default_background = UGSettings.get_string (UGSettings.KEY_BACKGROUND);
+ background.set_logo (UGSettings.get_string (UGSettings.KEY_LOGO), UGSettings.get_string (UGSettings.KEY_BACKGROUND_LOGO));
+ background.show ();
+ add (background);
+ UnityGreeter.add_style_class (background);
+
+ login_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ login_box.show ();
+ background.add (login_box);
+
+ /* Box for menubar shadow */
+ var menubox = new Gtk.EventBox ();
+ var menualign = new Gtk.Alignment (0.0f, 0.0f, 1.0f, 0.0f);
+ var shadow_path = Path.build_filename (Config.PKGDATADIR,
+ "shadow.png", null);
+ var shadow_style = "";
+ if (FileUtils.test (shadow_path, FileTest.EXISTS))
+ {
+ shadow_style = "background-image: url('%s');
+ background-repeat: repeat;".printf(shadow_path);
+ }
+ try
+ {
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {background-color: transparent;
+ %s
+ }".printf(shadow_style), -1);
+ var context = menubox.get_style_context ();
+ context.add_provider (style,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading menubox style: %s", e.message);
+ }
+ menubox.set_size_request (-1, MENUBAR_HEIGHT);
+ menubox.show ();
+ menualign.show ();
+ menubox.add (menualign);
+ login_box.add (menubox);
+ UnityGreeter.add_style_class (menualign);
+ UnityGreeter.add_style_class (menubox);
+
+ menubar = new MenuBar (background, accel_group);
+ menubar.show ();
+ menualign.add (menubar);
+ UnityGreeter.add_style_class (menubar);
+
+ hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ hbox.expand = true;
+ hbox.show ();
+ login_box.add (hbox);
+
+ var align = new Gtk.Alignment (0.5f, 0.5f, 0.0f, 0.0f);
+ align.set_size_request (grid_size, -1);
+ align.margin_bottom = MENUBAR_HEIGHT; /* offset for menubar at top */
+ align.show ();
+ hbox.add (align);
+
+ back_button = new FlatButton ();
+ back_button.get_accessible ().set_name (_("Back"));
+ back_button.focus_on_click = false;
+ var image = new Gtk.Image.from_file (Path.build_filename (Config.PKGDATADIR, "arrow_left.png", null));
+ image.show ();
+ back_button.set_size_request (grid_size - GreeterList.BORDER * 2, grid_size - GreeterList.BORDER * 2);
+ back_button.add (image);
+ back_button.clicked.connect (pop_list);
+ align.add (back_button);
+
+ align = new Gtk.Alignment (0.0f, 0.5f, 0.0f, 1.0f);
+ align.show ();
+ hbox.add (align);
+
+ stack = new ListStack ();
+ stack.show ();
+ align.add (stack);
+
+ add_user_list ();
+
+ if (UnityGreeter.singleton.test_mode)
+ {
+ /* Simulate an 800x600 monitor to the left of a 640x480 monitor */
+ monitors = new List<Monitor> ();
+ monitors.append (new Monitor (0, 0, 800, 600));
+ monitors.append (new Monitor (800, 120, 640, 480));
+ background.set_monitors (monitors);
+ move_to_monitor (monitors.nth_data (0));
+ resize (800 + 640, 600);
+ }
+ else
+ {
+ var screen = get_screen ();
+ screen.monitors_changed.connect (monitors_changed_cb);
+ monitors_changed_cb (screen);
+ }
+ }
+
+ public void push_list (GreeterList widget)
+ {
+ stack.push (widget);
+
+ if (stack.num_children > 1)
+ back_button.show ();
+ }
+
+ public void pop_list ()
+ {
+ if (stack.num_children <= 2)
+ back_button.hide ();
+
+ stack.pop ();
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+
+ if (hbox != null)
+ {
+ hbox.margin_left = get_grid_offset (get_allocated_width ()) + grid_size;
+ hbox.margin_right = get_grid_offset (get_allocated_width ());
+ hbox.margin_top = get_grid_offset (get_allocated_height ());
+ hbox.margin_bottom = get_grid_offset (get_allocated_height ());
+ }
+ }
+
+ private void monitors_changed_cb (Gdk.Screen screen)
+ {
+ int primary = screen.get_primary_monitor ();
+ debug ("Screen is %dx%d pixels", screen.get_width (), screen.get_height ());
+ monitors = new List<Monitor> ();
+ primary_monitor = null;
+
+ for (var i = 0; i < screen.get_n_monitors (); i++)
+ {
+ Gdk.Rectangle geometry;
+ screen.get_monitor_geometry (i, out geometry);
+ debug ("Monitor %d is %dx%d pixels at %d,%d", i, geometry.width, geometry.height, geometry.x, geometry.y);
+
+ if (monitor_is_unique_position (screen, i))
+ {
+ var monitor = new Monitor (geometry.x, geometry.y, geometry.width, geometry.height);
+ monitors.append (monitor);
+
+ if (primary_monitor == null || i == primary)
+ primary_monitor = monitor;
+ }
+ }
+
+ background.set_monitors (monitors);
+ resize (screen.get_width (), screen.get_height ());
+ move (0, 0);
+ move_to_monitor (primary_monitor);
+ }
+
+ /* Check if a monitor has a unique position */
+ private bool monitor_is_unique_position (Gdk.Screen screen, int n)
+ {
+ Gdk.Rectangle g0;
+ screen.get_monitor_geometry (n, out g0);
+
+ for (var i = n + 1; i < screen.get_n_monitors (); i++)
+ {
+ Gdk.Rectangle g1;
+ screen.get_monitor_geometry (i, out g1);
+
+ if (g0.x == g1.x && g0.y == g1.y)
+ return false;
+ }
+
+ return true;
+ }
+
+ public override bool motion_notify_event (Gdk.EventMotion event)
+ {
+ var x = (int) (event.x + 0.5);
+ var y = (int) (event.y + 0.5);
+
+ /* Get motion event relative to this widget */
+ if (event.window != get_window ())
+ {
+ int w_x, w_y;
+ get_window ().get_origin (out w_x, out w_y);
+ x -= w_x;
+ y -= w_y;
+ event.window.get_origin (out w_x, out w_y);
+ x += w_x;
+ y += w_y;
+ }
+
+ foreach (var m in monitors)
+ {
+ if (x >= m.x && x <= m.x + m.width && y >= m.y && y <= m.y + m.height)
+ {
+ move_to_monitor (m);
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private void move_to_monitor (Monitor monitor)
+ {
+ active_monitor = monitor;
+ login_box.set_size_request (monitor.width, monitor.height);
+ background.set_active_monitor (monitor);
+ background.move (login_box, monitor.x, monitor.y);
+
+ if (shutdown_dialog != null)
+ {
+ shutdown_dialog.set_active_monitor (monitor);
+ background.move (shutdown_dialog, monitor.x, monitor.y);
+ }
+ }
+
+ private void add_user_list ()
+ {
+ GreeterList greeter_list;
+ greeter_list = new UserList (background, menubar);
+ greeter_list.show ();
+ UnityGreeter.add_style_class (greeter_list);
+ push_list (greeter_list);
+ }
+
+ public override bool key_press_event (Gdk.EventKey event)
+ {
+ var top = stack.top ();
+
+ if (stack.top () is UserList)
+ {
+ var user_list = stack.top () as UserList;
+ if (!user_list.show_hidden_users)
+ {
+ var shift_mask = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK;
+ var control_mask = Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.MOD1_MASK;
+ var alt_mask = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK;
+ if (((event.keyval == Gdk.Key.Shift_L || event.keyval == Gdk.Key.Shift_R) && (event.state & shift_mask) == shift_mask) ||
+ ((event.keyval == Gdk.Key.Control_L || event.keyval == Gdk.Key.Control_R) && (event.state & control_mask) == control_mask) ||
+ ((event.keyval == Gdk.Key.Alt_L || event.keyval == Gdk.Key.Alt_R) && (event.state & alt_mask) == alt_mask))
+ {
+ debug ("Hidden user key combination detected");
+ user_list.show_hidden_users = true;
+ return true;
+ }
+ }
+ }
+
+ switch (event.keyval)
+ {
+ case Gdk.Key.Escape:
+ if (login_box.sensitive)
+ top.cancel_authentication ();
+ if (shutdown_dialog != null)
+ shutdown_dialog.cancel ();
+ return true;
+ case Gdk.Key.Page_Up:
+ case Gdk.Key.KP_Page_Up:
+ if (login_box.sensitive)
+ top.scroll (GreeterList.ScrollTarget.START);
+ return true;
+ case Gdk.Key.Page_Down:
+ case Gdk.Key.KP_Page_Down:
+ if (login_box.sensitive)
+ top.scroll (GreeterList.ScrollTarget.END);
+ return true;
+ case Gdk.Key.Up:
+ case Gdk.Key.KP_Up:
+ if (login_box.sensitive)
+ top.scroll (GreeterList.ScrollTarget.UP);
+ return true;
+ case Gdk.Key.Down:
+ case Gdk.Key.KP_Down:
+ if (login_box.sensitive)
+ top.scroll (GreeterList.ScrollTarget.DOWN);
+ return true;
+ case Gdk.Key.Left:
+ case Gdk.Key.KP_Left:
+ if (shutdown_dialog != null)
+ shutdown_dialog.focus_prev ();
+ return true;
+ case Gdk.Key.Right:
+ case Gdk.Key.KP_Right:
+ if (shutdown_dialog != null)
+ shutdown_dialog.focus_next ();
+ return true;
+ case Gdk.Key.F10:
+ if (login_box.sensitive)
+ menubar.select_first (false);
+ return true;
+ case Gdk.Key.PowerOff:
+ show_shutdown_dialog (ShutdownDialogType.SHUTDOWN);
+ return true;
+ case Gdk.Key.z:
+ if (UnityGreeter.singleton.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0)
+ {
+ show_shutdown_dialog (ShutdownDialogType.SHUTDOWN);
+ return true;
+ }
+ break;
+ case Gdk.Key.Z:
+ if (UnityGreeter.singleton.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0)
+ {
+ show_shutdown_dialog (ShutdownDialogType.RESTART);
+ return true;
+ }
+ break;
+ }
+
+ return base.key_press_event (event);
+ }
+
+ public void set_keyboard_state ()
+ {
+ menubar.set_keyboard_state ();
+ }
+
+ public void show_shutdown_dialog (ShutdownDialogType type)
+ {
+ if (shutdown_dialog != null)
+ shutdown_dialog.destroy ();
+
+ /* Stop input to login box */
+ login_box.sensitive = false;
+
+ shutdown_dialog = new ShutdownDialog (type, background);
+ shutdown_dialog.closed.connect (close_shutdown_dialog);
+ background.add (shutdown_dialog);
+ move_to_monitor (active_monitor);
+ shutdown_dialog.visible = true;
+ }
+
+ public void close_shutdown_dialog ()
+ {
+ if (shutdown_dialog == null)
+ return;
+
+ shutdown_dialog.destroy ();
+ shutdown_dialog = null;
+
+ login_box.sensitive = true;
+ }
+}
diff --git a/src/menu.vala b/src/menu.vala
new file mode 100644
index 0000000..71e70b1
--- /dev/null
+++ b/src/menu.vala
@@ -0,0 +1,47 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Andrea Cimitan <andrea.cimitan@canonical.com>
+ */
+
+
+public class Menu : Gtk.Menu
+{
+ public Background? background { get; construct; default = null; }
+
+ public Menu (Background bg)
+ {
+ Object (background: bg);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (background != null)
+ {
+ int x, y, bg_x, bg_y;
+
+ background.get_window ().get_origin (out bg_x, out bg_y);
+ get_window ().get_origin (out x, out y);
+ c.save ();
+ c.translate (bg_x - x, bg_y - y);
+ background.draw_full (c, Background.DrawFlags.NONE);
+ c.restore ();
+ }
+
+ base.draw (c);
+ return false;
+ }
+}
diff --git a/src/menubar.vala b/src/menubar.vala
new file mode 100644
index 0000000..79783da
--- /dev/null
+++ b/src/menubar.vala
@@ -0,0 +1,559 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+private class IndicatorMenuItem : Gtk.MenuItem
+{
+ public unowned Indicator.ObjectEntry entry;
+ private Gtk.Box hbox;
+
+ public IndicatorMenuItem (Indicator.ObjectEntry entry)
+ {
+ this.entry = entry;
+ this.hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3);
+ this.add (this.hbox);
+ this.hbox.show ();
+
+ if (entry.label != null)
+ {
+ entry.label.show.connect (this.visibility_changed_cb);
+ entry.label.hide.connect (this.visibility_changed_cb);
+ hbox.pack_start (entry.label, false, false, 0);
+ }
+ if (entry.image != null)
+ {
+ entry.image.show.connect (visibility_changed_cb);
+ entry.image.hide.connect (visibility_changed_cb);
+ hbox.pack_start (entry.image, false, false, 0);
+ }
+ if (entry.accessible_desc != null)
+ get_accessible ().set_name (entry.accessible_desc);
+ if (entry.menu != null)
+ set_submenu (entry.menu as Gtk.Widget);
+
+ if (has_visible_child ())
+ show ();
+ }
+
+ public bool has_visible_child ()
+ {
+ return (entry.image != null && entry.image.get_visible ()) ||
+ (entry.label != null && entry.label.get_visible ());
+ }
+
+ public void visibility_changed_cb (Gtk.Widget widget)
+ {
+ visible = has_visible_child ();
+ }
+}
+
+public class MenuBar : Gtk.MenuBar
+{
+ public Background? background { get; construct; default = null; }
+ public bool high_contrast { get; private set; default = false; }
+ public Gtk.Window? keyboard_window { get; private set; default = null; }
+ public Gtk.AccelGroup? accel_group { get; construct; }
+
+ private static const int HEIGHT = 24;
+
+ public MenuBar (Background bg, Gtk.AccelGroup ag)
+ {
+ Object (background: bg, accel_group: ag);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (background != null)
+ {
+ int x, y;
+ background.translate_coordinates (this, 0, 0, out x, out y);
+ c.save ();
+ c.translate (x, y);
+ background.draw_full (c, Background.DrawFlags.NONE);
+ c.restore ();
+ }
+
+ c.set_source_rgb (0.1, 0.1, 0.1);
+ c.paint_with_alpha (0.4);
+
+ foreach (var child in get_children ())
+ {
+ propagate_draw (child, c);
+ }
+
+ return false;
+ }
+
+ /* Due to LP #973922 the keyboard has to be loaded after the main window
+ * is shown and given focus. Therefore we don't enable the active state
+ * until now.
+ */
+ public void set_keyboard_state ()
+ {
+ onscreen_keyboard_item.set_active (UGSettings.get_boolean (UGSettings.KEY_ONSCREEN_KEYBOARD));
+ }
+
+ private string default_theme_name;
+ private List<Indicator.Object> indicator_objects;
+ private Gtk.CheckMenuItem high_contrast_item;
+ private Pid keyboard_pid = 0;
+ private Pid reader_pid = 0;
+ private Gtk.CheckMenuItem onscreen_keyboard_item;
+
+ construct
+ {
+ Gtk.Settings.get_default ().get ("gtk-theme-name", out default_theme_name);
+
+ pack_direction = Gtk.PackDirection.RTL;
+
+ if (UGSettings.get_boolean (UGSettings.KEY_SHOW_HOSTNAME))
+ {
+ var label = new Gtk.Label (Posix.utsname ().nodename);
+ label.show ();
+ var hostname_item = new Gtk.MenuItem ();
+ hostname_item.add (label);
+ hostname_item.sensitive = false;
+ hostname_item.right_justified = true;
+ hostname_item.show ();
+ append (hostname_item);
+
+ /* Hack to get a label showing on the menubar */
+ label.ensure_style ();
+ var fg = label.get_style_context ().get_color (Gtk.StateFlags.NORMAL);
+ label.override_color (Gtk.StateFlags.INSENSITIVE, fg);
+ }
+
+ /* Prevent dragging the window by the menubar */
+ try
+ {
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {-GtkWidget-window-dragging: false;}", -1);
+ get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading menubar style: %s", e.message);
+ }
+
+ setup_indicators ();
+
+ UnityGreeter.singleton.starting_session.connect (cleanup);
+ }
+
+ private void close_pid (ref Pid pid)
+ {
+ if (pid > 0)
+ {
+ Posix.kill (pid, Posix.SIGTERM);
+ int status;
+ Posix.waitpid (pid, out status, 0);
+ pid = 0;
+ }
+ }
+
+ public void cleanup ()
+ {
+ close_pid (ref keyboard_pid);
+ close_pid (ref reader_pid);
+ }
+
+ public override void get_preferred_height (out int min, out int nat)
+ {
+ min = HEIGHT;
+ nat = HEIGHT;
+ }
+
+ private void greeter_set_env (string key, string val)
+ {
+ GLib.Environment.set_variable (key, val, true);
+
+ /* And also set it in the DBus activation environment so that any
+ * indicator services pick it up. */
+ try
+ {
+ var proxy = new GLib.DBusProxy.for_bus_sync (GLib.BusType.SESSION,
+ GLib.DBusProxyFlags.NONE, null,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ null);
+
+ var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY);
+ builder.add ("{ss}", key, val);
+
+ proxy.call_sync ("UpdateActivationEnvironment", new GLib.Variant ("(a{ss})", builder), GLib.DBusCallFlags.NONE, -1, null);
+ }
+ catch (Error e)
+ {
+ warning ("Could not get set environment for indicators: %s", e.message);
+ return;
+ }
+ }
+
+ private Gtk.Widget make_a11y_indicator ()
+ {
+ var a11y_item = new Gtk.MenuItem ();
+ var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3);
+ hbox.show ();
+ a11y_item.add (hbox);
+ var image = new Gtk.Image.from_file (Path.build_filename (Config.PKGDATADIR, "a11y.svg"));
+ image.show ();
+ hbox.add (image);
+ a11y_item.show ();
+ a11y_item.set_submenu (new Gtk.Menu () as Gtk.Widget);
+ onscreen_keyboard_item = new Gtk.CheckMenuItem.with_label (_("Onscreen keyboard"));
+ onscreen_keyboard_item.toggled.connect (keyboard_toggled_cb);
+ onscreen_keyboard_item.show ();
+ unowned Gtk.Menu submenu = a11y_item.submenu;
+ submenu.append (onscreen_keyboard_item);
+ high_contrast_item = new Gtk.CheckMenuItem.with_label (_("High Contrast"));
+ high_contrast_item.toggled.connect (high_contrast_toggled_cb);
+ high_contrast_item.add_accelerator ("activate", accel_group, Gdk.Key.h, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
+ high_contrast_item.show ();
+ submenu.append (high_contrast_item);
+ high_contrast_item.set_active (UGSettings.get_boolean (UGSettings.KEY_HIGH_CONTRAST));
+ var item = new Gtk.CheckMenuItem.with_label (_("Screen Reader"));
+ item.toggled.connect (screen_reader_toggled_cb);
+ item.add_accelerator ("activate", accel_group, Gdk.Key.s, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
+ item.show ();
+ submenu.append (item);
+ item.set_active (UGSettings.get_boolean (UGSettings.KEY_SCREEN_READER));
+ return a11y_item;
+ }
+
+ private Indicator.Object? load_indicator_file (string indicator_name)
+ {
+ string dir = Config.INDICATOR_FILE_DIR;
+ string path;
+ Indicator.Object io;
+
+ /* To stay backwards compatible, use com.canonical.indicator as the default prefix */
+ if (indicator_name.index_of_char ('.') < 0)
+ path = @"$dir/com.canonical.indicator.$indicator_name";
+ else
+ path = @"$dir/$indicator_name";
+
+ try
+ {
+ io = new Indicator.Ng.for_profile (path, "desktop_greeter");
+ }
+ catch (FileError error)
+ {
+ /* the calling code handles file-not-found; don't warn here */
+ return null;
+ }
+ catch (Error error)
+ {
+ warning ("unable to load %s: %s", indicator_name, error.message);
+ return null;
+ }
+
+ return io;
+ }
+
+ private Indicator.Object? load_indicator_library (string indicator_name)
+ {
+ // Find file, if it exists
+ string[] names_to_try = {"lib" + indicator_name + ".so",
+ indicator_name + ".so",
+ indicator_name};
+ foreach (var filename in names_to_try)
+ {
+ var full_path = Path.build_filename (Config.INDICATORDIR, filename);
+ var io = new Indicator.Object.from_file (full_path);
+ if (io != null)
+ return io;
+ }
+
+ return null;
+ }
+
+ private void load_indicator (string indicator_name)
+ {
+ if (indicator_name == "ug-accessibility")
+ {
+ var a11y_item = make_a11y_indicator ();
+ insert (a11y_item, (int) get_children ().length () - 1);
+ }
+ else
+ {
+ var io = load_indicator_file (indicator_name);
+
+ if (io == null)
+ io = load_indicator_library (indicator_name);
+
+ if (io != null)
+ {
+ indicator_objects.append (io);
+ io.entry_added.connect (indicator_added_cb);
+ io.entry_removed.connect (indicator_removed_cb);
+ foreach (var entry in io.get_entries ())
+ indicator_added_cb (io, entry);
+ }
+ }
+ }
+
+ private void setup_indicators ()
+ {
+ /* Set indicators to run with reduced functionality */
+ greeter_set_env ("INDICATOR_GREETER_MODE", "1");
+
+ /* Don't allow virtual file systems? */
+ greeter_set_env ("GIO_USE_VFS", "local");
+ greeter_set_env ("GVFS_DISABLE_FUSE", "1");
+
+ /* Hint to have unity-settings-daemon run in greeter mode */
+ greeter_set_env ("RUNNING_UNDER_GDM", "1");
+
+ /* Let indicators know about our unique dbus name */
+ try
+ {
+ var conn = Bus.get_sync (BusType.SESSION);
+ greeter_set_env ("UNITY_GREETER_DBUS_NAME", conn.get_unique_name ());
+ }
+ catch (IOError e)
+ {
+ debug ("Could not set DBUS_NAME: %s", e.message);
+ }
+
+ debug ("LANG=%s LANGUAGE=%s", Environment.get_variable ("LANG"), Environment.get_variable ("LANGUAGE"));
+
+ var indicator_list = UGSettings.get_strv(UGSettings.KEY_INDICATORS);
+
+ var update_indicator_list = false;
+ for (var i = 0; i < indicator_list.length; i++)
+ {
+ if (indicator_list[i] == "ug-keyboard")
+ {
+ indicator_list[i] = "com.canonical.indicator.keyboard";
+ update_indicator_list = true;
+ }
+ }
+
+ if (update_indicator_list)
+ UGSettings.set_strv(UGSettings.KEY_INDICATORS, indicator_list);
+
+ foreach (var indicator in indicator_list)
+ load_indicator(indicator);
+
+ indicator_objects.sort((a, b) => {
+ int pos_a = a.get_position ();
+ int pos_b = b.get_position ();
+
+ if (pos_a < 0)
+ pos_a = 1000;
+ if (pos_b < 0)
+ pos_b = 1000;
+
+ return pos_a - pos_b;
+ });
+
+ debug ("LANG=%s LANGUAGE=%s", Environment.get_variable ("LANG"), Environment.get_variable ("LANGUAGE"));
+ }
+
+ private void keyboard_toggled_cb (Gtk.CheckMenuItem item)
+ {
+ /* FIXME: The below would be sufficient if gnome-session were running
+ * to notice and run a screen keyboard in /etc/xdg/autostart... But
+ * since we're not running gnome-session, we hardcode onboard here. */
+ /* var settings = new Settings ("org.gnome.desktop.a11y.applications");*/
+ /*settings.set_boolean ("screen-keyboard-enabled", item.active);*/
+
+ UGSettings.set_boolean (UGSettings.KEY_ONSCREEN_KEYBOARD, item.active);
+
+ if (keyboard_window == null)
+ {
+ int id = 0;
+
+ try
+ {
+ string[] argv;
+ int onboard_stdout_fd;
+
+ Shell.parse_argv ("onboard --xid", out argv);
+ Process.spawn_async_with_pipes (null,
+ argv,
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out keyboard_pid,
+ null,
+ out onboard_stdout_fd,
+ null);
+ var f = FileStream.fdopen (onboard_stdout_fd, "r");
+ var stdout_text = new char[1024];
+ if (f.gets (stdout_text) != null)
+ id = int.parse ((string) stdout_text);
+
+ }
+ catch (Error e)
+ {
+ warning ("Error setting up keyboard: %s", e.message);
+ return;
+ }
+
+ var keyboard_socket = new Gtk.Socket ();
+ keyboard_socket.show ();
+ keyboard_window = new Gtk.Window ();
+ keyboard_window.accept_focus = false;
+ keyboard_window.focus_on_map = false;
+ keyboard_window.add (keyboard_socket);
+ keyboard_socket.add_id (id);
+
+ /* Put keyboard at the bottom of the screen */
+ var screen = get_screen ();
+ var monitor = screen.get_monitor_at_window (get_window ());
+ Gdk.Rectangle geom;
+ screen.get_monitor_geometry (monitor, out geom);
+ keyboard_window.move (geom.x, geom.y + geom.height - 200);
+ keyboard_window.resize (geom.width, 200);
+ }
+
+ keyboard_window.visible = item.active;
+ }
+
+ private void high_contrast_toggled_cb (Gtk.CheckMenuItem item)
+ {
+ var settings = Gtk.Settings.get_default ();
+ if (item.active)
+ settings.set ("gtk-theme-name", "HighContrastInverse");
+ else
+ settings.set ("gtk-theme-name", default_theme_name);
+ high_contrast = item.active;
+ UGSettings.set_boolean (UGSettings.KEY_HIGH_CONTRAST, high_contrast);
+ }
+
+ private void screen_reader_toggled_cb (Gtk.CheckMenuItem item)
+ {
+ /* FIXME: The below would be sufficient if gnome-session were running
+ * to notice and run a screen reader in /etc/xdg/autostart... But
+ * since we're not running gnome-session, we hardcode orca here.
+ /*var settings = new Settings ("org.gnome.desktop.a11y.applications");*/
+ /*settings.set_boolean ("screen-reader-enabled", item.active);*/
+
+ UGSettings.set_boolean (UGSettings.KEY_SCREEN_READER, item.active);
+
+ /* Hardcoded orca: */
+ if (item.active)
+ {
+ try
+ {
+ string[] argv;
+ Shell.parse_argv ("orca --replace --no-setup --disable splash-window,", out argv);
+ Process.spawn_async (null,
+ argv,
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out reader_pid);
+ // This is a workaroud for bug https://launchpad.net/bugs/944159
+ // The problem is that orca seems to not notice that it's in a
+ // password field on startup. We just need to kick orca in the
+ // pants. We do this two ways: a racy way and a non-racy way.
+ // We kick it after a second which is ideal if we win the race,
+ // because the user gets to hear what widget they are in, and
+ // the first character will be masked. Otherwise, if we lose
+ // that race, the first time the user types (see
+ // DashEntry.key_press_event), we will kick orca again. While
+ // this is not racy with orca startup, it is racy with whether
+ // orca will read the first character or not out loud. Hence
+ // why we do both. Ideally this would be fixed in orca itself.
+ UnityGreeter.singleton.orca_needs_kick = true;
+ Timeout.add_seconds (1, () => {
+ Signal.emit_by_name ((get_toplevel () as Gtk.Window).get_focus ().get_accessible (), "focus-event", true);
+ return false;
+ });
+ }
+ catch (Error e)
+ {
+ warning ("Failed to run Orca: %s", e.message);
+ }
+ }
+ else
+ close_pid (ref reader_pid);
+ }
+
+ private uint get_indicator_index (Indicator.Object object)
+ {
+ uint index = 0;
+
+ foreach (var io in indicator_objects)
+ {
+ if (io == object)
+ return index;
+ index++;
+ }
+
+ return index;
+ }
+
+ private Indicator.Object? get_indicator_object_from_entry (Indicator.ObjectEntry entry)
+ {
+ foreach (var io in indicator_objects)
+ {
+ foreach (var e in io.get_entries ())
+ {
+ if (e == entry)
+ return io;
+ }
+ }
+
+ return null;
+ }
+
+ private void indicator_added_cb (Indicator.Object object, Indicator.ObjectEntry entry)
+ {
+ var index = get_indicator_index (object);
+ var pos = 0;
+ foreach (var child in get_children ())
+ {
+ if (!(child is IndicatorMenuItem))
+ break;
+
+ var menuitem = (IndicatorMenuItem) child;
+ var child_object = get_indicator_object_from_entry (menuitem.entry);
+ var child_index = get_indicator_index (child_object);
+ if (child_index > index)
+ break;
+ pos++;
+ }
+
+ debug ("Adding indicator object %p at position %d", entry, pos);
+
+ var menuitem = new IndicatorMenuItem (entry);
+ insert (menuitem, pos);
+ }
+
+ private void indicator_removed_cb (Indicator.Object object, Indicator.ObjectEntry entry)
+ {
+ debug ("Removing indicator object %p", entry);
+
+ foreach (var child in get_children ())
+ {
+ var menuitem = (IndicatorMenuItem) child;
+ if (menuitem.entry == entry)
+ {
+ remove (child);
+ return;
+ }
+ }
+
+ warning ("Indicator object %p not in menubar", entry);
+ }
+}
diff --git a/src/prompt-box.vala b/src/prompt-box.vala
new file mode 100644
index 0000000..894870d
--- /dev/null
+++ b/src/prompt-box.vala
@@ -0,0 +1,663 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+public class PromptBox : FadableBox
+{
+ public signal void respond (string[] response);
+ public signal void login ();
+ public signal void show_options ();
+ public signal void name_clicked ();
+
+ public bool has_errors { get; set; default = false; }
+ public string id { get; construct; }
+
+ public string label
+ {
+ get { return name_label.label; }
+ set
+ {
+ name_label.label = value;
+ small_name_label.label = value;
+ }
+ }
+
+ public double position { get; set; default = 0; }
+
+ private Gtk.Fixed fixed;
+ private Gtk.Widget zone; /* when overlapping zone we are fully expanded */
+
+ /* Expanded widgets */
+ protected Gtk.Grid box_grid;
+ protected Gtk.Grid name_grid;
+ private ActiveIndicator active_indicator;
+ protected FadingLabel name_label;
+ protected FlatButton option_button;
+ private CachedImage option_image;
+ private CachedImage message_image;
+
+ /* Condensed widgets */
+ protected Gtk.Widget small_box_widget;
+ private ActiveIndicator small_active_indicator;
+ protected FadingLabel small_name_label;
+ private CachedImage small_message_image;
+
+ protected static const int COL_ACTIVE = 0;
+ protected static const int COL_CONTENT = 1;
+ protected static const int COL_SPACER = 2;
+
+ protected static const int ROW_NAME = 0;
+ protected static const int COL_NAME_LABEL = 0;
+ protected static const int COL_NAME_MESSAGE = 1;
+ protected static const int COL_NAME_OPTIONS = 2;
+
+ protected static const int COL_ENTRIES_START = 1;
+ protected static const int COL_ENTRIES_END = 1;
+ protected static const int COL_ENTRIES_WIDTH = 1;
+
+ protected int start_row;
+ protected int last_row;
+
+ private enum PromptVisibility
+ {
+ HIDDEN,
+ FADING,
+ SHOWN,
+ }
+ private PromptVisibility prompt_visibility = PromptVisibility.HIDDEN;
+
+ public PromptBox (string id)
+ {
+ Object (id: id);
+ }
+
+ construct
+ {
+ set_start_row ();
+ reset_last_row ();
+ expand = true;
+
+ fixed = new Gtk.Fixed ();
+ fixed.show ();
+ add (fixed);
+
+ box_grid = new Gtk.Grid ();
+ box_grid.column_spacing = 4;
+ box_grid.row_spacing = 3;
+ box_grid.margin_top = GreeterList.BORDER;
+ box_grid.margin_bottom = 6;
+ box_grid.expand = true;
+
+ /** Grid layout:
+ 0 1 2 3 4
+ > Name M S <
+ Message.......
+ Entry.........
+ */
+
+ active_indicator = new ActiveIndicator ();
+ active_indicator.valign = Gtk.Align.START;
+ active_indicator.margin_top = (grid_size - ActiveIndicator.HEIGHT) / 2;
+ active_indicator.show ();
+ box_grid.attach (active_indicator, COL_ACTIVE, last_row, 1, 1);
+
+ /* Add a second one on right just for equal-spacing purposes */
+ var dummy_indicator = new ActiveIndicator ();
+ dummy_indicator.show ();
+ box_grid.attach (dummy_indicator, COL_SPACER, last_row, 1, 1);
+
+ box_grid.show ();
+
+ /* Create fully expanded version of ourselves */
+ name_grid = create_name_grid ();
+ box_grid.attach (name_grid, COL_CONTENT, last_row, 1, 1);
+
+ /* Now prep small versions of the above normal widgets. These are
+ * used when scrolling outside of the main dash box. */
+ var small_box_grid = new Gtk.Grid ();
+ small_box_grid.column_spacing = 4;
+ small_box_grid.row_spacing = 6;
+ small_box_grid.hexpand = true;
+ small_box_grid.show ();
+
+ small_active_indicator = new ActiveIndicator ();
+ small_active_indicator.valign = Gtk.Align.START;
+ small_active_indicator.margin_top = (grid_size - ActiveIndicator.HEIGHT) / 2;
+ small_active_indicator.show ();
+ small_box_grid.attach (small_active_indicator, 0, 0, 1, 1);
+
+ var small_name_grid = create_small_name_grid ();
+ small_box_grid.attach (small_name_grid, 1, 0, 1, 1);
+
+ /* Add a second indicator on right just for equal-spacing purposes */
+ var small_dummy_indicator = new ActiveIndicator ();
+ small_dummy_indicator.show ();
+ small_box_grid.attach (small_dummy_indicator, 3, 0, 1, 1);
+
+ var small_box_eventbox = new Gtk.EventBox ();
+ small_box_eventbox.visible_window = false;
+ small_box_eventbox.button_release_event.connect (() =>
+ {
+ name_clicked ();
+ return true;
+ });
+ small_box_eventbox.add (small_box_grid);
+ small_box_eventbox.show ();
+ small_box_widget = small_box_eventbox;
+
+ fixed.add (small_box_widget);
+ fixed.add (box_grid);
+ }
+
+ protected virtual Gtk.Grid create_name_grid ()
+ {
+ var name_grid = new Gtk.Grid ();
+ name_grid.column_spacing = 4;
+ name_grid.hexpand = true;
+
+ name_label = new FadingLabel ("");
+ name_label.override_font (Pango.FontDescription.from_string ("Ubuntu 13"));
+ name_label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f });
+ name_label.valign = Gtk.Align.START;
+ name_label.vexpand = true;
+ name_label.yalign = 0.5f;
+ name_label.xalign = 0.0f;
+ name_label.margin_left = 2;
+ name_label.set_size_request (-1, grid_size);
+ name_label.show ();
+ name_grid.attach (name_label, COL_NAME_LABEL, ROW_NAME, 1, 1);
+
+ message_image = new CachedImage (null);
+ try
+ {
+ message_image.pixbuf = new Gdk.Pixbuf.from_file (Path.build_filename (Config.PKGDATADIR, "message.png", null));
+ }
+ catch (Error e)
+ {
+ debug ("Error loading message image: %s", e.message);
+ }
+
+ var align = new Gtk.Alignment (0.5f, 0.5f, 0.0f, 0.0f);
+ align.valign = Gtk.Align.START;
+ align.set_size_request (-1, grid_size);
+ align.add (message_image);
+ align.show ();
+ name_grid.attach (align, COL_NAME_MESSAGE, ROW_NAME, 1, 1);
+
+ option_button = new FlatButton ();
+ option_button.hexpand = true;
+ option_button.halign = Gtk.Align.END;
+ option_button.valign = Gtk.Align.START;
+ // Keep as much space on top as on the right
+ option_button.margin_top = ActiveIndicator.WIDTH + box_grid.column_spacing;
+ option_button.focus_on_click = false;
+ option_button.relief = Gtk.ReliefStyle.NONE;
+ option_button.get_accessible ().set_name (_("Session Options"));
+ option_button.clicked.connect (option_button_clicked_cb);
+ option_image = new CachedImage (null);
+ option_image.show ();
+ try
+ {
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {padding: 2px;}", -1);
+ option_button.get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading session chooser style: %s", e.message);
+ }
+ option_button.add (option_image);
+ name_grid.attach (option_button, COL_NAME_OPTIONS, ROW_NAME, 1, 1);
+
+ name_grid.show ();
+
+ return name_grid;
+ }
+
+ protected virtual Gtk.Grid create_small_name_grid ()
+ {
+ var small_name_grid = new Gtk.Grid ();
+ small_name_grid.column_spacing = 4;
+
+ small_name_label = new FadingLabel ("");
+ small_name_label.override_font (Pango.FontDescription.from_string ("Ubuntu 13"));
+ small_name_label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f });
+ small_name_label.yalign = 0.5f;
+ small_name_label.xalign = 0.0f;
+ small_name_label.margin_left = 2;
+ small_name_label.set_size_request (-1, grid_size);
+ small_name_label.show ();
+ small_name_grid.attach (small_name_label, 1, 0, 1, 1);
+
+ small_message_image = new CachedImage (null);
+ small_message_image.pixbuf = message_image.pixbuf;
+
+ var align = new Gtk.Alignment (0.5f, 0.5f, 0.0f, 0.0f);
+ align.set_size_request (-1, grid_size);
+ align.add (small_message_image);
+ align.show ();
+ small_name_grid.attach (align, 2, 0, 1, 1);
+
+ small_name_grid.show ();
+ return small_name_grid;
+ }
+
+ protected virtual void set_start_row ()
+ {
+ start_row = 0;
+ }
+
+ protected virtual void reset_last_row ()
+ {
+ last_row = start_row;
+ }
+
+ private int round_to_grid (int size)
+ {
+ var num_grids = size / grid_size;
+ var remainder = size % grid_size;
+ if (remainder > 0)
+ num_grids += 1;
+ num_grids = int.max (num_grids, 3);
+ return num_grids * grid_size;
+ }
+
+ public override void get_preferred_height (out int min, out int nat)
+ {
+ base.get_preferred_height (out min, out nat);
+ min = round_to_grid (min + GreeterList.BORDER * 2) - GreeterList.BORDER * 2;
+ nat = round_to_grid (nat + GreeterList.BORDER * 2) - GreeterList.BORDER * 2;
+ }
+
+ public void set_zone (Gtk.Widget zone)
+ {
+ this.zone = zone;
+ queue_draw ();
+ }
+
+ public void set_options_image (Gdk.Pixbuf? image)
+ {
+ if (option_button == null)
+ return;
+
+ option_image.pixbuf = image;
+
+ if (image == null)
+ option_button.hide ();
+ else
+ option_button.show ();
+ }
+
+ private void option_button_clicked_cb (Gtk.Button button)
+ {
+ show_options ();
+ }
+
+ public void set_show_message_icon (bool show)
+ {
+ message_image.visible = show;
+ small_message_image.visible = show;
+ }
+
+ public void set_is_active (bool active)
+ {
+ active_indicator.active = active;
+ small_active_indicator.active = active;
+ }
+
+ protected void foreach_prompt_widget (Gtk.Callback cb)
+ {
+ var prompt_widgets = new List<Gtk.Widget> ();
+
+ var i = start_row + 1;
+ while (i <= last_row)
+ {
+ var c = box_grid.get_child_at (COL_ENTRIES_START, i);
+ if (c != null) /* c might have been deleted from selective clear */
+ prompt_widgets.append (c);
+ i++;
+ }
+
+ foreach (var w in prompt_widgets)
+ cb (w);
+ }
+
+ public void clear ()
+ {
+ prompt_visibility = PromptVisibility.HIDDEN;
+ foreach_prompt_widget ((w) => { w.destroy (); });
+ reset_last_row ();
+ has_errors = false;
+ }
+
+ /* Clears error messages */
+ public void reset_messages ()
+ {
+ has_errors = false;
+ foreach_prompt_widget ((w) =>
+ {
+ var is_error = w.get_data<bool> ("prompt-box-is-error");
+ if (is_error)
+ w.destroy ();
+ });
+ }
+
+ /* Stops spinners */
+ public void reset_spinners ()
+ {
+ foreach_prompt_widget ((w) =>
+ {
+ if (w is DashEntry)
+ {
+ var e = w as DashEntry;
+ e.did_respond = false;
+ }
+ });
+ }
+
+ /* Clears error messages and stops spinners. Basically gets the box back to a filled-by-user-but-no-status state. */
+ public void reset_state ()
+ {
+ reset_messages ();
+ reset_spinners ();
+ }
+
+ public virtual void add_static_prompts ()
+ {
+ /* Subclasses may want to add prompts that are always present here */
+ }
+
+ private void update_prompt_visibility (Gtk.Widget w)
+ {
+ switch (prompt_visibility)
+ {
+ case PromptVisibility.HIDDEN:
+ w.hide ();
+ break;
+ case PromptVisibility.FADING:
+ var f = w as Fadable;
+ w.sensitive = true;
+ if (f != null)
+ f.fade_in ();
+ else
+ w.show ();
+ break;
+ case PromptVisibility.SHOWN:
+ w.show ();
+ w.sensitive = true;
+ break;
+ }
+ }
+
+ public void fade_in_prompts ()
+ {
+ prompt_visibility = PromptVisibility.FADING;
+ show ();
+ foreach_prompt_widget ((w) => { update_prompt_visibility (w); });
+ }
+
+ public void show_prompts ()
+ {
+ prompt_visibility = PromptVisibility.SHOWN;
+ show ();
+ foreach_prompt_widget ((w) => { update_prompt_visibility (w); });
+ }
+
+ protected void attach_item (Gtk.Widget w, bool add_style_class = true)
+ {
+ w.set_data ("prompt-box-widget", this);
+ if (add_style_class)
+ UnityGreeter.add_style_class (w);
+
+ last_row += 1;
+ box_grid.attach (w, COL_ENTRIES_START, last_row, COL_ENTRIES_WIDTH, 1);
+
+ update_prompt_visibility (w);
+ queue_resize ();
+ }
+
+ public void add_message (string text, bool is_error)
+ {
+ var label = new FadingLabel (text);
+
+ label.override_font (Pango.FontDescription.from_string ("Ubuntu 10"));
+
+ Gdk.RGBA color = { 1.0f, 1.0f, 1.0f, 1.0f };
+ if (is_error)
+ color.parse ("#df382c");
+ label.override_color (Gtk.StateFlags.NORMAL, color);
+
+ label.xalign = 0.0f;
+ label.set_data<bool> ("prompt-box-is-error", is_error);
+
+ attach_item (label);
+
+ if (is_error)
+ has_errors = true;
+ }
+
+ public DashEntry add_prompt (string text, string? accessible_text, bool is_secret)
+ {
+ /* Stop other entry's arrows/spinners from showing */
+ foreach_prompt_widget ((w) =>
+ {
+ if (w is DashEntry)
+ {
+ var e = w as DashEntry;
+ if (e != null)
+ e.can_respond = false;
+ }
+ });
+
+ var entry = new DashEntry ();
+ entry.sensitive = false;
+
+ if (text.contains ("\n"))
+ {
+ add_message (text, false);
+ entry.constant_placeholder_text = "";
+ }
+ else
+ {
+ /* Strip trailing colon if present (also handle CJK version) */
+ var placeholder = text;
+ if (placeholder.has_suffix (":") || placeholder.has_suffix (":"))
+ {
+ var len = placeholder.char_count ();
+ placeholder = placeholder.substring (0, placeholder.index_of_nth_char (len - 1));
+ }
+ entry.constant_placeholder_text = placeholder;
+ }
+
+ var accessible = entry.get_accessible ();
+ if (accessible_text != null)
+ accessible.set_name (accessible_text);
+ else
+ accessible.set_name (text);
+
+ if (is_secret)
+ {
+ entry.visibility = false;
+ entry.caps_lock_warning = true;
+ }
+
+ entry.respond.connect (entry_activate_cb);
+
+ attach_item (entry);
+
+ return entry;
+ }
+
+ public Gtk.ComboBox add_combo (GenericArray<string> texts, bool read_only)
+ {
+ Gtk.ComboBoxText combo;
+ if (read_only)
+ combo = new Gtk.ComboBoxText ();
+ else
+ combo = new Gtk.ComboBoxText.with_entry ();
+
+ combo.get_style_context ().add_class ("lightdm-combo");
+ combo.get_child ().get_style_context ().add_class ("lightdm-combo");
+ combo.get_child ().override_font (Pango.FontDescription.from_string (DashEntry.font));
+
+ attach_item (combo, false);
+
+ texts.foreach ((text) => { combo.append_text (text); });
+
+ if (texts.length > 0)
+ combo.active = 0;
+
+ return combo;
+ }
+
+ protected void entry_activate_cb ()
+ {
+ var response = new string[0];
+
+ foreach_prompt_widget ((w) =>
+ {
+ if (w is Gtk.Entry)
+ {
+ var e = w as Gtk.Entry;
+ if (e != null)
+ response += e.text;
+ }
+ });
+ respond (response);
+ }
+
+ public void add_button (string text, string? accessible_text)
+ {
+ var button = new DashButton (text);
+
+ var accessible = button.get_accessible ();
+ accessible.set_name (accessible_text);
+
+ button.clicked.connect (button_clicked_cb);
+
+ attach_item (button);
+ }
+
+ private void button_clicked_cb (Gtk.Button button)
+ {
+ login ();
+ }
+
+ public override void grab_focus ()
+ {
+ var done = false;
+ Gtk.Widget best = null;
+ foreach_prompt_widget ((w) =>
+ {
+ if (done)
+ return;
+ best = w; /* last entry wins, all else considered */
+ var e = w as Gtk.Entry;
+ var b = w as Gtk.Button;
+ var c = w as Gtk.ComboBox;
+
+ /* We've found ideal entry (first empty one), so stop looking */
+ if ((e != null && e.text == "") || b != null || c != null)
+ done = true;
+ });
+ if (best != null)
+ best.grab_focus ();
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+ box_grid.size_allocate (allocation);
+
+ int small_height;
+ small_box_widget.get_preferred_height (null, out small_height);
+ allocation.height = small_height;
+ small_box_widget.size_allocate (allocation);
+ }
+
+ public override void draw_full_alpha (Cairo.Context c)
+ {
+ /* Draw either small or normal version of ourselves, depending on where
+ our allocation put us relative to our zone */
+ int x, y;
+ zone.translate_coordinates (this, 0, 0, out x, out y);
+
+ Gtk.Allocation alloc, zone_alloc;
+ this.get_allocation (out alloc);
+ zone.get_allocation (out zone_alloc);
+
+ /* Draw main grid only in that area */
+ c.save ();
+ c.rectangle (x, y, zone_alloc.width, zone_alloc.height);
+ c.clip ();
+ fixed.propagate_draw (box_grid, c);
+ c.restore ();
+
+ /* Do actual drawing */
+ c.save ();
+ if (y > 0)
+ c.rectangle (x, 0, zone_alloc.width, y);
+ else
+ c.rectangle (x, y + zone_alloc.height, zone_alloc.width, -y);
+ c.clip ();
+ fixed.propagate_draw (small_box_widget, c);
+ c.restore ();
+ }
+}
+
+private class ActiveIndicator : Gtk.Image
+{
+ public bool active { get; set; }
+ public static const int WIDTH = 8;
+ public static const int HEIGHT = 7;
+
+ construct
+ {
+ var filename = Path.build_filename (Config.PKGDATADIR, "active.png");
+ try
+ {
+ pixbuf = new Gdk.Pixbuf.from_file (filename);
+ }
+ catch (Error e)
+ {
+ debug ("Could not load active image: %s", e.message);
+ }
+ notify["active"].connect (() => { queue_draw (); });
+ xalign = 0.0f;
+ }
+
+ public override void get_preferred_width (out int min, out int nat)
+ {
+ min = WIDTH;
+ nat = min;
+ }
+
+ public override void get_preferred_height (out int min, out int nat)
+ {
+ min = HEIGHT;
+ nat = min;
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (!active)
+ return false;
+ return base.draw (c);
+ }
+}
diff --git a/src/remote-login-service.vala b/src/remote-login-service.vala
new file mode 100644
index 0000000..7099ac2
--- /dev/null
+++ b/src/remote-login-service.vala
@@ -0,0 +1,54 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ */
+
+protected struct RemoteServerField
+{
+ public string type;
+ public bool required;
+ public Variant default_value;
+ public HashTable<string, Variant> properties;
+}
+
+protected struct RemoteServerApplication
+{
+ public string application_id;
+ public int pin_position;
+}
+
+protected struct RemoteServer
+{
+ public string type;
+ public string name;
+ public string url;
+ public bool last_used_server;
+ public RemoteServerField[] fields;
+ public RemoteServerApplication[] applications;
+}
+
+[DBus (name = "com.canonical.RemoteLogin")]
+interface RemoteLoginService : Object
+{
+ public abstract async void get_servers (out RemoteServer[] serverList) throws IOError;
+ public abstract async void get_servers_for_login (string url, string emailAddress, string password, bool allowCache, out bool loginSuccess, out string dataType, out RemoteServer[] serverList) throws IOError;
+ public abstract async void get_cached_domains_for_server (string url, out string[] domains) throws IOError;
+ public abstract async void set_last_used_server (string uccsUrl, string serverUrl) throws IOError;
+
+ public signal void servers_updated (RemoteServer[] serverList);
+ public signal void login_servers_updated (string url, string emailAddress, string dataType, RemoteServer[] serverList);
+ public signal void login_changed (string url, string emailAddress);
+}
diff --git a/src/session-list.vala b/src/session-list.vala
new file mode 100644
index 0000000..7127023
--- /dev/null
+++ b/src/session-list.vala
@@ -0,0 +1,158 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class SessionPrompt : PromptBox
+{
+ public string session { get; construct; }
+ public string default_session { get; construct; }
+
+ public SessionPrompt (string id, string? session, string? default_session)
+ {
+ Object (id: id, session: session, default_session: default_session);
+ }
+
+ private ToggleBox box;
+
+ construct
+ {
+ label = _("Select desktop environment");
+ name_label.vexpand = false;
+
+ box = new ToggleBox (default_session, session);
+
+ if (UnityGreeter.singleton.test_mode)
+ {
+ box.add_item ("gnome", "GNOME", SessionList.get_badge ("gnome"));
+ box.add_item ("kde", "KDE", SessionList.get_badge ("kde"));
+ box.add_item ("ubuntu", "Ubuntu", SessionList.get_badge ("ubuntu"));
+ }
+ else
+ {
+ foreach (var session in LightDM.get_sessions ())
+ {
+ debug ("Adding session %s (%s)", session.key, session.name);
+ box.add_item (session.key, session.name, SessionList.get_badge (session.key));
+ }
+ }
+
+ box.notify["selected-key"].connect (selected_cb);
+ box.show ();
+
+ attach_item (box);
+ }
+
+ private void selected_cb ()
+ {
+ respond ({ box.selected_key });
+ }
+}
+
+public class SessionList : GreeterList
+{
+ public signal void session_clicked (string session);
+ public string session { get; construct; }
+ public string default_session { get; construct; }
+
+ private SessionPrompt prompt;
+
+ public SessionList (Background bg, MenuBar mb, string? session, string? default_session)
+ {
+ Object (background: bg, menubar: mb, session: session, default_session: default_session);
+ }
+
+ construct
+ {
+ prompt = add_session_prompt ("session");
+ }
+
+ private SessionPrompt add_session_prompt (string id)
+ {
+ var e = new SessionPrompt (id, session, default_session);
+ e.respond.connect ((responses) => { session_clicked (responses[0]); });
+ add_entry (e);
+ return e;
+ }
+
+ protected override void add_manual_entry () {}
+ public override void show_authenticated (bool successful = true) {}
+
+ private static string? get_badge_name (string session)
+ {
+ switch (session)
+ {
+ case "ubuntu":
+ case "ubuntu-2d":
+ return "ubuntu_badge.png";
+ case "gnome-classic":
+ case "gnome-flashback-compiz":
+ case "gnome-flashback":
+ case "gnome-fallback-compiz":
+ case "gnome-fallback":
+ case "gnome-shell":
+ case "gnome":
+ return "gnome_badge.png";
+ case "kde":
+ case "kde-plasma":
+ return "kde_badge.png";
+ case "xterm":
+ return "recovery_console_badge.png";
+ case "remote-login":
+ return "remote_login_help.png";
+ default:
+ return null;
+ }
+ }
+
+ private static HashTable<string, Gdk.Pixbuf> badges; /* cache of badges */
+ public static Gdk.Pixbuf? get_badge (string session)
+ {
+ var name = get_badge_name (session);
+
+ if (name == null)
+ {
+ /* Not a known name, but let's see if we have a custom badge before
+ giving up entirely and using the unknown badget. */
+ var maybe_name = "custom_%s_badge.png".printf (session);
+ var maybe_path = Path.build_filename (Config.PKGDATADIR, maybe_name, null);
+ if (FileUtils.test (maybe_path, FileTest.EXISTS))
+ name = maybe_name;
+ else
+ name = "unknown_badge.png";
+ }
+
+ if (badges == null)
+ badges = new HashTable<string, Gdk.Pixbuf> (str_hash, str_equal);
+
+ var pixbuf = badges.lookup (name);
+ if (pixbuf == null)
+ {
+ try
+ {
+ pixbuf = new Gdk.Pixbuf.from_file (Path.build_filename (Config.PKGDATADIR, name, null));
+ badges.insert (name, pixbuf);
+ }
+ catch (Error e)
+ {
+ debug ("Error loading badge %s: %s", name, e.message);
+ }
+ }
+
+ return pixbuf;
+ }
+}
diff --git a/src/settings-daemon.vala b/src/settings-daemon.vala
new file mode 100644
index 0000000..9cf3908
--- /dev/null
+++ b/src/settings-daemon.vala
@@ -0,0 +1,234 @@
+/* -*- 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: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class SettingsDaemon : Object
+{
+ private int logind_inhibit_fd = -1;
+ private ScreenSaverInterface screen_saver;
+ private SessionManagerInterface session_manager;
+ private int n_names = 0;
+
+ public void start ()
+ {
+ string[] disabled = { "org.gnome.settings-daemon.plugins.background",
+ "org.gnome.settings-daemon.plugins.clipboard",
+ "org.gnome.settings-daemon.plugins.font",
+ "org.gnome.settings-daemon.plugins.gconf",
+ "org.gnome.settings-daemon.plugins.gsdwacom",
+ "org.gnome.settings-daemon.plugins.housekeeping",
+ "org.gnome.settings-daemon.plugins.keybindings",
+ "org.gnome.settings-daemon.plugins.keyboard",
+ "org.gnome.settings-daemon.plugins.media-keys",
+ "org.gnome.settings-daemon.plugins.mouse",
+ "org.gnome.settings-daemon.plugins.print-notifications",
+ "org.gnome.settings-daemon.plugins.smartcard",
+ "org.gnome.settings-daemon.plugins.sound",
+ "org.gnome.settings-daemon.plugins.wacom" };
+
+ string[] enabled = { "org.gnome.settings-daemon.plugins.a11y-keyboard",
+ "org.gnome.settings-daemon.plugins.a11y-settings",
+ "org.gnome.settings-daemon.plugins.color",
+ "org.gnome.settings-daemon.plugins.cursor",
+ "org.gnome.settings-daemon.plugins.power",
+ "org.gnome.settings-daemon.plugins.xrandr",
+ "org.gnome.settings-daemon.plugins.xsettings" };
+
+ foreach (var schema in disabled)
+ set_plugin_enabled (schema, false);
+
+ foreach (var schema in enabled)
+ set_plugin_enabled (schema, true);
+
+ /* Pretend to be GNOME session */
+ session_manager = new SessionManagerInterface ();
+ n_names++;
+ GLib.Bus.own_name (BusType.SESSION, "org.gnome.SessionManager", BusNameOwnerFlags.NONE,
+ (c) =>
+ {
+ try
+ {
+ c.register_object ("/org/gnome/SessionManager", session_manager);
+ }
+ catch (Error e)
+ {
+ warning ("Failed to register /org/gnome/SessionManager: %s", e.message);
+ }
+ },
+ () =>
+ {
+ debug ("Acquired org.gnome.SessionManager");
+ start_settings_daemon ();
+ },
+ () => debug ("Failed to acquire name org.gnome.SessionManager"));
+
+ /* The power plugin does the screen saver screen blanking and disables
+ * the builtin X screen saver. It relies on gnome-screensaver to generate
+ * the event to trigger this (which actually comes from gnome-session).
+ * We implement the gnome-screensaver inteface and start the settings
+ * daemon once it is registered on the bus so gnome-screensaver is not
+ * started when it accesses this interface */
+ screen_saver = new ScreenSaverInterface ();
+ n_names++;
+ GLib.Bus.own_name (BusType.SESSION, "org.gnome.ScreenSaver", BusNameOwnerFlags.NONE,
+ (c) =>
+ {
+ try
+ {
+ c.register_object ("/org/gnome/ScreenSaver", screen_saver);
+ }
+ catch (Error e)
+ {
+ warning ("Failed to register /org/gnome/ScreenSaver: %s", e.message);
+ }
+ },
+ () =>
+ {
+ debug ("Acquired org.gnome.ScreenSaver");
+ start_settings_daemon ();
+ },
+ () => debug ("Failed to acquire name org.gnome.ScreenSaver"));
+
+ /* The media-keys plugin inhibits the power key, but we don't want
+ all the other keys doing things. So inhibit it ourselves */
+ /* NOTE: We are using the synchronous method here since there is a bug in Vala/GLib in that
+ * g_dbus_connection_call_with_unix_fd_list_finish and g_dbus_proxy_call_with_unix_fd_list_finish
+ * don't have the GAsyncResult as the second argument.
+ * https://bugzilla.gnome.org/show_bug.cgi?id=688907
+ */
+ try
+ {
+ var b = Bus.get_sync (BusType.SYSTEM);
+ UnixFDList fd_list;
+ var result = b.call_with_unix_fd_list_sync ("org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "Inhibit",
+ new Variant ("(ssss)",
+ "handle-power-key",
+ Environment.get_user_name (),
+ "Unity Greeter handling keypresses",
+ "block"),
+ new VariantType ("(h)"),
+ DBusCallFlags.NONE,
+ -1,
+ null,
+ out fd_list);
+ int32 index = -1;
+ result.get ("(h)", &index);
+ logind_inhibit_fd = fd_list.get (index);
+ }
+ catch (Error e)
+ {
+ warning ("Failed to inhibit power keys: %s", e.message);
+ }
+ }
+
+ private void set_plugin_enabled (string schema_name, bool enabled)
+ {
+ var source = SettingsSchemaSource.get_default ();
+ var schema = source.lookup (schema_name, false);
+ if (schema != null)
+ {
+ var settings = new Settings (schema_name);
+ settings.set_boolean ("active", enabled);
+ }
+ }
+
+ private void start_settings_daemon ()
+ {
+ n_names--;
+ if (n_names != 0)
+ return;
+
+ debug ("All bus names acquired, starting unity-settings-daemon");
+
+ try
+ {
+ Process.spawn_command_line_async (Config.USD_BINARY);
+ }
+ catch (SpawnError e)
+ {
+ debug ("Could not start unity-settings-daemon: %s", e.message);
+ }
+ }
+}
+
+[DBus (name="org.gnome.ScreenSaver")]
+public class ScreenSaverInterface : Object
+{
+ public signal void active_changed (bool value);
+
+ private Gnome.IdleMonitor idle_monitor;
+ private bool _active = false;
+ private uint idle_watch = 0;
+
+ public ScreenSaverInterface ()
+ {
+ idle_monitor = new Gnome.IdleMonitor ();
+ _set_active (false);
+ }
+
+ private void _set_active (bool value)
+ {
+ _active = value;
+ if (idle_watch != 0)
+ idle_monitor.remove_watch (idle_watch);
+ idle_watch = 0;
+ if (value)
+ idle_monitor.add_user_active_watch (() => set_active (false));
+ else
+ {
+ var timeout = UGSettings.get_integer (UGSettings.KEY_IDLE_TIMEOUT);
+ if (timeout > 0)
+ idle_watch = idle_monitor.add_idle_watch (timeout * 1000, () => set_active (true));
+ }
+ }
+
+ public void set_active (bool value)
+ {
+ if (_active == value)
+ return;
+
+ if (value)
+ debug ("Screensaver activated");
+ else
+ debug ("Screensaver disabled");
+
+ _set_active (value);
+ active_changed (value);
+ }
+
+ public bool get_active ()
+ {
+ return _active;
+ }
+
+ public uint32 get_active_time () { return 0; }
+ public void lock () {}
+ public void show_message (string summary, string body, string icon) {}
+ public void simulate_user_activity () {}
+}
+
+[DBus (name="org.gnome.SessionManager")]
+public class SessionManagerInterface : Object
+{
+ public bool session_is_active { get { return true; } }
+ public string session_name { get { return "ubuntu"; } }
+ public uint32 inhibited_actions { get { return 0; } }
+}
diff --git a/src/settings.vala b/src/settings.vala
new file mode 100644
index 0000000..ebdc9e7
--- /dev/null
+++ b/src/settings.vala
@@ -0,0 +1,101 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+public class UGSettings
+{
+ public static const string KEY_BACKGROUND = "background";
+ public static const string KEY_BACKGROUND_COLOR = "background-color";
+ public static const string KEY_DRAW_USER_BACKGROUNDS = "draw-user-backgrounds";
+ public static const string KEY_DRAW_GRID = "draw-grid";
+ public static const string KEY_SHOW_HOSTNAME = "show-hostname";
+ public static const string KEY_LOGO = "logo";
+ public static const string KEY_BACKGROUND_LOGO = "background-logo";
+ public static const string KEY_THEME_NAME = "theme-name";
+ public static const string KEY_ICON_THEME_NAME = "icon-theme-name";
+ public static const string KEY_FONT_NAME = "font-name";
+ public static const string KEY_XFT_ANTIALIAS = "xft-antialias";
+ public static const string KEY_XFT_DPI = "xft-dpi";
+ public static const string KEY_XFT_HINTSTYLE = "xft-hintstyle";
+ public static const string KEY_XFT_RGBA = "xft-rgba";
+ public static const string KEY_ONSCREEN_KEYBOARD = "onscreen-keyboard";
+ public static const string KEY_HIGH_CONTRAST = "high-contrast";
+ public static const string KEY_SCREEN_READER = "screen-reader";
+ public static const string KEY_PLAY_READY_SOUND = "play-ready-sound";
+ public static const string KEY_INDICATORS = "indicators";
+ public static const string KEY_HIDDEN_USERS = "hidden-users";
+ public static const string KEY_IDLE_TIMEOUT = "idle-timeout";
+
+ public static bool get_boolean (string key)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.get_boolean (key);
+ }
+
+ /* LP: 1006497 - utility function to make sure we have the key before trying to read it (which will segfault if the key isn't there) */
+ public static bool safe_get_boolean (string key, bool default)
+ {
+ var gsettings = new Settings (SCHEMA);
+ string[] keys = gsettings.list_keys ();
+ foreach (var k in keys)
+ if (k == key)
+ return gsettings.get_boolean (key);
+
+ /* key not in child list */
+ return default;
+ }
+
+ public static int get_integer (string key)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.get_int (key);
+ }
+
+ public static double get_double (string key)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.get_double (key);
+ }
+
+ public static string get_string (string key)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.get_string (key);
+ }
+
+ public static bool set_boolean (string key, bool value)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.set_boolean (key, value);
+ }
+
+ public static string[] get_strv (string key)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.get_strv (key);
+ }
+
+ public static bool set_strv (string key, string[] value)
+ {
+ var gsettings = new Settings (SCHEMA);
+ return gsettings.set_strv (key, value);
+ }
+
+ private static const string SCHEMA = "com.canonical.unity-greeter";
+}
diff --git a/src/shutdown-dialog.vala b/src/shutdown-dialog.vala
new file mode 100644
index 0000000..73e1bf4
--- /dev/null
+++ b/src/shutdown-dialog.vala
@@ -0,0 +1,622 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2013 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Marco Trevisan <marco.trevisan@canonical.com>
+ */
+
+public enum ShutdownDialogType
+{
+ LOGOUT,
+ SHUTDOWN,
+ RESTART
+}
+
+public class ShutdownDialog : Gtk.Fixed
+{
+ public signal void closed ();
+
+ private Cairo.ImageSurface? bg_surface = null;
+ private Cairo.ImageSurface? corner_surface = null;
+ private Cairo.ImageSurface? left_surface = null;
+ private Cairo.ImageSurface? top_surface = null;
+ private Cairo.Pattern? corner_pattern = null;
+ private Cairo.Pattern? left_pattern = null;
+ private Cairo.Pattern? top_pattern = null;
+
+ private const int BORDER_SIZE = 30;
+ private const int BORDER_INTERNAL_SIZE = 10;
+ private const int BORDER_EXTERNAL_SIZE = BORDER_SIZE - BORDER_INTERNAL_SIZE;
+ private const int CLOSE_OFFSET = 3;
+ private const int BUTTON_TEXT_SPACE = 9;
+ private const int BLUR_RADIUS = 8;
+
+ private Monitor monitor;
+ private weak Background background;
+ private Gdk.RGBA avg_color;
+
+ private Gtk.Box vbox;
+ private DialogButton close_button;
+ private Gtk.Box button_box;
+ private Gtk.EventBox monitor_events;
+ private Gtk.EventBox vbox_events;
+
+ private AnimateTimer animation;
+ private bool closing = false;
+
+
+ public ShutdownDialog (ShutdownDialogType type, Background bg)
+ {
+ background = bg;
+ background.notify["alpha"].connect (rebuild_background);
+ background.notify["average-color"].connect (update_background_color);
+ update_background_color ();
+
+ // This event box covers the monitor size, and closes the dialog on click.
+ monitor_events = new Gtk.EventBox ();
+ monitor_events.visible = true;
+ monitor_events.set_visible_window (false);
+ monitor_events.events |= Gdk.EventMask.BUTTON_PRESS_MASK;
+ monitor_events.button_press_event.connect (() => {
+ close ();
+ return true;
+ });
+ add (monitor_events);
+
+ vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 10);
+ vbox.visible = true;
+
+ vbox.margin = BORDER_INTERNAL_SIZE;
+ vbox.margin_top += 9;
+ vbox.margin_left += 20;
+ vbox.margin_right += 20;
+ vbox.margin_bottom += 2;
+
+ // This event box consumes the click events inside the vbox
+ vbox_events = new Gtk.EventBox();
+ vbox_events.visible = true;
+ vbox_events.set_visible_window (false);
+ vbox_events.events |= Gdk.EventMask.BUTTON_PRESS_MASK;
+ vbox_events.button_press_event.connect (() => { return true; });
+ vbox_events.add (vbox);
+ monitor_events.add (vbox_events);
+
+ string text;
+
+ if (type == ShutdownDialogType.SHUTDOWN)
+ {
+ text = _("Goodbye. Would you like to…");
+ }
+ else
+ {
+ var title_label = new Gtk.Label (_("Shut Down"));
+ title_label.visible = true;
+ title_label.override_font (Pango.FontDescription.from_string ("Ubuntu Light 15"));
+ title_label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f });
+ title_label.set_alignment (0.0f, 0.5f);
+ vbox.pack_start (title_label, false, false, 0);
+
+ text = _("Are you sure you want to shut down the computer?");
+ }
+
+ var have_open_sessions = false;
+ try
+ {
+ var b = Bus.get_sync (BusType.SYSTEM);
+ var result = b.call_sync ("org.freedesktop.DisplayManager",
+ "/org/freedesktop/DisplayManager",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ new Variant ("(ss)", "org.freedesktop.DisplayManager", "Sessions"),
+ new VariantType ("(v)"),
+ DBusCallFlags.NONE,
+ -1,
+ null);
+ Variant value;
+ result.get ("(v)", out value);
+ have_open_sessions = value.n_children () > 0;
+ }
+ catch (Error e)
+ {
+ warning ("Failed to check sessions from logind: %s", e.message);
+ }
+ if (have_open_sessions)
+ text = "%s\n\n%s".printf (_("Other users are currently logged in to this computer, shutting down now will also close these other sessions."), text);
+
+ var label = new Gtk.Label (text);
+ label.set_line_wrap (true);
+ label.override_font (Pango.FontDescription.from_string ("Ubuntu Light 12"));
+ label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f });
+ label.set_alignment (0.0f, 0.5f);
+ label.visible = true;
+ vbox.pack_start (label, false, false, 0);
+
+ button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 20);
+ button_box.visible = true;
+ vbox.pack_start (button_box, false, false, 0);
+
+ if (type == ShutdownDialogType.SHUTDOWN)
+ {
+ if (LightDM.get_can_suspend ())
+ {
+ var button = add_button (_("Suspend"), Path.build_filename (Config.PKGDATADIR, "suspend.png"), Path.build_filename (Config.PKGDATADIR, "suspend_highlight.png"));
+ button.clicked.connect (() =>
+ {
+ try
+ {
+ LightDM.suspend ();
+ close ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to suspend: %s", e.message);
+ }
+ });
+ }
+
+ if (LightDM.get_can_hibernate ())
+ {
+ var button = add_button (_("Hibernate"), Path.build_filename (Config.PKGDATADIR, "hibernate.png"), Path.build_filename (Config.PKGDATADIR, "hibernate_highlight.png"));
+ button.clicked.connect (() =>
+ {
+ try
+ {
+ LightDM.hibernate ();
+ close ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to hibernate: %s", e.message);
+ }
+ });
+ }
+ }
+
+ if (LightDM.get_can_restart ())
+ {
+ var button = add_button (_("Restart"), Path.build_filename (Config.PKGDATADIR, "restart.png"), Path.build_filename (Config.PKGDATADIR, "restart_highlight.png"));
+ button.clicked.connect (() =>
+ {
+ try
+ {
+ LightDM.restart ();
+ close ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to restart: %s", e.message);
+ }
+ });
+ }
+
+ if (LightDM.get_can_shutdown ())
+ {
+ var button = add_button (_("Shut Down"), Path.build_filename (Config.PKGDATADIR, "shutdown.png"), Path.build_filename (Config.PKGDATADIR, "shutdown_highlight.png"));
+ button.clicked.connect (() =>
+ {
+ try
+ {
+ LightDM.shutdown ();
+ close ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to shutdown: %s", e.message);
+ }
+ });
+
+ if (type != ShutdownDialogType.SHUTDOWN)
+ show.connect(() => { button.grab_focus (); });
+ }
+
+ close_button = new DialogButton (Path.build_filename (Config.PKGDATADIR, "dialog_close.png"), Path.build_filename (Config.PKGDATADIR, "dialog_close_highlight.png"), Path.build_filename (Config.PKGDATADIR, "dialog_close_press.png"));
+ close_button.can_focus = false;
+ close_button.clicked.connect (() => { close (); });
+ close_button.visible = true;
+ add (close_button);
+
+ animation = new AnimateTimer ((x) => { return x; }, AnimateTimer.INSTANT);
+ animation.animate.connect (() => { queue_draw (); });
+ show.connect (() => { animation.reset(); });
+ }
+
+ public void close ()
+ {
+ var start_value = 1.0f - animation.progress;
+ animation = new AnimateTimer ((x) => { return start_value + x; }, AnimateTimer.INSTANT);
+ animation.animate.connect ((p) =>
+ {
+ queue_draw ();
+
+ if (p >= 1.0f)
+ {
+ animation.stop ();
+ closed ();
+ }
+ });
+
+ closing = true;
+ animation.reset();
+ }
+
+ private void rebuild_background ()
+ {
+ bg_surface = null;
+ queue_draw ();
+ }
+
+ private void update_background_color ()
+ {
+ // Apply the same color corrections we do in Unity
+ // For reference, see unity's unity-shared/BGHash.cpp
+ double hue, saturation, value;
+ const double COLOR_ALPHA = 0.72f;
+
+ Gdk.RGBA color = background.average_color;
+ Gtk.RGB.to_hsv (color.red, color.green, color.blue,
+ out hue, out saturation, out value);
+
+ if (saturation < 0.08)
+ {
+ // Got a grayscale image
+ avg_color = {0.18f, 0.20f, 0.21f, COLOR_ALPHA };
+ }
+ else
+ {
+ const Gdk.RGBA[] cmp_colors =
+ {
+ {84/255.0f, 14/255.0f, 68/255.0f, 1.0f},
+ {110/255.0f, 11/255.0f, 42/255.0f, 1.0f},
+ {132/255.0f, 22/255.0f, 23/255.0f, 1.0f},
+ {132/255.0f, 55/255.0f, 27/255.0f, 1.0f},
+ {134/255.0f, 77/255.0f, 32/255.0f, 1.0f},
+ {133/255.0f, 127/255.0f, 49/255.0f, 1.0f},
+ {29/255.0f, 99/255.0f, 49/255.0f, 1.0f},
+ {17/255.0f, 88/255.0f, 46/255.0f, 1.0f},
+ {14/255.0f, 89/255.0f, 85/255.0f, 1.0f},
+ {25/255.0f, 43/255.0f, 89/255.0f, 1.0f},
+ {27/255.0f, 19/255.0f, 76/255.0f, 1.0f},
+ {2/255.0f, 192/255.0f, 212/255.0f, 1.0f}
+ };
+
+ avg_color = {0, 0, 0, 1};
+ double closest_diff = 200.0f;
+
+ foreach (var c in cmp_colors)
+ {
+ double cmp_hue, cmp_sat, cmp_value;
+ Gtk.RGB.to_hsv (c.red, c.green, c.blue,
+ out cmp_hue, out cmp_sat, out cmp_value);
+ double color_diff = Math.fabs (hue - cmp_hue);
+
+ if (color_diff < closest_diff)
+ {
+ avg_color = c;
+ closest_diff = color_diff;
+ }
+ }
+
+ double new_hue, new_saturation, new_value;
+ Gtk.RGB.to_hsv (avg_color.red, avg_color.green, avg_color.blue,
+ out new_hue, out new_saturation, out new_value);
+
+ saturation = double.min (saturation, new_saturation);
+ saturation *= (2.0f - saturation);
+ value = double.min (double.min (value, new_value), 0.26f);
+ Gtk.HSV.to_rgb (hue, saturation, value,
+ out avg_color.red, out avg_color.green, out avg_color.blue);
+ avg_color.alpha = COLOR_ALPHA;
+ }
+
+ rebuild_background ();
+ }
+
+ public void set_active_monitor (Monitor m)
+ {
+ if (m == this.monitor || m.equals (this.monitor))
+ return;
+
+ monitor = m;
+ rebuild_background ();
+ set_size_request (monitor.width, monitor.height);
+ }
+
+ public void focus_next ()
+ {
+ (get_toplevel () as Gtk.Window).move_focus (Gtk.DirectionType.TAB_FORWARD);
+ }
+
+ public void focus_prev ()
+ {
+ (get_toplevel () as Gtk.Window).move_focus (Gtk.DirectionType.TAB_BACKWARD);
+ }
+
+ public void cancel ()
+ {
+ var widget = (get_toplevel () as Gtk.Window).get_focus ();
+ if (widget is DialogButton)
+ (get_toplevel () as Gtk.Window).set_focus (null);
+ else
+ close ();
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+ monitor_events.size_allocate (allocation);
+
+ var content_allocation = Gtk.Allocation ();
+ int minimum_width, natural_width, minimum_height, natural_height;
+ vbox_events.get_preferred_width (out minimum_width, out natural_width);
+ vbox_events.get_preferred_height_for_width (minimum_width, out minimum_height, out natural_height);
+ content_allocation.x = allocation.x + (allocation.width - minimum_width) / 2;
+ content_allocation.y = allocation.y + (allocation.height - minimum_height) / 2;
+ content_allocation.width = minimum_width;
+ content_allocation.height = minimum_height;
+ vbox_events.size_allocate (content_allocation);
+
+ var a = Gtk.Allocation ();
+ close_button.get_preferred_width (out minimum_width, out natural_width);
+ close_button.get_preferred_height (out minimum_height, out natural_height);
+ a.x = content_allocation.x - BORDER_EXTERNAL_SIZE + CLOSE_OFFSET;
+ a.y = content_allocation.y - BORDER_EXTERNAL_SIZE + CLOSE_OFFSET;
+ a.width = minimum_width;
+ a.height = minimum_height;
+ close_button.size_allocate (a);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ if (corner_surface == null)
+ {
+ corner_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "switcher_corner.png"));
+ left_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "switcher_left.png"));
+ top_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "switcher_top.png"));
+ corner_pattern = new Cairo.Pattern.for_surface (corner_surface);
+ left_pattern = new Cairo.Pattern.for_surface (left_surface);
+ left_pattern.set_extend (Cairo.Extend.REPEAT);
+ top_pattern = new Cairo.Pattern.for_surface (top_surface);
+ top_pattern.set_extend (Cairo.Extend.REPEAT);
+ }
+
+ int width = vbox_events.get_allocated_width ();
+ int height = vbox_events.get_allocated_height ();
+ int x = (get_allocated_width () - width) / 2;
+ int y = (get_allocated_height () - height) / 2;
+
+ if (animation.is_running)
+ c.push_group ();
+
+ /* Darken background */
+ c.set_source_rgba (0, 0, 0, 0.25);
+ c.paint ();
+
+ if (bg_surface == null || animation.is_running)
+ {
+ /* Create a new blurred surface of the current surface */
+ bg_surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
+ var bg_cr = new Cairo.Context (bg_surface);
+
+ bg_cr.set_source_surface (c.get_target (), -x - monitor.x, -y - monitor.y);
+ bg_cr.rectangle (0, 0, width, height);
+ bg_cr.fill ();
+
+ CairoUtils.ExponentialBlur.surface (bg_surface, BLUR_RADIUS);
+ }
+
+ /* Background */
+ c.save ();
+ c.translate (x, y);
+
+ CairoUtils.rounded_rectangle (c, 0, 0, width, height, 4);
+ c.set_source_surface (bg_surface, 0, 0);
+ c.fill_preserve ();
+ c.set_source_rgba (avg_color.red, avg_color.green, avg_color.blue, avg_color.alpha);
+ c.fill ();
+
+ c.restore();
+
+ /* Draw borders */
+ x -= BORDER_EXTERNAL_SIZE;
+ y -= BORDER_EXTERNAL_SIZE;
+ width += BORDER_EXTERNAL_SIZE * 2;
+ height += BORDER_EXTERNAL_SIZE * 2;
+
+ c.save ();
+ c.translate (x, y);
+
+ /* Top left */
+ var m = Cairo.Matrix.identity ();
+ corner_pattern.set_matrix (m);
+ c.set_source (corner_pattern);
+ c.rectangle (0, 0, BORDER_SIZE, BORDER_SIZE);
+ c.fill ();
+
+ /* Top right */
+ m = Cairo.Matrix.identity ();
+ m.translate (width, 0);
+ m.scale (-1, 1);
+ corner_pattern.set_matrix (m);
+ c.set_source (corner_pattern);
+ c.rectangle (width - BORDER_SIZE, 0, BORDER_SIZE, BORDER_SIZE);
+ c.fill ();
+
+ /* Bottom left */
+ m = Cairo.Matrix.identity ();
+ m.translate (0, height);
+ m.scale (1, -1);
+ corner_pattern.set_matrix (m);
+ c.set_source (corner_pattern);
+ c.rectangle (0, height - BORDER_SIZE, BORDER_SIZE, BORDER_SIZE);
+ c.fill ();
+
+ /* Bottom right */
+ m = Cairo.Matrix.identity ();
+ m.translate (width, height);
+ m.scale (-1, -1);
+ corner_pattern.set_matrix (m);
+ c.set_source (corner_pattern);
+ c.rectangle (width - BORDER_SIZE, height - BORDER_SIZE, BORDER_SIZE, BORDER_SIZE);
+ c.fill ();
+
+ /* Left */
+ m = Cairo.Matrix.identity ();
+ left_pattern.set_matrix (m);
+ c.set_source (left_pattern);
+ c.rectangle (0, BORDER_SIZE, BORDER_SIZE, height - BORDER_SIZE * 2);
+ c.fill ();
+
+ /* Right */
+ m = Cairo.Matrix.identity ();
+ m.translate (width, 0);
+ m.scale (-1, 1);
+ left_pattern.set_matrix (m);
+ c.set_source (left_pattern);
+ c.rectangle (width - BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, height - BORDER_SIZE * 2);
+ c.fill ();
+
+ /* Top */
+ m = Cairo.Matrix.identity ();
+ top_pattern.set_matrix (m);
+ c.set_source (top_pattern);
+ c.rectangle (BORDER_SIZE, 0, width - BORDER_SIZE * 2, BORDER_SIZE);
+ c.fill ();
+
+ /* Bottom */
+ m = Cairo.Matrix.identity ();
+ m.translate (0, height);
+ m.scale (1, -1);
+ top_pattern.set_matrix (m);
+ c.set_source (top_pattern);
+ c.rectangle (BORDER_SIZE, height - BORDER_SIZE, width - BORDER_SIZE * 2, BORDER_SIZE);
+ c.fill ();
+
+ c.restore ();
+
+ var ret = base.draw (c);
+
+ if (animation.is_running)
+ {
+ c.pop_group_to_source ();
+ c.paint_with_alpha (closing ? 1.0f - animation.progress : animation.progress);
+ }
+
+ return ret;
+ }
+
+ private DialogButton add_button (string text, string inactive_filename, string active_filename)
+ {
+ var b = new Gtk.Box (Gtk.Orientation.VERTICAL, BUTTON_TEXT_SPACE);
+ b.visible = true;
+ button_box.pack_start (b, false, false, 0);
+
+ var label = new Gtk.Label (text);
+ var button = new DialogButton (inactive_filename, active_filename, null, label);
+ button.visible = true;
+
+ b.pack_start (button, false, false, 0);
+ b.pack_start (label, false, false, 0);
+
+ return button;
+ }
+}
+
+private class DialogButton : Gtk.Button
+{
+ private string inactive_filename;
+ private string focused_filename;
+ private string? active_filename;
+ private Gtk.Image i;
+ private Gtk.Label? l;
+
+ public DialogButton (string inactive_filename, string focused_filename, string? active_filename, Gtk.Label? label = null)
+ {
+ this.inactive_filename = inactive_filename;
+ this.focused_filename = focused_filename;
+ this.active_filename = active_filename;
+ relief = Gtk.ReliefStyle.NONE;
+ focus_on_click = false;
+ i = new Gtk.Image.from_file (inactive_filename);
+ i.visible = true;
+ add (i);
+
+ l = label;
+
+ if (l != null)
+ {
+ l.visible = true;
+ l.override_font (Pango.FontDescription.from_string ("Ubuntu Light 12"));
+ l.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 0.0f });
+ l.override_color (Gtk.StateFlags.FOCUSED, { 1.0f, 1.0f, 1.0f, 1.0f });
+ l.override_color (Gtk.StateFlags.ACTIVE, { 1.0f, 1.0f, 1.0f, 1.0f });
+ this.get_accessible ().set_name (l.get_text ());
+ }
+
+ UnityGreeter.add_style_class (this);
+ try
+ {
+ // Remove the default GtkButton paddings and border
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {padding: 0px 0px 0px 0px; border: 0px; }", -1);
+ get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading session chooser style: %s", e.message);
+ }
+ }
+
+ public override bool enter_notify_event (Gdk.EventCrossing event)
+ {
+ grab_focus ();
+ return base.enter_notify_event (event);
+ }
+
+ public override bool leave_notify_event (Gdk.EventCrossing event)
+ {
+ (get_toplevel () as Gtk.Window).set_focus (null);
+ return base.leave_notify_event (event);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ i.draw (c);
+ return true;
+ }
+
+ public override void state_flags_changed (Gtk.StateFlags previous_state)
+ {
+ var new_flags = get_state_flags ();
+
+ if ((new_flags & Gtk.StateFlags.PRELIGHT) != 0 && !can_focus ||
+ (new_flags & Gtk.StateFlags.FOCUSED) != 0)
+ {
+ if ((new_flags & Gtk.StateFlags.ACTIVE) != 0 && active_filename != null)
+ i.set_from_file (active_filename);
+ else
+ i.set_from_file (focused_filename);
+ }
+ else
+ {
+ i.set_from_file (inactive_filename);
+ }
+
+ if (l != null)
+ l.set_state_flags (new_flags, true);
+
+ base.state_flags_changed (previous_state);
+ }
+}
diff --git a/src/toggle-box.vala b/src/toggle-box.vala
new file mode 100644
index 0000000..1862951
--- /dev/null
+++ b/src/toggle-box.vala
@@ -0,0 +1,131 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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/>.
+ *
+ * Authors: Michael Terry <michael.terry@canonical.com>
+ */
+
+public class ToggleBox : Gtk.Box
+{
+ public string default_key {get; construct;}
+ public string starting_key {get; construct;}
+ public string selected_key {get; protected set;}
+
+ public ToggleBox (string? default_key, string? starting_key)
+ {
+ Object (default_key: default_key, starting_key: starting_key,
+ selected_key: starting_key);
+ }
+
+ public void add_item (string key, string label, Gdk.Pixbuf? icon)
+ {
+ var item = make_button (key, label, icon);
+
+ if (get_children () == null ||
+ (starting_key == null && default_key == key) ||
+ starting_key == key)
+ select (item);
+
+ item.show ();
+ add (item);
+ }
+
+ private Gtk.Button selected_button;
+
+ construct
+ {
+ orientation = Gtk.Orientation.VERTICAL;
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ CairoUtils.rounded_rectangle (c, 0, 0, allocation.width,
+ allocation.height, 0.1 * grid_size);
+ c.set_source_rgba (0.5, 0.5, 0.5, 0.5);
+ c.set_line_width (1);
+ c.stroke ();
+
+ return base.draw (c);
+ }
+
+ private void select (Gtk.Button button)
+ {
+ if (selected_button != null)
+ selected_button.relief = Gtk.ReliefStyle.NONE;
+ selected_button = button;
+ selected_button.relief = Gtk.ReliefStyle.NORMAL;
+ selected_key = selected_button.get_data<string> ("toggle-list-key");
+ }
+
+ private Gtk.Button make_button (string key, string name_in, Gdk.Pixbuf? icon)
+ {
+ var item = new FlatButton ();
+ item.relief = Gtk.ReliefStyle.NONE;
+ item.clicked.connect (button_clicked_cb);
+
+ var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+
+ if (icon != null)
+ {
+ var image = new CachedImage (icon);
+ hbox.pack_start (image, false, false, 0);
+ }
+
+ var name = name_in;
+ if (key == default_key)
+ {
+ /* Translators: %s is a session name like KDE or Ubuntu */
+ name = _("%s (Default)").printf (name);
+ }
+
+ var label = new Gtk.Label (null);
+ label.set_markup ("<span font=\"Ubuntu 13\">%s</span>".printf (name));
+ label.halign = Gtk.Align.START;
+ hbox.pack_start (label, true, true, 0);
+
+ item.hexpand = true;
+ item.add (hbox);
+ hbox.show_all ();
+
+ try
+ {
+ /* Tighten padding on buttons to not be so large */
+ var style = new Gtk.CssProvider ();
+ style.load_from_data ("* {padding: 8px;}", -1);
+ item.get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading session chooser style: %s", e.message);
+ }
+
+ item.set_data<string> ("toggle-list-key", key);
+ return item;
+ }
+
+ private void button_clicked_cb (Gtk.Button button)
+ {
+ selected_key = button.get_data<string> ("toggle-list-key");
+ }
+
+ public override void grab_focus ()
+ {
+ if (selected_button != null)
+ selected_button.grab_focus ();
+ }
+}
diff --git a/src/unity-greeter.vala b/src/unity-greeter.vala
new file mode 100644
index 0000000..cf501af
--- /dev/null
+++ b/src/unity-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 UnityGreeter
+{
+ public static UnityGreeter 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 UnityGreeter (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 (), "unity-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.UnityGreeter", 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)
+ {
+ UnityGreeter.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 unity-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 */
+ _("- Unity 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 ("unity-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 Unity Greeter");
+ var greeter = new UnityGreeter (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 ();
+ }
+}
diff --git a/src/user-list.vala b/src/user-list.vala
new file mode 100644
index 0000000..5f2bceb
--- /dev/null
+++ b/src/user-list.vala
@@ -0,0 +1,1603 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+int remote_server_field_sort_function (RemoteServerField? item1, RemoteServerField? item2)
+{
+ string[] sorted_fields = { "domain", "username", "email", "password" };
+ foreach (var field in sorted_fields)
+ {
+ if (item1.type == field)
+ return -1;
+ if (item2.type == field)
+ return 1;
+ }
+
+ return (item1.type < item2.type) ? -1 : 0;
+}
+
+public class UserList : GreeterList
+{
+ private bool _offer_guest = false;
+ public bool offer_guest
+ {
+ get { return _offer_guest; }
+ set
+ {
+ _offer_guest = value;
+ if (value)
+ add_user ("*guest", _("Guest Session"));
+ else
+ remove_entry ("*guest");
+ }
+ }
+
+ private Gdk.Pixbuf message_pixbuf;
+
+ private uint change_background_timeout = 0;
+
+ private uint remote_login_service_watch;
+ private RemoteLoginService remote_login_service;
+ private List<RemoteServer?> remote_directory_server_list = new List<RemoteServer?> ();
+ private List<RemoteServer?> remote_login_server_list = new List<RemoteServer?> ();
+ private HashTable<string, Gtk.Widget> current_remote_fields;
+ private string currently_browsing_server_url;
+ private string currently_browsing_server_email;
+ private EmailAutocompleter remote_server_email_field_autocompleter;
+
+ /* User to authenticate against */
+ private string ?authenticate_user = null;
+
+ private bool show_hidden_users_ = false;
+ public bool show_hidden_users
+ {
+ set
+ {
+ show_hidden_users_ = value;
+
+ if (UnityGreeter.singleton.test_mode)
+ {
+ if (value)
+ add_user ("hidden", "Hidden User", null, false, false, null);
+ else
+ remove_entry ("hidden");
+ return;
+ }
+
+ var hidden_users = UGSettings.get_strv (UGSettings.KEY_HIDDEN_USERS);
+ if (!value)
+ {
+ foreach (var username in hidden_users)
+ remove_entry (username);
+ return;
+ }
+
+ var users = LightDM.UserList.get_instance ();
+ foreach (var user in users.users)
+ {
+ foreach (var username in hidden_users)
+ {
+ if (user.name == username)
+ {
+ debug ("Showing hidden user %s", username);
+ user_added_cb (user);
+ }
+ }
+ }
+ }
+
+ get
+ {
+ return show_hidden_users_;
+ }
+ }
+
+ private string _default_session = "ubuntu";
+ public string default_session
+ {
+ get
+ {
+ return _default_session;
+ }
+ set
+ {
+ _default_session = value;
+ if (selected_entry != null)
+ selected_entry.set_options_image (get_badge ());
+ }
+ }
+
+ private string? _session = null;
+ public string? session
+ {
+ get
+ {
+ return _session;
+ }
+ set
+ {
+ _session = value;
+ if (selected_entry != null)
+ selected_entry.set_options_image (get_badge ());
+ }
+ }
+
+ public UserList (Background bg, MenuBar mb)
+ {
+ Object (background: bg, menubar: mb);
+ }
+
+ construct
+ {
+ menubar.notify["high-contrast"].connect (() => { change_background (); });
+ entry_displayed_start.connect (() => { change_background (); });
+ entry_displayed_done.connect (() => { change_background (); });
+
+ try
+ {
+ message_pixbuf = new Gdk.Pixbuf.from_file (Path.build_filename (Config.PKGDATADIR, "message.png", null));
+ }
+ catch (Error e)
+ {
+ debug ("Error loading message image: %s", e.message);
+ }
+
+ fill_list ();
+
+ entry_selected.connect (entry_selected_cb);
+
+ connect_to_lightdm ();
+
+ if (!UnityGreeter.singleton.test_mode &&
+ UnityGreeter.singleton.show_remote_login_hint ())
+ remote_login_service_watch = Bus.watch_name (BusType.SESSION,
+ "com.canonical.RemoteLogin",
+ BusNameWatcherFlags.AUTO_START,
+ on_remote_login_service_appeared,
+ on_remote_login_service_vanished);
+
+ }
+
+ private void remove_remote_servers ()
+ {
+ remote_directory_server_list = new List<RemoteServer?> ();
+ remote_login_server_list = new List<RemoteServer?> ();
+ remove_entries_with_prefix ("*remote");
+ }
+
+ private void remove_remote_login_servers ()
+ {
+ remote_login_server_list = new List<RemoteServer?> ();
+ remove_entries_with_prefix ("*remote_login");
+
+ /* If we have no entries at all, we should show manual */
+ if (!always_show_manual)
+ add_manual_entry ();
+ }
+
+ private async void query_directory_servers ()
+ {
+ try
+ {
+ RemoteServer[] server_list;
+ yield remote_login_service.get_servers (out server_list);
+ set_remote_directory_servers (server_list);
+ }
+ catch (IOError e)
+ {
+ debug ("Calling GetServers on com.canonical.RemoteLogin dbus service failed. Error: %s", e.message);
+ remove_remote_servers ();
+ }
+ }
+
+ private string user_list_name_for_remote_directory_server (RemoteServer remote_server)
+ {
+ return "*remote_directory*" + remote_server.url;
+ }
+
+ private string username_from_remote_server_fields(RemoteServer remote_server)
+ {
+ var username = "";
+ foreach (var f in remote_server.fields)
+ {
+ if (f.type == "username" && f.default_value != null)
+ {
+ username = f.default_value.get_string ();
+ break;
+ }
+ }
+ return username;
+ }
+
+ private string user_list_name_for_remote_login_server (RemoteServer remote_server)
+ {
+ var username = username_from_remote_server_fields (remote_server);
+ return "*remote_login*" + remote_server.url + "*" + username;
+ }
+
+ private string url_from_remote_loding_server_list_name (string remote_server_list_name)
+ {
+ return remote_server_list_name.split ("*")[2];
+ }
+
+ private string username_from_remote_loding_server_list_name (string remote_server_list_name)
+ {
+ return remote_server_list_name.split ("*")[3];
+ }
+
+ private void set_remote_directory_servers (RemoteServer[] server_list)
+ {
+ /* Add new servers */
+ foreach (var remote_server in server_list)
+ {
+ var list_name = user_list_name_for_remote_directory_server (remote_server);
+ if (find_entry (list_name) == null)
+ {
+ var e = new PromptBox (list_name);
+ e.label = remote_server.name;
+ e.respond.connect (remote_directory_respond_cb);
+ e.show_options.connect (show_remote_account_dialog);
+ add_entry (e);
+
+ remote_directory_server_list.append (remote_server);
+ }
+ }
+
+ /* Remove gone servers */
+ unowned List<RemoteServer?> it = remote_directory_server_list;
+ while (it != null)
+ {
+ var remote_server = it.data;
+ var found = false;
+ for (int i = 0; !found && i < server_list.length; i++)
+ {
+ found = remote_server.url == server_list[i].url;
+ }
+ if (!found)
+ {
+ if (remote_server.url == currently_browsing_server_url)
+ {
+ /* The server we where "browsing" disappeared, so kill its children */
+ remove_remote_login_servers ();
+ currently_browsing_server_url = "";
+ currently_browsing_server_email = "";
+ }
+ remove_entry (user_list_name_for_remote_directory_server (remote_server));
+ unowned List<RemoteServer?> newIt = it.next;
+ remote_directory_server_list.delete_link (it);
+ it = newIt;
+ }
+ else
+ {
+ it = it.next;
+ }
+ }
+
+ /* Remove manual option unless specified */
+ if (remote_directory_server_list.length() > 0 && !always_show_manual) {
+ debug ("removing manual login since we have a remote login entry");
+ remove_entry ("*other");
+ }
+ }
+
+ private PromptBox create_prompt_for_login_server (RemoteServer remote_server)
+ {
+ var e = new PromptBox (user_list_name_for_remote_login_server (remote_server));
+ e.label = remote_server.name;
+ e.respond.connect (remote_login_respond_cb);
+ add_entry (e);
+ remote_login_server_list.append (remote_server);
+
+ return e;
+ }
+
+ private void remote_login_servers_updated (string url, string email_address, string data_type, RemoteServer[] server_list)
+ {
+ if (currently_browsing_server_url == url && currently_browsing_server_email == email_address)
+ {
+ /* Add new servers */
+ foreach (var remote_server in server_list)
+ {
+ var list_name = user_list_name_for_remote_login_server (remote_server);
+ if (find_entry (list_name) == null)
+ create_prompt_for_login_server (remote_server);
+ }
+
+ /* Remove gone servers */
+ unowned List<RemoteServer?> it = remote_login_server_list;
+ while (it != null)
+ {
+ RemoteServer remote_server = it.data;
+ var found = false;
+ for (var i = 0; !found && i < server_list.length; i++)
+ found = remote_server.url == server_list[i].url;
+ if (!found)
+ {
+ remove_entry (user_list_name_for_remote_login_server (remote_server));
+ unowned List<RemoteServer?> newIt = it.next;
+ remote_login_server_list.delete_link (it);
+ it = newIt;
+ }
+ else
+ {
+ it = it.next;
+ }
+ }
+ }
+ }
+
+ private void remote_login_changed (string url, string email_address)
+ {
+ if (currently_browsing_server_url == url && currently_browsing_server_email == email_address)
+ {
+ /* Something happened and we are being asked for re-authentication by the remote-login-service */
+ remove_remote_login_servers ();
+ currently_browsing_server_url = "";
+ currently_browsing_server_email = "";
+
+ var directory_list_name = "*remote_directory*" + url;
+ set_active_entry (directory_list_name);
+ }
+ }
+
+ private void on_remote_login_service_appeared (DBusConnection conn, string name)
+ {
+ Bus.get_proxy.begin<RemoteLoginService> (BusType.SESSION,
+ "com.canonical.RemoteLogin",
+ "/com/canonical/RemoteLogin",
+ 0,
+ null,
+ (obj, res) => {
+ try
+ {
+ remote_login_service = Bus.get_proxy.end<RemoteLoginService> (res);
+ remote_login_service.servers_updated.connect (set_remote_directory_servers);
+ remote_login_service.login_servers_updated.connect (remote_login_servers_updated);
+ remote_login_service.login_changed.connect (remote_login_changed);
+ query_directory_servers.begin ();
+ }
+ catch (IOError e)
+ {
+ debug ("Getting the com.canonical.RemoteLogin dbus service failed. Error: %s", e.message);
+ remove_remote_servers ();
+ remote_login_service = null;
+ }
+ }
+ );
+ }
+
+ private void on_remote_login_service_vanished (DBusConnection conn, string name)
+ {
+ remove_remote_servers ();
+ remote_login_service = null;
+
+ /* provide a fallback manual login option */
+ if (UnityGreeter.singleton.hide_users_hint ()) {
+ add_manual_entry();
+ set_active_entry ("*other");
+ }
+ }
+
+ private async void remote_directory_respond_cb ()
+ {
+ remove_remote_login_servers ();
+ currently_browsing_server_url = "";
+ currently_browsing_server_email = "";
+
+ var password_field = current_remote_fields.get ("password") as DashEntry;
+ var email_field = current_remote_fields.get ("email") as Gtk.Entry;
+ if (password_field == null)
+ {
+ debug ("Something wrong happened in remote_directory_respond_cb. There was no password field");
+ return;
+ }
+ if (email_field == null)
+ {
+ debug ("Something wrong happened in remote_directory_respond_cb. There was no email field");
+ return;
+ }
+
+ RemoteServer[] server_list = {};
+ var email = email_field.text;
+ var email_valid = false;
+ try
+ {
+ /* Check email address is valid
+ * Using the html5 definition of a valid e-mail address
+ * http://www.w3.org/TR/html5/states-of-the-type-attribute.html#valid-e-mail-address */
+ var re = new Regex ("[a-zA-Z0-9.!#$%&'\\*\\+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*");
+ MatchInfo info;
+ email_valid = re.match_all (email, 0, out info);
+ email_valid = email_valid && info.get_match_count () > 0 && info.fetch (0) == email;
+ }
+ catch (RegexError e)
+ {
+ debug ("Calling email regex match failed. Error: %s", e.message);
+ }
+
+ selected_entry.reset_messages ();
+ if (!email_valid)
+ {
+ will_clear = true;
+ show_message (_("Please enter a complete e-mail address"), true);
+ create_remote_fields_for_current_item.begin (remote_directory_server_list);
+ }
+ else
+ {
+ var login_success = false;
+ try
+ {
+ var url = url_from_remote_loding_server_list_name (selected_entry.id);
+ if (UnityGreeter.singleton.test_mode)
+ {
+ if (password_field.text == "password")
+ {
+ test_fill_remote_login_servers (out server_list);
+ login_success = true;
+ }
+ else if (password_field.text == "delay1")
+ {
+ test_fill_remote_login_servers (out server_list);
+ login_success = true;
+ Timeout.add (5000, () => { test_call_set_remote_directory_servers (); return false; });
+ }
+ else if (password_field.text == "delay2")
+ {
+ test_fill_remote_login_servers (out server_list);
+ login_success = true;
+ Timeout.add (5000, () => { test_call_remote_login_servers_updated (); return false; });
+ }
+ else if (password_field.text == "delay3")
+ {
+ test_fill_remote_login_servers (out server_list);
+ login_success = true;
+ Timeout.add (5000, () => { remote_login_changed (currently_browsing_server_url, currently_browsing_server_email); return false; });
+ }
+ else if (password_field.text == "duplicate")
+ {
+ test_fill_remote_login_servers_duplicate_entries (out server_list);
+ login_success = true;
+ }
+ }
+ else
+ {
+ string data_type;
+ bool allowcache = true;
+ // If we had an error and are retrying the same user and server, do not use the cache on R-L-S
+ if (selected_entry.has_errors && currently_browsing_server_email == email && currently_browsing_server_url == url)
+ allowcache = false;
+ yield remote_login_service.get_servers_for_login (url, email, password_field.text, allowcache, out login_success, out data_type, out server_list);
+ }
+ currently_browsing_server_url = url;
+ currently_browsing_server_email = email;
+ }
+ catch (IOError e)
+ {
+ debug ("Calling get_servers in com.canonical.RemoteLogin dbus service failed. Error: %s", e.message);
+ }
+
+ if (login_success)
+ {
+ password_field.did_respond = false;
+ if (server_list.length == 0)
+ show_remote_account_dialog ();
+ else
+ {
+ var last_used_server_list_name = "";
+ foreach (var remote_server in server_list)
+ {
+ var e = create_prompt_for_login_server (remote_server);
+ if (remote_server.last_used_server)
+ last_used_server_list_name = e.id;
+ }
+ if (last_used_server_list_name != "")
+ set_active_entry (last_used_server_list_name);
+ else
+ set_active_first_entry_with_prefix ("*remote_login");
+ }
+ }
+ else
+ {
+ will_clear = true;
+ show_message (_("Incorrect e-mail address or password"), true);
+ create_remote_fields_for_current_item.begin (remote_directory_server_list);
+ }
+ }
+ }
+
+ private void remote_login_respond_cb ()
+ {
+ sensitive = false;
+ will_clear = true;
+ greeter_authenticating_user = selected_entry.id;
+ if (UnityGreeter.singleton.test_mode)
+ {
+ Gtk.Entry field = current_remote_fields.get ("password") as Gtk.Entry;
+ test_is_authenticated = field.text == "password";
+ if (field.text == "delay")
+ Timeout.add (5000, () => { authentication_complete_cb (); return false; });
+ else
+ authentication_complete_cb ();
+ }
+ else
+ {
+ UnityGreeter.singleton.authenticate_remote (get_lightdm_session (), null);
+ remote_login_service.set_last_used_server.begin (currently_browsing_server_url, url_from_remote_loding_server_list_name (selected_entry.id));
+ }
+ }
+
+ private void show_remote_account_dialog ()
+ {
+ var dialog = new Gtk.MessageDialog (null, 0, Gtk.MessageType.OTHER, Gtk.ButtonsType.NONE, "");
+ dialog.set_position (Gtk.WindowPosition.CENTER_ALWAYS);
+ dialog.secondary_text = _("If you have an account on an RDP or Citrix server, Remote Login lets you run applications from that server.");
+ // For 12.10 we still don't support Citrix
+ dialog.secondary_text = _("If you have an account on an RDP server, Remote Login lets you run applications from that server.");
+ if (offer_guest)
+ {
+ dialog.add_button (_("Cancel"), 0);
+ var b = dialog.add_button (_("Set Up…"), 1);
+ b.grab_focus ();
+ dialog.text = _("You need an Ubuntu Remote Login account to use this service. Would you like to set up an account now?");
+ }
+ else
+ {
+ dialog.add_button (_("OK"), 0);
+ dialog.text = _("You need an Ubuntu Remote Login account to use this service. Visit uccs.canonical.com to set up an account.");
+ }
+
+ dialog.show_all ();
+ dialog.response.connect ((id) =>
+ {
+ if (id == 1)
+ {
+ var config_session = "uccsconfigure";
+ if (is_supported_remote_session (config_session))
+ {
+ greeter_authenticating_user = selected_entry.id;
+ UnityGreeter.singleton.authenticate_remote (config_session, null);
+ }
+ }
+ dialog.destroy ();
+ });
+ dialog.run ();
+ }
+
+ private bool change_background_timeout_cb ()
+ {
+ string? new_background_file = null;
+ if (menubar.high_contrast || !UGSettings.get_boolean (UGSettings.KEY_DRAW_USER_BACKGROUNDS))
+ new_background_file = null;
+ else if (selected_entry is UserPromptBox)
+ new_background_file = (selected_entry as UserPromptBox).background;
+
+ background.current_background = new_background_file;
+
+ change_background_timeout = 0;
+ return false;
+ }
+
+ private void change_background ()
+ {
+ if (background.current_background != null)
+ {
+ if (change_background_timeout == 0)
+ change_background_timeout = Idle.add (change_background_timeout_cb);
+ }
+ else
+ change_background_timeout_cb ();
+ }
+
+ protected static int user_list_compare_entry (PromptBox a, PromptBox b)
+ {
+ if (a.id.has_prefix ("*remote_directory") && !b.id.has_prefix ("*remote_directory"))
+ return 1;
+ if (a.id.has_prefix ("*remote_login") && !b.id.has_prefix ("*remote_login"))
+ return 1;
+
+ /* Fall back to default behaviour of the GreeterList sorter */
+ return GreeterList.compare_entry (a, b);
+ }
+
+ protected override void insert_entry (PromptBox entry)
+ {
+ entries.insert_sorted (entry, user_list_compare_entry);
+ }
+
+ protected override void setup_prompt_box (bool fade = true)
+ {
+ base.setup_prompt_box (fade);
+ var userbox = selected_entry as UserPromptBox;
+ if (userbox != null)
+ selected_entry.set_is_active (userbox.is_active);
+ }
+
+ private void entry_selected_cb (string? username)
+ {
+ UnityGreeter.singleton.set_state ("last-user", username);
+ if (selected_entry is UserPromptBox)
+ session = (selected_entry as UserPromptBox).session;
+ else
+ session = null;
+ selected_entry.clear ();
+
+ /* Reset this variable so it can be freed */
+ remote_server_email_field_autocompleter = null;
+
+ start_authentication ();
+ }
+
+ protected override void start_authentication ()
+ {
+ sensitive = true;
+ greeter_authenticating_user = "";
+ if (selected_entry.id.has_prefix ("*remote_directory"))
+ {
+ prompted = true;
+ create_remote_fields_for_current_item.begin (remote_directory_server_list);
+ }
+ else if (selected_entry.id.has_prefix ("*remote_login"))
+ {
+ prompted = true;
+ create_remote_fields_for_current_item.begin (remote_login_server_list);
+ }
+ else
+ base.start_authentication ();
+ }
+
+ private async void create_remote_fields_for_current_item (List<RemoteServer?> server_list)
+ {
+ current_remote_fields = new HashTable<string, Gtk.Widget> (str_hash, str_equal);
+ var url = url_from_remote_loding_server_list_name (selected_entry.id);
+ var username = username_from_remote_loding_server_list_name (selected_entry.id);
+
+ foreach (var remote_server in server_list)
+ {
+ var remote_username = username_from_remote_server_fields (remote_server);
+ if (remote_server.url == url && (username == null || username == remote_username))
+ {
+ if (selected_entry.id.has_prefix ("*remote_login"))
+ {
+ if (!is_supported_remote_session (remote_server.type))
+ {
+ show_message (_("Server type not supported."), true);
+ }
+ }
+
+ var fields = new List<RemoteServerField?> ();
+ foreach (var field in remote_server.fields)
+ fields.append (field);
+ fields.sort (remote_server_field_sort_function);
+ foreach (var field in fields)
+ {
+ Gtk.Widget? widget = null;
+ var default_value = "";
+ if (field.default_value != null && field.default_value.is_of_type (VariantType.STRING))
+ default_value = field.default_value.get_string ();
+ if (field.type == "username")
+ {
+ var entry = add_prompt (_("Username:"));
+ entry.text = default_value;
+ widget = entry;
+ }
+ else if (field.type == "password")
+ {
+ var entry = add_prompt (_("Password:"), true);
+ entry.text = default_value;
+ widget = entry;
+ }
+ else if (field.type == "domain")
+ {
+ string[] domainsArray = {};
+ if (field.properties != null && field.properties.contains ("domains") && field.properties.get ("domains").is_of_type (VariantType.ARRAY))
+ domainsArray = field.properties.get ("domains").dup_strv ();
+ var domains = new GenericArray<string> ();
+ for (var i = 0; i < domainsArray.length; i++)
+ domains.add (domainsArray[i]);
+
+ var read_only = field.properties != null &&
+ field.properties.contains ("read-only") &&
+ field.properties.get ("read-only").is_of_type (VariantType.BOOLEAN) &&
+ field.properties.get ("read-only").get_boolean ();
+ if (domains.length == 0 || (domains.length == 1 && (domains[0] == default_value || default_value.length == 0)))
+ {
+ var prompt = add_prompt (_("Domain:"));
+ prompt.text = domains.length == 1 ? domains[0] : default_value;
+ prompt.sensitive = !read_only;
+ widget = prompt;
+ }
+ else
+ {
+ if (default_value.length > 0)
+ {
+ /* Make sure the domain list contains the default value */
+ var found = false;
+ for (var i = 0; !found && i < domains.length; i++)
+ found = default_value == domains[i];
+
+ if (!found)
+ domains.add (default_value);
+ }
+
+ /* Sort domains alphabetically */
+ domains.sort (strcmp);
+ var combo = add_combo (domains, read_only);
+
+ if (default_value.length > 0)
+ {
+ if (read_only)
+ {
+ for (var i = 0; i < domains.length; i++)
+ {
+ if (default_value == domains[i])
+ {
+ combo.active = i;
+ break;
+ }
+ }
+ }
+ else
+ {
+ var entry = combo.get_child () as Gtk.Entry;
+ entry.text = default_value;
+ }
+ }
+
+ widget = combo;
+ }
+ }
+ else if (field.type == "email")
+ {
+ string[] email_domains;
+ try
+ {
+ if (UnityGreeter.singleton.test_mode)
+ email_domains = { "canonical.com", "ubuntu.org", "candy.com", "urban.net" };
+ else
+ yield remote_login_service.get_cached_domains_for_server (url, out email_domains);
+ }
+ catch (IOError e)
+ {
+ email_domains.resize (0);
+ debug ("Calling get_cached_domains_for_server in com.canonical.RemoteLogin dbus service failed. Error: %s", e.message);
+ }
+
+ var entry = add_prompt (_("Email address:"));
+ entry.text = default_value;
+ widget = entry;
+ if (email_domains.length > 0)
+ remote_server_email_field_autocompleter = new EmailAutocompleter (entry, email_domains);
+ }
+ else
+ {
+ debug ("Found field of type %s, don't know what to do with it", field.type);
+ continue;
+ }
+ current_remote_fields.insert (field.type, widget);
+ }
+ break;
+ }
+ }
+ }
+
+ public override void focus_prompt ()
+ {
+ if (selected_entry.id.has_prefix ("*remote_login"))
+ {
+ var url = url_from_remote_loding_server_list_name(selected_entry.id);
+ foreach (var remote_server in remote_login_server_list)
+ {
+ if (remote_server.url == url)
+ {
+ if (!is_supported_remote_session (remote_server.type))
+ {
+ selected_entry.sensitive = false;
+ return;
+ }
+ }
+ }
+ }
+
+ base.focus_prompt ();
+ }
+
+ public override void show_authenticated (bool successful = true)
+ {
+ if (successful)
+ {
+ /* 'Log In' here is the button for logging in. */
+ selected_entry.add_button (_("Log In"),
+ _("Login as %s").printf (selected_entry.label));
+ }
+ else
+ {
+ selected_entry.add_button (_("Retry"),
+ _("Retry as %s").printf (selected_entry.label));
+ }
+
+ if (mode != Mode.SCROLLING)
+ selected_entry.show_prompts ();
+
+ focus_prompt ();
+ redraw_greeter_box ();
+ }
+
+ public void add_user (string name, string label, string? background = null, bool is_active = false, bool has_messages = false, string? session = null)
+ {
+ var e = find_entry (name) as UserPromptBox;
+ if (e == null)
+ {
+ e = new UserPromptBox (name);
+ e.respond.connect (prompt_box_respond_cb);
+ e.login.connect (prompt_box_login_cb);
+ e.show_options.connect (prompt_box_show_options_cb);
+ e.label = label; /* Do this before adding for sorting purposes */
+ add_entry (e);
+ }
+ e.background = background;
+ e.is_active = is_active;
+ e.session = session;
+ e.label = label;
+ e.set_show_message_icon (has_messages);
+ e.set_is_active (is_active);
+
+ /* Remove manual option when have users */
+ if (have_entries () && !always_show_manual)
+ remove_entry ("*other");
+ }
+
+ protected override void add_manual_entry ()
+ {
+ var text = manual_name;
+ if (text == null)
+ text = _("Login");
+ add_user ("*other", text);
+ }
+
+ protected void prompt_box_respond_cb (string[] responses)
+ {
+ selected_entry.sensitive = false;
+ will_clear = true;
+ unacknowledged_messages = false;
+
+ foreach (var response in responses)
+ {
+ if (UnityGreeter.singleton.test_mode)
+ test_respond (response);
+ else
+ UnityGreeter.singleton.respond (response);
+ }
+ }
+
+ private void prompt_box_login_cb ()
+ {
+ debug ("Start session for %s", selected_entry.id);
+
+ unacknowledged_messages = false;
+ var is_authenticated = false;
+ if (UnityGreeter.singleton.test_mode)
+ is_authenticated = test_is_authenticated;
+ else
+ is_authenticated = UnityGreeter.singleton.is_authenticated();
+
+ /* Finish authentication (again) or restart it */
+ if (is_authenticated)
+ authentication_complete_cb ();
+ else
+ {
+ selected_entry.clear ();
+ start_authentication ();
+ }
+ }
+
+ private void prompt_box_show_options_cb ()
+ {
+ var session_chooser = new SessionList (background, menubar, session, default_session);
+ session_chooser.session_clicked.connect (session_clicked_cb);
+ UnityGreeter.singleton.push_list (session_chooser);
+ }
+
+ private void session_clicked_cb (string session)
+ {
+ this.session = session;
+ UnityGreeter.singleton.pop_list ();
+ }
+
+ private bool should_show_session_badge ()
+ {
+ if (UnityGreeter.singleton.test_mode)
+ return get_selected_id () != "no-badge";
+ else
+ return LightDM.get_sessions ().length () > 1;
+ }
+
+ private Gdk.Pixbuf? get_badge ()
+ {
+ if (selected_entry is UserPromptBox)
+ {
+ if (!should_show_session_badge ())
+ return null;
+ else if (session == null)
+ return SessionList.get_badge (default_session);
+ else
+ return SessionList.get_badge (session);
+ }
+ else
+ {
+ if (selected_entry.id.has_prefix ("*remote_directory"))
+ return SessionList.get_badge ("remote-login");
+ else
+ return null;
+ }
+ }
+
+ private bool is_supported_remote_session (string session_internal_name)
+ {
+ if (UnityGreeter.singleton.test_mode)
+ return session_internal_name == "rdp";
+
+ var found = false;
+ foreach (var session in LightDM.get_remote_sessions ())
+ {
+ if (session.key == session_internal_name)
+ {
+ found = true;
+ break;
+ }
+ }
+ return found;
+ }
+
+ protected override string get_lightdm_session ()
+ {
+ if (selected_entry.id.has_prefix ("*remote_login"))
+ {
+ var url = url_from_remote_loding_server_list_name (selected_entry.id);
+ unowned List<RemoteServer?> it = remote_login_server_list;
+
+ var answer = "";
+ while (answer == "" && it != null)
+ {
+ RemoteServer remote_server = it.data;
+ if (remote_server.url == url)
+ answer = remote_server.type;
+ it = it.next;
+ }
+
+ if (is_supported_remote_session (answer))
+ return answer;
+ else
+ return "";
+ }
+ else
+ return session;
+ }
+
+ private void fill_list ()
+ {
+ if (UnityGreeter.singleton.test_mode)
+ test_fill_list ();
+ else
+ {
+ default_session = UnityGreeter.singleton.default_session_hint ();
+ always_show_manual = UnityGreeter.singleton.show_manual_login_hint ();
+ if (!UnityGreeter.singleton.hide_users_hint ())
+ {
+ var users = LightDM.UserList.get_instance ();
+ users.user_added.connect (user_added_cb);
+ users.user_changed.connect (user_added_cb);
+ users.user_removed.connect (user_removed_cb);
+ foreach (var user in users.users)
+ user_added_cb (user);
+ }
+
+ if (UnityGreeter.singleton.has_guest_account_hint ())
+ {
+ debug ("Adding guest account entry");
+ offer_guest = true;
+ }
+
+ /* If we have no entries at all, we should show manual */
+ if (!have_entries ())
+ add_manual_entry ();
+
+ var last_user = UnityGreeter.singleton.get_state ("last-user");
+ if (UnityGreeter.singleton.select_user_hint () != null)
+ set_active_entry (UnityGreeter.singleton.select_user_hint ());
+ else if (last_user != null)
+ set_active_entry (last_user);
+ }
+ }
+
+ private void user_added_cb (LightDM.User user)
+ {
+ debug ("Adding/updating user %s (%s)", user.name, user.real_name);
+
+ if (!show_hidden_users)
+ {
+ var hidden_users = UGSettings.get_strv (UGSettings.KEY_HIDDEN_USERS);
+ foreach (var username in hidden_users)
+ if (username == user.name)
+ return;
+ }
+
+ var label = user.real_name;
+ if (user.real_name == "")
+ label = user.name;
+
+ add_user (user.name, label, user.background, user.logged_in, user.has_messages, user.session);
+ }
+
+ private void user_removed_cb (LightDM.User user)
+ {
+ debug ("Removing user %s", user.name);
+ remove_entry (user.name);
+ }
+
+ protected override void show_prompt_cb (string text, LightDM.PromptType type)
+ {
+ if (selected_entry.id.has_prefix ("*remote_login"))
+ {
+ if (text == "remote login:")
+ {
+ Gtk.Entry field = current_remote_fields.get ("username") as Gtk.Entry;
+ var answer = field != null ? field.text : "";
+ UnityGreeter.singleton.respond (answer);
+ }
+ else if (text == "password:")
+ {
+ Gtk.Entry field = current_remote_fields.get ("password") as Gtk.Entry;
+ var answer = field != null ? field.text : "";
+ UnityGreeter.singleton.respond (answer);
+ }
+ else if (text == "remote host:")
+ {
+ var answer = url_from_remote_loding_server_list_name (selected_entry.id);
+ UnityGreeter.singleton.respond (answer);
+ }
+ else if (text == "domain:")
+ {
+ Gtk.Entry field = current_remote_fields.get ("domain") as Gtk.Entry;
+ var answer = field != null ? field.text : "";
+ UnityGreeter.singleton.respond (answer);
+ }
+ }
+ else
+ base.show_prompt_cb (text, type);
+ }
+
+ /* A lot of test code below here */
+
+ private struct TestEntry
+ {
+ string username;
+ string real_name;
+ string? background;
+ bool is_active;
+ bool has_messages;
+ string? session;
+ }
+
+ private const TestEntry[] test_entries =
+ {
+ { "has-password", "Has Password", "*" },
+ { "different-prompt", "Different Prompt", "*" },
+ { "no-password", "No Password", "*" },
+ { "change-password", "Change Password", "*" },
+ { "auth-error", "Auth Error", "*" },
+ { "two-factor", "Two Factor", "*" },
+ { "two-prompts", "Two Prompts", "*" },
+ { "info-prompt", "Info Prompt", "*" },
+ { "long-info-prompt", "Long Info Prompt", "*" },
+ { "wide-info-prompt", "Wide Info Prompt", "*" },
+ { "multi-info-prompt", "Multi Info Prompt", "*" },
+ { "very-very-long-name", "Long name (far far too long to fit)", "*" },
+ { "long-name-and-messages", "Long name and messages (too long to fit)", "*", false, true },
+ { "active", "Active Account", "*", true },
+ { "has-messages", "Has Messages", "*", false, true },
+ { "gnome", "GNOME", "*", false, false, "gnome" },
+ { "locked", "Locked Account", "*" },
+ { "color-background", "Color Background", "#dd4814" },
+ { "white-background", "White Background", "#ffffff" },
+ { "black-background", "Black Background", "#000000" },
+ { "no-background", "No Background", null },
+ { "unicode", "가나다라마", "*" },
+ { "no-response", "No Response", "*" },
+ { "no-badge", "No Badge", "*" },
+ { "messages-after-login", "Messages After Login", "*" },
+ { "" }
+ };
+ private List<string> test_backgrounds;
+ private int n_test_entries = 0;
+ private bool test_prompted_sso = false;
+ private string test_two_prompts_first = null;
+ private bool test_request_new_password = false;
+ private string? test_new_password = null;
+
+ private void test_fill_list ()
+ {
+ test_backgrounds = new List<string> ();
+ try
+ {
+ var dir = Dir.open ("/usr/share/backgrounds/");
+ while (true)
+ {
+ var bg = dir.read_name ();
+ if (bg == null)
+ break;
+ test_backgrounds.append ("/usr/share/backgrounds/" + bg);
+ }
+ }
+ catch (FileError e)
+ {
+ }
+
+ if (!UnityGreeter.singleton.hide_users_hint())
+ while (add_test_entry ());
+
+ /* add a manual entry if the list of entries is empty initially */
+ if (n_test_entries <= 0)
+ {
+ add_manual_entry();
+ set_active_entry ("*other");
+ n_test_entries++;
+ }
+
+ offer_guest = UnityGreeter.singleton.has_guest_account_hint();
+ always_show_manual = UnityGreeter.singleton.show_manual_login_hint();
+
+ key_press_event.connect (test_key_press_cb);
+
+ if (UnityGreeter.singleton.show_remote_login_hint())
+ Timeout.add (1000, () =>
+ {
+ RemoteServer[] test_server_list = {};
+ RemoteServer remote_server = RemoteServer ();
+ remote_server.type = "uccs";
+ remote_server.name = "Remote Login";
+ remote_server.url = "http://crazyurl.com";
+ remote_server.last_used_server = false;
+ remote_server.fields = {};
+ RemoteServerField field1 = RemoteServerField ();
+ field1.type = "email";
+ RemoteServerField field2 = RemoteServerField ();
+ field2.type = "password";
+ remote_server.fields = {field1, field2};
+
+ test_server_list += remote_server;
+ set_remote_directory_servers (test_server_list);
+
+ return false;
+ });
+
+ var last_user = UnityGreeter.singleton.get_state ("last-user");
+ if (last_user != null)
+ set_active_entry (last_user);
+
+ }
+
+ private void test_call_set_remote_directory_servers ()
+ {
+ RemoteServer[] test_server_list = {};
+ RemoteServer remote_server = RemoteServer ();
+ remote_server.type = "uccs";
+ remote_server.name = "Corporate Remote Login";
+ remote_server.url = "http://internalcompayserver.com";
+ remote_server.last_used_server = false;
+ remote_server.fields = {};
+ RemoteServerField field1 = RemoteServerField ();
+ field1.type = "email";
+ RemoteServerField field2 = RemoteServerField ();
+ field2.type = "password";
+ remote_server.fields = {field1, field2};
+
+ test_server_list += remote_server;
+ set_remote_directory_servers (test_server_list);
+ }
+
+ private void test_call_remote_login_servers_updated ()
+ {
+ RemoteServer[] server_list = {};
+ RemoteServer remote_server1 = RemoteServer ();
+ remote_server1.type = "rdp";
+ remote_server1.name = "Cool RDP server";
+ remote_server1.url = "http://coolrdpserver.com";
+ remote_server1.last_used_server = false;
+ remote_server1.fields = {};
+ RemoteServerField field1 = RemoteServerField ();
+ field1.type = "username";
+ RemoteServerField field2 = RemoteServerField ();
+ field2.type = "password";
+ RemoteServerField field3 = RemoteServerField ();
+ field3.type = "domain";
+ remote_server1.fields = {field1, field2, field3};
+
+ RemoteServer remote_server2 = RemoteServer ();
+ remote_server2.type = "rdp";
+ remote_server2.name = "MegaCool RDP server";
+ remote_server2.url = "http://megacoolrdpserver.com";
+ remote_server2.last_used_server = false;
+ remote_server2.fields = {};
+ RemoteServerField field21 = RemoteServerField ();
+ field21.type = "username";
+ RemoteServerField field22 = RemoteServerField ();
+ field22.type = "password";
+ remote_server2.fields = {field21, field22};
+
+ server_list.resize (2);
+ server_list[0] = remote_server1;
+ server_list[1] = remote_server2;
+
+ remote_login_servers_updated (currently_browsing_server_url, currently_browsing_server_email, "", server_list);
+ }
+
+ private void test_fill_remote_login_servers (out RemoteServer[] server_list)
+ {
+ string[] domains = { "SCANNERS", "PRINTERS", "ROUTERS" };
+
+ server_list = {};
+ RemoteServer remote_server1 = RemoteServer ();
+ remote_server1.type = "rdp";
+ remote_server1.name = "Cool RDP server";
+ remote_server1.url = "http://coolrdpserver.com";
+ remote_server1.last_used_server = false;
+ remote_server1.fields = {};
+ RemoteServerField field1 = RemoteServerField ();
+ field1.type = "username";
+ RemoteServerField field2 = RemoteServerField ();
+ field2.type = "password";
+ RemoteServerField field3 = RemoteServerField ();
+ field3.type = "domain";
+ remote_server1.fields = {field1, field2, field3};
+
+ RemoteServer remote_server2 = RemoteServer ();
+ remote_server2.type = "rdp";
+ remote_server2.name = "RDP server with default username, and editable domain";
+ remote_server2.url = "http://rdpdefaultusername.com";
+ remote_server2.last_used_server = false;
+ remote_server2.fields = {};
+ RemoteServerField field21 = RemoteServerField ();
+ field21.type = "username";
+ field21.default_value = new Variant.string ("alowl");
+ RemoteServerField field22 = RemoteServerField ();
+ field22.type = "password";
+ RemoteServerField field23 = RemoteServerField ();
+ field23.type = "domain";
+ field23.default_value = new Variant.string ("PRINTERS");
+ field23.properties = new HashTable<string, Variant> (str_hash, str_equal);
+ field23.properties["domains"] = domains;
+ remote_server2.fields = {field21, field22, field23};
+
+ RemoteServer remote_server3 = RemoteServer ();
+ remote_server3.type = "rdp";
+ remote_server3.name = "RDP server with default username, and non editable domain";
+ remote_server3.url = "http://rdpdefaultusername2.com";
+ remote_server3.last_used_server = true;
+ remote_server3.fields = {};
+ RemoteServerField field31 = RemoteServerField ();
+ field31.type = "username";
+ field31.default_value = new Variant.string ("lwola");
+ RemoteServerField field32 = RemoteServerField ();
+ field32.type = "password";
+ RemoteServerField field33 = RemoteServerField ();
+ field33.type = "domain";
+ field33.default_value = new Variant.string ("PRINTERS");
+ field33.properties = new HashTable<string, Variant> (str_hash, str_equal);
+ field33.properties["domains"] = domains;
+ field33.properties["read-only"] = true;
+
+ remote_server3.fields = {field31, field32, field33};
+
+ RemoteServer remote_server4 = RemoteServer ();
+ remote_server4.type = "notsupported";
+ remote_server4.name = "Not supported server";
+ remote_server4.url = "http://notsupportedserver.com";
+ remote_server4.fields = {};
+ RemoteServerField field41 = RemoteServerField ();
+ field41.type = "username";
+ RemoteServerField field42 = RemoteServerField ();
+ field42.type = "password";
+ RemoteServerField field43 = RemoteServerField ();
+ field43.type = "domain";
+
+ remote_server4.fields = {field41, field42, field43};
+
+ server_list.resize (4);
+ server_list[0] = remote_server1;
+ server_list[1] = remote_server2;
+ server_list[2] = remote_server3;
+ server_list[3] = remote_server4;
+ }
+
+ private void test_fill_remote_login_servers_duplicate_entries (out RemoteServer[] server_list)
+ {
+ /* Create two remote servers with same url but different username and domain. */
+ server_list = {};
+
+ RemoteServer remote_server2 = RemoteServer ();
+ remote_server2.type = "rdp";
+ remote_server2.name = "RDP server with default username, and editable domain";
+ remote_server2.url = "http://rdpdefaultusername.com";
+ remote_server2.last_used_server = false;
+ remote_server2.fields = {};
+ RemoteServerField field21 = RemoteServerField ();
+ field21.type = "username";
+ field21.default_value = new Variant.string ("alowl1");
+ RemoteServerField field22 = RemoteServerField ();
+ field22.type = "password";
+ field22.default_value = new Variant.string ("duplicate1");
+ RemoteServerField field23 = RemoteServerField ();
+ field23.type = "domain";
+ field23.default_value = new Variant.string ("SCANNERS");
+ remote_server2.fields = {field21, field22, field23};
+
+ RemoteServer remote_server5 = RemoteServer ();
+ remote_server5.type = "rdp";
+ remote_server5.name = "RDP server with default username, and editable domain";
+ remote_server5.url = "http://rdpdefaultusername.com";
+ remote_server5.last_used_server = false;
+ remote_server5.fields = {};
+ RemoteServerField field51 = RemoteServerField ();
+ field51.type = "username";
+ field51.default_value = new Variant.string ("alowl2");
+ RemoteServerField field52 = RemoteServerField ();
+ field52.type = "password";
+ field52.default_value = new Variant.string ("duplicate2");
+ RemoteServerField field53 = RemoteServerField ();
+ field53.type = "domain";
+ field53.default_value = new Variant.string ("PRINTERS");
+ remote_server5.fields = {field51, field52, field53};
+
+ server_list.resize (2);
+ server_list[0] = remote_server2;
+ server_list[1] = remote_server5;
+ }
+
+ private bool test_key_press_cb (Gdk.EventKey event)
+ {
+ if ((event.state & Gdk.ModifierType.CONTROL_MASK) == 0)
+ return false;
+
+ switch (event.keyval)
+ {
+ case Gdk.Key.plus:
+ add_test_entry ();
+ break;
+ case Gdk.Key.minus:
+ remove_test_entry ();
+ break;
+ case Gdk.Key.@0:
+ while (remove_test_entry ());
+ offer_guest = false;
+ break;
+ case Gdk.Key.equal:
+ while (add_test_entry ());
+ offer_guest = true;
+ break;
+ case Gdk.Key.g:
+ offer_guest = false;
+ break;
+ case Gdk.Key.G:
+ offer_guest = true;
+ break;
+ case Gdk.Key.m:
+ always_show_manual = false;
+ break;
+ case Gdk.Key.M:
+ always_show_manual = true;
+ break;
+ }
+
+ return false;
+ }
+
+ private bool add_test_entry ()
+ {
+ var e = test_entries[n_test_entries];
+ if (e.username == "")
+ return false;
+
+ var background = e.background;
+ if (background == "*")
+ {
+ var background_index = 0;
+ for (var i = 0; i < n_test_entries; i++)
+ {
+ if (test_entries[i].background == "*")
+ background_index++;
+ }
+ if (test_backgrounds.length () > 0)
+ background = test_backgrounds.nth_data (background_index % test_backgrounds.length ());
+ }
+ add_user (e.username, e.real_name, background, e.is_active, e.has_messages, e.session);
+ n_test_entries++;
+
+ return true;
+ }
+
+ private bool remove_test_entry ()
+ {
+ if (n_test_entries == 0)
+ return false;
+
+ remove_entry (test_entries[n_test_entries - 1].username);
+ n_test_entries--;
+
+ return true;
+ }
+
+ private void test_respond (string text)
+ {
+ debug ("response %s", text);
+ switch (get_selected_id ())
+ {
+ case "*other":
+ if (test_username == null)
+ {
+ debug ("username=%s", text);
+ test_username = text;
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ }
+ else
+ {
+ test_is_authenticated = text == "password";
+ authentication_complete_cb ();
+ }
+ break;
+ case "two-factor":
+ if (!test_prompted_sso)
+ {
+ if (text == "password")
+ {
+ debug ("prompt otp");
+ test_prompted_sso = true;
+ show_prompt_cb ("OTP:", LightDM.PromptType.QUESTION);
+ }
+ else
+ {
+ test_is_authenticated = false;
+ authentication_complete_cb ();
+ }
+ }
+ else
+ {
+ test_is_authenticated = text == "otp";
+ authentication_complete_cb ();
+ }
+ break;
+ case "two-prompts":
+ if (test_two_prompts_first == null)
+ test_two_prompts_first = text;
+ else
+ {
+ test_is_authenticated = test_two_prompts_first == "blue" && text == "password";
+ authentication_complete_cb ();
+ }
+ break;
+ case "change-password":
+ if (test_new_password != null)
+ {
+ test_is_authenticated = text == test_new_password;
+ authentication_complete_cb ();
+ }
+ else if (test_request_new_password)
+ {
+ test_new_password = text;
+ show_prompt_cb ("Retype new UNIX password: ", LightDM.PromptType.SECRET);
+ }
+ else
+ {
+ if (text != "password")
+ {
+ test_is_authenticated = false;
+ authentication_complete_cb ();
+ }
+ else
+ {
+ test_request_new_password = true;
+ show_message_cb ("You are required to change your password immediately (root enforced)", LightDM.MessageType.ERROR);
+ show_prompt_cb ("Enter new UNIX password: ", LightDM.PromptType.SECRET);
+ }
+ }
+ break;
+ case "no-response":
+ break;
+ case "locked":
+ test_is_authenticated = false;
+ show_message_cb ("Account is locked", LightDM.MessageType.ERROR);
+ authentication_complete_cb ();
+ break;
+ case "messages-after-login":
+ test_is_authenticated = text == "password";
+ if (test_is_authenticated)
+ show_message_cb ("Congratulations on logging in!", LightDM.MessageType.INFO);
+ authentication_complete_cb ();
+ break;
+ default:
+ test_is_authenticated = text == "password";
+ authentication_complete_cb ();
+ break;
+ }
+ }
+
+ protected override void test_start_authentication ()
+ {
+ test_username = null;
+ test_is_authenticated = false;
+ test_prompted_sso = false;
+ test_two_prompts_first = null;
+ test_request_new_password = false;
+ test_new_password = null;
+
+ switch (get_selected_id ())
+ {
+ case "*other":
+ if (authenticate_user != null)
+ {
+ test_username = authenticate_user;
+ authenticate_user = null;
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ }
+ else
+ show_prompt_cb ("Username:", LightDM.PromptType.QUESTION);
+ break;
+ case "*guest":
+ test_is_authenticated = true;
+ authentication_complete_cb ();
+ break;
+ case "different-prompt":
+ show_prompt_cb ("Secret word", LightDM.PromptType.SECRET);
+ break;
+ case "no-password":
+ test_is_authenticated = true;
+ authentication_complete_cb ();
+ break;
+ case "auth-error":
+ show_message_cb ("Authentication Error", LightDM.MessageType.ERROR);
+ test_is_authenticated = false;
+ authentication_complete_cb ();
+ break;
+ case "info-prompt":
+ show_message_cb ("Welcome to Unity Greeter", LightDM.MessageType.INFO);
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ break;
+ case "long-info-prompt":
+ show_message_cb ("Welcome to Unity Greeter\n\nWe like to annoy you with long messages.\nLike this one\n\nThis is the last line of a multiple line message.", LightDM.MessageType.INFO);
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ break;
+ case "wide-info-prompt":
+ show_message_cb ("Welcome to Unity Greeter, the greeteriest greeter that ever did appear in these fine lands", LightDM.MessageType.INFO);
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ break;
+ case "multi-info-prompt":
+ show_message_cb ("Welcome to Unity Greeter", LightDM.MessageType.INFO);
+ show_message_cb ("This is an error", LightDM.MessageType.ERROR);
+ show_message_cb ("You should have seen three messages", LightDM.MessageType.INFO);
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ break;
+ case "two-prompts":
+ show_prompt_cb ("Favorite Color (blue):", LightDM.PromptType.QUESTION);
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ break;
+ default:
+ show_prompt_cb ("Password:", LightDM.PromptType.SECRET);
+ break;
+ }
+ }
+}
diff --git a/src/user-prompt-box.vala b/src/user-prompt-box.vala
new file mode 100644
index 0000000..9b285da
--- /dev/null
+++ b/src/user-prompt-box.vala
@@ -0,0 +1,36 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2011,2012 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/>.
+ *
+ * Authors: Robert Ancell <robert.ancell@canonical.com>
+ * Michael Terry <michael.terry@canonical.com>
+ */
+
+public class UserPromptBox : PromptBox
+{
+ /* Background for this user */
+ public string background;
+
+ /* Default session for this user */
+ public string session;
+
+ /* True if should be marked as active */
+ public bool is_active;
+
+ public UserPromptBox (string name)
+ {
+ Object (id: name);
+ }
+}