0

I want my window manager (xmonad) to manage my electron app as a desktop status bar : it must be available on any workspace and have a reserved place on the screen (in the top for exemple)

To achieve this, I created a BrowserWindows like this :

mainWindow = new BrowserWindow({
    x:0,
    y:0,
    width:1024,
    height: 30,
    frame: false,
    title: 'electron-status-bar',
    type: 'dock'
});

My bar is visible on any workspace and above the other window. But it still have no reserved place, so there are overlaps with other windows.

I used xprop to compare with dzen2 (the actual status bar that perfectly work), and dzen2 have these properties :

_NET_WM_STRUT(CARDINAL) = 0, 0, 0, 34
_NET_WM_STRUT_PARTIAL(CARDINAL) = 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 899

In fact, the xmonad plugin (ManageDock) that manage the overlap is watching these two properties to calculate the window positions.

There is a way to set such properties in my electron app ?

Alexandre Brach
  • 325
  • 2
  • 12
  • If you want to go low level and don't mind connecting to X11 directly you can use https://github.com/sidorares/node-x11 and https://github.com/santigimeno/node-ewmh – Andrey Sidorov May 11 '17 at 06:52
  • Once Electron created the window, do you think it's possible to set X properties using one of these libraries ? – Alexandre Brach May 11 '17 at 11:05
  • yes, should be possible. Not sure if electron has some platform-specific helpers to get window id, if not you'll have to use XQueryTree and search yourself – Andrey Sidorov May 11 '17 at 23:39
  • You can set the property, but for the window manager to respect it you must set it before the window is mapped to the X server (i.e., before it becomes »visible«). If Electron has no hook for that (which is likely) you can workaround it by unmapping and mapping the window yourself again. – Ingo Bürk May 12 '17 at 08:19
  • Incidently I just wrote a little script for someone to turn a window into a dock. You can see the idea of un- and remapping the window in there as well. But it's not in node: https://gist.github.com/Airblader/d4fe0088b05f720fb4a3291cd9ec5e9a – Ingo Bürk May 12 '17 at 08:20
  • thanks to node-x11, I'm now able to retrieve the window id (using its name) and change the WM_NAME property. Is this library allow to set the _NET_WM_STRUT_PARTIAL property ? – Alexandre Brach May 16 '17 at 14:02

1 Answers1

2

This is a solution, it illustrate how to achieve this, the structure is quite unpretty.

This library will be used : https://github.com/sidorares/node-x11

const x11 = require( 'x11' );
var X;

Using electron, we create a browser window with an arbitrary window title.

mainWindow = new BrowserWindow(
    x: 0,
    y: 0,
    frame: false,
    type: 'dock',
    title: 'myTitle'
}

There is a limitation here : this window name must be unique because it will be used to retrieve the window id (that will be used to apply the property)

Two functions will be needed : to retrieve a window name using its id and to retrieve a window id using its name :

var getWindowName = function( wid ) {
    return new Promise( function( resolve, reject ) {
        X.InternAtom(false, '_NET_WM_NAME', function (wmNameErr, wmNameAtom) {
            X.InternAtom(false, 'UTF8_STRING', function (utf8Err, utf8Atom) {
                X.GetProperty(0, wid, wmNameAtom, utf8Atom, 0, 10000000, function(err, nameProp) {
                    if( err ) {
                      reject( err );
                    }
                    resolve( nameProp.data.toString() );
                });
            });
        });
    });
}


var getWindowId = function (name) {

    return new Promise( function( resolve, reject ) {
        x11.createClient(function( err, display ) {
          X = display.client;
          var root = display.screen[0].root;
          X.QueryTree(root, function(err, tree) {
              tree.children.map( function( id ) {
                  let prop = getWindowName( id ).then( function( n ) {
                      if( n === name ) {
                          resolve( id );
                      }
                  })
              } );
          }) 
        });
    });
}

To get the window id, we have to retrieve all windows from the root and loop on them. For each, the function compare their name with the searched one.

We assume that the getWindowId function will be called one time at the starting point so we instanciate the X client inside, but it should not be this way in a real app.

Finally, we need a function to set the _NET_WM_STRUT_PARTIAL property :

  var setStrutValues = function (wid, 
    left, right, top, bottom, 
    left_start_y, left_end_y, right_start_y, right_end_y, 
    top_start_x, top_end_x, bottom_start_x, bottom_end_x ) {

    var values = new Buffer( 4 * 12 );
    values.writeUInt32LE(left           ,0*4 );
    values.writeUInt32LE(right          ,1*4 );
    values.writeUInt32LE(top            ,2*4 );
    values.writeUInt32LE(bottom         ,3*4 );
    values.writeUInt32LE(left_start_y   ,4*4 );
    values.writeUInt32LE(left_end_y     ,5*4 );
    values.writeUInt32LE(right_start_y  ,6*4 );
    values.writeUInt32LE(right_end_y    ,7*4 );
    values.writeUInt32LE(top_start_x    ,8*4 );
    values.writeUInt32LE(top_end_x      ,9*4 );
    values.writeUInt32LE(bottom_start_x ,10*4 );
    values.writeUInt32LE(bottom_end_x   ,11*4 );

    X.InternAtom( false, '_NET_WM_STRUT_PARTIAL', function( err, strutAtom ) {
        X.ChangeProperty(0, wid, strutAtom, X.atoms.CARDINAL, 32, values) ;
    } );
  }

Using all of this, we can do :

getWindowId( 'myTitle' ).then( function( wid ) {
    setStrutValues( wid, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
} )
Alexandre Brach
  • 325
  • 2
  • 12