diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e2d7354..6dcfb8a8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: - name: Install Dependencies run: | apt update - apt install -y desktop-file-utils libgdk-pixbuf-2.0-dev libgnome-desktop-3-dev libgranite-dev libgtk-3-dev libhandy-1-dev libjson-glib-dev liblightdm-gobject-1-dev ${{ matrix.mutter_pkg }} libsoup-3.0-dev libx11-dev ${{ matrix.lightdm_vala }} meson valac + apt install -y desktop-file-utils libaccountsservice-dev libgdk-pixbuf-2.0-dev libgnome-desktop-3-dev libgranite-dev libgtk-3-dev libhandy-1-dev libjson-glib-dev liblightdm-gobject-1-dev ${{ matrix.mutter_pkg }} libsoup-3.0-dev libx11-dev ${{ matrix.lightdm_vala }} meson valac - name: Build env: DESTDIR: out @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v7 - name: Install Dependencies run: | - dnf install -y desktop-file-utils gdk-pixbuf2-devel gnome-desktop3-devel granite-devel gtk3-devel libhandy-devel lightdm-gobject-devel mutter-devel libX11-devel meson valac + dnf install -y accountsservice-devel desktop-file-utils gdk-pixbuf2-devel gnome-desktop3-devel granite-devel gtk3-devel libhandy-devel lightdm-gobject-devel mutter-devel libX11-devel meson valac - name: Build env: DESTDIR: out @@ -69,7 +69,7 @@ jobs: run: | zypper addrepo https://download.opensuse.org/repositories/X11:Pantheon/16.0/X11:Pantheon.repo zypper --gpg-auto-import-keys refresh - zypper --non-interactive install tar git desktop-file-utils gdk-pixbuf-devel libgnome-desktop-3-devel granite6-devel gtk3-devel libhandy-devel lightdm-gobject-devel mutter-devel meson vala gcc + zypper --non-interactive install accountsservice-devel accountsservice-vala tar git desktop-file-utils gdk-pixbuf-devel libgnome-desktop-3-devel granite6-devel gtk3-devel libhandy-devel lightdm-gobject-devel mutter-devel meson vala gcc - uses: actions/checkout@v7 - name: Build env: diff --git a/src/Application.vala b/src/Application.vala index ba7440ee..162211b5 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 elementary, Inc. (https://elementary.io) + * Copyright 2018-2026 elementary, Inc. (https://elementary.io) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public @@ -111,6 +111,9 @@ public class Greeter.Application : Gtk.Application { } public static int main (string[] args) { + // Connect to UserManager as early as possible + Act.UserManager.get_default (); + return new Greeter.Application ().run (args); } } diff --git a/src/Cards/UserCard.vala b/src/Cards/UserCard.vala index d60d00cd..a4882f27 100644 --- a/src/Cards/UserCard.vala +++ b/src/Cards/UserCard.vala @@ -1,12 +1,16 @@ /* - * Copyright 2018-2025 elementary, Inc. (https://elementary.io) + * Copyright 2018-2026 elementary, Inc. (https://elementary.io) * SPDX-License-Identifier: GPL-3.0-or-later * * Authors: Corentin Noël */ public class Greeter.UserCard : Greeter.BaseCard { - public LightDM.User lightdm_user { get; construct; } + /** + * We use Act.User instead of LightDM.User because lightdm is unmaintained + * and lacks some fields from Act.User such as `password_mode`. + */ + public Act.User user { get; construct; } public bool show_input { get; set; default = false; } public bool is_24h { get; set; default = true; } // TODO: In Gtk4 remove this gesture and move it to MainWindow @@ -15,22 +19,24 @@ public class Greeter.UserCard : Greeter.BaseCard { private Pantheon.AccountsService greeter_act; private Pantheon.SettingsDaemon.AccountsService settings_act; - private Gtk.Label username_label; private Gtk.Revealer form_revealer; private Gtk.Stack login_stack; private Greeter.PasswordEntry password_entry; private Gtk.Box main_box; + private Greeter.SessionButton password_session_button; + private Greeter.SessionButton login_button_session_button; + private Gtk.Overlay avatar_overlay; private SelectionCheck logged_in; - public UserCard (LightDM.User lightdm_user) { - Object (lightdm_user: lightdm_user); + public UserCard (Act.User user) requires (user.is_loaded) { + Object (user: user); } construct { need_password = true; - username_label = new Gtk.Label (lightdm_user.display_name) { + var username_label = new Gtk.Label (user.real_name) { hexpand = true, margin_top = 24, margin_bottom = 12, @@ -38,6 +44,7 @@ public class Greeter.UserCard : Greeter.BaseCard { margin_end = 24, }; username_label.get_style_context ().add_class (Granite.STYLE_CLASS_H2_LABEL); + user.bind_property ("locked", username_label, "sensitive", SYNC_CREATE | INVERT_BOOLEAN); password_entry = new Greeter.PasswordEntry (); bind_property ("connecting", password_entry, "sensitive", INVERT_BOOLEAN); @@ -46,7 +53,7 @@ public class Greeter.UserCard : Greeter.BaseCard { bind_property ("use-fingerprint", fingerprint_image, "no-show-all", SYNC_CREATE | INVERT_BOOLEAN); bind_property ("use-fingerprint", fingerprint_image, "visible", SYNC_CREATE); - var password_session_button = new Greeter.SessionButton () { + password_session_button = new Greeter.SessionButton () { vexpand = true }; @@ -63,7 +70,7 @@ public class Greeter.UserCard : Greeter.BaseCard { login_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); bind_property ("connecting", login_button, "sensitive", INVERT_BOOLEAN); - var login_button_session_button = new Greeter.SessionButton () { + login_button_session_button = new Greeter.SessionButton () { vexpand = true }; @@ -108,15 +115,15 @@ public class Greeter.UserCard : Greeter.BaseCard { update_collapsed_class (); - var avatar = new Hdy.Avatar (64, lightdm_user.display_name, true) { + var avatar = new Hdy.Avatar (64, user.real_name, true) { margin_top = 6, margin_bottom = 6, margin_start = 6, margin_end = 6, - loadable_icon = new FileIcon (File.new_for_path (lightdm_user.image)) + loadable_icon = new FileIcon (File.new_for_path (user.icon_file)) }; - var avatar_overlay = new Gtk.Overlay () { + avatar_overlay = new Gtk.Overlay () { halign = CENTER, valign = START, margin_top = 100, @@ -128,16 +135,6 @@ public class Greeter.UserCard : Greeter.BaseCard { valign = END }; - if (lightdm_user.logged_in) { - avatar_overlay.add_overlay (logged_in); - - password_session_button.sensitive = false; - password_session_button.tooltip_text = (_("Session cannot be changed while user is logged in")); - - login_button_session_button.sensitive = false; - login_button_session_button.tooltip_text = (_("Session cannot be changed while user is logged in")); - } - var card_overlay = new Gtk.Overlay () { margin_top = 12, margin_bottom = 12, @@ -149,9 +146,12 @@ public class Greeter.UserCard : Greeter.BaseCard { child = card_overlay; - connect_to_dbus_interfaces (); - lightdm_user.changed.connect (update_is_locked_ui); + user.changed.connect (update_is_locked_ui); notify["need-password"].connect (update_is_locked_ui); + update_is_locked_ui (); + + user.sessions_changed.connect (on_sessions_changed); + on_sessions_changed (); click_gesture = new Gtk.GestureMultiPress (this); @@ -161,6 +161,8 @@ public class Greeter.UserCard : Greeter.BaseCard { login_button.clicked.connect (on_login); grab_focus.connect (password_entry.grab_focus_without_selecting); + + connect_to_dbus_interfaces (); } private void set_check_style () { @@ -168,24 +170,27 @@ public class Greeter.UserCard : Greeter.BaseCard { logged_in.get_style_context ().add_class (accent_to_string (settings_act.accent_color)); } + private string generate_background_image_path () { + if (settings_act.picture_options == 0) { + return ""; + } + + var path = Path.build_filename ("/", "var", "lib", "lightdm-data", user.user_name, "wallpaper"); + if (FileUtils.test (path, EXISTS) && FileUtils.test (path, IS_REGULAR)) { + return path; + } + + return "/usr/share/backgrounds/elementaryos-default"; + } + private void set_background_image () { Greeter.BackgroundImage background_image; - var background_path = lightdm_user.background; - var background_exists = ( - background_path != null && - FileUtils.test (background_path, EXISTS) && - FileUtils.test (background_path, IS_REGULAR) - ); - - if (!background_exists) { - background_path = Path.build_filename ("/", "var", "lib", "lightdm-data", lightdm_user.name, "wallpaper"); - background_exists = FileUtils.test (background_path, EXISTS) && FileUtils.test (background_path, IS_REGULAR); - } + var background_path = generate_background_image_path (); - if (settings_act.picture_options != 0 && background_exists) { + if (settings_act.picture_options != 0) { background_image = new Greeter.BackgroundImage.from_path (background_path); - } else if (settings_act.picture_options == 0 && settings_act.primary_color != null) { + } else if (settings_act.primary_color != null) { background_image = new Greeter.BackgroundImage.from_color (settings_act.primary_color); } else { background_image = new Greeter.BackgroundImage.from_path (null); @@ -223,7 +228,7 @@ public class Greeter.UserCard : Greeter.BaseCard { } private void connect_to_dbus_interfaces () { - var account_path = "/org/freedesktop/Accounts/User%d".printf ((int )lightdm_user.uid); + var account_path = "/org/freedesktop/Accounts/User%d".printf (user.uid); try { greeter_act = Bus.get_proxy_sync ( SYSTEM, @@ -246,16 +251,10 @@ public class Greeter.UserCard : Greeter.BaseCard { set_background_image (); set_check_style (); - update_is_locked_ui (); } private void update_is_locked_ui () { - // lightdm_user.is_locked prints warnings so let's use a getter method here - var is_locked = lightdm_user.get_is_locked (); - - username_label.sensitive = !is_locked; - - if (is_locked) { + if (user.locked) { login_stack.visible_child_name = "disabled"; } else if (need_password) { login_stack.visible_child_name = "password"; @@ -264,6 +263,24 @@ public class Greeter.UserCard : Greeter.BaseCard { } } + private void on_sessions_changed () { + var user_is_logged_in = user.is_logged_in (); + + password_session_button.sensitive = !user_is_logged_in; + login_button_session_button.sensitive = user_is_logged_in; + + var tooltip_text = user_is_logged_in ? (_("Session cannot be changed while user is logged in")) : ""; + password_session_button.tooltip_text = tooltip_text; + login_button_session_button.tooltip_text = tooltip_text; + + if (user_is_logged_in) { + avatar_overlay.add_overlay (logged_in); + logged_in.show_all (); + } else { + avatar_overlay.remove (logged_in); + } + } + private void on_login () { if (connecting) { return; @@ -378,7 +395,7 @@ public class Greeter.UserCard : Greeter.BaseCard { var background_settings = new GLib.Settings ("org.gnome.desktop.background"); background_settings.set_enum ("picture-options", settings_act.picture_options); - set_or_reset_settings_key (background_settings, "picture-uri", lightdm_user.background); + set_or_reset_settings_key (background_settings, "picture-uri", generate_background_image_path ()); set_or_reset_settings_key (background_settings, "primary-color", settings_act.primary_color); } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 616ae938..a38cb050 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: GPL-2.0-or-later - * SPDX-FileCopyrightText: 2018-2025 elementary, Inc. (https://elementary.io) + * SPDX-FileCopyrightText: 2018-2026 elementary, Inc. (https://elementary.io) * * Authors: Corentin Noël */ @@ -16,7 +16,6 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { private GLib.Settings gsettings; private Gtk.Revealer datetime_revealer; private Greeter.DateTimeWidget datetime_widget; - private unowned LightDM.UserList lightdm_user_list; private int current_user_card_index = -1; private unowned Greeter.BaseCard? current_card = null; @@ -116,7 +115,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { current_card = user_cards.peek_nth (current_user_card_index); try { - lightdm_greeter.authenticate (((UserCard) current_card).lightdm_user.name); + lightdm_greeter.authenticate (((UserCard) current_card).user.user_name); } catch (Error e) { critical (e.message); } @@ -138,10 +137,14 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { lightdm_greeter.bind_property ("hide-users-hint", manual_login_button, "sensitive", GLib.BindingFlags.SYNC_CREATE | GLib.BindingFlags.INVERT_BOOLEAN); lightdm_greeter.bind_property ("hide-users-hint", manual_login_button, "active", GLib.BindingFlags.SYNC_CREATE); - lightdm_user_list = LightDM.UserList.get_instance (); - lightdm_user_list.user_added.connect (() => { - load_users.begin (); - }); + unowned var user_manager = Act.UserManager.get_default (); + user_manager.user_added.connect (() => load_users.begin ()); + + if (user_manager.is_loaded) { + load_users.begin (show_greeter_window); + } else { + user_manager.notify["is-loaded"].connect (() => load_users.begin (show_greeter_window)); + } manual_card.do_connect_username.connect (do_connect_username); manual_card.do_connect.connect (do_connect); @@ -182,21 +185,6 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { carousel.page_changed.connect (handle_page_changed); - load_users.begin (() => { - /* A significant delay is required in order for the window and card to be focused at - * at boot. TODO: Find whether boot sequence can be tweaked to fix this. - */ - Timeout.add (500, () => { - get_style_context ().add_class ("initialized"); - - if (current_card != null) { - current_card.grab_focus (); - } - - return Source.REMOVE; - }); - }); - maximize (); if (settings.activate_numlock) { @@ -210,6 +198,19 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { main_box.realize.connect (init_panel); } + /* A significant delay is required in order for the window and card to be focused at boot. + * TODO: Find whether boot sequence can be tweaked to fix this. + */ + private void show_greeter_window () { + Timeout.add (500, () => { + get_style_context ().add_class ("initialized"); + + current_card?.grab_focus (); + + return Source.REMOVE; + }); + } + private void init_panel () { if (Gdk.Display.get_default () is Gdk.Wayland.Display) { // We have to wrap in Idle otherwise the Meta.Window of the WaylandSurface in Gala is still null @@ -292,7 +293,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { private void authentication_complete () { var user_card = current_card as Greeter.UserCard; if (user_card != null) { - gsettings.set_string ("last-user", user_card.lightdm_user.name); + gsettings.set_string ("last-user", user_card.user.user_name); } if (lightdm_greeter.is_authenticated) { @@ -330,7 +331,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { if (user_card != null) { try { - lightdm_greeter.authenticate (user_card.lightdm_user.name); + lightdm_greeter.authenticate (user_card.user.user_name); } catch (Error e) { critical (e.message); } @@ -341,26 +342,25 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { carousel.interactive = true; } - private async void load_users () { + private async void load_users () requires (Act.UserManager.get_default ().is_loaded) { // Check if the installer is installed var installer_desktop = new DesktopAppInfo ("io.elementary.installer.desktop"); if (installer_desktop != null) { installer_mode = true; } - if (lightdm_user_list.length > 0) { - datetime_revealer.reveal_child = true; + var users_list = Act.UserManager.get_default ().list_users (); + if (users_list.length () > 0) { + users_list.foreach (add_card); - lightdm_user_list.users.foreach ((user) => { - add_card (user); - }); + datetime_revealer.reveal_child = true; unowned string? select_user = lightdm_greeter.select_user_hint; var user_to_select = select_user != null ? select_user : gsettings.get_string ("last-user"); bool user_selected = false; user_cards.head.foreach ((card) => { - if (card.lightdm_user.name == user_to_select) { + if (card.user.user_name == user_to_select) { carousel.scroll_to (card); user_selected = true; } @@ -399,8 +399,8 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { } } - private void add_card (LightDM.User lightdm_user) { - var user_card = new Greeter.UserCard (lightdm_user); + private void add_card (Act.User user) { + var user_card = new Greeter.UserCard (user); user_card.show_all (); user_card.do_connect.connect (do_connect); user_card.click_gesture.pressed.connect ((gesture, n_press, x, y) => { @@ -456,10 +456,6 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { user_card.show_input = true; user_card.grab_focus (); - if (user_card.lightdm_user.session != null) { - application.activate_action ("select-session", new GLib.Variant.string (user_card.lightdm_user.session)); - } - if (lightdm_greeter.in_authentication) { try { lightdm_greeter.cancel_authentication (); @@ -469,7 +465,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow { } try { - lightdm_greeter.authenticate (user_card.lightdm_user.name); + lightdm_greeter.authenticate (user_card.user.user_name); } catch (Error e) { critical (e.message); } diff --git a/src/meson.build b/src/meson.build index 4b5059d6..50c87f9d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,4 @@ +accountsservice_dep = dependency('accountsservice') gobject_dep = dependency('gobject-2.0') glib_dep = dependency('glib-2.0') gdk_wl_dep = dependency('gdk-wayland-3.0') @@ -31,7 +32,7 @@ executable( 'Widgets/PasswordEntry.vala', 'Widgets/SessionButton.vala', config_header, - dependencies: [ gobject_dep, glib_dep, gdk_wl_dep, gdk_x11_dep, gtk_dep, granite_dep, hdy_dep, lightdm_dep, m_dep, pantheon_desktop_shell_dep, wl_client_dep, x11_dep ], + dependencies: [ accountsservice_dep, gobject_dep, glib_dep, gdk_wl_dep, gdk_x11_dep, gtk_dep, granite_dep, hdy_dep, lightdm_dep, m_dep, pantheon_desktop_shell_dep, wl_client_dep, x11_dep ], install : true, install_dir: install_path )