2009-05-06 14:36:50 -04:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
const Lang = imports.lang;
|
|
|
|
const Mainloop = imports.mainloop;
|
|
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
|
|
|
// This manages the shell "chrome"; the UI that's visible in the
|
|
|
|
// normal mode (ie, outside the overlay), that surrounds the main
|
|
|
|
// workspace content.
|
|
|
|
|
|
|
|
function Chrome() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Chrome.prototype = {
|
|
|
|
_init: function() {
|
|
|
|
let global = Shell.Global.get();
|
|
|
|
|
2009-05-23 10:47:13 -04:00
|
|
|
// The group itself has zero size so it doesn't interfere with DND
|
|
|
|
this.actor = new Clutter.Group({ width: 0, height: 0 });
|
2009-05-06 14:36:50 -04:00
|
|
|
global.stage.add_actor(this.actor);
|
|
|
|
this.nonOverlayActor = new Clutter.Group();
|
|
|
|
this.actor.add_actor(this.nonOverlayActor);
|
|
|
|
|
|
|
|
this._obscuredByFullscreen = false;
|
|
|
|
|
|
|
|
this._trackedActors = [];
|
|
|
|
|
|
|
|
global.screen.connect('restacked',
|
|
|
|
Lang.bind(this, this._windowsRestacked));
|
|
|
|
|
|
|
|
// Need to update struts on new workspaces when they are added
|
|
|
|
global.screen.connect('notify::n-workspaces',
|
|
|
|
Lang.bind(this, this._queueUpdateRegions));
|
|
|
|
|
|
|
|
Main.overlay.connect('showing',
|
|
|
|
Lang.bind(this, this._overlayShowing));
|
|
|
|
Main.overlay.connect('hidden',
|
|
|
|
Lang.bind(this, this._overlayHidden));
|
|
|
|
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_verifyAncestry: function(actor, ancestor) {
|
|
|
|
while (actor) {
|
|
|
|
if (actor == ancestor)
|
|
|
|
return true;
|
|
|
|
actor = actor.get_parent();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
// addActor:
|
|
|
|
// @actor: an actor to add to the chrome layer
|
|
|
|
// @shapeActor: optional "shape actor".
|
|
|
|
//
|
|
|
|
// Adds @actor to the chrome layer and extends the input region
|
|
|
|
// and window manager struts to include it. (Window manager struts
|
|
|
|
// will only be affected if @actor is touching a screen edge.)
|
|
|
|
// Changes in @actor's size and position will automatically result
|
|
|
|
// in appropriate changes to the input region and struts. Changes
|
|
|
|
// in its visibility will affect the input region, but NOT the
|
|
|
|
// struts.
|
|
|
|
//
|
|
|
|
// If @shapeActor is provided, it will be used instead of @actor
|
|
|
|
// for the input region/strut shape. (This lets you have things like
|
|
|
|
// drop shadows in @actor that don't affect the struts.) It must
|
|
|
|
// be a child of @actor. Alternatively, you can pass %null for
|
|
|
|
// @shapeActor to indicate that @actor should not affect the input
|
|
|
|
// region or struts at all.
|
|
|
|
addActor: function(actor, shapeActor) {
|
|
|
|
if (shapeActor === undefined)
|
|
|
|
shapeActor = actor;
|
|
|
|
else if (shapeActor && !this._verifyAncestry(shapeActor, actor))
|
|
|
|
throw new Error('shapeActor is not a descendent of actor');
|
|
|
|
|
|
|
|
this.nonOverlayActor.add_actor(actor);
|
|
|
|
|
|
|
|
if (shapeActor)
|
|
|
|
this._trackActor(shapeActor, true, true);
|
|
|
|
},
|
|
|
|
|
|
|
|
// setVisibleInOverlay:
|
|
|
|
// @actor: an actor in the chrome layer
|
|
|
|
// @visible: overlay visibility
|
|
|
|
//
|
|
|
|
// By default, actors in the chrome layer are automatically hidden
|
|
|
|
// when the overlay is shown. This can be used to override that
|
|
|
|
// behavior
|
|
|
|
setVisibleInOverlay: function(actor, visible) {
|
|
|
|
if (!this._verifyAncestry(actor, this.actor))
|
|
|
|
throw new Error('actor is not a descendent of the chrome layer');
|
|
|
|
|
|
|
|
if (visible)
|
|
|
|
actor.reparent(this.actor);
|
|
|
|
else
|
|
|
|
actor.reparent(this.nonOverlayActor);
|
|
|
|
},
|
|
|
|
|
|
|
|
// addInputRegionActor:
|
|
|
|
// @actor: an actor to add to the stage input region
|
|
|
|
//
|
|
|
|
// Adds @actor to the stage input region, as with addActor(), but
|
|
|
|
// for actors that are already descendants of the chrome layer.
|
|
|
|
addInputRegionActor: function(actor) {
|
|
|
|
if (!this._verifyAncestry(actor, this.actor))
|
|
|
|
throw new Error('actor is not a descendent of the chrome layer');
|
|
|
|
|
|
|
|
this._trackActor(actor, true, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
// removeInputRegionActor:
|
|
|
|
// @actor: an actor previously added to the stage input region
|
|
|
|
//
|
|
|
|
// Undoes the effect of addInputRegionActor()
|
|
|
|
removeInputRegionActor: function(actor) {
|
|
|
|
this._untrackActor(actor, true, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
// removeActor:
|
|
|
|
// @actor: a child of the chrome layer
|
|
|
|
//
|
|
|
|
// Removes @actor from the chrome layer
|
|
|
|
removeActor: function(actor) {
|
2009-06-30 14:57:40 -04:00
|
|
|
if (actor.get_parent() == this.nonOverlayActor)
|
|
|
|
this.nonOverlayActor.remove_actor(actor);
|
|
|
|
else
|
|
|
|
this.actor.remove_actor(actor);
|
|
|
|
this._untrackActor(actor, true, true);
|
2009-05-06 14:36:50 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_findActor: function(actor) {
|
|
|
|
for (let i = 0; i < this._trackedActors.length; i++) {
|
|
|
|
let actorData = this._trackedActors[i];
|
|
|
|
if (actorData.actor == actor)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
},
|
|
|
|
|
|
|
|
_trackActor: function(actor, inputRegion, strut) {
|
|
|
|
let actorData;
|
|
|
|
let i = this._findActor(actor);
|
|
|
|
|
|
|
|
if (i != -1) {
|
|
|
|
actorData = this._trackedActors[i];
|
|
|
|
if (inputRegion)
|
|
|
|
actorData.inputRegion++;
|
|
|
|
if (strut)
|
|
|
|
actorData.strut++;
|
|
|
|
if (!inputRegion && !strut)
|
|
|
|
actorData.children++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
actorData = { actor: actor,
|
|
|
|
inputRegion: inputRegion ? 1 : 0,
|
|
|
|
strut: strut ? 1 : 0,
|
|
|
|
children: 0 };
|
|
|
|
|
|
|
|
actorData.visibleId = actor.connect('notify::visible',
|
|
|
|
Lang.bind(this, this._queueUpdateRegions));
|
|
|
|
actorData.allocationId = actor.connect('notify::allocation',
|
|
|
|
Lang.bind(this, this._queueUpdateRegions));
|
|
|
|
actorData.parentSetId = actor.connect('parent-set',
|
|
|
|
Lang.bind(this, this._actorReparented));
|
|
|
|
|
|
|
|
this._trackedActors.push(actorData);
|
|
|
|
|
|
|
|
actor = actor.get_parent();
|
2009-06-30 14:57:40 -04:00
|
|
|
if (actor != this.actor && actor != this.nonOverlayActor)
|
2009-05-06 14:36:50 -04:00
|
|
|
this._trackActor(actor, false, false);
|
|
|
|
|
|
|
|
if (inputRegion || strut)
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_untrackActor: function(actor, inputRegion, strut) {
|
|
|
|
let i = this._findActor(actor);
|
|
|
|
|
|
|
|
if (i == -1)
|
|
|
|
return;
|
|
|
|
let actorData = this._trackedActors[i];
|
|
|
|
|
|
|
|
if (inputRegion)
|
|
|
|
actorData.inputRegion--;
|
|
|
|
if (strut)
|
|
|
|
actorData.strut--;
|
|
|
|
if (!inputRegion && !strut)
|
|
|
|
actorData.children--;
|
|
|
|
|
|
|
|
if (actorData.inputRegion <= 0 && actorData.strut <= 0 && actorData.children <= 0) {
|
|
|
|
this._trackedActors.splice(i, 1);
|
|
|
|
actor.disconnect(actorData.visibleId);
|
|
|
|
actor.disconnect(actorData.allocationId);
|
|
|
|
actor.disconnect(actorData.parentSetId);
|
|
|
|
|
|
|
|
actor = actor.get_parent();
|
2009-06-30 14:57:40 -04:00
|
|
|
if (actor && actor != this.actor && actor != this.nonOverlayActor)
|
2009-05-06 14:36:50 -04:00
|
|
|
this._untrackActor(actor, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inputRegion || strut)
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_actorReparented: function(actor, oldParent) {
|
|
|
|
if (this._verifyAncestry(actor, this.actor)) {
|
|
|
|
let newParent = actor.get_parent();
|
2009-06-30 14:57:40 -04:00
|
|
|
if (newParent != this.actor && newParent != this.nonOverlayActor)
|
2009-05-06 14:36:50 -04:00
|
|
|
this._trackActor(newParent, false, false);
|
|
|
|
}
|
2009-06-30 14:57:40 -04:00
|
|
|
if (oldParent != this.actor && oldParent != this.nonOverlayActor)
|
2009-05-06 14:36:50 -04:00
|
|
|
this._untrackActor(oldParent, false, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
_overlayShowing: function() {
|
|
|
|
this.actor.show();
|
|
|
|
this.nonOverlayActor.hide();
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_overlayHidden: function() {
|
|
|
|
if (this._obscuredByFullscreen)
|
|
|
|
this.actor.hide();
|
|
|
|
this.nonOverlayActor.show();
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_queueUpdateRegions: function() {
|
|
|
|
if (!this._updateRegionIdle)
|
2009-06-11 18:34:56 -04:00
|
|
|
this._updateRegionIdle = Mainloop.idle_add(Lang.bind(this, this._updateRegions),
|
|
|
|
Meta.PRIORITY_BEFORE_REDRAW);
|
2009-05-06 14:36:50 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_windowsRestacked: function() {
|
|
|
|
let global = Shell.Global.get();
|
|
|
|
let windows = global.get_windows();
|
|
|
|
|
|
|
|
// The chrome layer should be visible unless there is a window
|
|
|
|
// with layer FULLSCREEN, or a window with layer
|
|
|
|
// OVERRIDE_REDIRECT that covers the whole screen.
|
|
|
|
// ("override_redirect" is not actually a layer above all
|
|
|
|
// other windows, but this seems to be how mutter treats it
|
|
|
|
// currently...) If we wanted to be extra clever, we could
|
|
|
|
// figure out when an OVERRIDE_REDIRECT window was trying to
|
|
|
|
// partially overlap us, and then adjust the input region and
|
|
|
|
// our clip region accordingly...
|
|
|
|
|
|
|
|
// @windows is sorted bottom to top.
|
|
|
|
|
|
|
|
this._obscuredByFullscreen = false;
|
|
|
|
for (let i = windows.length - 1; i > -1; i--) {
|
|
|
|
let layer = windows[i].get_meta_window().get_layer();
|
|
|
|
|
|
|
|
if (layer == Meta.StackLayer.OVERRIDE_REDIRECT) {
|
|
|
|
if (windows[i].x <= 0 &&
|
|
|
|
windows[i].x + windows[i].width >= global.screen_width &&
|
|
|
|
windows[i].y <= 0 &&
|
|
|
|
windows[i].y + windows[i].height >= global.screen_height) {
|
|
|
|
this._obscuredByFullscreen = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (layer == Meta.StackLayer.FULLSCREEN) {
|
|
|
|
this._obscuredByFullscreen = true;
|
|
|
|
break;
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let shouldBeVisible = !this._obscuredByFullscreen || Main.overlay.visible;
|
|
|
|
if (this.actor.visible != shouldBeVisible) {
|
|
|
|
this.actor.visible = shouldBeVisible;
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateRegions: function() {
|
|
|
|
let global = Shell.Global.get();
|
|
|
|
let rects = [], struts = [], i;
|
|
|
|
|
|
|
|
delete this._updateRegionIdle;
|
|
|
|
|
|
|
|
for (i = 0; i < this._trackedActors.length; i++) {
|
|
|
|
let actorData = this._trackedActors[i];
|
|
|
|
if (!actorData.inputRegion && !actorData.strut)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
let [x, y] = actorData.actor.get_transformed_position();
|
|
|
|
let [w, h] = actorData.actor.get_transformed_size();
|
2009-06-06 13:07:41 -04:00
|
|
|
x = Math.round(x);
|
|
|
|
y = Math.round(y);
|
|
|
|
w = Math.round(w);
|
|
|
|
h = Math.round(h);
|
2009-05-06 14:36:50 -04:00
|
|
|
let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h});
|
|
|
|
|
|
|
|
if (actorData.inputRegion && actorData.actor.get_paint_visibility())
|
|
|
|
rects.push(rect);
|
|
|
|
|
|
|
|
if (!actorData.strut)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Metacity wants to know what side of the screen the
|
|
|
|
// strut is considered to be attached to. If the actor is
|
|
|
|
// only touching one edge, or is touching the entire
|
|
|
|
// width/height of one edge, then it's obvious which side
|
|
|
|
// to call it. If it's in a corner, we pick a side
|
|
|
|
// arbitrarily. If it doesn't touch any edges, or it spans
|
|
|
|
// the width/height across the middle of the screen, then
|
|
|
|
// we don't create a strut for it at all.
|
|
|
|
let side;
|
|
|
|
if (w >= global.screen_width) {
|
|
|
|
if (y <= 0)
|
|
|
|
side = Meta.Side.TOP;
|
|
|
|
else if (y + h >= global.screen_height)
|
|
|
|
side = Meta.Side.BOTTOM;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
} else if (h >= global.screen_height) {
|
|
|
|
if (x <= 0)
|
|
|
|
side = Meta.Side.LEFT;
|
|
|
|
else if (x + w >= global.screen_width)
|
|
|
|
side = Meta.Side.RIGHT;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
} else if (x <= 0)
|
|
|
|
side = Meta.Side.LEFT;
|
|
|
|
else if (y <= 0)
|
|
|
|
side = Meta.Side.TOP;
|
|
|
|
else if (x + w >= global.screen_width)
|
|
|
|
side = Meta.Side.RIGHT;
|
|
|
|
else if (y + h >= global.screen_height)
|
|
|
|
side = Meta.Side.BOTTOM;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
|
|
|
let strut = new Meta.Strut({ rect: rect, side: side });
|
|
|
|
struts.push(strut);
|
|
|
|
}
|
|
|
|
|
|
|
|
global.set_stage_input_region(rects);
|
|
|
|
|
|
|
|
let screen = global.screen;
|
|
|
|
for (let w = 0; w < screen.n_workspaces; w++) {
|
|
|
|
let workspace = screen.get_workspace_by_index(w);
|
|
|
|
workspace.set_builtin_struts(struts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|