1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
3
const Clutter = imports.gi.Clutter;
4
const Gdk = imports.gi.Gdk;
5
const Gio = imports.gi.Gio;
6
const GLib = imports.gi.GLib;
7
const Lang = imports.lang;
8
const Mainloop = imports.mainloop;
9
const Meta = imports.gi.Meta;
10
const Shell = imports.gi.Shell;
11
const St = imports.gi.St;
13
const Components = imports.ui.components;
14
const CtrlAltTab = imports.ui.ctrlAltTab;
15
const EndSessionDialog = imports.ui.endSessionDialog;
16
const Environment = imports.ui.environment;
17
const ExtensionSystem = imports.ui.extensionSystem;
18
const ExtensionDownloader = imports.ui.extensionDownloader;
19
const Keyboard = imports.ui.keyboard;
20
const MessageTray = imports.ui.messageTray;
21
const Overview = imports.ui.overview;
22
const Panel = imports.ui.panel;
23
const RunDialog = imports.ui.runDialog;
24
const Layout = imports.ui.layout;
25
const LookingGlass = imports.ui.lookingGlass;
26
const NotificationDaemon = imports.ui.notificationDaemon;
27
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
28
const ScreenShield = imports.ui.screenShield;
29
const Scripting = imports.ui.scripting;
30
const SessionMode = imports.ui.sessionMode;
31
const ShellDBus = imports.ui.shellDBus;
32
const ShellMountOperation = imports.ui.shellMountOperation;
33
const UnlockDialog = imports.ui.unlockDialog;
34
const WindowManager = imports.ui.windowManager;
35
const Magnifier = imports.ui.magnifier;
36
const XdndHandler = imports.ui.xdndHandler;
37
const Util = imports.misc.util;
39
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
40
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
42
let componentManager = null;
46
let lookingGlass = null;
48
let messageTray = null;
49
let screenShield = null;
50
let notificationDaemon = null;
51
let windowAttentionHandler = null;
52
let ctrlAltTabManager = null;
53
let sessionMode = null;
54
let shellDBusService = null;
55
let shellMountOpDBusService = null;
56
let screenSaverDBus = null;
58
let modalActorFocusStack = [];
61
let xdndHandler = null;
63
let layoutManager = null;
65
let _defaultCssStylesheet = null;
66
let _cssStylesheet = null;
67
let _overridesSettings = null;
69
let background = null;
71
function _sessionUpdated() {
72
Meta.keybindings_set_custom_handler('panel-run-dialog', sessionMode.hasRunDialog ? openRunDialog : null);
73
if (sessionMode.isGreeter)
74
screenShield.showDialog();
78
// These are here so we don't break compatibility.
79
global.logError = window.log;
80
global.log = window.log;
82
// Chain up async errors reported from C
83
global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); });
85
Gio.DesktopAppInfo.set_desktop_env('GNOME');
87
sessionMode = new SessionMode.SessionMode();
88
shellDBusService = new ShellDBus.GnomeShell();
89
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
91
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
92
// also initialize ShellAppSystem first. ShellAppSystem
93
// needs to load all the .desktop files, and ShellWindowTracker
94
// will use those to associate with windows. Right now
95
// the Monitor doesn't listen for installed app changes
96
// and recalculate application associations, so to avoid
97
// races for now we initialize it here. It's better to
98
// be predictable anyways.
99
let tracker = Shell.WindowTracker.get_default();
100
Shell.AppUsage.get_default();
102
tracker.connect('startup-sequence-changed', _queueCheckWorkspaces);
104
// The stage is always covered so Clutter doesn't need to clear it; however
105
// the color is used as the default contents for the Mutter root background
106
// actor so set it anyways.
107
global.stage.color = DEFAULT_BACKGROUND_COLOR;
108
global.stage.no_clear_hint = true;
110
_defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css';
113
// Set up stage hierarchy to group all UI actors under one container.
114
uiGroup = new Shell.GenericContainer({ name: 'uiGroup' });
115
uiGroup.connect('allocate',
116
function (actor, box, flags) {
117
let children = uiGroup.get_children();
118
for (let i = 0; i < children.length; i++)
119
children[i].allocate_preferred_size(flags);
121
uiGroup.connect('get-preferred-width',
122
function(actor, forHeight, alloc) {
123
let width = global.stage.width;
124
[alloc.min_size, alloc.natural_size] = [width, width];
126
uiGroup.connect('get-preferred-height',
127
function(actor, forWidth, alloc) {
128
let height = global.stage.height;
129
[alloc.min_size, alloc.natural_size] = [height, height];
131
global.window_group.reparent(uiGroup);
132
global.overlay_group.reparent(uiGroup);
133
global.stage.add_actor(uiGroup);
135
layoutManager = new Layout.LayoutManager();
136
xdndHandler = new XdndHandler.XdndHandler();
137
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
138
overview = new Overview.Overview();
139
magnifier = new Magnifier.Magnifier();
140
if (UnlockDialog.isSupported())
141
screenShield = new ScreenShield.ScreenShield();
143
screenShield = new ScreenShield.ScreenShieldFallback();
144
panel = new Panel.Panel();
145
wm = new WindowManager.WindowManager();
146
messageTray = new MessageTray.MessageTray();
147
keyboard = new Keyboard.Keyboard();
148
notificationDaemon = new NotificationDaemon.NotificationDaemon();
149
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
150
componentManager = new Components.ComponentManager();
152
layoutManager.init();
156
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
158
Meta.keybindings_set_custom_handler('panel-main-menu', Lang.bind(overview, overview.toggle));
159
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
161
sessionMode.connect('update', _sessionUpdated);
164
// Provide the bus object for gnome-session to
166
EndSessionDialog.init();
168
_startDate = new Date();
170
global.stage.connect('captured-event', _globalKeyPressHandler);
172
log('GNOME Shell started at ' + _startDate);
174
let perfModuleName = GLib.getenv("SHELL_PERF_MODULE");
175
if (perfModuleName) {
176
let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT");
177
let module = eval('imports.perf.' + perfModuleName + ';');
178
Scripting.runPerfScript(module, perfOutput);
181
_overridesSettings = new Gio.Settings({ schema: OVERRIDES_SCHEMA });
182
_overridesSettings.connect('changed::dynamic-workspaces', _queueCheckWorkspaces);
184
global.screen.connect('notify::n-workspaces', _nWorkspacesChanged);
186
global.screen.connect('window-entered-monitor', _windowEnteredMonitor);
187
global.screen.connect('window-left-monitor', _windowLeftMonitor);
188
global.screen.connect('restacked', _windowsRestacked);
190
_nWorkspacesChanged();
192
ExtensionDownloader.init();
193
ExtensionSystem.init();
196
let _workspaces = [];
197
let _checkWorkspacesId = 0;
200
* When the last window closed on a workspace is a dialog or splash
201
* screen, we assume that it might be an initial window shown before
202
* the main window of an application, and give the app a grace period
203
* where it can map another window before we remove the workspace.
205
const LAST_WINDOW_GRACE_TIME = 1000;
207
function _checkWorkspaces() {
209
let emptyWorkspaces = [];
211
if (!Meta.prefs_get_dynamic_workspaces()) {
212
_checkWorkspacesId = 0;
216
for (i = 0; i < _workspaces.length; i++) {
217
let lastRemoved = _workspaces[i]._lastRemovedWindow;
219
(lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
220
lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
221
lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) ||
222
_workspaces[i]._keepAliveId)
223
emptyWorkspaces[i] = false;
225
emptyWorkspaces[i] = true;
228
let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
229
for (i = 0; i < sequences.length; i++) {
230
let index = sequences[i].get_workspace();
231
if (index >= 0 && index <= global.screen.n_workspaces)
232
emptyWorkspaces[index] = false;
235
let windows = global.get_window_actors();
236
for (i = 0; i < windows.length; i++) {
237
let win = windows[i];
239
if (win.get_meta_window().is_on_all_workspaces())
242
let workspaceIndex = win.get_workspace();
243
emptyWorkspaces[workspaceIndex] = false;
246
// If we don't have an empty workspace at the end, add one
247
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
248
global.screen.append_new_workspace(false, global.get_current_time());
249
emptyWorkspaces.push(false);
252
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
253
let removingCurrentWorkspace = (emptyWorkspaces[activeWorkspaceIndex] &&
254
activeWorkspaceIndex < emptyWorkspaces.length - 1);
255
// Don't enter the overview when removing multiple empty workspaces at startup
256
let showOverview = (removingCurrentWorkspace &&
257
!emptyWorkspaces.every(function(x) { return x; }));
259
if (removingCurrentWorkspace) {
260
// "Merge" the empty workspace we are removing with the one at the end
261
wm.blockAnimations();
264
// Delete other empty workspaces; do it from the end to avoid index changes
265
for (i = emptyWorkspaces.length - 2; i >= 0; i--) {
266
if (emptyWorkspaces[i])
267
global.screen.remove_workspace(_workspaces[i], global.get_current_time());
270
if (removingCurrentWorkspace) {
271
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
272
wm.unblockAnimations();
274
if (!overview.visible && showOverview)
278
_checkWorkspacesId = 0;
282
function keepWorkspaceAlive(workspace, duration) {
283
if (workspace._keepAliveId)
284
Mainloop.source_remove(workspace._keepAliveId);
286
workspace._keepAliveId = Mainloop.timeout_add(duration, function() {
287
workspace._keepAliveId = 0;
288
_queueCheckWorkspaces();
293
function _windowRemoved(workspace, window) {
294
workspace._lastRemovedWindow = window;
295
_queueCheckWorkspaces();
296
Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, function() {
297
if (workspace._lastRemovedWindow == window) {
298
workspace._lastRemovedWindow = null;
299
_queueCheckWorkspaces();
305
function _windowLeftMonitor(metaScreen, monitorIndex, metaWin) {
306
// If the window left the primary monitor, that
307
// might make that workspace empty
308
if (monitorIndex == layoutManager.primaryIndex)
309
_queueCheckWorkspaces();
312
function _windowEnteredMonitor(metaScreen, monitorIndex, metaWin) {
313
// If the window entered the primary monitor, that
314
// might make that workspace non-empty
315
if (monitorIndex == layoutManager.primaryIndex)
316
_queueCheckWorkspaces();
319
function _windowsRestacked() {
320
// Figure out where the pointer is in case we lost track of
321
// it during a grab. (In particular, if a trayicon popup menu
322
// is dismissed, see if we need to close the message tray.)
323
global.sync_pointer();
326
function _queueCheckWorkspaces() {
327
if (_checkWorkspacesId == 0)
328
_checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, _checkWorkspaces);
331
function _nWorkspacesChanged() {
332
let oldNumWorkspaces = _workspaces.length;
333
let newNumWorkspaces = global.screen.n_workspaces;
335
if (oldNumWorkspaces == newNumWorkspaces)
338
let lostWorkspaces = [];
339
if (newNumWorkspaces > oldNumWorkspaces) {
342
// Assume workspaces are only added at the end
343
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
344
_workspaces[w] = global.screen.get_workspace_by_index(w);
346
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
347
let workspace = _workspaces[w];
348
workspace._windowAddedId = workspace.connect('window-added', _queueCheckWorkspaces);
349
workspace._windowRemovedId = workspace.connect('window-removed', _windowRemoved);
353
// Assume workspaces are only removed sequentially
354
// (e.g. 2,3,4 - not 2,4,7)
356
let removedNum = oldNumWorkspaces - newNumWorkspaces;
357
for (let w = 0; w < oldNumWorkspaces; w++) {
358
let workspace = global.screen.get_workspace_by_index(w);
359
if (_workspaces[w] != workspace) {
365
let lostWorkspaces = _workspaces.splice(removedIndex, removedNum);
366
lostWorkspaces.forEach(function(workspace) {
367
workspace.disconnect(workspace._windowAddedId);
368
workspace.disconnect(workspace._windowRemovedId);
372
_queueCheckWorkspaces();
378
* getThemeStylesheet:
380
* Get the theme CSS file that the shell will load
382
* Returns: A file path that contains the theme CSS,
383
* null if using the default
385
function getThemeStylesheet()
387
return _cssStylesheet;
391
* setThemeStylesheet:
392
* @cssStylesheet: A file path that contains the theme CSS,
393
* set it to null to use the default
395
* Set the theme CSS file that the shell will load
397
function setThemeStylesheet(cssStylesheet)
399
_cssStylesheet = cssStylesheet;
405
* Reloads the theme CSS file
407
function loadTheme() {
408
let themeContext = St.ThemeContext.get_for_stage (global.stage);
409
let previousTheme = themeContext.get_theme();
411
let cssStylesheet = _defaultCssStylesheet;
412
if (_cssStylesheet != null)
413
cssStylesheet = _cssStylesheet;
415
let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
418
let customStylesheets = previousTheme.get_custom_stylesheets();
420
for (let i = 0; i < customStylesheets.length; i++)
421
theme.load_stylesheet(customStylesheets[i]);
424
themeContext.set_theme (theme);
430
* @details: Additional information
432
function notify(msg, details) {
433
let source = new MessageTray.SystemNotificationSource();
434
messageTray.add(source);
435
let notification = new MessageTray.Notification(source, msg, details);
436
notification.setTransient(true);
437
source.notify(notification);
442
* @msg: An error message
443
* @details: Additional information
445
* See shell_global_notify_problem().
447
function notifyError(msg, details) {
448
// Also print to stderr so it's logged somewhere
450
log('error: ' + msg + ': ' + details);
452
log('error: ' + msg);
454
notify(msg, details);
457
function isWindowActorDisplayedOnWorkspace(win, workspaceIndex) {
458
return win.get_workspace() == workspaceIndex ||
459
(win.get_meta_window() && win.get_meta_window().is_on_all_workspaces());
462
function getWindowActorsForWorkspace(workspaceIndex) {
463
return global.get_window_actors().filter(function (win) {
464
return isWindowActorDisplayedOnWorkspace(win, workspaceIndex);
468
// This function encapsulates hacks to make certain global keybindings
469
// work even when we are in one of our modes where global keybindings
470
// are disabled with a global grab. (When there is a global grab, then
471
// all key events will be delivered to the stage, so ::captured-event
472
// on the stage can be used for global keybindings.)
473
function _globalKeyPressHandler(actor, event) {
476
if (event.type() != Clutter.EventType.KEY_PRESS)
479
if (!sessionMode.allowKeybindingsWhenModal) {
480
if (modalCount > (overview.visible ? 1 : 0))
484
let symbol = event.get_key_symbol();
485
let keyCode = event.get_key_code();
486
let ignoredModifiers = global.display.get_ignored_modifier_mask();
487
let modifierState = event.get_state() & ~ignoredModifiers;
489
// This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
490
let action = global.display.get_keybinding_action(keyCode, modifierState);
492
if (action == Meta.KeyBindingAction.SWITCH_PANELS) {
493
ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK,
499
// left/right would effectively act as synonyms for up/down if we enabled them;
500
// but that could be considered confusing; we also disable them in the main view.
502
// case Meta.KeyBindingAction.WORKSPACE_LEFT:
503
// if (!sessionMode.hasWorkspaces)
506
// wm.actionMoveWorkspaceLeft();
508
// case Meta.KeyBindingAction.WORKSPACE_RIGHT:
509
// if (!sessionMode.hasWorkspaces)
512
// wm.actionMoveWorkspaceRight();
514
case Meta.KeyBindingAction.WORKSPACE_UP:
515
if (!sessionMode.hasWorkspaces)
518
wm.actionMoveWorkspace(Meta.MotionDirection.UP);
520
case Meta.KeyBindingAction.WORKSPACE_DOWN:
521
if (!sessionMode.hasWorkspaces)
524
wm.actionMoveWorkspace(Meta.MotionDirection.DOWN);
526
case Meta.KeyBindingAction.PANEL_RUN_DIALOG:
527
case Meta.KeyBindingAction.COMMAND_2:
528
if (!sessionMode.hasRunDialog)
533
case Meta.KeyBindingAction.PANEL_MAIN_MENU:
534
case Meta.KeyBindingAction.OVERLAY_KEY:
542
function _findModal(actor) {
543
for (let i = 0; i < modalActorFocusStack.length; i++) {
544
if (modalActorFocusStack[i].actor == actor)
550
function isInModalStack(actor) {
551
return _findModal(actor) != -1;
556
* @actor: #ClutterActor which will be given keyboard focus
557
* @timestamp: optional timestamp
559
* Ensure we are in a mode where all keyboard and mouse input goes to
560
* the stage, and focus @actor. Multiple calls to this function act in
561
* a stacking fashion; the effect will be undone when an equal number
562
* of popModal() invocations have been made.
564
* Next, record the current Clutter keyboard focus on a stack. If the
565
* modal stack returns to this actor, reset the focus to the actor
566
* which was focused at the time pushModal() was invoked.
568
* @timestamp is optionally used to associate the call with a specific user
569
* initiated event. If not provided then the value of
570
* global.get_current_time() is assumed.
572
* @options: optional Meta.ModalOptions flags to indicate that the
573
* pointer is alrady grabbed
575
* Returns: true iff we successfully acquired a grab or already had one
577
function pushModal(actor, timestamp, options) {
578
if (timestamp == undefined)
579
timestamp = global.get_current_time();
581
if (modalCount == 0) {
582
if (!global.begin_modal(timestamp, options ? options : 0)) {
583
log('pushModal: invocation of begin_modal failed');
586
Meta.disable_unredirect_for_screen(global.screen);
589
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
592
let actorDestroyId = actor.connect('destroy', function() {
593
let index = _findModal(actor);
597
let curFocus = global.stage.get_key_focus();
598
let curFocusDestroyId;
599
if (curFocus != null) {
600
curFocusDestroyId = curFocus.connect('destroy', function() {
601
let index = _findModal(actor);
603
modalActorFocusStack[index].actor = null;
606
modalActorFocusStack.push({ actor: actor,
608
destroyId: actorDestroyId,
609
focusDestroyId: curFocusDestroyId });
611
global.stage.set_key_focus(actor);
617
* @actor: #ClutterActor passed to original invocation of pushModal().
618
* @timestamp: optional timestamp
620
* Reverse the effect of pushModal(). If this invocation is undoing
621
* the topmost invocation, then the focus will be restored to the
622
* previous focus at the time when pushModal() was invoked.
624
* @timestamp is optionally used to associate the call with a specific user
625
* initiated event. If not provided then the value of
626
* global.get_current_time() is assumed.
628
function popModal(actor, timestamp) {
629
if (timestamp == undefined)
630
timestamp = global.get_current_time();
632
let focusIndex = _findModal(actor);
633
if (focusIndex < 0) {
634
global.stage.set_key_focus(null);
635
global.end_modal(timestamp);
636
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
638
throw new Error('incorrect pop');
643
let record = modalActorFocusStack[focusIndex];
644
record.actor.disconnect(record.destroyId);
646
if (focusIndex == modalActorFocusStack.length - 1) {
648
record.focus.disconnect(record.focusDestroyId);
649
global.stage.set_key_focus(record.focus);
651
let t = modalActorFocusStack[modalActorFocusStack.length - 1];
653
t.focus.disconnect(t.focusDestroyId);
654
// Remove from the middle, shift the focus chain up
655
for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
656
modalActorFocusStack[i].focus = modalActorFocusStack[i - 1].focus;
657
modalActorFocusStack[i].focusDestroyId = modalActorFocusStack[i - 1].focusDestroyId;
660
modalActorFocusStack.splice(focusIndex, 1);
665
global.end_modal(timestamp);
666
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
667
Meta.enable_unredirect_for_screen(global.screen);
670
function createLookingGlass() {
671
if (lookingGlass == null) {
672
lookingGlass = new LookingGlass.LookingGlass();
677
function openRunDialog() {
678
if (runDialog == null) {
679
runDialog = new RunDialog.RunDialog();
686
* @window: the Meta.Window to activate
687
* @time: (optional) current event time
688
* @workspaceNum: (optional) window's workspace number
690
* Activates @window, switching to its workspace first if necessary,
691
* and switching out of the overview if it's currently active
693
function activateWindow(window, time, workspaceNum) {
694
let activeWorkspaceNum = global.screen.get_active_workspace_index();
695
let windowWorkspaceNum = (workspaceNum !== undefined) ? workspaceNum : window.get_workspace().index();
698
time = global.get_current_time();
700
if (windowWorkspaceNum != activeWorkspaceNum) {
701
let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum);
702
workspace.activate_with_focus(window, time);
704
window.activate(time);
710
// TODO - replace this timeout with some system to guess when the user might
711
// be e.g. just reading the screen and not likely to interact.
712
const DEFERRED_TIMEOUT_SECONDS = 20;
713
var _deferredWorkData = {};
714
// Work scheduled for some point in the future
715
var _deferredWorkQueue = [];
716
// Work we need to process before the next redraw
717
var _beforeRedrawQueue = [];
718
// Counter to assign work ids
719
var _deferredWorkSequence = 0;
720
var _deferredTimeoutId = 0;
722
function _runDeferredWork(workId) {
723
if (!_deferredWorkData[workId])
725
let index = _deferredWorkQueue.indexOf(workId);
729
_deferredWorkQueue.splice(index, 1);
730
_deferredWorkData[workId].callback();
731
if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
732
Mainloop.source_remove(_deferredTimeoutId);
733
_deferredTimeoutId = 0;
737
function _runAllDeferredWork() {
738
while (_deferredWorkQueue.length > 0)
739
_runDeferredWork(_deferredWorkQueue[0]);
742
function _runBeforeRedrawQueue() {
743
for (let i = 0; i < _beforeRedrawQueue.length; i++) {
744
let workId = _beforeRedrawQueue[i];
745
_runDeferredWork(workId);
747
_beforeRedrawQueue = [];
750
function _queueBeforeRedraw(workId) {
751
_beforeRedrawQueue.push(workId);
752
if (_beforeRedrawQueue.length == 1) {
753
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function () {
754
_runBeforeRedrawQueue();
761
* initializeDeferredWork:
762
* @actor: A #ClutterActor
763
* @callback: Function to invoke to perform work
765
* This function sets up a callback to be invoked when either the
766
* given actor is mapped, or after some period of time when the machine
767
* is idle. This is useful if your actor isn't always visible on the
768
* screen (for example, all actors in the overview), and you don't want
769
* to consume resources updating if the actor isn't actually going to be
770
* displaying to the user.
772
* Note that queueDeferredWork is called by default immediately on
773
* initialization as well, under the assumption that new actors
776
* Returns: A string work identifer
778
function initializeDeferredWork(actor, callback, props) {
779
// Turn into a string so we can use as an object property
780
let workId = '' + (++_deferredWorkSequence);
781
_deferredWorkData[workId] = { 'actor': actor,
782
'callback': callback };
783
actor.connect('notify::mapped', function () {
784
if (!(actor.mapped && _deferredWorkQueue.indexOf(workId) >= 0))
786
_queueBeforeRedraw(workId);
788
actor.connect('destroy', function() {
789
let index = _deferredWorkQueue.indexOf(workId);
791
_deferredWorkQueue.splice(index, 1);
792
delete _deferredWorkData[workId];
794
queueDeferredWork(workId);
800
* @workId: work identifier
802
* Ensure that the work identified by @workId will be
803
* run on map or timeout. You should call this function
804
* for example when data being displayed by the actor has
807
function queueDeferredWork(workId) {
808
let data = _deferredWorkData[workId];
810
let message = 'Invalid work id %d'.format(workId);
811
logError(new Error(message), message);
814
if (_deferredWorkQueue.indexOf(workId) < 0)
815
_deferredWorkQueue.push(workId);
816
if (data.actor.mapped) {
817
_queueBeforeRedraw(workId);
819
} else if (_deferredTimeoutId == 0) {
820
_deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () {
821
_runAllDeferredWork();
822
_deferredTimeoutId = 0;