ae2751a68b
Commit 6c2f3d1d17
moved pref overrides into JS to implement
session mode specific overrides in a clean and generic way.
However that approach comes with a cost - doing the overrides only
after having handled over control to JS means that the core will
be initialized with the non-overridden settings before changing
to the correct values. In the best case this is unnecessary work,
but it can in fact have a worse effect: when initializing workspaces,
we will restore the previous number of workspaces when using
dynamic-workspaces and reset to the configured number otherwise.
As the non-overridden default for dynamic-workspaces is FALSE, we
can easily end up moving the user's windows to the "wrong" workspace.
Now GSettings is expected to grow support for session specific defaults,
which will render our entire override system obsolete (yay!). Given
that, it seems acceptable to use a less generic (and uglier) approach
in the meanwhile, in order to fix aforementioned problems. So move
overrides back before core initialization and just hardcode the
session-mode => override-schema relation.
https://bugzilla.gnome.org/show_bug.cgi?id=695487
611 lines
20 KiB
JavaScript
611 lines
20 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gdk = imports.gi.Gdk;
|
|
const Gio = imports.gi.Gio;
|
|
const GLib = imports.gi.GLib;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
const Meta = imports.gi.Meta;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
|
|
const Components = imports.ui.components;
|
|
const CtrlAltTab = imports.ui.ctrlAltTab;
|
|
const EndSessionDialog = imports.ui.endSessionDialog;
|
|
const Environment = imports.ui.environment;
|
|
const ExtensionSystem = imports.ui.extensionSystem;
|
|
const ExtensionDownloader = imports.ui.extensionDownloader;
|
|
const Keyboard = imports.ui.keyboard;
|
|
const MessageTray = imports.ui.messageTray;
|
|
const OsdWindow = imports.ui.osdWindow;
|
|
const Overview = imports.ui.overview;
|
|
const Panel = imports.ui.panel;
|
|
const Params = imports.misc.params;
|
|
const RunDialog = imports.ui.runDialog;
|
|
const Layout = imports.ui.layout;
|
|
const LoginManager = imports.misc.loginManager;
|
|
const LookingGlass = imports.ui.lookingGlass;
|
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
|
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
|
const Screencast = imports.ui.screencast;
|
|
const ScreenShield = imports.ui.screenShield;
|
|
const Scripting = imports.ui.scripting;
|
|
const SessionMode = imports.ui.sessionMode;
|
|
const ShellDBus = imports.ui.shellDBus;
|
|
const ShellMountOperation = imports.ui.shellMountOperation;
|
|
const WindowManager = imports.ui.windowManager;
|
|
const Magnifier = imports.ui.magnifier;
|
|
const XdndHandler = imports.ui.xdndHandler;
|
|
const Util = imports.misc.util;
|
|
|
|
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
|
|
|
|
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
|
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
|
|
const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a';
|
|
|
|
let componentManager = null;
|
|
let panel = null;
|
|
let overview = null;
|
|
let runDialog = null;
|
|
let lookingGlass = null;
|
|
let wm = null;
|
|
let messageTray = null;
|
|
let screenShield = null;
|
|
let notificationDaemon = null;
|
|
let windowAttentionHandler = null;
|
|
let ctrlAltTabManager = null;
|
|
let osdWindowManager = null;
|
|
let sessionMode = null;
|
|
let shellDBusService = null;
|
|
let shellMountOpDBusService = null;
|
|
let screenSaverDBus = null;
|
|
let screencastService = null;
|
|
let modalCount = 0;
|
|
let keybindingMode = Shell.KeyBindingMode.NONE;
|
|
let modalActorFocusStack = [];
|
|
let uiGroup = null;
|
|
let magnifier = null;
|
|
let xdndHandler = null;
|
|
let keyboard = null;
|
|
let layoutManager = null;
|
|
let _startDate;
|
|
let _defaultCssStylesheet = null;
|
|
let _cssStylesheet = null;
|
|
let _a11ySettings = null;
|
|
|
|
function _sessionUpdated() {
|
|
_loadDefaultStylesheet();
|
|
|
|
wm.setCustomKeybindingHandler('panel-main-menu',
|
|
Shell.KeyBindingMode.NORMAL |
|
|
Shell.KeyBindingMode.OVERVIEW,
|
|
sessionMode.hasOverview ? Lang.bind(overview, overview.toggle) : null);
|
|
wm.allowKeybinding('overlay-key', Shell.KeyBindingMode.NORMAL |
|
|
Shell.KeyBindingMode.OVERVIEW);
|
|
|
|
wm.setCustomKeybindingHandler('panel-run-dialog',
|
|
Shell.KeyBindingMode.NORMAL |
|
|
Shell.KeyBindingMode.OVERVIEW,
|
|
sessionMode.hasRunDialog ? openRunDialog : null);
|
|
|
|
if (!sessionMode.hasRunDialog) {
|
|
if (runDialog)
|
|
runDialog.close();
|
|
if (lookingGlass)
|
|
lookingGlass.close();
|
|
}
|
|
}
|
|
|
|
function start() {
|
|
// These are here so we don't break compatibility.
|
|
global.logError = window.log;
|
|
global.log = window.log;
|
|
|
|
// Chain up async errors reported from C
|
|
global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); });
|
|
|
|
Gio.DesktopAppInfo.set_desktop_env('GNOME');
|
|
|
|
sessionMode = new SessionMode.SessionMode();
|
|
sessionMode.connect('updated', _sessionUpdated);
|
|
_initializeUI();
|
|
|
|
shellDBusService = new ShellDBus.GnomeShell();
|
|
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
|
|
|
|
_sessionUpdated();
|
|
}
|
|
|
|
function _initializeUI() {
|
|
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
|
|
// also initialize ShellAppSystem first. ShellAppSystem
|
|
// needs to load all the .desktop files, and ShellWindowTracker
|
|
// will use those to associate with windows. Right now
|
|
// the Monitor doesn't listen for installed app changes
|
|
// and recalculate application associations, so to avoid
|
|
// races for now we initialize it here. It's better to
|
|
// be predictable anyways.
|
|
Shell.WindowTracker.get_default();
|
|
Shell.AppUsage.get_default();
|
|
|
|
_loadDefaultStylesheet();
|
|
|
|
// Setup the stage hierarchy early
|
|
layoutManager = new Layout.LayoutManager();
|
|
|
|
// Various parts of the codebase still refers to Main.uiGroup
|
|
// instead using the layoutManager. This keeps that code
|
|
// working until it's updated.
|
|
uiGroup = layoutManager.uiGroup;
|
|
|
|
screencastService = new Screencast.ScreencastService();
|
|
xdndHandler = new XdndHandler.XdndHandler();
|
|
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
|
osdWindowManager = new OsdWindow.OsdWindowManager();
|
|
overview = new Overview.Overview();
|
|
wm = new WindowManager.WindowManager();
|
|
magnifier = new Magnifier.Magnifier();
|
|
if (LoginManager.canLock())
|
|
screenShield = new ScreenShield.ScreenShield();
|
|
|
|
panel = new Panel.Panel();
|
|
messageTray = new MessageTray.MessageTray();
|
|
keyboard = new Keyboard.Keyboard();
|
|
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
|
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
|
componentManager = new Components.ComponentManager();
|
|
|
|
layoutManager.init();
|
|
overview.init();
|
|
|
|
_a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA });
|
|
|
|
global.display.connect('overlay-key', Lang.bind(overview, function () {
|
|
if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE))
|
|
overview.toggle();
|
|
}));
|
|
|
|
// Provide the bus object for gnome-session to
|
|
// initiate logouts.
|
|
EndSessionDialog.init();
|
|
|
|
// We're ready for the session manager to move to the next phase
|
|
Meta.register_with_session();
|
|
|
|
_startDate = new Date();
|
|
|
|
let perfModuleName = GLib.getenv("SHELL_PERF_MODULE");
|
|
if (perfModuleName) {
|
|
let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT");
|
|
let module = eval('imports.perf.' + perfModuleName + ';');
|
|
Scripting.runPerfScript(module, perfOutput);
|
|
}
|
|
|
|
ExtensionDownloader.init();
|
|
ExtensionSystem.init();
|
|
|
|
if (sessionMode.isGreeter && screenShield) {
|
|
layoutManager.connect('startup-prepared', function() {
|
|
screenShield.showDialog();
|
|
});
|
|
}
|
|
|
|
layoutManager.connect('startup-complete', function() {
|
|
if (keybindingMode == Shell.KeyBindingMode.NONE) {
|
|
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
|
}
|
|
if (screenShield) {
|
|
screenShield.lockIfWasLocked();
|
|
}
|
|
if (LoginManager.haveSystemd() &&
|
|
sessionMode.currentMode != 'gdm' &&
|
|
sessionMode.currentMode != 'initial-setup') {
|
|
// Do not import globally to not depend
|
|
// on systemd on non-systemd systems.
|
|
let GSystem = imports.gi.GSystem;
|
|
GSystem.log_structured_print('GNOME Shell started at ' + _startDate,
|
|
['MESSAGE_ID=' + GNOMESHELL_STARTED_MESSAGE_ID]);
|
|
} else {
|
|
log('GNOME Shell started at ' + _startDate);
|
|
}
|
|
});
|
|
}
|
|
|
|
function _loadDefaultStylesheet() {
|
|
if (!sessionMode.isPrimary)
|
|
return;
|
|
|
|
let stylesheet = global.datadir + '/theme/' + sessionMode.stylesheetName;
|
|
if (_defaultCssStylesheet == stylesheet)
|
|
return;
|
|
|
|
_defaultCssStylesheet = stylesheet;
|
|
loadTheme();
|
|
}
|
|
|
|
/**
|
|
* getThemeStylesheet:
|
|
*
|
|
* Get the theme CSS file that the shell will load
|
|
*
|
|
* Returns: A file path that contains the theme CSS,
|
|
* null if using the default
|
|
*/
|
|
function getThemeStylesheet()
|
|
{
|
|
return _cssStylesheet;
|
|
}
|
|
|
|
/**
|
|
* setThemeStylesheet:
|
|
* @cssStylesheet: A file path that contains the theme CSS,
|
|
* set it to null to use the default
|
|
*
|
|
* Set the theme CSS file that the shell will load
|
|
*/
|
|
function setThemeStylesheet(cssStylesheet)
|
|
{
|
|
_cssStylesheet = cssStylesheet;
|
|
}
|
|
|
|
/**
|
|
* loadTheme:
|
|
*
|
|
* Reloads the theme CSS file
|
|
*/
|
|
function loadTheme() {
|
|
let themeContext = St.ThemeContext.get_for_stage (global.stage);
|
|
let previousTheme = themeContext.get_theme();
|
|
|
|
let theme = new St.Theme ({ application_stylesheet: _cssStylesheet,
|
|
default_stylesheet: _defaultCssStylesheet });
|
|
|
|
if (previousTheme) {
|
|
let customStylesheets = previousTheme.get_custom_stylesheets();
|
|
|
|
for (let i = 0; i < customStylesheets.length; i++)
|
|
theme.load_stylesheet(customStylesheets[i]);
|
|
}
|
|
|
|
themeContext.set_theme (theme);
|
|
}
|
|
|
|
/**
|
|
* notify:
|
|
* @msg: A message
|
|
* @details: Additional information
|
|
*/
|
|
function notify(msg, details) {
|
|
let source = new MessageTray.SystemNotificationSource();
|
|
messageTray.add(source);
|
|
let notification = new MessageTray.Notification(source, msg, details);
|
|
notification.setTransient(true);
|
|
source.notify(notification);
|
|
}
|
|
|
|
/**
|
|
* notifyError:
|
|
* @msg: An error message
|
|
* @details: Additional information
|
|
*
|
|
* See shell_global_notify_problem().
|
|
*/
|
|
function notifyError(msg, details) {
|
|
// Also print to stderr so it's logged somewhere
|
|
if (details)
|
|
log('error: ' + msg + ': ' + details);
|
|
else
|
|
log('error: ' + msg);
|
|
|
|
notify(msg, details);
|
|
}
|
|
|
|
function _findModal(actor) {
|
|
for (let i = 0; i < modalActorFocusStack.length; i++) {
|
|
if (modalActorFocusStack[i].actor == actor)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* pushModal:
|
|
* @actor: #ClutterActor which will be given keyboard focus
|
|
* @params: optional parameters
|
|
*
|
|
* Ensure we are in a mode where all keyboard and mouse input goes to
|
|
* the stage, and focus @actor. Multiple calls to this function act in
|
|
* a stacking fashion; the effect will be undone when an equal number
|
|
* of popModal() invocations have been made.
|
|
*
|
|
* Next, record the current Clutter keyboard focus on a stack. If the
|
|
* modal stack returns to this actor, reset the focus to the actor
|
|
* which was focused at the time pushModal() was invoked.
|
|
*
|
|
* @params may be used to provide the following parameters:
|
|
* - timestamp: used to associate the call with a specific user initiated
|
|
* event. If not provided then the value of
|
|
* global.get_current_time() is assumed.
|
|
*
|
|
* - options: Meta.ModalOptions flags to indicate that the pointer is
|
|
* already grabbed
|
|
*
|
|
* - keybindingMode: used to set the current Shell.KeyBindingMode to filter
|
|
* global keybindings; the default of NONE will filter
|
|
* out all keybindings
|
|
*
|
|
* Returns: true iff we successfully acquired a grab or already had one
|
|
*/
|
|
function pushModal(actor, params) {
|
|
params = Params.parse(params, { timestamp: global.get_current_time(),
|
|
options: 0,
|
|
keybindingMode: Shell.KeyBindingMode.NONE });
|
|
|
|
if (modalCount == 0) {
|
|
if (!global.begin_modal(params.timestamp, params.options)) {
|
|
log('pushModal: invocation of begin_modal failed');
|
|
return false;
|
|
}
|
|
Meta.disable_unredirect_for_screen(global.screen);
|
|
}
|
|
|
|
modalCount += 1;
|
|
let actorDestroyId = actor.connect('destroy', function() {
|
|
let index = _findModal(actor);
|
|
if (index >= 0)
|
|
popModal(actor);
|
|
});
|
|
|
|
let prevFocus = global.stage.get_key_focus();
|
|
let prevFocusDestroyId;
|
|
if (prevFocus != null) {
|
|
prevFocusDestroyId = prevFocus.connect('destroy', function() {
|
|
let index = _findModal(actor);
|
|
if (index >= 0)
|
|
modalActorFocusStack[index].prevFocus = null;
|
|
});
|
|
}
|
|
modalActorFocusStack.push({ actor: actor,
|
|
destroyId: actorDestroyId,
|
|
prevFocus: prevFocus,
|
|
prevFocusDestroyId: prevFocusDestroyId,
|
|
keybindingMode: keybindingMode });
|
|
|
|
keybindingMode = params.keybindingMode;
|
|
global.stage.set_key_focus(actor);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* popModal:
|
|
* @actor: #ClutterActor passed to original invocation of pushModal().
|
|
* @timestamp: optional timestamp
|
|
*
|
|
* Reverse the effect of pushModal(). If this invocation is undoing
|
|
* the topmost invocation, then the focus will be restored to the
|
|
* previous focus at the time when pushModal() was invoked.
|
|
*
|
|
* @timestamp is optionally used to associate the call with a specific user
|
|
* initiated event. If not provided then the value of
|
|
* global.get_current_time() is assumed.
|
|
*/
|
|
function popModal(actor, timestamp) {
|
|
if (timestamp == undefined)
|
|
timestamp = global.get_current_time();
|
|
|
|
let focusIndex = _findModal(actor);
|
|
if (focusIndex < 0) {
|
|
global.stage.set_key_focus(null);
|
|
global.end_modal(timestamp);
|
|
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
|
|
|
throw new Error('incorrect pop');
|
|
}
|
|
|
|
modalCount -= 1;
|
|
|
|
let record = modalActorFocusStack[focusIndex];
|
|
record.actor.disconnect(record.destroyId);
|
|
|
|
if (focusIndex == modalActorFocusStack.length - 1) {
|
|
if (record.prevFocus)
|
|
record.prevFocus.disconnect(record.prevFocusDestroyId);
|
|
keybindingMode = record.keybindingMode;
|
|
global.stage.set_key_focus(record.prevFocus);
|
|
} else {
|
|
// If we have:
|
|
// global.stage.set_focus(a);
|
|
// Main.pushModal(b);
|
|
// Main.pushModal(c);
|
|
// Main.pushModal(d);
|
|
//
|
|
// then we have the stack:
|
|
// [{ prevFocus: a, actor: b },
|
|
// { prevFocus: b, actor: c },
|
|
// { prevFocus: c, actor: d }]
|
|
//
|
|
// When actor c is destroyed/popped, if we only simply remove the
|
|
// record, then the focus stack will be [a, c], rather than the correct
|
|
// [a, b]. Shift the focus stack up before removing the record to ensure
|
|
// that we get the correct result.
|
|
let t = modalActorFocusStack[modalActorFocusStack.length - 1];
|
|
if (t.prevFocus)
|
|
t.prevFocus.disconnect(t.prevFocusDestroyId);
|
|
// Remove from the middle, shift the focus chain up
|
|
for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
|
|
modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus;
|
|
modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId;
|
|
modalActorFocusStack[i].keybindingMode = modalActorFocusStack[i - 1].keybindingMode;
|
|
}
|
|
}
|
|
modalActorFocusStack.splice(focusIndex, 1);
|
|
|
|
if (modalCount > 0)
|
|
return;
|
|
|
|
global.end_modal(timestamp);
|
|
Meta.enable_unredirect_for_screen(global.screen);
|
|
keybindingMode = Shell.KeyBindingMode.NORMAL;
|
|
}
|
|
|
|
function createLookingGlass() {
|
|
if (lookingGlass == null) {
|
|
lookingGlass = new LookingGlass.LookingGlass();
|
|
}
|
|
return lookingGlass;
|
|
}
|
|
|
|
function openRunDialog() {
|
|
if (runDialog == null) {
|
|
runDialog = new RunDialog.RunDialog();
|
|
}
|
|
runDialog.open();
|
|
}
|
|
|
|
/**
|
|
* activateWindow:
|
|
* @window: the Meta.Window to activate
|
|
* @time: (optional) current event time
|
|
* @workspaceNum: (optional) window's workspace number
|
|
*
|
|
* Activates @window, switching to its workspace first if necessary,
|
|
* and switching out of the overview if it's currently active
|
|
*/
|
|
function activateWindow(window, time, workspaceNum) {
|
|
let activeWorkspaceNum = global.screen.get_active_workspace_index();
|
|
let windowWorkspaceNum = (workspaceNum !== undefined) ? workspaceNum : window.get_workspace().index();
|
|
|
|
if (!time)
|
|
time = global.get_current_time();
|
|
|
|
if (windowWorkspaceNum != activeWorkspaceNum) {
|
|
let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum);
|
|
workspace.activate_with_focus(window, time);
|
|
} else {
|
|
window.activate(time);
|
|
}
|
|
|
|
overview.hide();
|
|
}
|
|
|
|
// TODO - replace this timeout with some system to guess when the user might
|
|
// be e.g. just reading the screen and not likely to interact.
|
|
const DEFERRED_TIMEOUT_SECONDS = 20;
|
|
var _deferredWorkData = {};
|
|
// Work scheduled for some point in the future
|
|
var _deferredWorkQueue = [];
|
|
// Work we need to process before the next redraw
|
|
var _beforeRedrawQueue = [];
|
|
// Counter to assign work ids
|
|
var _deferredWorkSequence = 0;
|
|
var _deferredTimeoutId = 0;
|
|
|
|
function _runDeferredWork(workId) {
|
|
if (!_deferredWorkData[workId])
|
|
return;
|
|
let index = _deferredWorkQueue.indexOf(workId);
|
|
if (index < 0)
|
|
return;
|
|
|
|
_deferredWorkQueue.splice(index, 1);
|
|
_deferredWorkData[workId].callback();
|
|
if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
|
|
Mainloop.source_remove(_deferredTimeoutId);
|
|
_deferredTimeoutId = 0;
|
|
}
|
|
}
|
|
|
|
function _runAllDeferredWork() {
|
|
while (_deferredWorkQueue.length > 0)
|
|
_runDeferredWork(_deferredWorkQueue[0]);
|
|
}
|
|
|
|
function _runBeforeRedrawQueue() {
|
|
for (let i = 0; i < _beforeRedrawQueue.length; i++) {
|
|
let workId = _beforeRedrawQueue[i];
|
|
_runDeferredWork(workId);
|
|
}
|
|
_beforeRedrawQueue = [];
|
|
}
|
|
|
|
function _queueBeforeRedraw(workId) {
|
|
_beforeRedrawQueue.push(workId);
|
|
if (_beforeRedrawQueue.length == 1) {
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function () {
|
|
_runBeforeRedrawQueue();
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* initializeDeferredWork:
|
|
* @actor: A #ClutterActor
|
|
* @callback: Function to invoke to perform work
|
|
*
|
|
* This function sets up a callback to be invoked when either the
|
|
* given actor is mapped, or after some period of time when the machine
|
|
* is idle. This is useful if your actor isn't always visible on the
|
|
* screen (for example, all actors in the overview), and you don't want
|
|
* to consume resources updating if the actor isn't actually going to be
|
|
* displaying to the user.
|
|
*
|
|
* Note that queueDeferredWork is called by default immediately on
|
|
* initialization as well, under the assumption that new actors
|
|
* will need it.
|
|
*
|
|
* Returns: A string work identifer
|
|
*/
|
|
function initializeDeferredWork(actor, callback, props) {
|
|
// Turn into a string so we can use as an object property
|
|
let workId = '' + (++_deferredWorkSequence);
|
|
_deferredWorkData[workId] = { 'actor': actor,
|
|
'callback': callback };
|
|
actor.connect('notify::mapped', function () {
|
|
if (!(actor.mapped && _deferredWorkQueue.indexOf(workId) >= 0))
|
|
return;
|
|
_queueBeforeRedraw(workId);
|
|
});
|
|
actor.connect('destroy', function() {
|
|
let index = _deferredWorkQueue.indexOf(workId);
|
|
if (index >= 0)
|
|
_deferredWorkQueue.splice(index, 1);
|
|
delete _deferredWorkData[workId];
|
|
});
|
|
queueDeferredWork(workId);
|
|
return workId;
|
|
}
|
|
|
|
/**
|
|
* queueDeferredWork:
|
|
* @workId: work identifier
|
|
*
|
|
* Ensure that the work identified by @workId will be
|
|
* run on map or timeout. You should call this function
|
|
* for example when data being displayed by the actor has
|
|
* changed.
|
|
*/
|
|
function queueDeferredWork(workId) {
|
|
let data = _deferredWorkData[workId];
|
|
if (!data) {
|
|
let message = 'Invalid work id %d'.format(workId);
|
|
logError(new Error(message), message);
|
|
return;
|
|
}
|
|
if (_deferredWorkQueue.indexOf(workId) < 0)
|
|
_deferredWorkQueue.push(workId);
|
|
if (data.actor.mapped) {
|
|
_queueBeforeRedraw(workId);
|
|
return;
|
|
} else if (_deferredTimeoutId == 0) {
|
|
_deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () {
|
|
_runAllDeferredWork();
|
|
_deferredTimeoutId = 0;
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
GLib.Source.set_name_by_id(_deferredTimeoutId, '[gnome-shell] _runAllDeferredWork');
|
|
}
|
|
}
|