File SLEClassicExt.js of Package gnome-shell.6244

// -*- 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 Atk = imports.gi.Atk;

const BoxPointer = imports.ui.boxpointer;
const GrabHelper = imports.ui.grabHelper;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Separator = imports.ui.separator;
const Slider = imports.ui.slider;
const Tweener = imports.ui.tweener;

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() ){
        // FIXME: "mainPanel.height" may be uninitialized at this point: fixed to
        // a known value for now.
        let mainPanelHeight = 28;
        // 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);
}
openSUSE Build Service is sponsored by