gnome-shell/SLEClassicExt.js

299 lines
12 KiB
JavaScript
Raw Normal View History

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
//
//
// Extra features needed by SLE Classic mode. A lot of features here are
// designed as modular functions or classes, so it's cumbersome to fit in a
// patch. However, most functionality requires a patch to the GNOME Shell core
// to work. A design principle is to keep this adapter patch as small and clear as possible.
//
// NOTE This file should be packed into GNOME Shell GResource files, with inner
// path "%{_datadir}/gnome-shell/js/ui/" and used by "imports.ui.SLEClassicExt".
//
// [5-5] Major Update: SLE Classic is no longer a full-fledged GNOME session,
// instead it's a runtime modified GNOME Classic.
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const Main = imports.ui.main;
const GNOME_CLASSIC_SESSION_NAME = "classic";
// NOTE About `global.session_mode`, which maps
// normal GNOME session -> "user"
// GNOME classic -> "classic" (i.e. the "gnome-" prefix is stripped)
// SLE classic -> "sle-classic" (depreciated)
//
// This var is defined in the C code and some parts of the Shell use it instead
// of `Main.sessionMode.currentMode`, notably `exensionSystem`. An important
// difference is that `global.session_mode` is initialized much earlier than
// `currentMode` and it does NOT reflect in-Session mode change, e.g. the
// lock/unlock screen.
// NOTE a dedicated env var to mark this classic mode a SLE classic, see
// sle-classic.desktop.
var isSLEClassicEnv = ( GLib.getenv('SLE_CLASSIC_MODE') !== null );
// NOTE: A mode can be verified as SLE classic iff it's GNOME Classic and it has
// SLE_CLASSIC_MODE envar set.
function isSLEClassicMode() {
// NOTE: must check this part so lock screen is not SLE Classic.
if ( Main.sessionMode.currentMode !== GNOME_CLASSIC_SESSION_NAME )
return false;
return isSLEClassicEnv;
}
// NOTE: safe to use BEFORE `sessionMode` has been properly initialized
function isSLEClassicModeGlobal () {
if ( global.session_mode !== GNOME_CLASSIC_SESSION_NAME )
return false;
return isSLEClassicEnv;
}
// NOTE: Originally "sle-classic.json" under "gnome-shell/modes" dir.
//
// TODO: strip down to differences with "classic.json"
var SLE_CLASSIC_JSON = {
"parentMode": "user",
"stylesheetName": "gnome-classic.css",
"enabledExtensions": [
"apps-menu@gnome-shell-extensions.gcampax.github.com", "places-menu@gnome-shell-extensions.gcampax.github.com",
"alternate-tab@gnome-shell-extensions.gcampax.github.com",
"launch-new-instance@gnome-shell-extensions.gcampax.github.com",
"window-list@gnome-shell-extensions.gcampax.github.com",
"workspace-indicator@gnome-shell-extensions.gcampax.github.com"
],
"panel": {
"left": ["activities"],
"center": [],
"right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"]
}
};
// NOTE: used in "sessionMode.js::_loadMode"
//
// If the current mode is SLE Classic, return `SLE_CLASSIC_JSON`. O/w no
// changes.
function convertClassic2SLE(json) {
// NOTE: `Main.sessionMode` have not initialized.
if ( ! isSLEClassicModeGlobal() )
return json;
return SLE_CLASSIC_JSON;
}
// NOTE: used in `SLEClassicExt.init`
//
// NOTE: it's mandatory to disable app menu in SLE Classic but restore the old
// settings after switching back. A more proper solution would be to introduce a
// dconf overrides key AND change the GSD part to read the overriden value. Too
// much work, keep it a hack for now.
function toggleAppMenu() {
let xsetting = new Gio.Settings({ schema: 'org.gnome.settings-daemon.plugins.xsettings' });
let valueObj = xsetting.get_value('overrides').unpack();
// NOTE: as dictated by the definitions in gschema, the values for the
// following flags can NOT be booleans, but instead 0 or 1.
// NOTE: special handling, as if `Gtk/ShellShowsAppMenu` is NOT set, it's
// treated as true by XSettings
const showAppMenuKey = 'Gtk/ShellShowsAppMenu';
const showAppMenuSLESetKey = 'Gtk/ShellShowsAppMenu/SLESet';
// NOTE double `unpack` is needed as 'a{sv}' construction would wrap the value
// in an extra Variant container.
let showAppMenuP = valueObj[showAppMenuKey]
? valueObj[showAppMenuKey].unpack().unpack()
: 1;
let showAppMenuSLESet = valueObj[showAppMenuSLESetKey]
? valueObj[showAppMenuSLESetKey].unpack().unpack()
: 0;
// NOTE extra check to make sure `showAppMenuP` and `showAppMenuSLESet` are
// numbers. ('v' can be many other types and it's possible the user sets so)
// The fallback value is the same as above defaults.
if (typeof showAppMenuP !== 'number') {
showAppMenuP = 1;
}
if (typeof showAppMenuSLESet !== 'number') {
showAppMenuSLESet = 0;
}
// NOTE: In SLE Classic mode, if app menu is set to shown, hide it and set a
// special flag to mark this change for restoring.
if (isSLEClassicModeGlobal() && showAppMenuP) {
showAppMenuP = 0;
showAppMenuSLESet = 1;
}
// NOTE: in none-SLE Classic mode, if app menu is hidden AND `SLESet` flag
// is set, show the app menu and reset the `SLESet` flag.
if ( !isSLEClassicModeGlobal() && showAppMenuSLESet) {
showAppMenuP = 1;
showAppMenuSLESet = 0;
}
valueObj[showAppMenuKey] = GLib.Variant.new('i', showAppMenuP);
valueObj[showAppMenuSLESetKey] = GLib.Variant.new('i', showAppMenuSLESet);
xsetting.set_value('overrides', GLib.Variant.new('a{sv}', valueObj));
}
// NOTE: used in `main.js::start`
function init() {
toggleAppMenu();
}
var panelPosUpdateId = null;
// layout.js: Replace the origin "box.set_position" call
function setMainPanelPosition (mainPanel, primaryMonitor) {
if ( isSLEClassicMode() ){
let mainPanelHeight = mainPanel.height == 0
? 28 : mainPanel.height;
// Main Panel at the bottom
mainPanel.set_position(primaryMonitor.x,
primaryMonitor.y + primaryMonitor.height - mainPanelHeight);
// NOTE: needed s.t. lock screen would have top panel
//
// TODO use the `updated` signal to change main panel to remove the patch?
if ( !panelPosUpdateId ){
panelPosUpdateId = Main.sessionMode.connect('updated', function(){
setMainPanelPosition(Main.layoutManager.panelBox, Main.layoutManager.primaryMonitor);
});
}
}
else {
mainPanel.set_position(primaryMonitor.x, primaryMonitor.y);
}
}
// panel.js: Replace the original "Panel._allocate".
//
// The differences are quite subtle but yet pervasive, so despite the
// similarity, there will be two versions of allocation functions.
//
// The main difference is to use the empty(in Classic) _centerBox for window
// list.
//
// NOTE: has to be a *direct* replacement to have proper `this` setup
function _allocate (actor, box, flags) {
// NOTE: for convenience, the followings are shared. The params of this
// function is also shared.
let allocWidth = box.x2 - box.x1;
let allocHeight = box.y2 - box.y1;
let [leftMinWidth, leftNaturalWidth] = this._leftBox.get_preferred_width(-1);
let [centerMinWidth, centerNaturalWidth] = this._centerBox.get_preferred_width(-1);
let [rightMinWidth, rightNaturalWidth] = this._rightBox.get_preferred_width(-1);
// bind sub function's `this` to the current `this`, i.e. Main.panel
let allocateSC = _allocateSC.bind(this);
// For convenience, defined here to allow access to local vars
function _allocateSC () {
let isTextDirRTL = ( this.actor.get_text_direction() === Clutter.TextDirection.RTL );
// A mask to create allocation.
let maskBox = new Clutter.ActorBox();
// NOTE: policy here is to satisfy left&right boxes space needs first with
// center box can be shrinked. (This is the opposite of the original
// allocator, this idea here is to accomodate SLE-Classic window list within
// center box first. )
let maxSideWidth = Math.floor( (allocWidth - centerMinWidth) / 2 );
let leftWidth = Math.min(maxSideWidth, leftNaturalWidth);
let rightWidth = Math.min(maxSideWidth, rightNaturalWidth);
// NOTE: in the case, flooring for maxSideWidth does happen, the following equation yields:
// : centerWidth = centerMinWidth + 1
// this avoids the cumbersome floor/ceil in orginal code.
//
// `centerWidth` is shrinkable with an lower bound `centerMinWidth` or `centerMinWidth + 1`
let centerWidth = allocWidth - leftWidth - rightWidth;
// Adjust left/center/right boxes start Y position
//
// After moving the panel to the bottom side, the top border will be
// added instead of the bottom border. However all 3 box of the main
// panel are absolutely positioned starting from the top edge of the
// panel originally, this results that the bottom pixel line is not
// clickable. Add a Y offset to fix it.
//
// NOTE: we can set y1 as (MainLayoutManager.primaryMonitor.height -
// allocHeight), which seems to be easier, however the solution below is
// more generic and versatile.
let node = Main.panel.actor.get_theme_node();
let borderTopWidth = node.get_length("border-top");
let y_offset = borderTopWidth;
maskBox.y1 = y_offset;
// TODO: in previous version of SLE Classic the following is
// `allocHeight + y_offset`, why?
maskBox.y2 = allocHeight;
// Normal order, from left to right
if ( ! isTextDirRTL ) {
maskBox.x1 = 0;
maskBox.x2 = maskBox.x1 + leftWidth;
this._leftBox.allocate(maskBox, flags);
maskBox.x1 = maskBox.x2;
maskBox.x2 = maskBox.x1 + centerWidth;
this._centerBox.allocate(maskBox, flags);
maskBox.x1 = maskBox.x2;
maskBox.x2 = maskBox.x1 + rightWidth;
this._rightBox.allocate(maskBox, flags);
}
// Right to Left: essentially right box left box are swapped
else {
maskBox.x2 = allocWidth;
maskBox.x1 = maskBox.x2 - leftWidth;
this._leftBox.allocate(maskBox, flags);
maskBox.x2 = maskBox.x1;
maskBox.x1 = maskBox.x2 - centerWidth;
this._centerBox.allocate(maskBox, flags)
maskBox.x2 = maskBox.x1;
maskBox.x1 = maskBox.x2 - rightWidth;
this._rightBox.allocate(maskBox, flags)
}
}
// Real work...
if (isSLEClassicMode()) {
// SC: Sle Classic
allocateSC();
}
else {
this._allocateOrigin(actor, box, flags);
}
// TODO: what are these corners for? They are zero on my test machines.
// Without proper understanding of their purposes, they are the same for
// both original code and SLE Classic.
let cornerMinWidth, cornerMinHeight;
let cornerWidth, cornerHeight;
let childBox = new Clutter.ActorBox();
[cornerMinWidth, cornerWidth] = this._leftCorner.actor.get_preferred_width(-1);
[cornerMinHeight, cornerHeight] = this._leftCorner.actor.get_preferred_height(-1);
childBox.x1 = 0;
childBox.x2 = cornerWidth;
childBox.y1 = allocHeight;
childBox.y2 = allocHeight + cornerHeight;
this._leftCorner.actor.allocate(childBox, flags);
[cornerMinWidth, cornerWidth] = this._rightCorner.actor.get_preferred_width(-1);
[cornerMinHeight, cornerHeight] = this._rightCorner.actor.get_preferred_height(-1);
childBox.x1 = allocWidth - cornerWidth;
childBox.x2 = allocWidth;
childBox.y1 = allocHeight;
childBox.y2 = allocHeight + cornerHeight;
this._rightCorner.actor.allocate(childBox, flags);
}