HTTPS SSH

Window Heads Up Display GNOME Shell Extension

This extension merely shows an up-to-date heads up display of the current windows on your workspace. On its own it is not very helpful, but if you are writing an extension that wants to know where every window is all the time you are welcome to use this code.

It provides a utility class WindowListener (and helper class Region) that keeps an always-up-to-date snapshot of every window on your screen. You can choose to override what happens whenever a new window event is received.

The extension demonstrates how to use the WindowListener class by using it to draw the current window region onto your screen.

TODO: Screenshot of the extension.

Written 2012 by mathematical.coffee mathematical.coffee@gmail.com.
Project webpage: at bitbucket.


The WindowListener class

The core class of this extension that I anticipate will be useful to others is WindowListener.

All this class does is strive to maintain an up-to-date snapshot of the windows currently on the screen. It stores this as a Region.Region in this.windowRegion.

Every time a window is maximized, minimized, unmaximized, unminimized, moved, resized, created, destroyed or brought to focus, this.windowRegion gets updated with the new window.get_outer_rect(). this.windowRegion is sorted in stacking order (if you set the 'Update with stacking order' option to true); as one iterates through this.windowRegion.rectangles[i], windows move from on the bottom to the top of the stacking order.

To use this class, subclass it. Use this.start() to start listening to events, and this.stop() to stop. Also available are pause, resume, is_playing, and is_paused functions.

Every time a window event is recorded that should update this.windowRegion, the function _onWindowEvent is called.

In the WindowListener class, _onWindowEvent simply prints a message to the console and also calls this._updateWindows(), which updates this.windowRegion with the new snapshot of all the windows.

In your class, you will probably want to override this function in order to add your own code to occur on any window event being caught. In it, you will want to either call WindowListener.prototype._onWindowEvent OR this._updateWindows() in order to make sure that this.windowRegion is updated.

The _onWindowEvent function has one argument which is the name of the event that fired it (for example notify::minimized, maximize, window-added, switch-workspace).

As an example, see the WindowHUD class in extension.js which subclasses WindowListener to provide a graphical snapshot of this.windowRegion, updated as events are fired. Note how WindowHUD._onWindowEvent calls WindowListener._onWindowEvent (to make sure this.windowRegion is updated) and then calls this.draw() to update the graphical display.

It also overrides the start, stop etc functions to show and hide the display (but it makes sure to call WindowListener.start and WindowListener.stop in these functions).

TODO: further explanation of signals listened to etc here, since this extension really will only be used by developers.

WindowListener Options

The WindowListener class also has a number of options to configure it.

These should be changed using the changeOption function (for example, this.changeOption('onAllWorkspaces', false);).

The options are as follows:

Ignore popups (this.options.ignorePopups)

Whether to include popup windows (tooltips, drop-down file menu, ...) are included in this.windowRegion.

Always on visible workspace (this.options.alwaysOnVisibleWorkspace)

Whether to update the window region as the user changes workspace, or whether to tie the window listener to its starting workspace (it pauses on switching to another workspace until we switch back to the original one).

Update with stacking order (this.options.stackingOrder)

Whether the window region gets updated whenever stacking order changes (e.g. a window is raised).

Verbose (this.options.verbose)

Whether messages get printed to the console (when you start gnome-shell in a terminal). You can use this.LOG like you would use printf and it will print messages to the console if the verbose option is turned on.

Recalc Mode (this.options.recalcMode)

When the user resizes or moves a window, by default the window region is updated at every frame of the move/resize (recalc mode ALWAYS). If this is too memory-intensive (it really shouldn't be), you can instead only update the window region once the user has finished moving or resizing the window. This means that while the user is moving/resizing, the current this.windowRegion will not be up to date until the user finishes.

Using recalc mode PAUSE will pause all the signals until the drag is done. Using mode END will still accept signals whilst the resize/move is in progress but only recalculate the result of the resize/move event once it's finished.

This is a pretty useless option as far as I can tell. Just use the ALWAYS mode.


the Region class

The Region class is basically a collection of rectangles, being the outer_rect() of the windows on the screen. To iterate through the rectangles, iterate through region.rectangles as an array:

for (let i = 0; i < region.rectangles.length; ++i) {
    /* do something with region.rectangles[i], which has properties
       x, y, width and height. */
}

To clear the region, use region.clear().

To add a new rectangle to the region, use region.addRectangle(rectangle). rectangle should be a Meta.Rectangle, but it will probably work with any object that simply has properties .x, .y, .width and .height.

To see if a rectangle overlaps with the region, use region.overlap(x, y, width, height). Omit the width and height arguments to simply see if a point is within the region.


Use WindowListener in your extension

  1. copy windowListener.js to your extension.
  2. add these lines to your extension.js (or your file in which you plan to use these classes) to import the files:

    /* if GNOME 3.2: */
    const Me = imports.ui.extensionSystem.extensions['your_extension_uuid'];
    const WindowListener = Me.windowListener;
    /* if GNOME 3.4: */
    const Me = imports.misc.extensionUtils.getCurrentExtension();
    const WindowListener = Me.imports.windowListener;
    
  3. subclass WindowListener.

    /* if GNOME 3.2: */
    function MyClass() {
        this._init.apply(this, arguments);
    }
    MyClass.prototype = {
        __proto__: WindowListener.WindowListener.prototype,
    
        _init: function () {
            WindowListener.WindowListener.prototype._init.apply(this);
            /* other initialisation code for MyClass here */
        },
    
        /* what you want to happen when a window event occurs */
        _onWindowEvent: function (eventName) {
            WindowListener.WindowListener.prototype._onWindowEvent.apply(this, eventName);
            /* MyClass code here */
        }
        /* and so on. */
    };
    
    /* if GNOME 3.4 */
    const Lang = imports.lang;
    const MyClass = new Lang.Class({
        Name: 'MyClass',
        Extends: WindowListener.WindowListener,
        _init: function () {
            this.parent();
            /* other initialisation code for MyClass here */
        },
    
        /* what you want to happen when a window event occurs */
        _onWindowEvent: function (eventName) {
            this.parent(eventName);
            /* MyClass code here */
        }
        /* and so on. */
    });
    
  4. Don't forget you should probably override _onWindowEvent.


Files

List of files contained in the extension:

  • extension.js: this contains the code for the extension's drop-down menu and the code for the heads-up-display class showing an example of how to implement WindowListener.
  • windowListener.js: this defines the WindowListener and Region classes an is the file of interest to copy to your own extension.

Note - the repo history is long and convoluted because I originally used this in the XPenguins gnome shell extension to track windows, and have only just decided to separate it back out into its own standalone extension.


Installation

Since you're probably a developer if you want to look at this extension, checkout the code.

hg clone https://bitbucket.org/mathematicalcoffee/window-HUD-gnome-shell-extension

The default branch is a polyglot - it should work in both GNOME 3.2 & GNOME 3.4 (it has a try/catch statement for the imports up the top of extension.js and is written in syntax that works with both 3.2 and 3.4).

The 'gnome3.2' branch is compatible with GNOME 3.2. It is basically the default branch with the initial try/catch statement removed and replaced with 3.2-specific syntax to import the windowListener file.

The 'gnome3.4' branch is compatible with GNOME 3.4 ONLY. It replaces the extension imports polyglot from the default branch with the 3.4-specific syntax. It also uses the Lang.Class method of defining new classes instead of the __proto__: .... method.

The 3.2 and 3.4 branches are meant to be stable.

Otherwise, you can download the .zip file on the Downloads page. Then open gnome-tweak-tool, go to "Shell Extensions", "Install Extension" and select the .zip file.