Search code examples
javascriptnode.jselectronx11xmonad

How to set X properties in order to create a desktop status bar with electron?


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 ?


Solution

  • 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 );
    } )