diff --git a/data/gala.gschema.xml b/data/gala.gschema.xml
index 80325833..2ea9bd9a 100644
--- a/data/gala.gschema.xml
+++ b/data/gala.gschema.xml
@@ -92,6 +92,16 @@
Automatically move maximized windows to a new workspace
+
+ true
+ Automatically lock the screen when the display turns off due to inactivity
+
+
+
+ true
+ Automatically lock the screen when the computer suspends
+
+
diff --git a/src/DBus.vala b/src/DBus.vala
index 47b0b4f4..31d63b1e 100644
--- a/src/DBus.vala
+++ b/src/DBus.vala
@@ -60,6 +60,28 @@ namespace Gala {
},
() => {},
() => critical ("Could not acquire name") );
+
+ unowned WindowManagerGala? gala_wm = wm as WindowManagerGala;
+ if (gala_wm != null) {
+ var screensaver_manager = gala_wm.screensaver;
+ Bus.own_name (BusType.SESSION, "org.freedesktop.ScreenSaver", BusNameOwnerFlags.REPLACE,
+ (connection) => {
+ try {
+ connection.register_object ("/org/freedesktop/ScreenSaver", screensaver_manager);
+ } catch (Error e) { warning (e.message); }
+ },
+ () => {},
+ () => critical ("Could not acquire freedesktop ScreenSaver bus") );
+
+ Bus.own_name (BusType.SESSION, "org.gnome.ScreenSaver", BusNameOwnerFlags.REPLACE,
+ (connection) => {
+ try {
+ connection.register_object ("/org/gnome/ScreenSaver", screensaver_manager.gnome_manager);
+ } catch (Error e) { warning (e.message); }
+ },
+ () => {},
+ () => critical ("Could not acquire ScreenSaver bus") );
+ }
}
private DBus () {
diff --git a/src/ScreenSaver.vala b/src/ScreenSaver.vala
deleted file mode 100644
index b653f4a3..00000000
--- a/src/ScreenSaver.vala
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// Copyright (C) 2012 Jaap Broekhuizen
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// 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 .
-//
-
-namespace Gala {
- [DBus (name = "org.gnome.ScreenSaver")]
- public interface ScreenSaver : GLib.Object {
- public abstract bool get_active () throws DBusError, IOError;
- public signal void active_changed (bool active);
- }
-}
diff --git a/src/ScreenSaverManager.vala b/src/ScreenSaverManager.vala
new file mode 100644
index 00000000..2813c65e
--- /dev/null
+++ b/src/ScreenSaverManager.vala
@@ -0,0 +1,102 @@
+//
+// Copyright 2020 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 License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 .
+//
+
+namespace Gala {
+ [DBus (name="org.freedesktop.ScreenSaver")]
+ public class ScreenSaverManager : Object {
+ public signal void active_changed (bool new_value);
+
+ [DBus (visible = false)]
+ public GNOMEScreenSaverManager gnome_manager { get; construct; }
+
+ [DBus (visible = false)]
+ public ScreenShield screen_shield { get; construct; }
+
+ public ScreenSaverManager (ScreenShield shield) {
+ Object (screen_shield: shield);
+ }
+
+ construct {
+ gnome_manager = new GNOMEScreenSaverManager (this);
+ screen_shield.active_changed.connect (() => {
+ active_changed (screen_shield.active);
+ gnome_manager.active_changed (screen_shield.active);
+ });
+ }
+
+ public void @lock () throws GLib.Error {
+ screen_shield.@lock (true);
+ }
+
+ public bool get_active () throws GLib.Error {
+ return screen_shield.active;
+ }
+
+ public void set_active (bool active) throws GLib.Error {
+ if (active) {
+ screen_shield.activate (true);
+ } else {
+ screen_shield.deactivate (false);
+ }
+ }
+
+ public uint get_active_time () throws GLib.Error {
+ var started = screen_shield.activation_time;
+ if (started > 0) {
+ return (uint)Math.floor ((GLib.get_monotonic_time () - started) / 1000000);
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ [DBus (name="org.gnome.ScreenSaver")]
+ public class GNOMEScreenSaverManager : GLib.Object {
+ [DBus (visible = false)]
+ public weak ScreenSaverManager manager { get; construct; }
+
+ public signal void active_changed (bool new_value);
+
+ public signal void wake_up_screen ();
+
+ public GNOMEScreenSaverManager (ScreenSaverManager manager) {
+ Object (manager: manager);
+ }
+
+ construct {
+ manager.screen_shield.wake_up_screen.connect (() => {
+ wake_up_screen ();
+ });
+ }
+
+ public new void @lock () throws GLib.Error {
+ manager.@lock ();
+ }
+
+ public new bool get_active () throws GLib.Error {
+ return manager.get_active ();
+ }
+
+ public new void set_active (bool active) throws GLib.Error {
+ manager.set_active (active);
+ }
+
+ public new uint get_active_time () throws GLib.Error {
+ return manager.get_active_time ();
+ }
+ }
+}
diff --git a/src/Widgets/ScreenShield.vala b/src/Widgets/ScreenShield.vala
new file mode 100644
index 00000000..8be2ca13
--- /dev/null
+++ b/src/Widgets/ScreenShield.vala
@@ -0,0 +1,422 @@
+//
+// Copyright 2020 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 License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 .
+//
+
+namespace Gala {
+ [DBus (name = "org.freedesktop.login1.Manager")]
+ interface LoginManager : Object {
+ public signal void prepare_for_sleep (bool about_to_suspend);
+
+ public abstract GLib.ObjectPath get_session (string session_id) throws GLib.Error;
+
+ public abstract UnixInputStream inhibit (string what, string who, string why, string mode) throws GLib.Error;
+ }
+
+ [DBus (name = "org.freedesktop.login1.Session")]
+ interface LoginSessionManager : Object {
+ public abstract bool active { get; }
+
+ public signal void lock ();
+ public signal void unlock ();
+
+ public abstract void set_locked_hint (bool locked) throws GLib.Error;
+ }
+
+ public struct LoginDisplay {
+ string session;
+ GLib.ObjectPath objectpath;
+ }
+
+ [DBus (name = "org.freedesktop.login1.User")]
+ interface LoginUserManager : Object {
+ public abstract LoginDisplay? display { owned get; }
+ }
+
+ [CCode (type_signature = "u")]
+ enum PresenceStatus {
+ AVAILABLE = 0,
+ INVISIBLE = 1,
+ BUSY = 2,
+ IDLE = 3
+ }
+
+ [DBus (name = "org.gnome.SessionManager.Presence")]
+ interface SessionPresence : Object {
+ public abstract PresenceStatus status { get; }
+ public signal void status_changed (PresenceStatus new_status);
+ }
+
+ [DBus (name = "org.freedesktop.DisplayManager.Seat")]
+ interface DisplayManagerSeat : Object {
+ public abstract void switch_to_greeter () throws GLib.Error;
+ }
+
+ public class ScreenShield : Clutter.Actor {
+ // Animation length for when computer has been sitting idle and display
+ // is about to turn off
+ public const uint LONG_ANIMATION_TIME = 3000;
+ // Animation length used for manual lock action (i.e. Super+L or GUI action)
+ public const uint SHORT_ANIMATION_TIME = 300;
+
+ private const string LOCK_ENABLED_KEY = "lock-on-screen-off";
+ private const string LOCK_PROHIBITED_KEY = "disable-lock-screen";
+ private const string LOCK_ON_SUSPEND_KEY = "lock-on-suspend";
+
+ public signal void active_changed ();
+ public signal void wake_up_screen ();
+
+ // Screensaver active but not necessarily locked
+ public bool active { get; private set; default = false; }
+
+ public bool is_locked { get; private set; default = false; }
+ public bool in_greeter { get; private set; default = false; }
+ public int64 activation_time { get; private set; default = 0; }
+
+ public WindowManager wm { get; construct; }
+
+ private ModalProxy? modal_proxy;
+
+ private LoginManager? login_manager;
+ private LoginUserManager? login_user_manager;
+ private LoginSessionManager? login_session;
+ private SessionPresence? session_presence;
+
+ private DisplayManagerSeat? display_manager;
+
+ private uint animate_id = 0;
+
+ private UnixInputStream? inhibitor;
+
+ private GLib.Settings lockdown_settings;
+ private GLib.Settings gala_settings;
+
+ private bool connected_to_buses = false;
+
+ public ScreenShield (WindowManager wm) {
+ Object (wm: wm);
+ }
+
+ construct {
+ lockdown_settings = new GLib.Settings ("org.gnome.desktop.lockdown");
+ gala_settings = new GLib.Settings (Config.SCHEMA + ".behavior");
+
+ visible = false;
+ reactive = true;
+
+ // Listen for keypresses or mouse movement
+ key_press_event.connect ((event) => {
+ on_user_became_active ();
+ });
+
+ motion_event.connect ((event) => {
+ on_user_became_active ();
+ });
+
+ background_color = Clutter.Color.from_string ("black");
+
+ expand_to_screen_size ();
+
+ bool success = true;
+
+ try {
+ login_manager = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1");
+ login_user_manager = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1/user/self");
+
+ // Listen for sleep/resume events from logind
+ login_manager.prepare_for_sleep.connect (prepare_for_sleep);
+ login_session = get_current_session_manager ();
+ if (login_session != null) {
+ // Listen for lock unlock events from logind
+ login_session.lock.connect (() => @lock (false));
+ login_session.unlock.connect (() => {
+ deactivate (false);
+ in_greeter = false;
+ });
+
+ login_session.notify.connect (sync_inhibitor);
+ sync_inhibitor ();
+ }
+ } catch (Error e) {
+ success = false;
+ critical ("Unable to connect to logind bus, screen locking disabled: %s", e.message);
+ }
+
+ try {
+ session_presence = Bus.get_proxy_sync (BusType.SESSION, "org.gnome.SessionManager", "/org/gnome/SessionManager/Presence");
+ on_status_changed (session_presence.status);
+ session_presence.status_changed.connect ((status) => on_status_changed (status));
+ } catch (Error e) {
+ success = false;
+ critical ("Unable to connect to session presence bus, screen locking disabled: %s", e.message);
+ }
+
+
+ string? seat_path = GLib.Environment.get_variable ("XDG_SEAT_PATH");
+ if (seat_path != null) {
+ try {
+ display_manager = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.DisplayManager", seat_path);
+ } catch (Error e) {
+ success = false;
+ critical ("Unable to connect to display manager bus, screen locking disabled");
+ }
+ } else {
+ success = false;
+ critical ("XDG_SEAT_PATH unset, screen locking disabled");
+ }
+
+ connected_to_buses = success;
+ }
+
+ public void expand_to_screen_size () {
+ int screen_width, screen_height;
+#if HAS_MUTTER330
+ wm.get_display ().get_size (out screen_width, out screen_height);
+#else
+ wm.get_screen ().get_size (out screen_width, out screen_height);
+#endif
+ width = screen_width;
+ height = screen_height;
+ }
+
+ private void prepare_for_sleep (bool about_to_suspend) {
+ if (!connected_to_buses) {
+ return;
+ }
+
+ if (about_to_suspend) {
+ if (gala_settings.get_boolean (LOCK_ON_SUSPEND_KEY)) {
+ debug ("about to sleep, locking screen");
+ this.@lock (false);
+ }
+ } else {
+ debug ("resumed from suspend, waking screen");
+ on_user_became_active ();
+ wake_up_screen ();
+ expand_to_screen_size ();
+ }
+ }
+
+ // status becomes idle after interval defined at /org/gnome/desktop/session/idle-delay
+ private void on_status_changed (PresenceStatus status) {
+ if (status != PresenceStatus.IDLE || !connected_to_buses) {
+ return;
+ }
+
+ debug ("session became idle, activating screensaver");
+
+ activate (true);
+ }
+
+ // We briefly inhibit sleep so that we can try and lock before sleep occurs if necessary
+ private void sync_inhibitor () {
+ if (!connected_to_buses) {
+ return;
+ }
+
+ var lock_enabled = gala_settings.get_boolean (LOCK_ON_SUSPEND_KEY);
+ var lock_prohibited = lockdown_settings.get_boolean (LOCK_PROHIBITED_KEY);
+
+ var inhibit = login_session != null && login_session.active && !active && lock_enabled && !lock_prohibited;
+ if (inhibit) {
+ try {
+ var new_inhibitor = login_manager.inhibit ("sleep", "Pantheon", "Pantheon needs to lock the screen", "delay");
+ if (inhibitor != null) {
+ inhibitor.close ();
+ inhibitor = null;
+ }
+
+ inhibitor = new_inhibitor;
+ } catch (Error e) {
+ warning ("Unable to inhibit sleep, may be unable to lock before sleep starts: %s", e.message);
+ }
+ } else {
+ if (inhibitor != null) {
+ try {
+ inhibitor.close ();
+ } catch (Error e) {
+ warning ("Unable to remove sleep inhibitor: %s", e.message);
+ }
+
+ inhibitor = null;
+ }
+ }
+ }
+
+ private void on_user_became_active () {
+ if (!connected_to_buses) {
+ return;
+ }
+
+ // User became active in some way, switch to the greeter if we're not there already
+ if (is_locked && !in_greeter) {
+ debug ("user became active, switching to greeter");
+ cancel_animation ();
+ try {
+ display_manager.switch_to_greeter ();
+ } catch (Error e) {
+ critical ("Unable to switch to greeter to unlock: %s", e.message);
+ }
+ in_greeter = true;
+ // Otherwise, we're in screensaver mode, just deactivate
+ } else if (!is_locked) {
+ debug ("user became active in unlocked session, closing screensaver");
+ deactivate (false);
+ }
+ }
+
+ private LoginSessionManager? get_current_session_manager () throws GLib.Error {
+ string? session_id = GLib.Environment.get_variable ("XDG_SESSION_ID");
+ if (session_id == null) {
+ debug ("Unset XDG_SESSION_ID, asking logind");
+ if (login_user_manager == null) {
+ return null;
+ }
+
+ session_id = login_user_manager.display.session;
+ }
+
+ if (session_id == null) {
+ return null;
+ }
+
+ var session_path = login_manager.get_session (session_id);
+ LoginSessionManager? session = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.login1", session_path);
+
+ return session;
+ }
+
+ public void @lock (bool animate) {
+ if (is_locked || !connected_to_buses) {
+ return;
+ }
+
+ if (lockdown_settings.get_boolean (LOCK_PROHIBITED_KEY)) {
+ debug ("Lock prohibited, ignoring lock request");
+ return;
+ }
+
+ is_locked = true;
+
+ activate (animate, SHORT_ANIMATION_TIME);
+ }
+
+ public void activate (bool animate, uint animation_time = LONG_ANIMATION_TIME) {
+ if (visible || !connected_to_buses) {
+ return;
+ }
+
+ expand_to_screen_size ();
+
+ if (activation_time == 0) {
+ activation_time = GLib.get_monotonic_time ();
+ }
+
+#if HAS_MUTTER330
+ wm.get_display ().get_cursor_tracker ().set_pointer_visible (false);
+#else
+ wm.get_screen ().get_cursor_tracker ().set_pointer_visible (false);
+#endif
+
+ visible = true;
+ grab_key_focus ();
+ modal_proxy = wm.push_modal ();
+
+ if (animate) {
+ animate_and_lock (animation_time);
+ } else {
+ _set_active (true);
+
+ if (gala_settings.get_boolean (LOCK_ENABLED_KEY)) {
+ @lock (false);
+ }
+ }
+ }
+
+ private void animate_and_lock (uint animation_time) {
+ opacity = 0;
+ save_easing_state ();
+ set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
+ set_easing_duration (animation_time);
+ opacity = 255;
+
+ animate_id = Timeout.add (animation_time, () => {
+ animate_id = 0;
+
+ restore_easing_state ();
+
+ _set_active (true);
+
+ if (gala_settings.get_boolean (LOCK_ENABLED_KEY)) {
+ @lock (false);
+ }
+
+ return GLib.Source.REMOVE;
+ });
+ }
+
+ private void cancel_animation () {
+ if (animate_id != 0) {
+ GLib.Source.remove (animate_id);
+ animate_id = 0;
+
+ restore_easing_state ();
+ }
+ }
+
+ public void deactivate (bool animate) {
+ if (!connected_to_buses) {
+ return;
+ }
+
+ cancel_animation ();
+
+ is_locked = false;
+
+ if (modal_proxy != null) {
+ wm.pop_modal (modal_proxy);
+ modal_proxy = null;
+ }
+
+#if HAS_MUTTER330
+ wm.get_display ().get_cursor_tracker ().set_pointer_visible (true);
+#else
+ wm.get_screen ().get_cursor_tracker ().set_pointer_visible (true);
+#endif
+
+ visible = false;
+
+ wake_up_screen ();
+
+ activation_time = 0;
+ _set_active (false);
+ }
+
+ private void _set_active (bool new_active) {
+ if (!connected_to_buses) {
+ return;
+ }
+
+ var prev_is_active = active;
+ active = new_active;
+
+ if (prev_is_active != active) {
+ active_changed ();
+ }
+
+ sync_inhibitor ();
+ }
+ }
+}
diff --git a/src/WindowManager.vala b/src/WindowManager.vala
index 90652de7..3de8b2f8 100644
--- a/src/WindowManager.vala
+++ b/src/WindowManager.vala
@@ -55,6 +55,8 @@ namespace Gala {
*/
public bool enable_animations { get; protected set; }
+ public ScreenShield? screen_shield { get; private set; }
+
Meta.PluginInfo info;
WindowSwitcher? winswitcher = null;
@@ -63,14 +65,14 @@ namespace Gala {
// used to detect which corner was used to trigger an action
Clutter.Actor? last_hotcorner;
- ScreenSaver? screensaver;
+ public ScreenSaverManager? screensaver { get; private set; }
Clutter.Actor? tile_preview;
private Meta.Window? moving; //place for the window that is being moved over
Daemon? daemon_proxy = null;
-
+
NotificationStack notification_stack;
Gee.LinkedList modal_stack = new Gee.LinkedList ();
@@ -151,6 +153,9 @@ namespace Gala {
var display = screen.get_display ();
#endif
+ screen_shield = new ScreenShield (this);
+ screensaver = new ScreenSaverManager (screen_shield);
+
DBus.init (this);
DBusAccelerator.init (this);
MediaFeedback.init ();
@@ -170,14 +175,7 @@ namespace Gala {
// Due to a bug which enables access to the stage when using multiple monitors
// in the screensaver, we have to listen for changes and make sure the input area
// is set to NONE when we are in locked mode
- try {
- screensaver = Bus.get_proxy_sync (BusType.SESSION, "org.gnome.ScreenSaver",
- "/org/gnome/ScreenSaver");
- screensaver.active_changed.connect (update_input_area);
- } catch (Error e) {
- screensaver = null;
- warning (e.message);
- }
+ screensaver.active_changed.connect (update_input_area);
#if HAS_MUTTER330
stage = display.get_stage () as Clutter.Stage;
@@ -241,6 +239,8 @@ namespace Gala {
#else
top_window_group = screen.get_top_window_group ();
#endif
+ ui_group.add_child (screen_shield);
+
stage.remove_child (top_window_group);
ui_group.add_child (top_window_group);
@@ -296,9 +296,9 @@ namespace Gala {
/*hot corner, getting enum values from GraniteServicesSettings did not work, so we use GSettings directly*/
configure_hotcorners ();
#if HAS_MUTTER330
- Meta.MonitorManager.@get ().monitors_changed.connect (configure_hotcorners);
+ Meta.MonitorManager.@get ().monitors_changed.connect (on_monitors_changed);
#else
- screen.monitors_changed.connect (configure_hotcorners);
+ screen.monitors_changed.connect (on_monitors_changed);
#endif
BehaviorSettings.get_default ().schema.changed.connect (configure_hotcorners);
@@ -368,6 +368,11 @@ namespace Gala {
return false;
}
+ void on_monitors_changed () {
+ configure_hotcorners ();
+ screen_shield.expand_to_screen_size ();
+ }
+
void configure_hotcorners () {
#if HAS_MUTTER330
unowned Meta.Display display = get_display ();
@@ -2186,7 +2191,7 @@ namespace Gala {
return info;
}
- /**
+ /**
* Notification windows are a special case where the transition state needs
* to be preserved when reparenting the actor. Because Clutter doesn't allow specifying
* remove_child flags we will save the elapsed time of required transitions and
diff --git a/src/meson.build b/src/meson.build
index 1bbdc23c..ea2a4750 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -9,7 +9,7 @@ gala_bin_sources = files(
'Main.vala',
'MediaFeedback.vala',
'PluginManager.vala',
- 'ScreenSaver.vala',
+ 'ScreenSaverManager.vala',
'ScreenshotManager.vala',
'SessionManager.vala',
'Settings.vala',
@@ -30,6 +30,7 @@ gala_bin_sources = files(
'Widgets/MultitaskingView.vala',
'Widgets/PixelPicker.vala',
'Widgets/SafeWindowClone.vala',
+ 'Widgets/ScreenShield.vala',
'Widgets/SelectionArea.vala',
'Widgets/WindowClone.vala',
'Widgets/WindowCloneContainer.vala',