1
/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
3
* Copyright (C) 2011,2012 Canonical Ltd
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License version 3 as
7
* published by the Free Software Foundation.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Authors: Robert Ancell <robert.ancell@canonical.com>
18
* Michael Terry <michael.terry@canonical.com>
21
private class IndicatorMenuItem : Gtk.MenuItem
23
public unowned Indicator.ObjectEntry entry;
26
public IndicatorMenuItem (Indicator.ObjectEntry entry)
29
this.hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3);
33
if (entry.label != null)
35
entry.label.show.connect (this.visibility_changed_cb);
36
entry.label.hide.connect (this.visibility_changed_cb);
37
hbox.pack_start (entry.label, false, false, 0);
39
if (entry.image != null)
41
entry.image.show.connect (visibility_changed_cb);
42
entry.image.hide.connect (visibility_changed_cb);
43
hbox.pack_start (entry.image, false, false, 0);
45
if (entry.accessible_desc != null)
46
get_accessible ().set_name (entry.accessible_desc);
47
if (entry.menu != null)
48
set_submenu (entry.menu as Gtk.Widget);
50
if (has_visible_child ())
54
public bool has_visible_child ()
56
return (entry.image != null && entry.image.get_visible ()) ||
57
(entry.label != null && entry.label.get_visible ());
60
public void visibility_changed_cb (Gtk.Widget widget)
62
visible = has_visible_child ();
66
public class MenuBar : Gtk.MenuBar
68
public Background? background { get; construct; default = null; }
69
public bool high_contrast { get; private set; default = false; }
70
public Gtk.Window? keyboard_window { get; private set; default = null; }
71
public Gtk.AccelGroup? accel_group { get; construct; }
73
private static const int HEIGHT = 24;
75
public MenuBar (Background bg, Gtk.AccelGroup ag)
77
Object (background: bg, accel_group: ag);
80
public void set_layouts (List <LightDM.Layout> layouts)
83
layouts.append (LightDM.get_layout ()); /* default layout */
85
var default_item = recreate_menu (layouts);
87
/* Activate first item */
88
if (default_item != null)
90
if (default_item.active) /* Started active, have to manually trigger callback */
91
layout_toggled_cb (default_item);
93
default_item.active = true; /* will trigger callback to do rest of work */
97
public override bool draw (Cairo.Context c)
99
if (background != null)
102
background.translate_coordinates (this, 0, 0, out x, out y);
105
background.draw_full (c, Background.DrawFlags.NONE);
109
c.set_source_rgb (0.1, 0.1, 0.1);
110
c.paint_with_alpha (0.4);
112
foreach (var child in get_children ())
114
propagate_draw (child, c);
120
/* Due to LP #973922 the keyboard has to be loaded after the main window
121
* is shown and given focus. Therefore we don't enable the active state
124
public void set_keyboard_state ()
126
onscreen_keyboard_item.set_active (UGSettings.get_boolean (UGSettings.KEY_ONSCREEN_KEYBOARD));
129
private string default_theme_name;
130
private List<Indicator.Object> indicator_objects;
131
private Gtk.MenuItem keyboard_item;
132
private Gtk.CheckMenuItem high_contrast_item;
133
private Gtk.Label keyboard_label = null;
134
private Pid keyboard_pid = 0;
135
private Pid reader_pid = 0;
136
private Gtk.CheckMenuItem onscreen_keyboard_item;
140
Gtk.Settings.get_default ().get ("gtk-theme-name", out default_theme_name);
142
pack_direction = Gtk.PackDirection.RTL;
144
var label = new Gtk.Label (Posix.utsname ().nodename);
146
var hostname_item = new Gtk.MenuItem ();
147
hostname_item.add (label);
148
hostname_item.sensitive = false;
149
hostname_item.right_justified = true;
150
hostname_item.show ();
151
append (hostname_item);
153
/* Hack to get a label showing on the menubar */
154
label.ensure_style ();
155
var fg = label.get_style_context ().get_color (Gtk.StateFlags.NORMAL);
156
label.override_color (Gtk.StateFlags.INSENSITIVE, fg);
158
/* Prevent dragging the window by the menubar */
161
var style = new Gtk.CssProvider ();
162
style.load_from_data ("* {-GtkWidget-window-dragging: false;}", -1);
163
get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
167
debug ("Internal error loading menubar style: %s", e.message);
172
UnityGreeter.singleton.starting_session.connect (cleanup);
175
private void close_pid (ref Pid pid)
179
Posix.kill (pid, Posix.SIGTERM);
181
Posix.waitpid (pid, out status, 0);
186
public void cleanup ()
188
close_pid (ref keyboard_pid);
189
close_pid (ref reader_pid);
192
public override void get_preferred_height (out int min, out int nat)
198
private void greeter_set_env (string key, string val)
200
GLib.Environment.set_variable (key, val, true);
202
/* And also set it in the DBus activation environment so that any
203
* indicator services pick it up. */
206
var proxy = new GLib.DBusProxy.for_bus_sync (GLib.BusType.SESSION,
207
GLib.DBusProxyFlags.NONE, null,
208
"org.freedesktop.DBus",
209
"/org/freedesktop/DBus",
210
"org.freedesktop.DBus",
213
var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY);
214
builder.add ("{ss}", key, val);
216
proxy.call_sync ("UpdateActivationEnvironment", new GLib.Variant ("(a{ss})", builder), GLib.DBusCallFlags.NONE, -1, null);
220
warning ("Could not get set environment for indicators: %s", e.message);
225
private Gtk.Widget make_a11y_indicator ()
227
var a11y_item = new Gtk.MenuItem ();
228
var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3);
230
a11y_item.add (hbox);
231
var image = new Gtk.Image.from_file (Path.build_filename (Config.PKGDATADIR, "a11y.svg"));
235
a11y_item.set_submenu (new Gtk.Menu () as Gtk.Widget);
236
onscreen_keyboard_item = new Gtk.CheckMenuItem.with_label (_("Onscreen keyboard"));
237
onscreen_keyboard_item.toggled.connect (keyboard_toggled_cb);
238
onscreen_keyboard_item.show ();
239
unowned Gtk.Menu submenu = a11y_item.submenu;
240
submenu.append (onscreen_keyboard_item);
241
high_contrast_item = new Gtk.CheckMenuItem.with_label (_("High Contrast"));
242
high_contrast_item.toggled.connect (high_contrast_toggled_cb);
243
high_contrast_item.add_accelerator ("activate", accel_group, Gdk.KEY_h, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
244
high_contrast_item.show ();
245
submenu.append (high_contrast_item);
246
high_contrast_item.set_active (UGSettings.get_boolean (UGSettings.KEY_HIGH_CONTRAST));
247
var item = new Gtk.CheckMenuItem.with_label (_("Screen Reader"));
248
item.toggled.connect (screen_reader_toggled_cb);
249
item.add_accelerator ("activate", accel_group, Gdk.KEY_s, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
251
submenu.append (item);
252
item.set_active (UGSettings.get_boolean (UGSettings.KEY_SCREEN_READER));
256
private void layout_toggled_cb (Gtk.CheckMenuItem item)
261
var layout = item.get_data<LightDM.Layout> ("unity-greeter-layout");
265
var desc = layout.short_description;
266
if (desc == null || desc == "")
268
var parts = layout.name.split ("\t", 2);
269
if (parts[0] == layout.name)
273
/* Lookup parent layout, get its short_description */
274
var parent_layout = UnityGreeter.get_layout_by_name (parts[0]);
275
if (parent_layout.short_description == null ||
276
parent_layout.short_description == "")
279
desc = parent_layout.short_description;
282
keyboard_label.label = desc;
284
if (UnityGreeter.singleton.test_mode)
285
debug ("Setting layout to %s", layout.name);
287
LightDM.set_layout (layout);
290
private static int cmp_layout (LightDM.Layout? a, LightDM.Layout? b)
292
if (a == null && b == null)
300
/* Use a dumb, ascii comparison for now. If it turns out that some
301
descriptions can be in unicode, we'll have to use libicu's collation
303
return strcmp (a.description, b.description);
307
private Gtk.Widget make_keyboard_indicator ()
309
keyboard_item = new Gtk.MenuItem ();
310
var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3);
312
keyboard_item.add (hbox);
313
var image = new Gtk.Image.from_icon_name ("keyboard", Gtk.IconSize.LARGE_TOOLBAR);
316
keyboard_label = new Gtk.Label ("");
317
keyboard_label.width_chars = 2;
318
keyboard_label.show ();
319
hbox.add (keyboard_label);
320
keyboard_item.show ();
322
return keyboard_item;
325
private void setup_indicators ()
327
/* Set indicators to run with reduced functionality */
328
greeter_set_env ("INDICATOR_GREETER_MODE", "1");
330
/* Don't allow virtual file systems? */
331
greeter_set_env ("GIO_USE_VFS", "local");
332
greeter_set_env ("GVFS_DISABLE_FUSE", "1");
334
/* Hint to have gnome-settings-daemon run in greeter mode */
335
greeter_set_env ("RUNNING_UNDER_GDM", "1");
337
var keyboard_item = make_keyboard_indicator ();
338
insert (keyboard_item, (int) get_children ().length () - 1);
340
var a11y_item = make_a11y_indicator ();
341
insert (a11y_item, (int) get_children ().length () - 1);
343
debug ("LANG=%s LANGUAGE=%s", Environment.get_variable ("LANG"), Environment.get_variable ("LANGUAGE"));
344
string[] filenames = { Path.build_filename (Config.INDICATORDIR, "libsession.so"),
345
Path.build_filename (Config.INDICATORDIR, "libdatetime.so"),
346
Path.build_filename (Config.INDICATORDIR, "libpower.so"),
347
Path.build_filename (Config.INDICATORDIR, "libsoundmenu.so"),
348
Path.build_filename (Config.INDICATORDIR, "libapplication.so") };
349
foreach (var filename in filenames)
351
var io = new Indicator.Object.from_file (filename);
355
indicator_objects.append (io);
356
io.entry_added.connect (indicator_added_cb);
357
io.entry_removed.connect (indicator_removed_cb);
358
foreach (var entry in io.get_entries ())
359
indicator_added_cb (io, entry);
361
debug ("LANG=%s LANGUAGE=%s", Environment.get_variable ("LANG"), Environment.get_variable ("LANGUAGE"));
364
private void keyboard_toggled_cb (Gtk.CheckMenuItem item)
366
/* FIXME: The below would be sufficient if gnome-session were running
367
* to notice and run a screen keyboard in /etc/xdg/autostart... But
368
* since we're not running gnome-session, we hardcode onboard here. */
369
/* var settings = new Settings ("org.gnome.desktop.a11y.applications");*/
370
/*settings.set_boolean ("screen-keyboard-enabled", item.active);*/
372
UGSettings.set_boolean (UGSettings.KEY_ONSCREEN_KEYBOARD, item.active);
374
if (keyboard_window == null)
381
int onboard_stdout_fd;
383
Shell.parse_argv ("onboard --xid", out argv);
384
Process.spawn_async_with_pipes (null,
387
SpawnFlags.SEARCH_PATH,
391
out onboard_stdout_fd,
393
var f = FileStream.fdopen (onboard_stdout_fd, "r");
394
var stdout_text = new char[1024];
395
if (f.gets (stdout_text) != null)
396
id = int.parse ((string) stdout_text);
401
warning ("Error setting up keyboard: %s", e.message);
405
var keyboard_socket = new Gtk.Socket ();
406
keyboard_socket.show ();
407
keyboard_window = new Gtk.Window ();
408
keyboard_window.accept_focus = false;
409
keyboard_window.focus_on_map = false;
410
keyboard_window.add (keyboard_socket);
411
Gtk.socket_add_id (keyboard_socket, id);
413
/* Put keyboard at the bottom of the screen */
414
var screen = get_screen ();
415
var monitor = screen.get_monitor_at_window (get_window ());
417
screen.get_monitor_geometry (monitor, out geom);
418
keyboard_window.move (geom.x, geom.y + geom.height - 200);
419
keyboard_window.resize (geom.width, 200);
422
keyboard_window.visible = item.active;
425
/* Returns menuitem for first layout in list */
426
private Gtk.RadioMenuItem recreate_menu (List <LightDM.Layout> layouts_in)
428
var submenu = new Gtk.Menu ();
429
keyboard_item.set_submenu (submenu as Gtk.Widget);
431
var layouts = layouts_in.copy ();
432
layouts.sort (cmp_layout);
434
Gtk.RadioMenuItem? default_item = null;
435
Gtk.RadioMenuItem? last_item = null;
436
foreach (var layout in layouts)
438
var item = new Gtk.RadioMenuItem.with_label (last_item == null ? null : last_item.get_group (), layout.description);
443
if (layouts_in.data == layout)
446
/* LightDM does not change its layout list during its lifetime, so this is safe */
447
item.set_data ("unity-greeter-layout", layout);
449
item.toggled.connect (layout_toggled_cb);
451
submenu.append (item);
457
private void high_contrast_toggled_cb (Gtk.CheckMenuItem item)
459
var settings = Gtk.Settings.get_default ();
461
settings.set ("gtk-theme-name", "HighContrastInverse");
463
settings.set ("gtk-theme-name", default_theme_name);
464
high_contrast = item.active;
465
UGSettings.set_boolean (UGSettings.KEY_HIGH_CONTRAST, high_contrast);
468
private void screen_reader_toggled_cb (Gtk.CheckMenuItem item)
470
/* FIXME: The below would be sufficient if gnome-session were running
471
* to notice and run a screen reader in /etc/xdg/autostart... But
472
* since we're not running gnome-session, we hardcode orca here.
473
/*var settings = new Settings ("org.gnome.desktop.a11y.applications");*/
474
/*settings.set_boolean ("screen-reader-enabled", item.active);*/
476
UGSettings.set_boolean (UGSettings.KEY_SCREEN_READER, item.active);
478
/* Hardcoded orca: */
484
Shell.parse_argv ("orca --replace --no-setup --disable splash-window,", out argv);
485
Process.spawn_async (null,
488
SpawnFlags.SEARCH_PATH,
494
warning ("Failed to run Orca: %s", e.message);
498
close_pid (ref reader_pid);
501
private uint get_indicator_index (Indicator.Object object)
505
foreach (var io in indicator_objects)
515
private Indicator.Object? get_indicator_object_from_entry (Indicator.ObjectEntry entry)
517
foreach (var io in indicator_objects)
519
foreach (var e in io.get_entries ())
529
private void indicator_added_cb (Indicator.Object object, Indicator.ObjectEntry entry)
531
var index = get_indicator_index (object);
533
foreach (var child in get_children ())
535
if (!(child is IndicatorMenuItem))
538
var menuitem = (IndicatorMenuItem) child;
539
var child_object = get_indicator_object_from_entry (menuitem.entry);
540
var child_index = get_indicator_index (child_object);
541
if (child_index > index)
546
debug ("Adding indicator object %p at position %d", entry, pos);
548
var menuitem = new IndicatorMenuItem (entry);
549
insert (menuitem, pos);
552
private void indicator_removed_cb (Indicator.Object object, Indicator.ObjectEntry entry)
554
debug ("Removing indicator object %p", entry);
556
foreach (var child in get_children ())
558
var menuitem = (IndicatorMenuItem) child;
559
if (menuitem.entry == entry)
566
warning ("Indicator object %p not in menubar", entry);