2016-09-04 15:22:57 +02:00
|
|
|
// -*- 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';
|
2017-02-22 21:41:15 +01:00
|
|
|
// NOTE double `unpack` is needed as 'a{sv}' construction would wrap the value
|
|
|
|
// in an extra Variant container.
|
2016-09-04 15:22:57 +02:00
|
|
|
let showAppMenuP = valueObj[showAppMenuKey]
|
2017-02-22 21:41:15 +01:00
|
|
|
? valueObj[showAppMenuKey].unpack().unpack()
|
2016-09-04 15:22:57 +02:00
|
|
|
: 1;
|
|
|
|
let showAppMenuSLESet = valueObj[showAppMenuSLESetKey]
|
2017-02-22 21:41:15 +01:00
|
|
|
? valueObj[showAppMenuSLESetKey].unpack().unpack()
|
2016-09-04 15:22:57 +02:00
|
|
|
: 0;
|
|
|
|
|
2017-02-22 21:41:15 +01:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2016-09-04 15:22:57 +02:00
|
|
|
// 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() ){
|
2017-08-24 18:46:54 +02:00
|
|
|
let mainPanelHeight = mainPanel.height == 0
|
|
|
|
? 28 : mainPanel.height;
|
2016-09-04 15:22:57 +02:00
|
|
|
// 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);
|
|
|
|
}
|