nwm.js | |
---|---|
| |
Modules | |
Native extension | var X11wm = require('./build/default/nwm.node').NodeWM;
var Collection = require('./lib/collection.js');
var Monitor = require('./lib/monitor.js');
var Window = require('./lib/window.js'); |
Node Window Manager | var NWM = function(binding) { |
A reference to the nwm C++ X11 binding | this.wm = (binding ? binding : new X11wm()); |
Known layous | this.layouts = {}; |
Keyboard shortcut lookup | this.shortcuts = []; |
monitors | this.monitors = new Collection(this, 'monitor', 0); |
windows -- this is the global storage for windows, any other objects just store ids referring to this hash. | this.windows = new Collection(this, 'window', 1);
this.floaters = [];
}
require('util').inherits(NWM, require('events').EventEmitter); |
Events | NWM.prototype.events = { |
Monitor eventsA new monitor is added | addMonitor: function(monitor) {
this.monitors.add(new Monitor(this, monitor));
this.monitors.current = monitor.id; |
TODO: When a monitor is added, check that the x coordinates represent a continuous space. Virtualbox incorrectly reports the starting x position even when it correctly detects the w/h of the monitor | }, |
A monitor is updated | updateMonitor: function(monitor) {
this.monitors.update(monitor.id, monitor);
}, |
A monitor is removed | removeMonitor: function(id) {
console.log('Remove monitor', id);
this.monitors.remove(function(monitor){ return (monitor.id != id); });
}, |
Window eventsA new window is added | addWindow: function(window) {
if(window.id) {
var monitor = this.monitors.get(this.monitors.current);
window.workspace = monitor.workspaces.current; |
ignore monitor number from binding as windows should open on the focused monitor | window.monitor = this.monitors.current;
var current_monitor = this.monitors.get(this.monitors.current);
if(current_monitor.focused_window == null) {
current_monitor.focused_window = window.id;
} |
do not add floating windows | if(window.isfloating) {
console.log('Ignoring floating window: ', window);
this.floaters.push(window.id);
return;
}
var window = new Window(this, window); |
windows might be placed outside the screen if the wm was terminated | if(window.x > monitor.width || window.y > monitor.height) {
window.move(1, 1);
}
console.log('Add window', window);
this.windows.add(window);
}
}, |
When a window is removed | removeWindow: function(id) {
this.windows.remove(function(window) { return (window.id != id); });
var pos = this.floaters.indexOf(id);
if(pos > -1) {
this.floaters = this.floaters.splice(pos, 1);
}
}, |
When a window is updated This is only triggered for title and class updates, never coordinates or monitors. | updateWindow: function(window) {
if(this.windows.exists(window.id)) {
var old = this.windows.get(window.id);
this.windows.update(window.id, {
title: window.title || old.title || '',
class: window.class || old.class || ''
});
}
}, |
When a window requests full screen mode | fullscreen: function(id, status) {
if(this.windows.exists(id)) {
var window = this.windows.get(id);
if(!this.monitors.exists(window.monitor)) { |
TODO handle this error, which occurs when a win was in fullscren and then the monitor was removed, then fullscreen is toggled back | return;
} |
use the monitor dimensions associated with the window | var monitor = this.monitors.get(window.monitor);
var workspace = monitor.workspaces.get(monitor.workspaces.current);
if(status) {
this.wm.moveWindow(id, monitor.x, monitor.y);
this.wm.resizeWindow(id, monitor.width, monitor.height);
} else {
workspace.rearrange();
}
}
}, |
ConfigureRequest is generated when a client window wants to change its size, stacking order or border width | configureRequest: function(ev){
console.log('configureRequest', ev);
this.wm.configureWindow(ev.id, ev.x, ev.y, ev.width, ev.height, ev.border_width,
ev.above, ev.detail, ev.value_mask);
return;
if(ev.id && this.windows.exists(ev.id)) { |
Check whether the window is known (e.g. managed and not floating) Known windows should not be allowed to reconfigure themselves. They should just be send back a ConfigureNotify() with the current info | console.log('denying configureRequest');
var window = this.windows.get(id);
this.wm.notifyWindow(ev.id, window.x, window.y, window.width, window.height, ev.border_width,
ev.above, ev.detail, ev.value_mask);
} else {
console.log('allowing configureRequest');
if(ev.id && this.floaters.indexOf(ev.id)) { |
If the window is floating, it should be moved and resized The size should be modifiable, but the floating window should be centered on the current monitor (or the monitor the floater is on, but we don't track that now) | var monitor = this.currentMonitor();
ev.x = monitor.x + ev.x;
ev.y = monitor.y + ev.y;
if ( (ev.x + ev.width) > monitor.x + monitor.width) {
ev.x = monitor.x + Math.floor(monitor.width / 2 - ev.width / 2);
}
if ( (ev.y + ev.heigth) > monitor.y + monitor.height) {
ev.y = monitor.y + Math.floor(monitor.height / 2 - ev.height / 2);
}
} |
Unknown windows should be passed through with a XConfigureWindow() | this.wm.configureWindow(ev.id, ev.x, ev.y, ev.width, ev.height, ev.border_width,
ev.above, ev.detail, ev.value_mask);
}
}, |
Mouse eventsA mouse button has been clicked | mouseDown: function(event) {
this.wm.focusWindow(event.id);
}, |
A mouse drag is in progress | mouseDrag: function(event) { |
move when drag is triggered | var change_x = event.move_x - event.x;
var change_y = event.move_y - event.y;
var window = this.windows.exists(event.id) && this.windows.get(event.id);
if(window) {
this.wm.moveWindow(event.id, window.x+change_x, window.y+change_y);
}
}, |
Mouse enters a window | enterNotify: function(event){
if(this.windows.exists(event.id)) {
var window = this.windows.get(event.id);
console.log('focused monitor is ', this.monitors.current, 'focusing to', window.monitor, window.title);
if(this.monitors.exists(window.monitor)) {
this.monitors.get(window.monitor).focused_window = event.id;
}
this.wm.focusWindow(event.id);
} else {
console.log('WARNING got focus event for nonexistent (transient) window', event); |
transients need this to happen to get keyboard focus | this.wm.focusWindow(event.id);
} |
This event is also emitted for the root window so in any case, we want to set the current monitor based on the event coordinates | var x = event.x_root || event.x;
var y = event.y_root || event.y;
console.log('Focus monitor by coordinates', x, y); |
go through the monitors and find a matching monitor | var monitor_ids = Object.keys(this.monitors.items);
var self = this;
monitor_ids.some(function(monid) {
var monitor = self.monitors.get(monid);
if(monitor.inside(x, y)) {
console.log('Change focused monitor: ', monid);
self.monitors.current = monid;
return true; // end iteration
}
return false; // continue iteration
});
}, |
Screen events | rearrange: function() {
var self = this; |
rearrange current workspace on all monitors | var monitors = Object.keys(this.monitors.items);
monitors.forEach(function(id) {
var monitor = self.monitors.get(id);
monitor.workspaces.get(monitor.workspaces.current).rearrange();
});
}, |
Keyboard eventsA key has been pressed | keyPress: function(event) {
console.log('keyPress', event, String.fromCharCode(event.keysym)); |
find the matching callback and emit it | this.shortcuts.forEach(function(shortcut) {
if(event.keysym == shortcut.key && event.modifier == shortcut.modifier ) {
shortcut.callback(event);
};
});
}
}; |
Layout operations | |
Register a new layout | NWM.prototype.addLayout = function(name, callback){
this.layouts[name] = callback;
}; |
Given the current layout, get the next layout (e.g. for switching layouts via keyboard shortcut) | NWM.prototype.nextLayout = function(name) {
var keys = Object.keys(this.layouts);
var pos = keys.indexOf(name); |
Wrap around the array | return (keys[pos+1] ? keys[pos+1] : keys[0] );
}; |
Keyboard shortcut operations | |
Add a new keyboard shortcut | NWM.prototype.addKey = function(keyobj, callback) {
this.shortcuts.push({ key: keyobj.key, modifier: keyobj.modifier, callback: callback });
}; |
Start the window manager | NWM.prototype.start = function(callback) {
var self = this; |
Initialize event handlers, bind this in the functions to nwm | Object.keys(this.events).forEach(function(eventname) {
self.wm.on(eventname, function() {
var args = Array.prototype.slice.call(arguments);
console.log('JS: ', eventname, args);
self.events[eventname].apply(self, args);
});
});
var grab_keys = [];
console.log(this.shortcuts);
this.shortcuts.forEach(function(shortcut) {
grab_keys.push( { key: shortcut.key, modifier: shortcut.modifier });
});
this.wm.keys(grab_keys);
this.wm.start();
if(callback) {
callback();
}
};
NWM.prototype.currentMonitor = function() {
return this.monitors.get(this.monitors.current);
};
if (module == require.main) {
console.log('Please run nwm via "node nwm-user-sample.js" (or some other custom config file).');
}
module.exports = NWM;
|