Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 66 additions & 26 deletions js/ui/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,40 @@ function _findModal(actor) {
return -1;
}

function _completeModalSetup(actor, mode) {
if (modalCount == 0)
Meta.disable_unredirect_for_display(global.display);

global.set_stage_input_mode(Cinnamon.StageInputMode.FULLSCREEN);

actionMode = mode;

modalCount += 1;
let actorDestroyId = actor.connect('destroy', function() {
let index = _findModal(actor);
if (index >= 0)
popModal(actor);
});

let record = {
actor: actor,
focus: global.stage.get_key_focus(),
destroyId: actorDestroyId,
actionMode: mode
};
if (record.focus != null) {
record.focusDestroyId = record.focus.connect('destroy', function() {
record.focus = null;
record.focusDestroyId = null;
});
}
modalActorFocusStack.push(record);

global.stage.set_key_focus(actor);

layoutManager.updateChrome(true);
}

/**
* pushModal:
* @actor (Clutter.Actor): actor which will be given keyboard focus
Expand Down Expand Up @@ -1356,38 +1390,44 @@ function pushModal(actor, timestamp, options, mode) {
log('pushModal: invocation of begin_modal failed');
return false;
}
Meta.disable_unredirect_for_display(global.display);
}

global.set_stage_input_mode(Cinnamon.StageInputMode.FULLSCREEN);
_completeModalSetup(actor, mode);
return true;
}

actionMode = mode;
/**
* pushScreensaverModal:
* @actor (Clutter.Actor): actor which will be given keyboard focus.
* @timestamp (number): optional X server timestamp.
* @mode (Cinnamon.ActionMode): the action mode for the modal grab.
* @callback (function): called with (success) when the grab completes.
*
* Like pushModal(), but uses begin_modal_with_retry() to asynchronously
* retry the grab on X11, using libxdo to break stuck grabs from popup
* menus. The callback is called with true on success, false on failure.
*/
function pushScreensaverModal(actor, timestamp, mode, callback) {
if (timestamp == undefined)
timestamp = global.get_current_time();

modalCount += 1;
let actorDestroyId = actor.connect('destroy', function() {
let index = _findModal(actor);
if (index >= 0)
popModal(actor);
});
global.begin_modal_with_retry(timestamp, 0,
(obj, success) => {
if (!success) {
log('pushScreensaverModal: failed to acquire modal grab after retries (or cancelled)');
callback(false);
return;
}

let record = {
actor: actor,
focus: global.stage.get_key_focus(),
destroyId: actorDestroyId,
actionMode: mode
};
if (record.focus != null) {
record.focusDestroyId = record.focus.connect('destroy', function() {
record.focus = null;
record.focusDestroyId = null;
try {
_completeModalSetup(actor, mode);
callback(true);
} catch (e) {
global.logError(`pushScreensaverModal: error during modal setup: ${e.message}`);
global.end_modal(global.get_current_time());
callback(false);
}
});
}
modalActorFocusStack.push(record);

global.stage.set_key_focus(actor);

layoutManager.updateChrome(true);
return true;
}

/**
Expand Down
138 changes: 98 additions & 40 deletions js/ui/screensaver/screenShield.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ var ScreenShield = GObject.registerClass({
this._widgetLoadIdleId = 0;
this._infoPanel = null;
this._inhibitor = null;
this._activationPending = false;

this._nameBlocker = new NameBlocker.NameBlocker();

Expand Down Expand Up @@ -356,19 +357,26 @@ var ScreenShield = GObject.registerClass({
}

lock(immediate = false, awayMessage = null) {
if (this.isLocked())
if (this.isLocked() || this._activationPending) {
_log('ScreenShield: Already locked or activation pending, ignoring lock request');
return;
}

this._awayMessage = awayMessage;

_log(`ScreenShield: Locking screen (immediate=${immediate})`);

if (this._state === State.HIDDEN) {
this.activate(immediate);
this.activate(immediate, (success) => {
if (success) {
this._stopLockDelay();
this._setLocked(true);
}
});
} else {
this._stopLockDelay();
this._setLocked(true);
}

this._stopLockDelay();
this._setLocked(true);
}

_setLocked(locked) {
Expand Down Expand Up @@ -397,9 +405,9 @@ var ScreenShield = GObject.registerClass({
this._hideShield(true);
}

activate(immediate = false) {
if (this._state !== State.HIDDEN) {
_log('ScreenShield: Already active');
activate(immediate = false, lock_callback = null) {
if (this._state !== State.HIDDEN || this._activationPending) {
_log('ScreenShield: Already active or activation pending');
return;
}

Expand All @@ -409,44 +417,87 @@ var ScreenShield = GObject.registerClass({
this._lastMotionY = -1;
this._activationTime = GLib.get_monotonic_time();

if (!Main.pushModal(this, global.get_current_time(), 0, Cinnamon.ActionMode.LOCK_SCREEN)) {
global.logError('ScreenShield: Failed to acquire modal grab');
return;
}
this._activationPending = true;
_log('ScreenShield: requesting screensaver modal grab');
Main.pushScreensaverModal(this, global.get_current_time(), Cinnamon.ActionMode.LOCK_SCREEN,
(success) => {
this._activationPending = false;

this._setState(State.SHOWN);
if (success) {
_log('ScreenShield: modal grab acquired, finishing activation');

this._createBackgrounds();
if (this._finishActivation(immediate)) {
if (lock_callback)
lock_callback(true);

this._capturedEventId = global.stage.connect('captured-event',
this._onCapturedEvent.bind(this));
return;
} else {
Main.popModal(this);
}
}

global.stage.hide_cursor();
global.logWarning('ScreenShield: Failed to acquire modal grab (or cancelled)');
this._activationTime = 0;
this._syncInhibitor();
if (lock_callback)
lock_callback(false);
});
}

if (Main.deskletContainer)
Main.deskletContainer.actor.hide();
_finishActivation(immediate) {
try {
this._createBackgrounds();

Main.layoutManager.screenShieldGroup.show();
this.show();
this._capturedEventId = global.stage.connect('captured-event',
this._onCapturedEvent.bind(this));

if (immediate) {
this.opacity = 255;
this._activateBackupLocker();
this._scheduleWidgetLoading();
} else {
this.opacity = 0;
this.ease({
opacity: 255,
duration: FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._activateBackupLocker();
this._scheduleWidgetLoading();
}
});
}
global.stage.hide_cursor();

if (Main.deskletContainer)
Main.deskletContainer.actor.hide();

Main.layoutManager.screenShieldGroup.show();
this.show();

if (immediate) {
this.opacity = 255;
this._activateBackupLocker();
this._scheduleWidgetLoading();
} else {
this.opacity = 0;
this.ease({
opacity: 255,
duration: FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._activateBackupLocker();
this._scheduleWidgetLoading();
}
});
}

this._setState(State.SHOWN);
this._startLockDelay();
return true;
} catch (e) {
global.logError(`ScreenShield: error during activation: ${e.message}`);

if (this._capturedEventId) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}

global.stage.show_cursor();

if (Main.deskletContainer)
Main.deskletContainer.actor.show();

this._startLockDelay();
Main.layoutManager.screenShieldGroup.hide();
this.hide();
this._destroyBackgrounds();

return false;
}
}

_startLockDelay() {
Expand Down Expand Up @@ -495,6 +546,12 @@ var ScreenShield = GObject.registerClass({
return;
}

if (this._activationPending) {
_log('ScreenShield: Cancelling pending activation');
global.end_modal(global.get_current_time());
return;
}

this._stopLockDelay();
this._hideShield(false);
}
Expand Down Expand Up @@ -552,7 +609,8 @@ var ScreenShield = GObject.registerClass({
_syncInhibitor() {
let lockEnabled = this._settings.get_boolean('lock-enabled');
let lockDisabled = Main.lockdownSettings.get_boolean('disable-lock-screen');
let shouldInhibit = this._state === State.HIDDEN && lockEnabled && !lockDisabled;
let shouldInhibit = (this._state === State.HIDDEN || this._activationPending) &&
lockEnabled && !lockDisabled;

if (shouldInhibit && !this._inhibitor) {
_log('ScreenShield: Acquiring sleep inhibitor');
Expand All @@ -563,7 +621,7 @@ var ScreenShield = GObject.registerClass({
}

// Re-check after async - conditions may have changed
let stillNeeded = this._state === State.HIDDEN &&
let stillNeeded = (this._state === State.HIDDEN || this._activationPending) &&
this._settings.get_boolean('lock-enabled') &&
!Main.lockdownSettings.get_boolean('disable-lock-screen');
if (stillNeeded) {
Expand Down
15 changes: 15 additions & 0 deletions src/cinnamon-global-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#endif

#include <meta/meta-plugin.h>
#include <xdo.h>


#include "cinnamon-enum-types.h"
Expand All @@ -31,6 +32,16 @@

#include <cjs/gjs.h>

typedef struct {
CinnamonGlobal *global;
guint32 timestamp;
MetaModalOptions options;
gint attempt;
gboolean tried_xdo;
CinnamonModalCallback callback;
gpointer user_data;
} ModalRetryData;

struct _CinnamonGlobal {
GObject parent;

Expand Down Expand Up @@ -64,6 +75,10 @@ struct _CinnamonGlobal {
gboolean has_modal;

guint notif_service_id;

xdo_t *xdo;
guint modal_retry_source_id;
ModalRetryData *modal_retry_data;
};

void _cinnamon_global_init (const char *first_property_name,
Expand Down
Loading
Loading