PaperWM/utils.js
Ole Jørgen Brønner ecb1e5a6ca Support vertical tiling resizing
`Space.layout` not accepts custom "column allocators" (per column)

An allocator allocates the available vertical space of the column.

The default column layout allocates the vertical space proportionally to the
member's existing height, but strives to preserve the height of the selected
window.

During a grab the grabWindow's geometry is never changed (as this is very
jarring for the user) and the column is laid out accordingly.

The layout triggered from grabEnd cleans things up (eg. if the grab-resize freed
up space not possible to allocate without moving the grab window)

Pending changes/cleanups:
- Rewrite `deferLayout` hack

Resolves #62
2018-12-30 23:58:37 +01:00

262 lines
7.1 KiB
JavaScript

const Extension = imports.misc.extensionUtils.extensions['paperwm@hedning:matrix.org']
const Gdk = imports.gi.Gdk;
var GLib = imports.gi.GLib;
var Meta = imports.gi.Meta;
var workspaceManager = global.workspace_manager;
var display = global.display;
var debug_all = false; // Turn off by default
var debug_filter = {'#paperwm': true, '#stacktrace': true};
function debug() {
let keyword = arguments[0];
let filter = debug_filter[keyword];
if (filter === false)
return;
if (debug_all || filter === true)
print(Array.prototype.join.call(arguments, " | "));
}
function warn(...args) {
print("WARNING:", ...args);
}
function assert(condition, message, ...dump) {
if (!condition) {
throw new Error(message + "\n", dump);
}
}
function withTimer(message, fn) {
let start = GLib.get_monotonic_time();
let ret = fn();
let stop = GLib.get_monotonic_time();
log(`${message} ${((stop - start)/1000).toFixed(1)}ms`);
}
function print_stacktrace(error) {
let trace;
if (!error) {
trace = (new Error()).stack.split("\n");
// Remove _this_ frame
trace.splice(0, 1);
} else {
trace = error.stack.split("\n");
}
// Remove some uninteresting frames
let filtered = trace.filter((frame) => {
return frame !== "wrapper@resource:///org/gnome/gjs/modules/lang.js:178";
});
log(`JS ERROR: ${error}\n ${trace.join('\n')}`);
}
function framestr(rect) {
return "[ x:"+rect.x + ", y:" + rect.y + " w:" + rect.width + " h:"+rect.height + " ]";
}
/**
* Returns a human-readable enum value representation
*/
function ppEnumValue(value, genum) {
let entry = Object.entries(genum).find(([k, v]) => v === value);
if (entry) {
return `${entry[0]} (${entry[1]})`
} else {
return `<not-found> (${value})`
}
}
/**
* Look up the function by name at call time. This makes it convenient to
* redefine the function without re-registering all signal handler, keybindings,
* etc. (this is like a function symbol in lisp)
*/
function dynamic_function_ref(handler_name, owner_obj) {
owner_obj = owner_obj || window;
return function() {
owner_obj[handler_name].apply(this, arguments);
}
}
function swap(array, i, j) {
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
function in_bounds(array, i) {
return i >= 0 && i < array.length;
}
function isPointInsideActor(actor, x, y) {
return (actor.x <= x && x <= actor.x+actor.width)
&& (actor.y <= y && y <= actor.y+actor.height);
}
//// Debug and development utils
const Tiling = Extension.imports.tiling;
function setDevGlobals() {
// Accept the risk of this interfering with existing code for now
metaWindow = display.focus_window;
meta_window = display.focus_window;
workspace = workspaceManager.get_active_workspace();
actor = metaWindow.get_compositor_private();
space = Tiling.spaces.spaceOfWindow(metaWindow);
}
/**
* Visualize the frame and buffer bounding boxes of a meta window
*/
function toggleWindowBoxes(metaWindow) {
metaWindow = metaWindow || display.focus_window;
if(metaWindow._paperDebugBoxes) {
metaWindow._paperDebugBoxes.forEach(box => {
box.destroy();
});
delete metaWindow._paperDebugBoxes;
return [];
}
let frame = metaWindow.get_frame_rect()
let inputFrame = metaWindow.get_buffer_rect()
let actor = metaWindow.get_compositor_private();
makeFrameBox = function({x, y, width, height}, color) {
let frameBox = new imports.gi.St.Widget();
frameBox.set_position(x, y)
frameBox.set_size(width, height)
frameBox.set_style("border: 2px" + color + " solid");
return frameBox;
}
let boxes = [];
boxes.push(makeFrameBox(frame, "red"));
boxes.push(makeFrameBox(inputFrame, "blue"));
if(inputFrame.x !== actor.x || inputFrame.y !== actor.y
|| inputFrame.width !== actor.width || inputFrame.height !== actor.height) {
boxes.push(makeFrameBox(actor, "yellow"));
}
boxes.forEach(box => global.stage.add_actor(box));
metaWindow._paperDebugBoxes = boxes;
return boxes;
}
var markNewClonesSignalId = null;
function toggleCloneMarks() {
// NB: doesn't clean up signal on disable
function markCloneOf(metaWindow) {
if (metaWindow.clone)
metaWindow.clone.opacity = 190;
}
function unmarkCloneOf(metaWindow) {
if (metaWindow.clone)
metaWindow.clone.opacity = 255;
}
let windows = display.get_tab_list(Meta.TabList.NORMAL_ALL, null);
if (markNewClonesSignalId) {
display.disconnect(markNewClonesSignalId);
markNewClonesSignalId = null;
windows.forEach(unmarkCloneOf);
} else {
markNewClonesSignalId = display.connect_after(
"window-created", (_, mw) => markCloneOf(mw))
windows.forEach(markCloneOf);
}
}
function sum(array) {
return array.reduce((a, b) => a + b, 0);
}
function zip(...as) {
let r = [];
let minLength = Math.min(...as.map(x => x.length));
for (let i = 0; i < minLength; i++) {
r.push(as.map(a => a[i]));
}
return r;
}
function setWorkspaceName(name, workspace) {
let i;
if (workspace === undefined) {
i = workspaceManager.get_active_workspace_index();
} else {
i = workspace.index();
}
let settings = new Gio.Settings({ schema_id:
'org.gnome.desktop.wm.preferences'});
let names = settings.get_strv('workspace-names');
let oldName = names[i];
names[i] = name;
settings.set_strv('workspace-names', names);
return oldName;
}
function isOverrideRedirectWindow(metaWindow) {
// https://gitlab.gnome.org/GNOME/mutter/blob/master/src/core/window.c#L2246 (override redirect types:)
switch(metaWindow.window_type) {
case Meta.WindowType.DROPDOWN_MENU:
case Meta.WindowType.POPUP_MENU:
case Meta.WindowType.TOOLTIP:
case Meta.WindowType.NOTIFICATION:
case Meta.WindowType.COMBO:
case Meta.WindowType.DND:
case Meta.WindowType.OVERRIDE_OTHER:
return true;
default:
return false;
}
}
function warpPointer(x, y) {
let display = Gdk.Display.get_default();
let deviceManager = display.get_device_manager();
let pointer = deviceManager.get_client_pointer();
pointer.warp(Gdk.Screen.get_default(), x, y)
}
class Signals extends Map {
static get [Symbol.species]() { return Map; }
connect(object, signal, handler) {
let signals = this.get(object);
if (!signals) {
signals = [];
this.set(object, signals);
}
let id = object.connect(signal, handler);
signals.push(id);
return id;
}
disconnect(object) {
let ids = this.get(object);
if (ids) {
ids.forEach(id => object.disconnect(id));
this.delete(object);
}
}
destroy() {
for (let [object, signals] of this) {
signals.forEach(id => object.disconnect(id));
this.delete(object);
}
}
}