From 600fbb680106b697c1801fcd67aa51477743a30d Mon Sep 17 00:00:00 2001 From: Mihai Moldovan Date: Tue, 6 Dec 2022 01:48:20 +0100 Subject: src/settings.vala: make SingleInstance class. This way, we will be able to use it everywhere, as long as we hold a reference in the main greeter object. We will extend this class with other properties later on. --- src/arctica-greeter.vala | 10 ++++++++++ src/settings.vala | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/arctica-greeter.vala b/src/arctica-greeter.vala index 96a98ce..09c1feb 100644 --- a/src/arctica-greeter.vala +++ b/src/arctica-greeter.vala @@ -807,6 +807,16 @@ public class ArcticaGreeter if (value != "") settings.set ("gtk-xft-rgba", value, null); + /* + * Keep a reference to an AGSettings instance for the whole program + * run, so that the SingleInstance property is working the way we'd + * like it to work. + * + * We want to do this before creating the actual greeter, since the + * latter is using AGSettings quite extensively. + */ + var agsettings = new AGSettings (); + debug ("Creating Arctica Greeter"); var greeter = new ArcticaGreeter (do_test_mode); diff --git a/src/settings.vala b/src/settings.vala index 701d5a1..3c50bbc 100644 --- a/src/settings.vala +++ b/src/settings.vala @@ -2,6 +2,7 @@ * * Copyright (C) 2011,2012 Canonical Ltd * Copyright (C) 2015,2017 Mike Gabriel + * Copyright (C) 2022 Mihai Moldovan * * 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 @@ -20,7 +21,8 @@ * Mike Gabriel */ -public class AGSettings +[SingleInstance] +public class AGSettings : Object { public const string KEY_BACKGROUND = "background"; public const string KEY_BACKGROUND_COLOR = "background-color"; @@ -105,5 +107,12 @@ public class AGSettings return gsettings.set_strv (key, value); } + public AGSettings () + { + } + + construct { + } + private const string SCHEMA = "org.ArcticaProject.arctica-greeter"; } -- cgit v1.2.3 From 32d28d7bf2646fc7a0008937034246fcc96dbc8a Mon Sep 17 00:00:00 2001 From: Mihai Moldovan Date: Tue, 6 Dec 2022 02:49:19 +0100 Subject: misc src/: make ArcticaGreeter a proper vala SingleInstance class. This allows us to drop the rather awkward self-referencing static singleton member and use a standard vala/glib feature. --- src/arctica-greeter.vala | 30 +++++++++++----- src/background.vala | 3 +- src/dash-box.vala | 3 +- src/dash-entry.vala | 5 +-- src/greeter-list.vala | 37 +++++++++++-------- src/main-window.vala | 8 +++-- src/menubar.vala | 12 ++++--- src/session-list.vala | 3 +- src/user-list.vala | 89 +++++++++++++++++++++++++++------------------- tests/arctica-greeter.vala | 10 ++++-- tests/test.vala | 70 +++++++++++++++++++++++------------- 11 files changed, 171 insertions(+), 99 deletions(-) diff --git a/src/arctica-greeter.vala b/src/arctica-greeter.vala index 09c1feb..013739c 100644 --- a/src/arctica-greeter.vala +++ b/src/arctica-greeter.vala @@ -21,16 +21,15 @@ public const int grid_size = 40; -public class ArcticaGreeter +[SingleInstance] +public class ArcticaGreeter : Object { - public static ArcticaGreeter singleton; - public signal void show_message (string text, LightDM.MessageType type); public signal void show_prompt (string text, LightDM.PromptType type); public signal void authentication_complete (); public signal void starting_session (); - public bool test_mode = false; + public bool test_mode { get; construct; default = false; } private string state_file; private KeyFile state; @@ -53,11 +52,8 @@ public class ArcticaGreeter public signal void xsettings_ready (); public signal void greeter_ready (); - private ArcticaGreeter (bool test_mode_) + construct { - singleton = this; - test_mode = test_mode_; - greeter = new LightDM.Greeter (); greeter.show_message.connect ((text, type) => { show_message (text, type); }); greeter.show_prompt.connect ((text, type) => { show_prompt (text, type); }); @@ -140,6 +136,22 @@ public class ArcticaGreeter xsettings_ready_cb (); } + /* + * Note that we need a way to specify a parameter for the initial instance + * creation of the singleton, but also a constructor that takes no + * parameters for later usage. + * + * Making the parameter optional is a good compromise. + * + * This this parameter is construct-only, initializing it by passing it to + * the GObject constructor is both the correct way to do it, and it will + * additionally avoid changing it in later calls of our constructor. + */ + public ArcticaGreeter (bool test_mode_ = false) + { + Object (test_mode: test_mode_); + } + public string? get_state (string key) { try @@ -340,7 +352,7 @@ public class ArcticaGreeter { try { - ArcticaGreeter.singleton.greeter.authenticate_remote (session, userid); + greeter.authenticate_remote (session, userid); } catch (Error e) { diff --git a/src/background.vala b/src/background.vala index 9a77047..18bf169 100644 --- a/src/background.vala +++ b/src/background.vala @@ -778,7 +778,8 @@ public class Background : Gtk.Fixed { notify_property ("average-color"); - if (!ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (!greeter.test_mode) { var rgba = current.average_color.to_string (); var root = get_screen ().get_root_window (); diff --git a/src/dash-box.vala b/src/dash-box.vala index cc1d715..d8fe6fd 100644 --- a/src/dash-box.vala +++ b/src/dash-box.vala @@ -54,7 +54,8 @@ public class DashBox : Gtk.Box /* 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) { - if (!ArcticaGreeter.singleton.test_mode) { + var greeter = new ArcticaGreeter (); + if (!greeter.test_mode) { return_if_fail (pushed == null); return_if_fail (mode == Mode.NORMAL); } diff --git a/src/dash-entry.vala b/src/dash-entry.vala index 526b631..4d1146d 100644 --- a/src/dash-entry.vala +++ b/src/dash-entry.vala @@ -301,10 +301,11 @@ public class DashEntry : Gtk.Entry, Fadable // This is a workaround 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 (ArcticaGreeter.singleton.orca_needs_kick) + var greeter = new ArcticaGreeter (); + if (greeter.orca_needs_kick) { Signal.emit_by_name (get_accessible (), "focus-event", true); - ArcticaGreeter.singleton.orca_needs_kick = false; + greeter.orca_needs_kick = false; } return base.key_press_event (event); diff --git a/src/greeter-list.vala b/src/greeter-list.vala index 0cc3a1f..98c8b48 100644 --- a/src/greeter-list.vala +++ b/src/greeter-list.vala @@ -229,7 +229,8 @@ public abstract class GreeterList : FadableBox public void cancel_authentication () { - ArcticaGreeter.singleton.cancel_authentication (); + var greeter = new ArcticaGreeter (); + greeter.cancel_authentication (); entry_selected (selected_entry.id); } @@ -483,7 +484,8 @@ public abstract class GreeterList : FadableBox entry.destroy (); /* Show a manual login if no users and no remote login entry */ - if (!have_entries () && !ArcticaGreeter.singleton.show_remote_login_hint ()) + var greeter = new ArcticaGreeter (); + if (!have_entries () && !greeter.show_remote_login_hint ()) add_manual_entry (); queue_draw (); @@ -793,9 +795,10 @@ public abstract class GreeterList : FadableBox protected void connect_to_lightdm () { - ArcticaGreeter.singleton.show_message.connect (show_message_cb); - ArcticaGreeter.singleton.show_prompt.connect (show_prompt_cb); - ArcticaGreeter.singleton.authentication_complete.connect (authentication_complete_cb); + var greeter = new ArcticaGreeter (); + greeter.show_message.connect (show_message_cb); + greeter.show_prompt.connect (show_prompt_cb); + greeter.authentication_complete.connect (authentication_complete_cb); } protected void show_message_cb (string text, LightDM.MessageType type) @@ -809,10 +812,11 @@ public abstract class GreeterList : FadableBox /* Notify the greeter on what user has been logged */ if (get_selected_id () == "*other" && manual_name == null) { - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) manual_name = test_username; else - manual_name = ArcticaGreeter.singleton.authentication_user(); + manual_name = greeter.authentication_user(); } prompted = true; @@ -841,10 +845,11 @@ public abstract class GreeterList : FadableBox return; bool is_authenticated; - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) is_authenticated = test_is_authenticated; else - is_authenticated = ArcticaGreeter.singleton.is_authenticated(); + is_authenticated = greeter.is_authenticated(); if (is_authenticated) { @@ -852,7 +857,7 @@ public abstract class GreeterList : FadableBox if (prompted && !unacknowledged_messages) { login_complete (); - if (ArcticaGreeter.singleton.test_mode) + if (greeter.test_mode) start_session (); else { @@ -905,16 +910,17 @@ public abstract class GreeterList : FadableBox greeter_authenticating_user = get_selected_id (); - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) test_start_authentication (); else { if (get_selected_id () == "*other") - ArcticaGreeter.singleton.authenticate (); + greeter.authenticate (); else if (get_selected_id () == "*guest") - ArcticaGreeter.singleton.authenticate_as_guest (); + greeter.authenticate_as_guest (); else - ArcticaGreeter.singleton.authenticate (get_selected_id ()); + greeter.authenticate (get_selected_id ()); } } @@ -929,7 +935,8 @@ public abstract class GreeterList : FadableBox private void start_session () { - if (!ArcticaGreeter.singleton.start_session (get_lightdm_session (), background)) + var greeter = new ArcticaGreeter (); + if (!greeter.start_session (get_lightdm_session (), background)) { show_message (_("Failed to start session"), true); start_authentication (); diff --git a/src/main-window.vala b/src/main-window.vala index 993e43e..172c6f4 100644 --- a/src/main-window.vala +++ b/src/main-window.vala @@ -159,7 +159,8 @@ public class MainWindow : Gtk.Window only_on_monitor = AGSettings.get_string(AGSettings.KEY_ONLY_ON_MONITOR); monitor_setting_ok = only_on_monitor == "auto"; - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) { /* Simulate an 800x600 monitor to the left of a 640x480 monitor */ monitors = new List (); @@ -375,6 +376,7 @@ public class MainWindow : Gtk.Window } } + var greeter = new ArcticaGreeter (); switch (event.keyval) { case Gdk.Key.Escape: @@ -434,14 +436,14 @@ public class MainWindow : Gtk.Window } return true; case Gdk.Key.z: - if (ArcticaGreeter.singleton.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0) + if (greeter.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0) { show_shutdown_dialog (ShutdownDialogType.SHUTDOWN); return true; } break; case Gdk.Key.Z: - if (ArcticaGreeter.singleton.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0) + if (greeter.test_mode && (event.state & Gdk.ModifierType.MOD1_MASK) != 0) { show_shutdown_dialog (ShutdownDialogType.RESTART); return true; diff --git a/src/menubar.vala b/src/menubar.vala index a2b6b99..3f9f72c 100644 --- a/src/menubar.vala +++ b/src/menubar.vala @@ -108,7 +108,8 @@ public class MenuBar : Gtk.MenuBar */ public void set_keyboard_state () { - if (!ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (!greeter.test_mode) onscreen_keyboard_item.set_active (AGSettings.get_boolean (AGSettings.KEY_ONSCREEN_KEYBOARD)); } @@ -155,7 +156,8 @@ public class MenuBar : Gtk.MenuBar setup_indicators (); - ArcticaGreeter.singleton.starting_session.connect (cleanup); + var greeter = new ArcticaGreeter (); + greeter.starting_session.connect (cleanup); } private void close_pid (ref Pid pid) @@ -293,7 +295,8 @@ public class MenuBar : Gtk.MenuBar private void load_indicator (string indicator_name) { - if (!ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (!greeter.test_mode) { if (indicator_name == "ug-accessibility") { @@ -484,7 +487,8 @@ public class MenuBar : Gtk.MenuBar // 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. - ArcticaGreeter.singleton.orca_needs_kick = true; + var greeter = new ArcticaGreeter (); + greeter.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; diff --git a/src/session-list.vala b/src/session-list.vala index 820b4c6..290a7af 100644 --- a/src/session-list.vala +++ b/src/session-list.vala @@ -44,7 +44,8 @@ public class SessionPrompt : PromptBox box = new ToggleBox (default_session, session); - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) { box.add_item ("gnome", "GNOME", SessionList.get_badge ("gnome")); box.add_item ("kde", "KDE", SessionList.get_badge ("kde")); diff --git a/src/user-list.vala b/src/user-list.vala index 0991703..3012c7d 100644 --- a/src/user-list.vala +++ b/src/user-list.vala @@ -73,7 +73,8 @@ public class UserList : GreeterList { show_hidden_users_ = value; - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) { if (value) add_user ("hidden", "Hidden User", null, false, false, null); @@ -166,8 +167,9 @@ public class UserList : GreeterList connect_to_lightdm (); - if (!ArcticaGreeter.singleton.test_mode && - ArcticaGreeter.singleton.show_remote_login_hint ()) + var greeter = new ArcticaGreeter (); + if (!greeter.test_mode && + greeter.show_remote_login_hint ()) remote_logon_service_watch = Bus.watch_name (BusType.SESSION, "org.ArcticaProject.RemoteLogon", BusNameWatcherFlags.AUTO_START, @@ -390,7 +392,8 @@ public class UserList : GreeterList remote_logon_service = null; /* provide a fallback manual login option */ - if (ArcticaGreeter.singleton.hide_users_hint ()) { + var greeter = new ArcticaGreeter (); + if (greeter.hide_users_hint ()) { add_manual_entry(); set_active_entry ("*other"); } @@ -443,10 +446,11 @@ public class UserList : GreeterList else { var login_success = false; + var greeter = new ArcticaGreeter (); try { var url = url_from_remote_loding_server_list_name (selected_entry.id); - if (ArcticaGreeter.singleton.test_mode) + if (greeter.test_mode) { if (password_field.text == "password") { @@ -528,7 +532,8 @@ public class UserList : GreeterList sensitive = false; will_clear = true; greeter_authenticating_user = selected_entry.id; - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) { Gtk.Entry field = current_remote_fields.get ("password") as Gtk.Entry; test_is_authenticated = field.text == "password"; @@ -539,7 +544,7 @@ public class UserList : GreeterList } else { - ArcticaGreeter.singleton.authenticate_remote (get_lightdm_session (), null); + greeter.authenticate_remote (get_lightdm_session (), null); remote_logon_service.set_last_used_server.begin (currently_browsing_server_url, url_from_remote_loding_server_list_name (selected_entry.id)); } } @@ -576,7 +581,8 @@ public class UserList : GreeterList if (is_supported_remote_session (config_session)) { greeter_authenticating_user = selected_entry.id; - ArcticaGreeter.singleton.authenticate_remote (config_session, null); + var greeter = new ArcticaGreeter (); + greeter.authenticate_remote (config_session, null); } } dialog.destroy (); @@ -635,7 +641,8 @@ public class UserList : GreeterList private void entry_selected_cb (string? username) { - ArcticaGreeter.singleton.set_state ("last-user", username); + var greeter = new ArcticaGreeter (); + greeter.set_state ("last-user", username); if (selected_entry is UserPromptBox) session = (selected_entry as UserPromptBox).session; else @@ -777,9 +784,10 @@ public class UserList : GreeterList else if (field.type == "email") { string[] email_domains; + var greeter = new ArcticaGreeter (); try { - if (ArcticaGreeter.singleton.test_mode) + if (greeter.test_mode) email_domains = { "canonical.com", "ubuntu.org", "candy.com", "urban.net" }; else yield remote_logon_service.get_cached_domains_for_server (url, out email_domains); @@ -889,12 +897,13 @@ public class UserList : GreeterList will_clear = true; unacknowledged_messages = false; + var greeter = new ArcticaGreeter (); foreach (var response in responses) { - if (ArcticaGreeter.singleton.test_mode) + if (greeter.test_mode) test_respond (response); else - ArcticaGreeter.singleton.respond (response); + greeter.respond (response); } } @@ -904,10 +913,11 @@ public class UserList : GreeterList unacknowledged_messages = false; var is_authenticated = false; - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) is_authenticated = test_is_authenticated; else - is_authenticated = ArcticaGreeter.singleton.is_authenticated(); + is_authenticated = greeter.is_authenticated(); /* Finish authentication (again) or restart it */ if (is_authenticated) @@ -923,18 +933,21 @@ public class UserList : GreeterList { var session_chooser = new SessionList (background, menubar, session, default_session); session_chooser.session_clicked.connect (session_clicked_cb); - ArcticaGreeter.singleton.push_list (session_chooser); + var greeter = new ArcticaGreeter (); + greeter.push_list (session_chooser); } private void session_clicked_cb (string session) { this.session = session; - ArcticaGreeter.singleton.pop_list (); + var greeter = new ArcticaGreeter (); + greeter.pop_list (); } private bool should_show_session_badge () { - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) return get_selected_id () != "no-badge"; else return LightDM.get_sessions ().length () > 1; @@ -962,7 +975,8 @@ public class UserList : GreeterList private bool is_supported_remote_session (string session_internal_name) { - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) return session_internal_name == "rdp"; var found = false; @@ -1004,13 +1018,14 @@ public class UserList : GreeterList private void fill_list () { - if (ArcticaGreeter.singleton.test_mode) + var greeter = new ArcticaGreeter (); + if (greeter.test_mode) test_fill_list (); else { - default_session = ArcticaGreeter.singleton.default_session_hint (); - always_show_manual = ArcticaGreeter.singleton.show_manual_login_hint (); - if (!ArcticaGreeter.singleton.hide_users_hint ()) + default_session = greeter.default_session_hint (); + always_show_manual = greeter.show_manual_login_hint (); + if (!greeter.hide_users_hint ()) { var users = LightDM.UserList.get_instance (); users.user_added.connect (user_added_cb); @@ -1020,7 +1035,7 @@ public class UserList : GreeterList user_added_cb (user); } - if (ArcticaGreeter.singleton.has_guest_account_hint ()) + if (greeter.has_guest_account_hint ()) { debug ("Adding guest account entry"); offer_guest = true; @@ -1030,9 +1045,9 @@ public class UserList : GreeterList if (!have_entries ()) add_manual_entry (); - var last_user = ArcticaGreeter.singleton.get_state ("last-user"); - if (ArcticaGreeter.singleton.select_user_hint () != null) - set_active_entry (ArcticaGreeter.singleton.select_user_hint ()); + var last_user = greeter.get_state ("last-user"); + if (greeter.select_user_hint () != null) + set_active_entry (greeter.select_user_hint ()); else if (last_user != null) set_active_entry (last_user); } @@ -1098,39 +1113,40 @@ public class UserList : GreeterList { if (selected_entry.id.has_prefix ("*remote_login")) { + var greeter = new ArcticaGreeter (); if ((text == pam_x2go.PROMPT_USER) || (text == pam_freerdp2.PROMPT_USER)) { Gtk.Entry field = current_remote_fields.get ("username") as Gtk.Entry; var answer = field != null ? field.text : ""; debug ("remote_login prompt parsing: username -> %s", answer); - ArcticaGreeter.singleton.respond (answer); + greeter.respond (answer); } else if ((text == pam_x2go.PROMPT_PASSWORD) || (text == pam_freerdp2.PROMPT_PASSWORD)) { Gtk.Entry field = current_remote_fields.get ("password") as Gtk.Entry; var answer = field != null ? field.text : ""; debug ("remote_login prompt parsing: password -> "); - ArcticaGreeter.singleton.respond (answer); + greeter.respond (answer); } else if ((text == pam_x2go.PROMPT_HOST) || (text == pam_freerdp2.PROMPT_HOST)) { var answer = url_from_remote_loding_server_list_name (selected_entry.id); debug ("remote_login prompt parsing: host -> %s", answer); - ArcticaGreeter.singleton.respond (answer); + greeter.respond (answer); } else if (text == pam_freerdp2.PROMPT_DOMAIN) { Gtk.Entry field = current_remote_fields.get ("domain") as Gtk.Entry; var answer = field != null ? field.text : ""; debug ("remote_login prompt parsing: domain -> %s", answer); - ArcticaGreeter.singleton.respond (answer); + greeter.respond (answer); } else if (text == pam_x2go.PROMPT_COMMAND) { Gtk.Entry field = current_remote_fields.get ("command") as Gtk.Entry; var answer = field != null ? field.text : ""; debug ("remote_login prompt parsing: command -> %s", answer); - ArcticaGreeter.singleton.respond (answer); + greeter.respond (answer); } } else @@ -1210,7 +1226,8 @@ public class UserList : GreeterList { } - if (!ArcticaGreeter.singleton.hide_users_hint()) + var greeter = new ArcticaGreeter (); + if (!greeter.hide_users_hint()) while (add_test_entry ()); /* add a manual entry if the list of entries is empty initially */ @@ -1221,12 +1238,12 @@ public class UserList : GreeterList n_test_entries++; } - offer_guest = ArcticaGreeter.singleton.has_guest_account_hint(); - always_show_manual = ArcticaGreeter.singleton.show_manual_login_hint(); + offer_guest = greeter.has_guest_account_hint(); + always_show_manual = greeter.show_manual_login_hint(); key_press_event.connect (test_key_press_cb); - if (ArcticaGreeter.singleton.show_remote_login_hint()) + if (greeter.show_remote_login_hint()) Timeout.add (1000, () => { RemoteServer[] test_server_list = {}; @@ -1248,7 +1265,7 @@ public class UserList : GreeterList return false; }); - var last_user = ArcticaGreeter.singleton.get_state ("last-user"); + var last_user = greeter.get_state ("last-user"); if (last_user != null) set_active_entry (last_user); diff --git a/tests/arctica-greeter.vala b/tests/arctica-greeter.vala index 2a6581b..3bcfeb8 100644 --- a/tests/arctica-greeter.vala +++ b/tests/arctica-greeter.vala @@ -19,7 +19,8 @@ public const int grid_size = 40; -public class ArcticaGreeter +[SingleInstance] +public class ArcticaGreeter : Object { public static ArcticaGreeter singleton; @@ -27,11 +28,16 @@ public class ArcticaGreeter public signal void show_prompt (string text, LightDM.PromptType type); public signal void authentication_complete (); - public bool test_mode = false; + public bool test_mode { get; construct; default = false; } public bool session_started = false; public string last_respond_response; public bool orca_needs_kick; + public ArcticaGreeter (bool test_mode_ = false) + { + Object (test_mode: test_mode_); + } + public bool is_authenticated () { return false; diff --git a/tests/test.vala b/tests/test.vala index b129bfd..9348b82 100644 --- a/tests/test.vala +++ b/tests/test.vala @@ -137,6 +137,21 @@ public class Test } } + public static void greeter_test_mode () + { + var greeter = new ArcticaGreeter (); + + /* + * Test that fetching the greeter singleton worked, even though we use + * the default value for test mode (false). + */ + GLib.assert (true == greeter.test_mode); + + // And explicitly try to override it, too. + greeter = new ArcticaGreeter (false); + GLib.assert (true == greeter.test_mode); + } + public static void simple_navigation () { MainWindow mw = setup (); @@ -486,11 +501,12 @@ public class Test GLib.assert (list.selected_entry.id == "*remote_login*http://rdpdefaultusername2.com*lwola"); wait_for_scrolling_end (list); - ArcticaGreeter.singleton.session_started = false; + var greeter = new ArcticaGreeter (); + greeter.session_started = false; pwd = remote_login_entry_password_field (list); pwd.text = "password"; list.selected_entry.respond ({}); - GLib.assert (ArcticaGreeter.singleton.session_started); + GLib.assert (greeter.session_started); mw.hide (); } @@ -516,7 +532,8 @@ public class Test GLib.assert (list.selected_entry.id == "*remote_login*http://rdpdefaultusername2.com*lwola"); wait_for_scrolling_end (list); - ArcticaGreeter.singleton.session_started = false; + var greeter = new ArcticaGreeter (); + greeter.session_started = false; pwd = remote_login_entry_password_field (list); pwd.text = "delay"; pwd.activate (); @@ -650,14 +667,15 @@ public class Test username.text = "bar"; pwd.text = "foobar"; - ArcticaGreeter.singleton.show_prompt("remote login:", LightDM.PromptType.QUESTION); - GLib.assert (ArcticaGreeter.singleton.last_respond_response == username.text); - ArcticaGreeter.singleton.show_prompt("remote host:", LightDM.PromptType.QUESTION); - GLib.assert (ArcticaGreeter.singleton.last_respond_response == "http://coolrdpserver.com"); - ArcticaGreeter.singleton.show_prompt("domain:", LightDM.PromptType.QUESTION); - GLib.assert (ArcticaGreeter.singleton.last_respond_response == domain.text); - ArcticaGreeter.singleton.show_prompt("password:", LightDM.PromptType.SECRET); - GLib.assert (ArcticaGreeter.singleton.last_respond_response == pwd.text); + var greeter = new ArcticaGreeter (); + greeter.show_prompt("remote login:", LightDM.PromptType.QUESTION); + GLib.assert (greeter.last_respond_response == username.text); + greeter.show_prompt("remote host:", LightDM.PromptType.QUESTION); + GLib.assert (greeter.last_respond_response == "http://coolrdpserver.com"); + greeter.show_prompt("domain:", LightDM.PromptType.QUESTION); + GLib.assert (greeter.last_respond_response == domain.text); + greeter.show_prompt("password:", LightDM.PromptType.SECRET); + GLib.assert (greeter.last_respond_response == pwd.text); mw.hide (); } @@ -690,15 +708,16 @@ public class Test public static void remote_login_only () { - ArcticaGreeter.singleton.test_mode = true; - ArcticaGreeter.singleton.session_started = false; + var greeter = new ArcticaGreeter (); + greeter.test_mode = true; + greeter.session_started = false; /* this configuration should result in the list containing only the remote login entry, without any fallback manual entry */ - ArcticaGreeter.singleton._hide_users_hint = true; - ArcticaGreeter.singleton._show_remote_login_hint = true; - ArcticaGreeter.singleton._has_guest_account_hint = false; - ArcticaGreeter.singleton._show_manual_login_hint = false; + greeter._hide_users_hint = true; + greeter._show_remote_login_hint = true; + greeter._has_guest_account_hint = false; + greeter._show_manual_login_hint = false; MainWindow mw = setup (); TestList list = mw.stack.top () as TestList; @@ -725,14 +744,15 @@ public class Test public static void manual_login_fallback () { - ArcticaGreeter.singleton.test_mode = true; - ArcticaGreeter.singleton.session_started = false; + var greeter = new ArcticaGreeter (); + greeter.test_mode = true; + greeter.session_started = false; /* this configuration should result in the list containing at least a manual entry */ - ArcticaGreeter.singleton._hide_users_hint = true; - ArcticaGreeter.singleton._show_remote_login_hint = false; - ArcticaGreeter.singleton._has_guest_account_hint = false; - ArcticaGreeter.singleton._show_manual_login_hint = true; + greeter._hide_users_hint = true; + greeter._show_remote_login_hint = false; + greeter._has_guest_account_hint = false; + greeter._show_manual_login_hint = true; MainWindow mw = setup (); TestList list = mw.stack.top () as TestList; @@ -782,9 +802,9 @@ public class Test setup_gsettings (); - ArcticaGreeter.singleton = new ArcticaGreeter(); - ArcticaGreeter.singleton.test_mode = true; + var greeter = new ArcticaGreeter (true); + GLib.Test.add_func ("/Greeter Test Mode", greeter_test_mode); GLib.Test.add_func ("/Simple Navigation", simple_navigation); GLib.Test.add_func ("/Remote Login", remote_login); GLib.Test.add_func ("/Remote Login duplicate entries", remote_login_duplicate_entries); -- cgit v1.2.3 From 41bd371dda0af150b729e5430450595376361cc0 Mon Sep 17 00:00:00 2001 From: Mihai Moldovan Date: Tue, 6 Dec 2022 03:03:43 +0100 Subject: src/toggle-box.vala: fix CSS style. CSS element ids called "GtkElement" haven't been in use since 2014 and they never had a class called "element". --- src/toggle-box.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/toggle-box.vala b/src/toggle-box.vala index 5502c1d..c157f60 100644 --- a/src/toggle-box.vala +++ b/src/toggle-box.vala @@ -65,12 +65,12 @@ public class ToggleBox : Gtk.Box /* Tighten padding on buttons to not be so large, default color scheme for buttons */ var style = new Gtk.CssProvider (); style.load_from_data ("* {padding: 8px;}\n"+ - "GtkButton {\n"+ + "GtkButton, button {\n"+ " background-color: %s;\n".printf("rgba(0,0,0,0)")+ " background-image: none;"+ "}\n"+ - ".button:hover,\n"+ - ".button:hover:active {\n"+ + "button:hover,\n"+ + "button:hover:active {\n"+ " background-color: %s;\n".printf(AGSettings.get_string (AGSettings.KEY_TOGGLEBOX_BUTTON_BGCOLOR))+ "}\n", -1); button.get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); -- cgit v1.2.3 From 131bcb9889b9eb9b8eeb895f23ae1d53a25aefe8 Mon Sep 17 00:00:00 2001 From: Mihai Moldovan Date: Tue, 6 Dec 2022 03:29:30 +0100 Subject: src/: add util.vala, wrapping a GTK 3 function for use within vala. Since this function is only needed for and available in GTK 3, add configure checks for GTK 4.0 and higher as well. We will use this function later on. --- configure.ac | 10 ++++++++++ src/Makefile.am | 3 ++- src/util.vala | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/util.vala diff --git a/configure.ac b/configure.ac index 222556a..7619254 100644 --- a/configure.ac +++ b/configure.ac @@ -91,6 +91,16 @@ if test x$gtk_check_pass = xyes ; then AC_SUBST([AM_VALAFLAGS]) fi +dnl ########################################################################### +dnl Check for GTK version - 4.0 +dnl ########################################################################### + +PKG_CHECK_MODULES(GTK_4_0, gtk4 >= 4.0.0 , gtk4_check_pass=yes, gtk4_check_pass=no) +if test x$gtk4_check_pass = xyes ; then + AM_VALAFLAGS="$AM_VALAFLAGS -D HAVE_GTK_4_0" + AC_SUBST([AM_VALAFLAGS]) +fi + dnl ########################################################################## dnl Remote Logon Dependencies dnl ########################################################################## diff --git a/src/Makefile.am b/src/Makefile.am index 5c0c78f..4c10950 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,7 +37,8 @@ arctica_greeter_SOURCES = \ toggle-box.vala \ arctica-greeter.vala \ user-list.vala \ - user-prompt-box.vala + user-prompt-box.vala \ + util.vala logo_generator_SOURCES = logo-generator.vala diff --git a/src/util.vala b/src/util.vala new file mode 100644 index 0000000..6b82f50 --- /dev/null +++ b/src/util.vala @@ -0,0 +1,28 @@ +/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*- + * + * Copyright (C) 2011,2012 Canonical Ltd + * Copyright (C) 2015-2017 Mike Gabriel + * Copyright (C) 2022 Mihai Moldovan + * + * 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 + * Mike Gabriel + * Mihai Moldovan + */ + +#if !HAVE_GTK_4_0 +[CCode(cname = "GTK_IS_CONTAINER", cheader_filename="gtk/gtk.h", simple_generics = true, has_target = false)] +static extern bool gtk_is_container (T widget); +#endif -- cgit v1.2.3 From 33d2f8d0461314c5eeaab2c74b10b545906e1491 Mon Sep 17 00:00:00 2001 From: Mihai Moldovan Date: Tue, 6 Dec 2022 05:10:29 +0100 Subject: misc src/: completely rework high contrast mode, add stub for big font mode. This is a work-in-progress. The reworked high contrast mode adds support for a configurable high contrast GTK theme and changes a lot of widgets to change their color. The big font mode is a stub currently. --- .../org.ArcticaProject.arctica-greeter.gschema.xml | 12 ++ src/arctica-greeter.vala | 110 ++++++++++++ src/dash-box.vala | 19 ++- src/main-window.vala | 5 +- src/menubar.vala | 182 +++++++++++++++++--- src/prompt-box.vala | 188 ++++++++++++++++++++- src/settings.vala | 60 +++++++ src/toggle-box.vala | 39 ++++- src/user-list.vala | 6 +- tests/menubar.vala | 1 - 10 files changed, 586 insertions(+), 36 deletions(-) diff --git a/data/org.ArcticaProject.arctica-greeter.gschema.xml b/data/org.ArcticaProject.arctica-greeter.gschema.xml index 9efc200..b0069c2 100644 --- a/data/org.ArcticaProject.arctica-greeter.gschema.xml +++ b/data/org.ArcticaProject.arctica-greeter.gschema.xml @@ -37,6 +37,10 @@ 'Blue-Submarine' GTK+ theme to use + + 'HighContrastInverse' + GTK+ theme to use in high contrast mode + 'Adwaita' Icon theme to use @@ -82,6 +86,10 @@ false Whether to use a high contrast theme + + false + Whether to use big fonts throughout the application + false Whether to enable the screen reader @@ -115,6 +123,10 @@ 'auto' Whether to enable HiDPI support + + 0.5 + Alpha value for menubar, multiplied with the theme-provided transparency value. Not used in high contrast mode. + '' Default FQDN for host offering Remote Logon Service diff --git a/src/arctica-greeter.vala b/src/arctica-greeter.vala index 013739c..6d31a9b 100644 --- a/src/arctica-greeter.vala +++ b/src/arctica-greeter.vala @@ -306,6 +306,47 @@ public class ArcticaGreeter : Object Canberra.PROP_EVENT_ID, "system-ready"); + /* Synchronize properties in AGSettings once. */ + var agsettings = new AGSettings (); + agsettings.high_contrast = !(!(agsettings.high_contrast)); + agsettings.big_font = !(!(agsettings.big_font)); + + /* + * Add timeouts to process the full node hierarchy to handle a11y + * changes. + * + * That's the easiest way to handle a changing node hierarchy. + * + * Alternatives would involve connecting a function for every a11y + * change to the GtkWidget::parent-set event to *every widget* we + * create, but that would make the code incredibly messy. + * + * The value has been determined by a fair dice roll and should make + * sure that changes are visible almost instantaneously to users. + */ + Timeout.add_full (GLib.Priority.HIGH_IDLE, 302, () => { + var agsettings_intimer = new AGSettings (); + /* + if (0 == GLib.Random.int_range (0, 10)) { + debug ("Syncing up high contrast value via timer: %s", agsettings_intimer.high_contrast.to_string ()); + } + */ + switch_contrast (agsettings_intimer.high_contrast); + + return true; + }); + Timeout.add_full (GLib.Priority.HIGH_IDLE, 302, () => { + var agsettings_intimer = new AGSettings (); + /* + if (0 == GLib.Random.int_range (0, 10)) { + debug ("Syncing up big font value via timer: %s", agsettings_intimer.big_font.to_string ()); + } + */ + switch_font (agsettings_intimer.big_font); + + return true; + }); + return false; } @@ -419,6 +460,75 @@ public class ArcticaGreeter : Object return greeter.has_guest_account_hint; } + private delegate void SwitchClassType (Gtk.Widget widget, string classname, bool enable); + + private delegate void IterateChildrenType (Gtk.Widget widget); + + private void switch_generic (Gtk.Widget widget, string classname, bool enable) + { + var style_ctx = widget.get_style_context (); + if (enable) + { + style_ctx.add_class (classname); + } + else + { + style_ctx.remove_class (classname); + } + } + + private void iterate_children_generic (Gtk.Widget widget, SwitchClassType switch_func, string classname, bool enable) + { + /* + * GTK 4 changed its API quite dramatically, got rid of GtkContainer + * and made each GtkWidget accept children, while also defining a new + * way to access those. + */ + IterateChildrenType rec_func = null; + rec_func = (widget) => { +#if HAVE_GTK_4_0 + Gtk.Widget child = widget.get_first_child (); + while (null != child) + { + rec_func (child); + child = child.get_next_sibling (); + } +#else + if (gtk_is_container (widget)) + { + ((Gtk.Container)(widget)).@foreach (rec_func); + } +#endif + + /* Common code to add or remove the CSS class. */ + switch_func (widget, classname, enable); + }; + + /* + * Actually recursively iterate through this item and all of its + * children. + */ + rec_func (widget); + } + + public void switch_contrast (bool high) + { + var time_pre = GLib.get_monotonic_time (); + iterate_children_generic (main_window, switch_generic, "high_contrast", high); + var time_post = GLib.get_monotonic_time (); + var time_diff = time_post - time_pre; + assert (0 <= time_diff); + var time_diff_sec = time_diff / 1000000; + var time_diff_msec = time_diff / 1000; + var time_diff_usec = time_diff % 1000000; + // debug ("Time passed: %" + int64.FORMAT + " s, %" + int64.FORMAT + " ms, %" + int64.FORMAT + " us", time_diff_sec, time_diff_msec, time_diff_usec); + } + + public void switch_font (bool big) + { + iterate_children_generic (main_window, switch_generic, "big_font", big); + } + private Gdk.FilterReturn focus_upon_map (Gdk.XEvent gxevent, Gdk.Event event) { var xevent = (X.Event*)gxevent; diff --git a/src/dash-box.vala b/src/dash-box.vala index d8fe6fd..1073aa5 100644 --- a/src/dash-box.vala +++ b/src/dash-box.vala @@ -221,10 +221,25 @@ public class DashBox : Gtk.Box 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); + var agsettings = new AGSettings (); + if (agsettings.high_contrast) + { + c.set_source_rgba (1.0, 1.0, 1.0, 1.0); + } + else + { + 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); + if (agsettings.high_contrast) + { + c.set_source_rgba (0.0, 0.0, 0.0, 1.0); + } + else + { + c.set_source_rgba (0.4, 0.4, 0.4, 0.4); + } c.set_line_width (1); c.stroke (); diff --git a/src/main-window.vala b/src/main-window.vala index 172c6f4..d7fe6f0 100644 --- a/src/main-window.vala +++ b/src/main-window.vala @@ -40,7 +40,7 @@ public class MainWindow : Gtk.Window public ListStack stack; // Menubar is smaller, but with shadow, we reserve more space - public const int MENUBAR_HEIGHT = 32; + public const int MENUBAR_HEIGHT = 40; construct { @@ -75,6 +75,8 @@ public class MainWindow : Gtk.Window shadow_style = "background-image: url('%s'); background-repeat: repeat;".printf(shadow_path); } + /* Disable the shadow image, we will use CSS instead. */ + /* try { var style = new Gtk.CssProvider (); @@ -89,6 +91,7 @@ public class MainWindow : Gtk.Window { debug ("Internal error loading menubox style: %s", e.message); } + */ menubox.set_size_request (-1, MENUBAR_HEIGHT); menubox.show (); menualign.show (); diff --git a/src/menubar.vala b/src/menubar.vala index 3f9f72c..8915530 100644 --- a/src/menubar.vala +++ b/src/menubar.vala @@ -68,7 +68,6 @@ private class IndicatorMenuItem : Gtk.MenuItem 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; } @@ -83,16 +82,57 @@ public class MenuBar : Gtk.MenuBar { if (background != null) { + /* Disable background drawing to see how it changes the visuals. */ + /* 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); + /* Get the style and dimensions. */ + var style_ctx = this.get_style_context (); + + var w = this.get_allocated_width (); + var h = this.get_allocated_height (); + + /* Add a group. */ + c.push_group (); + + /* Draw the background normally. */ + style_ctx.render_background (c, 0, 0, w, h); + + /* Draw the frame normally. */ + style_ctx.render_frame (c, 0, 0, w, h); + + /* Go back to the original widget. */ + c.pop_group_to_source (); + + var agsettings = new AGSettings (); + if (agsettings.high_contrast) { + /* + * In case the high contrast mode is enabled, do not add any + * transparency. While the GTK theme might define one (even though + * it better should not, given that we are also switching to a + * high contrast theme), we certainly do not want to make the look + * fuzzy. + */ + c.paint (); + } + else { + /* + * And finally repaint it with additional transparency. + * Note that most GTK styles already define a transparency for OSD + * menus. We want to have something more transparent, but also + * make sure that it is not too transparent, so do not choose a + * value that is too low here - certainly not your desired final + * alpha value. + */ + c.paint_with_alpha (AGSettings.get_double (AGSettings.KEY_MENUBAR_ALPHA)); + } foreach (var child in get_children ()) { @@ -102,6 +142,16 @@ public class MenuBar : Gtk.MenuBar return false; } + public static void add_style_class (Gtk.Widget widget) + { + /* + * Add style context class osd, which makes the widget respect the GTK + * style definitions for this type of elements. + */ + var ctx = widget.get_style_context (); + ctx.add_class ("osd"); + } + /* 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. @@ -113,33 +163,110 @@ public class MenuBar : Gtk.MenuBar onscreen_keyboard_item.set_active (AGSettings.get_boolean (AGSettings.KEY_ONSCREEN_KEYBOARD)); } - private string default_theme_name; private List indicator_objects; private Gtk.CheckMenuItem high_contrast_item; + private Gtk.CheckMenuItem big_font_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); + add_style_class (this); + + /* Add shadow. */ + var shadow_style = new Gtk.CssProvider (); + shadow_style.load_from_data ("* { box-shadow: 0px 0px 5px 5px #000000; }", -1); + this.get_style_context ().add_provider (shadow_style, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); pack_direction = Gtk.PackDirection.RTL; if (AGSettings.get_boolean (AGSettings.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 (); + var hostname_item = new Gtk.MenuItem.with_label (Posix.utsname ().nodename); append (hostname_item); + hostname_item.show (); + + /* + * Even though this (menu) item is insensitive, we want its label + * text to have the sensitive color as to not look out of place + * and difficult to read. + * + * There's a really weird bug that leads to always fetch the + * sensitive color after the widget (menuitem in this case) has + * been set to insensitive once - at least in this constructor. + * + * I haven't found a way to fix that, or, for that matter, what is + * actually causing the issue. Even waiting on the main event loop + * until all events are processed didn't help. + * + * We'll work around this issue by fetching the color before + * setting the widget to insensitive and call it proper. + */ + var insensitive_override_style = new Gtk.CssProvider (); + + /* + * First, fetch the associated GtkStyleContext and save the state, + * we'll override the state later on. + */ + var hostname_item_ctx = hostname_item.get_style_context (); + hostname_item_ctx.save (); + + try { + /* Get the actual color. */ + var sensitive_color = hostname_item_ctx.get_color (Gtk.StateFlags.NORMAL); + debug ("Directly fetched sensitive color: %s", sensitive_color.to_string ()); + + insensitive_override_style.load_from_data ("*:disabled { color: %s; }".printf(sensitive_color.to_string ()), -1); + } + catch (Error e) + { + debug ("Internal error loading hostname menu item text color: %s", e.message); + } + finally { + /* + * Restore the context, which we might have changed through the + * previous get_color () call. + */ + hostname_item_ctx.restore (); + } + + try { + /* And finally override the insensitive color. */ + hostname_item_ctx.add_provider (insensitive_override_style, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + + /* + * Just overriding the color for the Gtk.MenuItem widget + * doesn't help, we'll also apply it to the children. + * + * In theory, we could just use the get_child () method to + * fetch the only child we should ever have on that widget, + * namely a GtkAccelLabel, but that isn't future-proof enough, + * especially if that is ever extended into having a submenu. + * + * Thus, iterate over all children and override the style for + * all of them. + */ + if (gtk_is_container (hostname_item)) { + var children = hostname_item.get_children (); + foreach (Gtk.Widget element in children) { + var child_ctx = element.get_style_context (); + debug ("Adding override style provider to child widget %s", element.name); + child_ctx.add_provider (insensitive_override_style, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + } + } + catch (Error e) + { + debug ("Internal error overriding hostname menu item text color: %s", e.message); + } - /* Hack to get a label showing on the menubar */ - var fg = label.get_style_context ().get_color (Gtk.StateFlags.NORMAL); - label.override_color (Gtk.StateFlags.INSENSITIVE, fg); + hostname_item.set_sensitive (false); + + hostname_item.set_right_justified (true); } /* Prevent dragging the window by the menubar */ @@ -235,7 +362,15 @@ public class MenuBar : Gtk.MenuBar 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 (AGSettings.get_boolean (AGSettings.KEY_HIGH_CONTRAST)); + var agsettings = new AGSettings (); + debug ("Initializing high contrast menu item to state %s", agsettings.high_contrast.to_string ()); + high_contrast_item.set_active (agsettings.high_contrast); + big_font_item = new Gtk.CheckMenuItem.with_label (_("Big Font")); + big_font_item.toggled.connect (big_font_toggled_cb); + big_font_item.add_accelerator ("activate", accel_group, Gdk.Key.b, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE); + big_font_item.show (); + submenu.append (big_font_item); + big_font_item.set_active (agsettings.big_font); 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.SUPER_MASK | Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.VISIBLE); @@ -443,13 +578,14 @@ public class MenuBar : Gtk.MenuBar 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; - AGSettings.set_boolean (AGSettings.KEY_HIGH_CONTRAST, high_contrast); + var agsettings = new AGSettings (); + agsettings.high_contrast = item.active; + } + + private void big_font_toggled_cb (Gtk.CheckMenuItem item) + { + var agsettings = new AGSettings (); + agsettings.big_font = item.active; } private void screen_reader_toggled_cb (Gtk.CheckMenuItem item) diff --git a/src/prompt-box.vala b/src/prompt-box.vala index 1a0007f..2180c70 100644 --- a/src/prompt-box.vala +++ b/src/prompt-box.vala @@ -208,7 +208,21 @@ public class PromptBox : FadableBox debug ("Internal error loading font style (%s, %dpt): %s", font_family, font_size+2, e.message); } - name_label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f }); + var agsettings = new AGSettings (); + try + { + var color_provider = new Gtk.CssProvider (); + var css = "* { color: rgba(255, 255, 255, 1.0); }\n" + + ".high_contrast { color: rgba (0, 0, 0, 1.0); }"; + color_provider.load_from_data (css, -1); + style_ctx.add_provider (color_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + catch (Error e) + { + debug ("Internal error setting color on name label: %s", e.message); + } + name_label.valign = Gtk.Align.START; name_label.vexpand = true; name_label.yalign = 0.5f; @@ -236,7 +250,40 @@ public class PromptBox : FadableBox name_grid.attach (align, COL_NAME_MESSAGE, ROW_NAME, 1, 1); option_button = new FlatButton (); - option_button.get_style_context ().add_class ("option-button"); + var option_button_ctx = option_button.get_style_context (); + option_button_ctx.add_class ("option-button"); + + try { + /* + * Override background for both high-contrast and normal modes. + * Note that we have to use CSS selectors here, since this code + * is only executed once. + */ + var background_style = new Gtk.CssProvider (); + background_style.load_from_data ("button.flat.option-button.high_contrast {\n" + + " background-color: %s;\n".printf("rgba(0,0,0,1.0)") + + " background-image: none;\n" + + "}\n" + + "button.flat.option-button:hover:not(.high_contrast), " + + "button.flat.option-button:active:not(.high_contrast), " + + "button.flat.option-button:hover:active:not(.high_contrast) {\n"+ + " background-color: %s;\n".printf("rgba(255,255,255,0.5)")+ + " background-image: none;"+ + "}\n" + + "button.flat.option-button:hover.high_contrast," + + "button.flat.option-button:active.high_contrast," + + "button.flat.option-button:hover:active.high_contrast {\n" + + " background-color:%s;\n".printf("rgba(70, 70, 70, 1.0)") + + " background-image: none;" + + "}\n", -1); + option_button_ctx.add_provider (background_style, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + catch (Error e) + { + debug ("Internal error loading option button background style: %s", e.message); + } + option_button.hexpand = true; option_button.halign = Gtk.Align.END; option_button.valign = Gtk.Align.START; @@ -388,6 +435,7 @@ public class PromptBox : FadableBox public void clear () { prompt_visibility = PromptVisibility.HIDDEN; + active_indicator.expanded = false; /* Hold a ref while removing the prompt widgets - * if we just do w.destroy() we get this warning: @@ -452,6 +500,7 @@ public class PromptBox : FadableBox { case PromptVisibility.HIDDEN: w.hide (); + active_indicator.expanded = false; break; case PromptVisibility.FADING: var f = w as Fadable; @@ -460,10 +509,12 @@ public class PromptBox : FadableBox f.fade_in (); else w.show (); + active_indicator.expanded = true; break; case PromptVisibility.SHOWN: w.show (); w.sensitive = true; + active_indicator.expanded = true; break; } } @@ -473,6 +524,7 @@ public class PromptBox : FadableBox prompt_visibility = PromptVisibility.FADING; show (); foreach_prompt_widget ((w) => { update_prompt_visibility (w); }); + active_indicator.expanded = true; } public void show_prompts () @@ -480,6 +532,7 @@ public class PromptBox : FadableBox prompt_visibility = PromptVisibility.SHOWN; show (); foreach_prompt_widget ((w) => { update_prompt_visibility (w); }); + active_indicator.expanded = true; } protected void attach_item (Gtk.Widget w, bool add_style_class = true) @@ -713,6 +766,7 @@ public class PromptBox : FadableBox private class ActiveIndicator : Gtk.Image { public bool active { get; set; } + public bool expanded { get; set; } public const int WIDTH = 8; public const int HEIGHT = 7; @@ -722,12 +776,16 @@ private class ActiveIndicator : Gtk.Image try { pixbuf = new Gdk.Pixbuf.from_file (filename); + pixbuf_changed_helper (); } catch (Error e) { debug ("Could not load active image: %s", e.message); } notify["active"].connect (() => { queue_draw (); }); + notify["expanded"].connect (() => { notify_expanded (); }); + notify["pixbuf"].connect ((s, p) => { notify_pixbuf_changed (s, p); }); + this.style_updated.connect ((w) => { handle_style_updated (w); }); xalign = 0.0f; } @@ -747,6 +805,132 @@ private class ActiveIndicator : Gtk.Image { if (!active) return false; + return base.draw (c); } + + private void notify_pixbuf_changed (Object src, ParamSpec prop) { + assert ("pixbuf" == prop.name); + + if (!(this.swapping_)) { + pixbuf_changed_helper (); + } + } + + private void pixbuf_changed_helper () { + /* Copy the new pixbuf. */ + this.pixbuf_orig_ = this.pixbuf.copy (); + this.pixbuf.copy_options (this.pixbuf_orig_); + this.pixbuf_inverted_ = this.pixbuf.copy (); + this.pixbuf.copy_options (this.pixbuf_inverted_); + + /* Invert the new pixbuf. */ + invert_pixbuf (); + + if (this.inverted_) { + this.swapping_ = true; + this.pixbuf = this.pixbuf_inverted_; + this.swapping_ = false; + } + } + + private void handle_style_updated (Gtk.Widget widget) { + var style_ctx = this.get_style_context (); + if (style_ctx.has_class ("high_contrast")) { + if ((!(this.inverted_)) && (this.expanded)) { + this.swapping_ = true; + this.pixbuf = this.pixbuf_inverted_; + this.inverted_ = true; + this.swapping_ = false; + } + } + else { + if (this.inverted_) { + this.swapping_ = true; + this.pixbuf = this.pixbuf_orig_; + this.inverted_ = false; + this.swapping_ = false; + } + } + } + + private void invert_pixbuf () { + assert (Gdk.Colorspace.RGB == this.pixbuf_inverted_.get_colorspace ()); + var sample_size = this.pixbuf_inverted_.get_bits_per_sample (); + var alpha = this.pixbuf_inverted_.get_has_alpha (); + var channels = this.pixbuf_inverted_.get_n_channels (); + + assert (((4 == channels) && (alpha)) || + (3 == channels)); + + /* + * Fun fact: we don't need to decompose the actual colors. Since + * the inversion is just a simple XOR operation, we can just + * invert all the bits minus the alpha value. + */ + assert (1 <= sample_size); + + var rowstride = this.pixbuf_inverted_.get_rowstride (); + var col_bytes = (((channels * sample_size) + 7) / 8); + var row_bytes = (this.pixbuf_inverted_.width * col_bytes); + unowned var pixels = this.pixbuf_inverted_.get_pixels (); + assert (pixels != null); + size_t color_bits = channels; + if (alpha) { + --color_bits; + } + color_bits *= sample_size; + for (size_t i = 0; i < this.pixbuf_inverted_.height; (++i)) { + for (size_t y = 0; y < this.pixbuf_inverted_.width; (++y)) { + /* Invert full bytes first. */ + for (size_t x = 0; x < (color_bits / 8); (++(x))) { + pixels[(i * rowstride) + (y * col_bytes) + (x)] ^= (~(0)); + } + + /* + * And now, invert the last actual color bits minus alpha + * and padding. + */ + uint8 rest = (uint8) (color_bits % 8); + if (0 < rest) { + uint8 mask = (~(0)); + mask <<= (8 - rest); + pixels[(i * rowstride) + (y * col_bytes) + (color_bits / 8)] ^= mask; + } + } + } + } + + private void notify_expanded () { + if (!(this.expanded)) { + /* + * In non-expanded form, we always want to have the original + * look. + */ + if (this.inverted_) { + this.swapping_ = true; + this.pixbuf = this.pixbuf_orig_; + this.inverted_ = false; + this.swapping_ = false; + } + } + else { + /* + * In expanded form, we want to restore the inverted form iff the + * high contrast mode is enabled. + */ + var agsettings = new AGSettings (); + if ((!(this.inverted_)) && (agsettings.high_contrast)) { + this.swapping_ = true; + this.pixbuf = this.pixbuf_inverted_; + this.inverted_ = true; + this.swapping_ = false; + } + } + } + + private bool inverted_ = false; + private bool swapping_ = false; + private Gdk.Pixbuf? pixbuf_orig_ = null; + private Gdk.Pixbuf? pixbuf_inverted_ = null; } diff --git a/src/settings.vala b/src/settings.vala index 3c50bbc..af753ce 100644 --- a/src/settings.vala +++ b/src/settings.vala @@ -31,6 +31,7 @@ public class AGSettings : Object public const string KEY_SHOW_HOSTNAME = "show-hostname"; public const string KEY_LOGO = "logo"; public const string KEY_THEME_NAME = "theme-name"; + public const string KEY_HIGH_CONTRAST_THEME_NAME = "high-contrast-theme-name"; public const string KEY_ICON_THEME_NAME = "icon-theme-name"; public const string KEY_FONT_NAME = "font-name"; public const string KEY_XFT_ANTIALIAS = "xft-antialias"; @@ -39,6 +40,7 @@ public class AGSettings : Object public const string KEY_XFT_RGBA = "xft-rgba"; public const string KEY_ONSCREEN_KEYBOARD = "onscreen-keyboard"; public const string KEY_HIGH_CONTRAST = "high-contrast"; + public const string KEY_BIG_FONT = "big-font"; public const string KEY_SCREEN_READER = "screen-reader"; public const string KEY_PLAY_READY_SOUND = "play-ready-sound"; public const string KEY_INDICATORS = "indicators"; @@ -51,6 +53,8 @@ public class AGSettings : Object public const string KEY_TOGGLEBOX_FONT_FGCOLOR = "togglebox-font-fgcolor"; public const string KEY_TOGGLEBOX_BUTTON_BGCOLOR = "togglebox-button-bgcolor"; public const string KEY_ENABLE_HIDPI = "enable-hidpi"; + public const string KEY_MENUBAR_ALPHA = "menubar-alpha"; + public static bool get_boolean (string key) { @@ -112,7 +116,63 @@ public class AGSettings : Object } construct { + Gtk.Settings.get_default ().get ("gtk-theme-name", out this.default_theme_name_); + /* + debug ("Fetched default theme name in construct: %s", this.default_theme_name_); + */ + } + + public bool high_contrast { + get { + return this.high_contrast_; + } + + set { + debug ("Called high contrast setter with value %s", value.to_string ()); + this.high_contrast_ = value; + + /* Also sync back to dconf, so that this state is persistent. */ + set_boolean (AGSettings.KEY_HIGH_CONTRAST, value); + + var greeter = new ArcticaGreeter (); + greeter.switch_contrast (value); + + var settings = Gtk.Settings.get_default (); + if (value) + { + /* + debug ("Switching GTK Theme to high contrast theme \"%s\"", AGSettings.get_string (AGSettings.KEY_HIGH_CONTRAST_THEME_NAME)); + */ + settings.set ("gtk-theme-name", AGSettings.get_string (AGSettings.KEY_HIGH_CONTRAST_THEME_NAME)); + } + else + { + /* + debug ("Switching GTK Theme to default theme \"%s\"", this.default_theme_name_); + */ + settings.set ("gtk-theme-name", this.default_theme_name_); + } + } + } + + public bool big_font { + get { + return this.big_font_; + } + + set { + this.big_font_ = value; + + /* Also sync back to dconf, so that this state is persistent. */ + set_boolean (AGSettings.KEY_BIG_FONT, value); + + var greeter = new ArcticaGreeter (); + greeter.switch_font (value); + } } private const string SCHEMA = "org.ArcticaProject.arctica-greeter"; + private bool high_contrast_ = AGSettings.get_boolean (AGSettings.KEY_HIGH_CONTRAST); + private bool big_font_ = AGSettings.get_boolean (AGSettings.KEY_BIG_FONT); + private string default_theme_name_; } diff --git a/src/toggle-box.vala b/src/toggle-box.vala index c157f60..518275f 100644 --- a/src/toggle-box.vala +++ b/src/toggle-box.vala @@ -70,8 +70,21 @@ public class ToggleBox : Gtk.Box " background-image: none;"+ "}\n"+ "button:hover,\n"+ - "button:hover:active {\n"+ + "button:active,\n" + + "button:hover:active,\n" + + "button.selected {\n"+ " background-color: %s;\n".printf(AGSettings.get_string (AGSettings.KEY_TOGGLEBOX_BUTTON_BGCOLOR))+ + "}\n" + + "button.high_contrast {\n" + + " background-color: %s;\n".printf ("rgba(70, 70, 70, 1.0)") + + " background-image: none;\n" + + " border-color: %s\n;".printf ("rgba(0, 0, 0, 1.0)") + + "}\n" + + "button.high_contrast:hover,\n" + + "button.high_contrast:active,\n" + + "button.high_contrast:hover:active,\n" + + "button.high_contrast.selected {\n" + + " background-color: %s;\n".printf ("rgba(0, 0, 0, 1.0)") + "}\n", -1); button.get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } @@ -113,9 +126,8 @@ public class ToggleBox : Gtk.Box selected_button = button; selected_key = selected_button.get_data ("toggle-list-key"); - var bg_color = Gdk.RGBA (); - bg_color.parse (AGSettings.get_string (AGSettings.KEY_TOGGLEBOX_BUTTON_BGCOLOR)); - selected_button.override_background_color(Gtk.StateFlags.NORMAL, bg_color); + /* Handle color via CSS. */ + selected_button.get_style_context ().add_class ("selected"); } private Gtk.Button make_button (string key, string name_in, Gdk.Pixbuf? icon) @@ -140,7 +152,24 @@ public class ToggleBox : Gtk.Box } var label = new Gtk.Label (null); - label.set_markup ("%s".printf (font_family, font_size+2, AGSettings.get_string (AGSettings.KEY_TOGGLEBOX_FONT_FGCOLOR), name)); + /* Font and other properties are being handled via CSS. */ + label.set_text (name); + try { + var style = new Gtk.CssProvider (); + style.load_from_data ("label {\n" + + " font-family: \"%s\", sans-serif;\n".printf (font_family) + + " font-size: %d;\n".printf (font_size + 2) + + " color: %s;\n".printf (AGSettings.get_string (AGSettings.KEY_TOGGLEBOX_FONT_FGCOLOR)) + + "}\n" + + "label.high_contrast {\n" + + " color: %s;\n".printf ("rgba(255, 255, 255, 1.0)") + + "}\n", -1); + label.get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + catch (Error e) + { + debug ("Internal error loading session chooser label style: %s", e.message); + } label.halign = Gtk.Align.START; hbox.pack_start (label, true, true, 0); diff --git a/src/user-list.vala b/src/user-list.vala index 3012c7d..9f4b265 100644 --- a/src/user-list.vala +++ b/src/user-list.vala @@ -148,7 +148,8 @@ public class UserList : GreeterList construct { - menubar.notify["high-contrast"].connect (() => { change_background (); }); + var agsettings = new AGSettings (); + agsettings.notify["high-contrast"].connect (() => { change_background (); }); entry_displayed_start.connect (() => { change_background (); }); entry_displayed_done.connect (() => { change_background (); }); @@ -593,7 +594,8 @@ public class UserList : GreeterList private bool change_background_timeout_cb () { string? new_background_file = null; - if (menubar.high_contrast || !AGSettings.get_boolean (AGSettings.KEY_DRAW_USER_BACKGROUNDS)) + var agsettings = new AGSettings (); + if (agsettings.high_contrast || !AGSettings.get_boolean (AGSettings.KEY_DRAW_USER_BACKGROUNDS)) new_background_file = null; else if (selected_entry is UserPromptBox) new_background_file = (selected_entry as UserPromptBox).background; diff --git a/tests/menubar.vala b/tests/menubar.vala index f222957..4162dc0 100644 --- a/tests/menubar.vala +++ b/tests/menubar.vala @@ -19,7 +19,6 @@ public class MenuBar : Gtk.MenuBar { public const int HEIGHT = 32; - public bool high_contrast { get; private set; default = false; } public MenuBar (Background bg, Gtk.AccelGroup ag) { -- cgit v1.2.3 From bf00dadc0584d911bf4c23cc4ab25e8a09c611ce Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Mon, 6 Feb 2023 08:33:47 +0100 Subject: src/menubar.vala: Hide the bigfont feature until it's really available. --- src/menubar.vala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/menubar.vala b/src/menubar.vala index 8915530..a417a03 100644 --- a/src/menubar.vala +++ b/src/menubar.vala @@ -365,11 +365,15 @@ public class MenuBar : Gtk.MenuBar var agsettings = new AGSettings (); debug ("Initializing high contrast menu item to state %s", agsettings.high_contrast.to_string ()); high_contrast_item.set_active (agsettings.high_contrast); + +/* Hide the Big Font feature until it's available. big_font_item = new Gtk.CheckMenuItem.with_label (_("Big Font")); big_font_item.toggled.connect (big_font_toggled_cb); big_font_item.add_accelerator ("activate", accel_group, Gdk.Key.b, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE); big_font_item.show (); submenu.append (big_font_item); +*/ + big_font_item.set_active (agsettings.big_font); var item = new Gtk.CheckMenuItem.with_label (_("Screen Reader")); item.toggled.connect (screen_reader_toggled_cb); -- cgit v1.2.3