2008-12-01 19:51:43 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
2008-10-31 18:09:20 +00:00
|
|
|
|
2008-10-31 04:22:44 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2009-09-23 18:30:05 +00:00
|
|
|
const DBus = imports.dbus;
|
2009-05-01 18:13:51 +00:00
|
|
|
const Gdk = imports.gi.Gdk;
|
2009-01-22 21:28:19 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
2009-08-28 19:11:25 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2009-05-07 13:47:48 +00:00
|
|
|
const Lang = imports.lang;
|
2008-12-05 21:50:09 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
2009-04-29 18:01:09 +00:00
|
|
|
const Meta = imports.gi.Meta;
|
2009-02-02 23:02:16 +00:00
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
const Signals = imports.signals;
|
2009-09-10 05:36:41 +00:00
|
|
|
const St = imports.gi.St;
|
2008-10-31 04:22:44 +00:00
|
|
|
|
2009-05-06 18:36:50 +00:00
|
|
|
const Chrome = imports.ui.chrome;
|
2009-09-18 19:51:15 +00:00
|
|
|
const Environment = imports.ui.environment;
|
2009-10-25 22:53:10 +00:00
|
|
|
const ExtensionSystem = imports.ui.extensionSystem;
|
2009-08-11 11:46:10 +00:00
|
|
|
const Overview = imports.ui.overview;
|
2009-02-02 23:02:16 +00:00
|
|
|
const Panel = imports.ui.panel;
|
2009-11-01 02:25:28 +00:00
|
|
|
const PlaceDisplay = imports.ui.placeDisplay;
|
2008-12-09 22:10:43 +00:00
|
|
|
const RunDialog = imports.ui.runDialog;
|
2009-08-02 07:46:01 +00:00
|
|
|
const LookingGlass = imports.ui.lookingGlass;
|
2009-09-23 18:30:05 +00:00
|
|
|
const ShellDBus = imports.ui.shellDBus;
|
2009-04-24 14:01:34 +00:00
|
|
|
const Sidebar = imports.ui.sidebar;
|
2008-12-09 22:10:43 +00:00
|
|
|
const WindowManager = imports.ui.windowManager;
|
2008-10-31 18:09:20 +00:00
|
|
|
|
2008-10-31 18:29:42 +00:00
|
|
|
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
|
|
|
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
|
|
|
|
|
2009-05-06 18:36:50 +00:00
|
|
|
let chrome = null;
|
2008-10-31 18:09:20 +00:00
|
|
|
let panel = null;
|
2009-04-24 14:01:34 +00:00
|
|
|
let sidebar = null;
|
2009-11-01 02:25:28 +00:00
|
|
|
let placesManager = null;
|
2009-08-11 11:46:10 +00:00
|
|
|
let overview = null;
|
2008-12-09 22:10:43 +00:00
|
|
|
let runDialog = null;
|
2009-08-02 07:46:01 +00:00
|
|
|
let lookingGlass = null;
|
2008-11-21 14:02:09 +00:00
|
|
|
let wm = null;
|
2009-03-13 21:14:31 +00:00
|
|
|
let recorder = null;
|
2009-09-23 18:30:05 +00:00
|
|
|
let shellDBusService = null;
|
2009-09-15 19:53:07 +00:00
|
|
|
let modalCount = 0;
|
|
|
|
let modalActorFocusStack = [];
|
2009-10-24 17:36:52 +00:00
|
|
|
let _errorLogStack = [];
|
|
|
|
let _startDate;
|
2008-10-31 18:09:20 +00:00
|
|
|
|
2008-10-31 04:22:44 +00:00
|
|
|
function start() {
|
2009-09-08 20:12:50 +00:00
|
|
|
// Add a binding for "global" in the global JS namespace; (gjs
|
|
|
|
// keeps the web browser convention of having that namespace be
|
|
|
|
// called "window".)
|
|
|
|
window.global = Shell.Global.get();
|
2009-01-22 21:28:19 +00:00
|
|
|
|
2009-10-24 17:36:52 +00:00
|
|
|
// Now monkey patch utility functions into the global proxy;
|
|
|
|
// This is easier and faster than indirecting down into global
|
|
|
|
// if we want to call back up into JS.
|
|
|
|
global.logError = _logError;
|
|
|
|
global.log = _logDebug;
|
|
|
|
|
2009-01-22 21:28:19 +00:00
|
|
|
Gio.DesktopAppInfo.set_desktop_env("GNOME");
|
|
|
|
|
2009-01-19 23:21:57 +00:00
|
|
|
global.grab_dbus_service();
|
2009-09-23 18:30:05 +00:00
|
|
|
shellDBusService = new ShellDBus.GnomeShell();
|
|
|
|
// Force a connection now; dbus.js will do this internally
|
|
|
|
// if we use its name acquisition stuff but we aren't right
|
|
|
|
// now; to do so we'd need to convert from its async calls
|
|
|
|
// back into sync ones.
|
|
|
|
DBus.session.flush();
|
2008-10-31 18:29:42 +00:00
|
|
|
|
2009-09-18 19:51:15 +00:00
|
|
|
Environment.init();
|
2008-11-23 04:12:34 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
|
2009-08-12 17:32:54 +00:00
|
|
|
// also initialize ShellAppSystem first. ShellAppSystem
|
2009-10-15 23:28:29 +00:00
|
|
|
// needs to load all the .desktop files, and ShellWindowTracker
|
2009-08-12 17:32:54 +00:00
|
|
|
// 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.
|
2009-10-15 23:28:29 +00:00
|
|
|
Shell.WindowTracker.get_default();
|
|
|
|
Shell.AppUsage.get_default();
|
2009-08-12 17:32:54 +00:00
|
|
|
|
2008-10-31 18:29:42 +00:00
|
|
|
// The background color really only matters if there is no desktop
|
|
|
|
// window (say, nautilus) running. We set it mostly so things look good
|
|
|
|
// when we are running inside Xephyr.
|
|
|
|
global.stage.color = DEFAULT_BACKGROUND_COLOR;
|
|
|
|
|
|
|
|
// Mutter currently hardcodes putting "Yessir. The compositor is running""
|
2009-08-11 11:46:10 +00:00
|
|
|
// in the Overview. Clear that out.
|
2008-10-31 23:09:46 +00:00
|
|
|
let children = global.overlay_group.get_children();
|
2008-10-31 18:29:42 +00:00
|
|
|
for (let i = 0; i < children.length; i++)
|
2008-11-28 20:12:20 +00:00
|
|
|
children[i].destroy();
|
2008-10-31 18:29:42 +00:00
|
|
|
|
2009-09-20 01:10:15 +00:00
|
|
|
let themeContext = St.ThemeContext.get_for_stage (global.stage);
|
2009-09-18 20:29:28 +00:00
|
|
|
let stylesheetPath = global.datadir + "/theme/gnome-shell.css";
|
2009-09-20 01:10:15 +00:00
|
|
|
let theme = new St.Theme ({ application_stylesheet: stylesheetPath });
|
|
|
|
themeContext.set_theme (theme);
|
2009-09-10 05:36:41 +00:00
|
|
|
|
2008-11-19 23:21:42 +00:00
|
|
|
global.connect('panel-run-dialog', function(panel) {
|
|
|
|
// Make sure not more than one run dialog is shown.
|
2009-09-14 19:08:20 +00:00
|
|
|
getRunDialog().open();
|
2008-11-07 18:42:23 +00:00
|
|
|
});
|
2009-09-25 02:53:16 +00:00
|
|
|
let shellwm = global.window_manager;
|
|
|
|
shellwm.takeover_keybinding("panel_main_menu");
|
|
|
|
shellwm.connect("keybinding::panel_main_menu", function () {
|
|
|
|
overview.toggle();
|
|
|
|
});
|
|
|
|
shellwm.takeover_keybinding("panel_run_dialog");
|
|
|
|
shellwm.connect("keybinding::panel_run_dialog", function () {
|
|
|
|
getRunDialog().open();
|
|
|
|
});
|
2008-11-07 18:42:23 +00:00
|
|
|
|
2009-11-01 02:25:28 +00:00
|
|
|
placesManager = new PlaceDisplay.PlacesManager();
|
2009-08-11 11:46:10 +00:00
|
|
|
overview = new Overview.Overview();
|
2009-05-06 18:36:50 +00:00
|
|
|
chrome = new Chrome.Chrome();
|
2008-10-31 18:09:20 +00:00
|
|
|
panel = new Panel.Panel();
|
2009-04-24 14:01:34 +00:00
|
|
|
sidebar = new Sidebar.Sidebar();
|
2008-11-21 14:02:09 +00:00
|
|
|
wm = new WindowManager.WindowManager();
|
2009-08-12 17:32:54 +00:00
|
|
|
|
2009-10-24 17:36:52 +00:00
|
|
|
_startDate = new Date();
|
|
|
|
|
2009-03-13 21:14:31 +00:00
|
|
|
global.screen.connect('toggle-recording', function() {
|
|
|
|
if (recorder == null) {
|
|
|
|
recorder = new Shell.Recorder({ stage: global.stage });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recorder.is_recording()) {
|
|
|
|
recorder.pause();
|
|
|
|
} else {
|
|
|
|
recorder.record();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2009-08-11 15:16:25 +00:00
|
|
|
_relayout();
|
|
|
|
|
2009-10-25 22:53:10 +00:00
|
|
|
ExtensionSystem.init();
|
|
|
|
ExtensionSystem.loadExtensions();
|
|
|
|
|
2009-07-02 04:52:21 +00:00
|
|
|
panel.startupAnimation();
|
|
|
|
|
2009-05-07 13:47:48 +00:00
|
|
|
let display = global.screen.get_display();
|
2009-08-11 11:46:10 +00:00
|
|
|
display.connect('overlay-key', Lang.bind(overview, overview.toggle));
|
|
|
|
global.connect('panel-main-menu', Lang.bind(overview, overview.toggle));
|
2009-08-28 19:11:25 +00:00
|
|
|
|
|
|
|
global.stage.connect('captured-event', _globalKeyPressHandler);
|
|
|
|
|
2009-10-24 17:36:52 +00:00
|
|
|
_log('info', 'loaded at ' + _startDate);
|
2010-01-08 16:27:52 +00:00
|
|
|
log('GNOME Shell started at ' + _startDate);
|
2009-10-24 17:36:52 +00:00
|
|
|
|
2009-02-16 19:32:04 +00:00
|
|
|
Mainloop.idle_add(_removeUnusedWorkspaces);
|
|
|
|
}
|
|
|
|
|
2009-10-24 17:36:52 +00:00
|
|
|
/**
|
|
|
|
* _log:
|
|
|
|
* @category: string message type ('info', 'error')
|
|
|
|
* @msg: A message string
|
|
|
|
* ...: Any further arguments are converted into JSON notation,
|
|
|
|
* and appended to the log message, separated by spaces.
|
|
|
|
*
|
|
|
|
* Log a message into the LookingGlass error
|
|
|
|
* stream. This is primarily intended for use by the
|
|
|
|
* extension system as well as debugging.
|
|
|
|
*/
|
|
|
|
function _log(category, msg) {
|
|
|
|
let text = msg;
|
|
|
|
if (arguments.length > 2) {
|
|
|
|
text += ': ';
|
|
|
|
for (let i = 2; i < arguments.length; i++) {
|
|
|
|
text += JSON.stringify(arguments[i]);
|
|
|
|
if (i < arguments.length - 1)
|
|
|
|
text += " ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_errorLogStack.push({timestamp: new Date().getTime(),
|
|
|
|
category: category,
|
|
|
|
message: text });
|
|
|
|
}
|
|
|
|
|
|
|
|
function _logError(msg) {
|
|
|
|
return _log('error', msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _logDebug(msg) {
|
|
|
|
return _log('debug', msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used by the error display in lookingGlass.js
|
|
|
|
function _getAndClearErrorStack() {
|
|
|
|
let errors = _errorLogStack;
|
|
|
|
_errorLogStack = [];
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
2009-08-11 15:16:25 +00:00
|
|
|
function _relayout() {
|
2009-08-25 19:23:53 +00:00
|
|
|
let primary = global.get_primary_monitor();
|
|
|
|
panel.actor.set_position(primary.x, primary.y);
|
|
|
|
panel.actor.set_size(primary.width, Panel.PANEL_HEIGHT);
|
2009-08-11 15:16:25 +00:00
|
|
|
overview.relayout();
|
|
|
|
}
|
|
|
|
|
2009-02-16 19:32:04 +00:00
|
|
|
// metacity-clutter currently uses the same prefs as plain metacity,
|
|
|
|
// which probably means we'll be starting out with multiple workspaces;
|
|
|
|
// remove any unused ones. (We do this from an idle handler, because
|
|
|
|
// global.get_windows() still returns NULL at the point when start()
|
|
|
|
// is called.)
|
|
|
|
function _removeUnusedWorkspaces() {
|
|
|
|
|
|
|
|
let windows = global.get_windows();
|
|
|
|
let maxWorkspace = 0;
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
let win = windows[i];
|
|
|
|
|
|
|
|
if (!win.get_meta_window().is_on_all_workspaces() &&
|
|
|
|
win.get_workspace() > maxWorkspace) {
|
|
|
|
maxWorkspace = win.get_workspace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let screen = global.screen;
|
|
|
|
if (screen.n_workspaces > maxWorkspace) {
|
|
|
|
for (let w = screen.n_workspaces - 1; w > maxWorkspace; w--) {
|
|
|
|
let workspace = screen.get_workspace_by_index(w);
|
|
|
|
screen.remove_workspace(workspace, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2008-10-31 23:09:46 +00:00
|
|
|
}
|
|
|
|
|
2009-08-28 19:11:25 +00:00
|
|
|
// This function encapsulates hacks to make certain global keybindings
|
|
|
|
// work even when we are in one of our modes where global keybindings
|
|
|
|
// are disabled with a global grab. (When there is a global grab, then
|
|
|
|
// all key events will be delivered to the stage, so ::captured-event
|
|
|
|
// on the stage can be used for global keybindings.)
|
|
|
|
//
|
|
|
|
// We expect to need to conditionally enable just a few keybindings
|
|
|
|
// depending on circumstance; the main hackiness here is that we are
|
|
|
|
// assuming that keybindings have their default values; really we
|
|
|
|
// should be asking Mutter to resolve the key into an action and then
|
|
|
|
// base our handling based on the action.
|
|
|
|
function _globalKeyPressHandler(actor, event) {
|
2009-09-15 19:53:07 +00:00
|
|
|
if (modalCount == 0)
|
2009-08-28 19:11:25 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
let type = event.type();
|
|
|
|
|
|
|
|
if (type == Clutter.EventType.KEY_PRESS) {
|
2009-09-08 20:58:57 +00:00
|
|
|
let symbol = event.get_key_symbol();
|
2009-08-28 19:11:25 +00:00
|
|
|
if (symbol == Clutter.Print) {
|
|
|
|
// We want to be able to take screenshots of the shell at all times
|
|
|
|
let gconf = Shell.GConf.get_default();
|
|
|
|
let command = gconf.get_string("/apps/metacity/keybinding_commands/command_screenshot");
|
|
|
|
if (command != null && command != "") {
|
|
|
|
let [ok, len, args] = GLib.shell_parse_argv(command);
|
|
|
|
let p = new Shell.Process({'args' : args});
|
|
|
|
p.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else if (type == Clutter.EventType.KEY_RELEASE) {
|
2009-09-08 20:58:57 +00:00
|
|
|
let symbol = event.get_key_symbol();
|
2009-08-28 19:11:25 +00:00
|
|
|
if (symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
|
|
|
|
// The super key is the default for triggering the overview, and should
|
|
|
|
// get us out of the overview when we are already in it.
|
|
|
|
if (overview.visible)
|
|
|
|
overview.hide();
|
|
|
|
|
|
|
|
return true;
|
2009-10-07 21:20:33 +00:00
|
|
|
} else if (symbol == Clutter.F2 && (Shell.get_event_state(event) & Clutter.ModifierType.MOD1_MASK)) {
|
2009-09-14 19:08:20 +00:00
|
|
|
getRunDialog().open();
|
2009-08-28 19:11:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-09-15 19:53:07 +00:00
|
|
|
function _findModal(actor) {
|
|
|
|
for (let i = 0; i < modalActorFocusStack.length; i++) {
|
|
|
|
let [stackActor, stackFocus] = modalActorFocusStack[i];
|
|
|
|
if (stackActor == actor) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pushModal:
|
|
|
|
* @actor: #ClutterActor which will be given keyboard focus
|
|
|
|
*
|
|
|
|
* Ensure we are in a mode where all keyboard and mouse input goes to
|
|
|
|
* the stage. 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.
|
2009-09-16 15:37:51 +00:00
|
|
|
*
|
|
|
|
* Returns: true iff we successfully acquired a grab or already had one
|
2009-09-15 19:53:07 +00:00
|
|
|
*/
|
|
|
|
function pushModal(actor) {
|
2009-09-16 15:37:51 +00:00
|
|
|
if (modalCount == 0) {
|
2009-12-03 20:59:52 +00:00
|
|
|
if (!global.begin_modal(global.get_current_time())) {
|
2009-09-16 15:37:51 +00:00
|
|
|
log("pushModal: invocation of begin_modal failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
|
|
|
|
|
2009-09-15 19:53:07 +00:00
|
|
|
modalCount += 1;
|
|
|
|
actor.connect('destroy', function() {
|
|
|
|
let index = _findModal(actor);
|
|
|
|
if (index >= 0)
|
|
|
|
modalActorFocusStack.splice(index, 1);
|
|
|
|
});
|
|
|
|
let curFocus = global.stage.get_key_focus();
|
|
|
|
if (curFocus != null) {
|
|
|
|
curFocus.connect('destroy', function() {
|
|
|
|
let index = _findModal(actor);
|
|
|
|
if (index >= 0)
|
|
|
|
modalActorFocusStack[index][1] = null;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
modalActorFocusStack.push([actor, curFocus]);
|
2008-11-24 19:07:18 +00:00
|
|
|
|
2009-09-16 15:37:51 +00:00
|
|
|
return true;
|
2008-10-31 23:09:46 +00:00
|
|
|
}
|
|
|
|
|
2009-09-15 19:53:07 +00:00
|
|
|
/**
|
|
|
|
* popModal:
|
|
|
|
* @actor: #ClutterActor passed to original invocation of pushModal().
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
function popModal(actor) {
|
|
|
|
modalCount -= 1;
|
|
|
|
let focusIndex = _findModal(actor);
|
|
|
|
if (focusIndex >= 0) {
|
|
|
|
if (focusIndex == modalActorFocusStack.length - 1) {
|
|
|
|
let [stackActor, stackFocus] = modalActorFocusStack[focusIndex];
|
|
|
|
global.stage.set_key_focus(stackFocus);
|
|
|
|
} else {
|
|
|
|
// Remove from the middle, shift the focus chain up
|
|
|
|
for (let i = focusIndex; i < modalActorFocusStack.length - 1; i++) {
|
|
|
|
modalActorFocusStack[i + 1][1] = modalActorFocusStack[i][1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
modalActorFocusStack.splice(focusIndex, 1);
|
|
|
|
}
|
|
|
|
if (modalCount > 0)
|
|
|
|
return;
|
|
|
|
|
2009-12-03 20:59:52 +00:00
|
|
|
global.end_modal(global.get_current_time());
|
2009-04-29 18:01:09 +00:00
|
|
|
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
2008-10-31 04:22:44 +00:00
|
|
|
}
|
2008-11-24 19:07:18 +00:00
|
|
|
|
2009-08-02 07:46:01 +00:00
|
|
|
function createLookingGlass() {
|
|
|
|
if (lookingGlass == null) {
|
|
|
|
lookingGlass = new LookingGlass.LookingGlass();
|
|
|
|
lookingGlass.slaveTo(panel.actor);
|
|
|
|
}
|
|
|
|
return lookingGlass;
|
|
|
|
}
|
|
|
|
|
2009-09-14 19:08:20 +00:00
|
|
|
function getRunDialog() {
|
|
|
|
if (runDialog == null) {
|
|
|
|
runDialog = new RunDialog.RunDialog();
|
|
|
|
}
|
|
|
|
return runDialog;
|
|
|
|
}
|
|
|
|
|
2009-09-21 20:29:37 +00:00
|
|
|
/**
|
|
|
|
* activateWindow:
|
|
|
|
* @window: the Meta.Window to activate
|
|
|
|
* @time: (optional) current event time
|
|
|
|
*
|
|
|
|
* Activates @window, switching to its workspace first if necessary
|
|
|
|
*/
|
|
|
|
function activateWindow(window, time) {
|
|
|
|
let activeWorkspaceNum = global.screen.get_active_workspace_index();
|
|
|
|
let windowWorkspaceNum = window.get_workspace().index();
|
|
|
|
|
|
|
|
if (!time)
|
2009-12-03 20:59:52 +00:00
|
|
|
time = global.get_current_time();
|
2009-09-21 20:29:37 +00:00
|
|
|
|
|
|
|
if (windowWorkspaceNum != activeWorkspaceNum) {
|
|
|
|
let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum);
|
|
|
|
workspace.activate_with_focus(window, time);
|
|
|
|
} else {
|
|
|
|
window.activate(time);
|
|
|
|
}
|
|
|
|
}
|
2009-12-03 17:19:38 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
global.logError("invalid work id ", workId);
|
|
|
|
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 false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|