Skip to content

Commit e1a7960

Browse files
committed
screensaver: Do more to acquire a modal grab attempt when locking.
cinnamon-screensaver made muultiple attempts to acquire a modal 'grab' when activating, including using xdo to cancel ui toolkit popups. - Add an asynchronous pushScreensaverModal to do the same. - Screensaver service 'response' is delayed until this ultimately succeeds or fails. - Suspend inhibitor state is held during this time also.
1 parent c59b689 commit e1a7960

6 files changed

Lines changed: 368 additions & 41 deletions

File tree

js/ui/main.js

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,40 @@ function _findModal(actor) {
13181318
return -1;
13191319
}
13201320

1321+
function _completeModalSetup(actor, mode) {
1322+
if (modalCount == 0)
1323+
Meta.disable_unredirect_for_display(global.display);
1324+
1325+
global.set_stage_input_mode(Cinnamon.StageInputMode.FULLSCREEN);
1326+
1327+
actionMode = mode;
1328+
1329+
modalCount += 1;
1330+
let actorDestroyId = actor.connect('destroy', function() {
1331+
let index = _findModal(actor);
1332+
if (index >= 0)
1333+
popModal(actor);
1334+
});
1335+
1336+
let record = {
1337+
actor: actor,
1338+
focus: global.stage.get_key_focus(),
1339+
destroyId: actorDestroyId,
1340+
actionMode: mode
1341+
};
1342+
if (record.focus != null) {
1343+
record.focusDestroyId = record.focus.connect('destroy', function() {
1344+
record.focus = null;
1345+
record.focusDestroyId = null;
1346+
});
1347+
}
1348+
modalActorFocusStack.push(record);
1349+
1350+
global.stage.set_key_focus(actor);
1351+
1352+
layoutManager.updateChrome(true);
1353+
}
1354+
13211355
/**
13221356
* pushModal:
13231357
* @actor (Clutter.Actor): actor which will be given keyboard focus
@@ -1356,38 +1390,46 @@ function pushModal(actor, timestamp, options, mode) {
13561390
log('pushModal: invocation of begin_modal failed');
13571391
return false;
13581392
}
1359-
Meta.disable_unredirect_for_display(global.display);
13601393
}
13611394

1362-
global.set_stage_input_mode(Cinnamon.StageInputMode.FULLSCREEN);
1363-
1364-
actionMode = mode;
1395+
_completeModalSetup(actor, mode);
1396+
return true;
1397+
}
13651398

1366-
modalCount += 1;
1367-
let actorDestroyId = actor.connect('destroy', function() {
1368-
let index = _findModal(actor);
1369-
if (index >= 0)
1370-
popModal(actor);
1371-
});
1399+
/**
1400+
* pushScreensaverModal:
1401+
* @actor (Clutter.Actor): actor which will be given keyboard focus.
1402+
* @timestamp (number): optional X server timestamp.
1403+
* @mode (Cinnamon.ActionMode): the action mode for the modal grab.
1404+
* @callback (function): called with (success) when the grab completes.
1405+
*
1406+
* Like pushModal(), but uses begin_modal_with_retry() to asynchronously
1407+
* retry the grab on X11, using libxdo to break stuck grabs from popup
1408+
* menus. The callback is called with true on success, false on failure.
1409+
*/
1410+
function pushScreensaverModal(actor, timestamp, mode, callback) {
1411+
if (timestamp == undefined)
1412+
timestamp = global.get_current_time();
13721413

1373-
let record = {
1374-
actor: actor,
1375-
focus: global.stage.get_key_focus(),
1376-
destroyId: actorDestroyId,
1377-
actionMode: mode
1378-
};
1379-
if (record.focus != null) {
1380-
record.focusDestroyId = record.focus.connect('destroy', function() {
1381-
record.focus = null;
1382-
record.focusDestroyId = null;
1383-
});
1384-
}
1385-
modalActorFocusStack.push(record);
1414+
log('pushScreensaverModal: requesting modal grab with retry');
13861415

1387-
global.stage.set_key_focus(actor);
1416+
global.begin_modal_with_retry(timestamp, 0,
1417+
(obj, success) => {
1418+
if (!success) {
1419+
log('pushScreensaverModal: failed to acquire modal grab after retries');
1420+
callback(false);
1421+
return;
1422+
}
13881423

1389-
layoutManager.updateChrome(true);
1390-
return true;
1424+
try {
1425+
_completeModalSetup(actor, mode);
1426+
callback(true);
1427+
} catch (e) {
1428+
global.logError(`pushScreensaverModal: error during modal setup: ${e.message}`);
1429+
global.end_modal(global.get_current_time());
1430+
callback(false);
1431+
}
1432+
});
13911433
}
13921434

13931435
/**

js/ui/screensaver/screenShield.js

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ var ScreenShield = GObject.registerClass({
129129
this._widgetLoadIdleId = 0;
130130
this._infoPanel = null;
131131
this._inhibitor = null;
132+
this._activationPending = false;
132133

133134
this._nameBlocker = new NameBlocker.NameBlocker();
134135

@@ -356,19 +357,24 @@ var ScreenShield = GObject.registerClass({
356357
}
357358

358359
lock(immediate = false, awayMessage = null) {
359-
if (this.isLocked())
360+
if (this.isLocked() || this._activationPending) {
361+
_log('ScreenShield: Already locked or activation pending, ignoring lock request');
360362
return;
363+
}
361364

362365
this._awayMessage = awayMessage;
363366

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

366369
if (this._state === State.HIDDEN) {
367-
this.activate(immediate);
370+
this.activate(immediate, () => {
371+
this._stopLockDelay();
372+
this._setLocked(true);
373+
});
374+
} else {
375+
this._stopLockDelay();
376+
this._setLocked(true);
368377
}
369-
370-
this._stopLockDelay();
371-
this._setLocked(true);
372378
}
373379

374380
_setLocked(locked) {
@@ -397,9 +403,9 @@ var ScreenShield = GObject.registerClass({
397403
this._hideShield(true);
398404
}
399405

400-
activate(immediate = false) {
401-
if (this._state !== State.HIDDEN) {
402-
_log('ScreenShield: Already active');
406+
activate(immediate = false, callback = null) {
407+
if (this._state !== State.HIDDEN || this._activationPending) {
408+
_log('ScreenShield: Already active or activation pending');
403409
return;
404410
}
405411

@@ -409,11 +415,34 @@ var ScreenShield = GObject.registerClass({
409415
this._lastMotionY = -1;
410416
this._activationTime = GLib.get_monotonic_time();
411417

412-
if (!Main.pushModal(this, global.get_current_time(), 0, Cinnamon.ActionMode.LOCK_SCREEN)) {
413-
global.logError('ScreenShield: Failed to acquire modal grab');
414-
return;
415-
}
418+
this._activationPending = true;
419+
_log('ScreenShield: requesting screensaver modal grab');
420+
Main.pushScreensaverModal(this, global.get_current_time(), Cinnamon.ActionMode.LOCK_SCREEN,
421+
(success) => {
422+
this._activationPending = false;
423+
424+
if (!success) {
425+
global.logError('ScreenShield: Failed to acquire modal grab');
426+
this._syncInhibitor();
427+
return;
428+
}
416429

430+
_log('ScreenShield: modal grab acquired, finishing activation');
431+
432+
try {
433+
this._finishActivation(immediate);
434+
435+
if (callback)
436+
callback();
437+
} catch (e) {
438+
global.logError(`ScreenShield: error during activation: ${e.message}`);
439+
Main.popModal(this);
440+
this._syncInhibitor();
441+
}
442+
});
443+
}
444+
445+
_finishActivation(immediate) {
417446
this._setState(State.SHOWN);
418447

419448
this._createBackgrounds();
@@ -495,6 +524,12 @@ var ScreenShield = GObject.registerClass({
495524
return;
496525
}
497526

527+
if (this._activationPending) {
528+
_log('ScreenShield: Cancelling pending activation');
529+
global.end_modal(global.get_current_time());
530+
return;
531+
}
532+
498533
this._stopLockDelay();
499534
this._hideShield(false);
500535
}
@@ -552,7 +587,8 @@ var ScreenShield = GObject.registerClass({
552587
_syncInhibitor() {
553588
let lockEnabled = this._settings.get_boolean('lock-enabled');
554589
let lockDisabled = Main.lockdownSettings.get_boolean('disable-lock-screen');
555-
let shouldInhibit = this._state === State.HIDDEN && lockEnabled && !lockDisabled;
590+
let shouldInhibit = (this._state === State.HIDDEN || this._activationPending) &&
591+
lockEnabled && !lockDisabled;
556592

557593
if (shouldInhibit && !this._inhibitor) {
558594
_log('ScreenShield: Acquiring sleep inhibitor');
@@ -563,7 +599,7 @@ var ScreenShield = GObject.registerClass({
563599
}
564600

565601
// Re-check after async - conditions may have changed
566-
let stillNeeded = this._state === State.HIDDEN &&
602+
let stillNeeded = (this._state === State.HIDDEN || this._activationPending) &&
567603
this._settings.get_boolean('lock-enabled') &&
568604
!Main.lockdownSettings.get_boolean('disable-lock-screen');
569605
if (stillNeeded) {

src/cinnamon-global-private.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#endif
2121

2222
#include <meta/meta-plugin.h>
23+
#include <xdo.h>
2324

2425

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

3233
#include <cjs/gjs.h>
3334

35+
typedef struct {
36+
CinnamonGlobal *global;
37+
guint32 timestamp;
38+
MetaModalOptions options;
39+
gint attempt;
40+
gboolean tried_xdo;
41+
CinnamonModalCallback callback;
42+
gpointer user_data;
43+
} ModalRetryData;
44+
3445
struct _CinnamonGlobal {
3546
GObject parent;
3647

@@ -64,6 +75,10 @@ struct _CinnamonGlobal {
6475
gboolean has_modal;
6576

6677
guint notif_service_id;
78+
79+
xdo_t *xdo;
80+
guint modal_retry_source_id;
81+
ModalRetryData *modal_retry_data;
6782
};
6883

6984
void _cinnamon_global_init (const char *first_property_name,

0 commit comments

Comments
 (0)