/* -*- 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 .
*
* Authors: Robert Ancell
* Michael Terry
*/
private class IndicatorMenuItem : Gtk.MenuItem
{
public unowned Indicator.ObjectEntry entry;
private Gtk.HBox hbox;
public IndicatorMenuItem (Indicator.ObjectEntry entry)
{
this.entry = entry;
this.hbox = new Gtk.HBox (false, 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)
submenu = entry.menu;
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 string user_language{get; private set; default = "";}
public Gtk.Window? keyboard_window {get; private set; default = null;}
public Gtk.AccelGroup? accel_group {get; construct;}
public MenuBar (Background bg, Gtk.AccelGroup ag)
{
Object (background: bg, accel_group: ag);
}
public void set_layouts (List layouts)
{
if (layouts == null)
layouts.append (LightDM.get_layout ()); /* default layout */
var default_item = recreate_menu (layouts);
/* Activate first item */
if (default_item != null)
{
if (default_item.active) /* Started active, have to manually trigger callback */
layout_toggled_cb (default_item);
else
default_item.active = true; /* will trigger callback to do rest of work */
}
}
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;
}
private string default_theme_name;
private List indicator_objects;
private Gtk.MenuItem keyboard_item;
private Gtk.CheckMenuItem high_contrast_item;
private Gtk.Label keyboard_label = null;
private Pid keyboard_pid = 0;
construct
{
Gtk.Settings.get_default ().get ("gtk-theme-name", out default_theme_name);
pack_direction = Gtk.PackDirection.RTL;
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 ();
label.modify_fg (Gtk.StateType.INSENSITIVE, label.get_style ().fg[Gtk.StateType.NORMAL]);
/* 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 session chooser style: %s", e.message);
}
setup_indicators ();
set_size_request (-1, 32);
}
~MenuBar ()
{
if (keyboard_pid != 0)
{
Posix.kill (keyboard_pid, Posix.SIGKILL);
int status;
Posix.waitpid (keyboard_pid, out status, 0);
keyboard_pid = 0;
}
}
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 ("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.HBox (false, 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.submenu = new Gtk.Menu ();
var item = new Gtk.CheckMenuItem.with_label (_("Onscreen keyboard"));
item.toggled.connect (keyboard_toggled_cb);
item.show ();
a11y_item.submenu.append (item);
item.set_active (UGSettings.get_boolean (UGSettings.KEY_ONSCREEN_KEYBOARD));
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 ();
a11y_item.submenu.append (high_contrast_item);
high_contrast_item.set_active (UGSettings.get_boolean (UGSettings.KEY_HIGH_CONTRAST));
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 ();
a11y_item.submenu.append (item);
item.set_active (UGSettings.get_boolean (UGSettings.KEY_SCREEN_READER));
return a11y_item;
}
private Gtk.Widget make_language_selector ()
{
var selector_item = new Gtk.MenuItem ();
var hbox = new Gtk.HBox (false, 3);
hbox.show ();
selector_item.add (hbox);
var label= new Gtk.Label(_("Language selection"));
label.show();
hbox.add(label);
selector_item.show ();
selector_item.submenu = new Gtk.Menu ();
Gtk.RadioMenuItem? default_item = null;
Gtk.RadioMenuItem? last_item = null;
var selected = new Gtk.RadioMenuItem.with_label (last_item == null ? null : last_item.get_group (), _("User defined (Language Support)"));
selected.show ();
selector_item.submenu.append (selected);
last_item = selected;
selected = new Gtk.RadioMenuItem.with_label (last_item == null ? null : last_item.get_group (), _("Spanish"));
selected.show ();
selector_item.submenu.append (selected);
last_item = selected;
selected.toggled.connect (spanish_toggled_cb);
selected = new Gtk.RadioMenuItem.with_label (last_item == null ? null : last_item.get_group (), _("Valencian"));
selected.show ();
selector_item.submenu.append (selected);
last_item = selected;
selected.toggled.connect (valencian_toggled_cb);
selected = new Gtk.RadioMenuItem.with_label (last_item == null ? null : last_item.get_group (), _("English"));
selected.show ();
selector_item.submenu.append (selected);
last_item = selected;
selected.toggled.connect (english_toggled_cb);
return selector_item;
}
private void spanish_toggled_cb(Gtk.CheckMenuItem item)
{
if(!item.active)
return;
user_language="es_ES";
}
private void valencian_toggled_cb(Gtk.CheckMenuItem item)
{
if(!item.active)
return;
user_language="ca_ES@valencia";
}
private void english_toggled_cb(Gtk.CheckMenuItem item)
{
if(!item.active)
return;
user_language="en";
}
private void layout_toggled_cb (Gtk.CheckMenuItem item)
{
if (!item.active)
return;
var layout = item.get_data ("unity-greeter-layout");
if (layout == null)
return;
var desc = layout.short_description;
if (desc == null || desc == "")
{
var parts = layout.name.split ("\t", 2);
if (parts[0] == layout.name)
desc = layout.name;
else
{
// Lookup parent layout, get its short_description
var parent_layout = UnityGreeter.get_layout_by_name (parts[0]);
if (parent_layout.short_description == null ||
parent_layout.short_description == "")
desc = parts[0];
else
desc = parent_layout.short_description;
}
}
keyboard_label.label = desc;
if (UnityGreeter.test_mode)
debug ("Setting layout to %s", layout.name);
else
LightDM.set_layout (layout);
}
private static int cmp_layout (LightDM.Layout? a, LightDM.Layout? b)
{
if (a == null && b == null)
return 0;
else if (a == null)
return 1;
else if (b == null)
return -1;
else
{
/* Use a dumb, ascii comparison for now. If it turns out that some
descriptions can be in unicode, we'll have to use libicu's collation
algorithms. */
return strcmp (a.description, b.description);
}
}
private Gtk.Widget make_keyboard_indicator ()
{
keyboard_item = new Gtk.MenuItem ();
var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3);
hbox.show ();
keyboard_item.add (hbox);
var image = new Gtk.Image.from_icon_name ("keyboard", Gtk.IconSize.LARGE_TOOLBAR);
image.show ();
hbox.add (image);
keyboard_label = new Gtk.Label ("");
keyboard_label.width_chars = 2;
keyboard_label.show ();
hbox.add (keyboard_label);
keyboard_item.show ();
return keyboard_item;
}
private void load_indicator (string indicator_name)
{
if (indicator_name == "ug-keyboard")
{
var keyboard_item = make_keyboard_indicator ();
insert (keyboard_item, (int) get_children ().length () - 1);
var language_selector=make_language_selector();
insert (language_selector, (int) get_children ().length () - 1);
}
else if (indicator_name == "ug-accessibility")
{
var a11y_item = make_a11y_indicator ();
insert (a11y_item, (int) get_children ().length () - 1);
}
else
{
// 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)
continue;
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);
break;
}
}
}
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 gnome-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);
foreach (var indicator in indicator_list)
load_indicator(indicator);
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;
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];
f.gets (stdout_text);
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);
Gtk.socket_add_id (keyboard_socket, 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;
}
/* Returns menuitem for first layout in list */
private Gtk.RadioMenuItem recreate_menu (List layouts_in)
{
var submenu = new Gtk.Menu ();
keyboard_item.set_submenu (submenu);
var layouts = layouts_in.copy ();
layouts.sort (cmp_layout);
Gtk.RadioMenuItem? default_item = null;
Gtk.RadioMenuItem? last_item = null;
foreach (var layout in layouts)
{
var item = new Gtk.RadioMenuItem.with_label (last_item == null ? null : last_item.get_group (), layout.description);
last_item = item;
item.show ();
if (layouts_in.data == layout)
default_item = item;
/* LightDM does not change its layout list during its lifetime, so this is safe */
item.set_data ("unity-greeter-layout", layout);
item.toggled.connect (layout_toggled_cb);
submenu.append (item);
}
return default_item;
}
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: */
try
{
if (item.active)
Process.spawn_command_line_async ("orca --replace --no-setup --disable splash-window,main-window");
else
Process.spawn_command_line_async ("orca --quit");
}
catch (Error e)
{
warning ("Failed to run Orca: %s", e.message);
}
}
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);
}
}