Skip to content

Commit

Permalink
fix(tiling): Improve display update behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
mmstick committed Jan 5, 2021
1 parent c97b9b2 commit 90585cb
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 85 deletions.
253 changes: 169 additions & 84 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class Ext extends Ecs.System<ExtEvent> {
row_size: number = 32;

/** The known display configuration, for tracking monitor removals and changes */
displays: Map<number, Display> = new Map();
displays: [number, Map<number, Display>] = [global.display.get_primary_monitor(), new Map()];

/** The current scaling factor in GNOME Shell */
dpi: number = St.ThemeContext.get_for_stage(global.stage).scale_factor;
Expand Down Expand Up @@ -145,6 +145,8 @@ export class Ext extends Ecs.System<ExtEvent> {

was_locked: boolean = false;

private workareas_update: null | SignalID = null

/** Record of misc. global objects and their attached signals */
private signals: Map<GObject.Object, Array<SignalID>> = new Map();

Expand Down Expand Up @@ -542,30 +544,48 @@ export class Ext extends Ecs.System<ExtEvent> {

find_monitor_to_retach(width: number, height: number): [number, Display] {
if (!this.settings.workspaces_only_on_primary()) {
for (const [index, display] of this.displays) {
for (const [index, display] of this.displays[1]) {
if (display.area.width == width && display.area.height == height) {
return [index, display];
}
}
}

const primary = display.get_primary_monitor();
return [primary, this.displays.get(primary) as Display];
return [primary, this.displays[1].get(primary) as Display];
}

find_unused_workspace(monitor: number): [number, any] {
if (!this.auto_tiler) return [0, wom.get_workspace_by_index(0)]

let id = 0

for (const fork of this.auto_tiler.forest.forks.values()) {
if (fork.monitor === monitor && id < fork.workspace) id = fork.workspace
const tiled_windows = new Array<Window.ShellWindow>()

for (const [window] of this.auto_tiler.attached.iter()) {
if (!this.auto_tiler.attached.contains(window)) continue

const win = this.windows.get(window)

if (win && !win.reassignment && win.meta.get_monitor() === monitor) tiled_windows.push(win)
}

cancel:
while (true) {
for (const window of tiled_windows) {
if (window.workspace_id() === id) {
id += 1
continue cancel
}
}

break
}

id += 1
let new_work

if (id === wom.get_n_workspaces()) {
if (id + 1 === wom.get_n_workspaces()) {
id += 1
new_work = wom.append_new_workspace(true, global.get_current_time())
} else {
new_work = wom.get_workspace_by_index(id)
Expand Down Expand Up @@ -599,7 +619,7 @@ export class Ext extends Ecs.System<ExtEvent> {
}

monitor_work_area(monitor: number): Rectangle {
const meta = display.get_workspace_manager()
const meta = wom
.get_active_workspace()
.get_work_area_for_monitor(monitor);

Expand Down Expand Up @@ -1458,7 +1478,7 @@ export class Ext extends Ecs.System<ExtEvent> {
return true;
});

const workspace_manager = display.get_workspace_manager();
const workspace_manager = wom;

for (const [, ws] of iter_workspaces(workspace_manager)) {
let index = ws.index();
Expand Down Expand Up @@ -1794,7 +1814,7 @@ export class Ext extends Ecs.System<ExtEvent> {
}
}

update_display_configuration(_workareas_only: boolean) {
update_display_configuration(workareas_only: boolean) {
if (!this.auto_tiler || sessionMode.isLocked) return

if (this.ignore_display_update) {
Expand All @@ -1805,7 +1825,49 @@ export class Ext extends Ecs.System<ExtEvent> {
// Ignore the update if there are no monitors to assign to
if (layoutManager.monitors.length === 0) return

if (this.displays_updating !== null) GLib.source_remove(this.displays_updating)
const primary_display = global.display.get_primary_monitor()

const primary_display_ready = (ext: Ext): boolean => {
const area = global.display.get_monitor_geometry(primary_display)
const work_area = ext.monitor_work_area(primary_display)

if (!area || !work_area) return false

return !(area.width === work_area.width && area.height === work_area.height)
}

function displays_ready(): boolean {
const monitors = global.display.get_n_monitors()

if (monitors === 0) return false

for (let i = 0; i < monitors; i += 1) {
const display = global.display.get_monitor_geometry(i)

if (!display) return false

if (display.width < 1 || display.height < 1) return false
}

return true
}

if (!displays_ready() || !primary_display_ready(this)) {
if (this.displays_updating !== null) return
if (this.workareas_update !== null) GLib.source_remove(this.workareas_update)

this.workareas_update = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
this.register_fn(() => {
this.update_display_configuration(workareas_only)
})

this.workareas_update = null

return false
})

return
}

// Update every tree on each display with the new dimensions
const update_tiling = () => {
Expand All @@ -1814,28 +1876,82 @@ export class Ext extends Ecs.System<ExtEvent> {
for (const f of this.auto_tiler.forest.forks.values()) {
if (!f.is_toplevel) continue

const display = this.displays.get(f.monitor);
const display = this.monitor_work_area(f.monitor)

if (display) {
f.set_area(display.ws)
const area = new Rect.Rectangle([display.x, display.y, display.width, display.height])

f.smart_gapped = false
f.set_area(area.clone());
this.auto_tiler.update_toplevel(this, f, f.monitor, this.settings.smart_gaps());
}
}
}

let updated = new Map()
let changes = new Map()
let migrations: Array<[Fork, number, Rectangle, boolean]> = new Array()

const apply_migrations = (assigned_monitors: Set<number>) => {
if (!migrations) return

const iterator = migrations[Symbol.iterator]()

GLib.timeout_add(GLib.PRIORITY_LOW, 500, () => {
let next: null | [Fork, number, Rectangle, boolean] = iterator.next().value;

if (next) {
const [fork, new_monitor, workspace, find_workspace] = next
let new_workspace

if (find_workspace) {
if (assigned_monitors.has(new_monitor)) {
[new_workspace] = this.find_unused_workspace(new_monitor)
} else {
assigned_monitors.add(new_monitor)
new_workspace = 0
}
} else {
new_workspace = fork.workspace
}

fork.migrate(this, forest, workspace, new_monitor, new_workspace);
fork.set_ratio(fork.length() / 2)

return true
}

update_tiling()

return false
})
}

function mark_for_reassignment(ext: Ext, fork: Ecs.Entity) {
for (const win of forest.iter(fork, node.NodeKind.WINDOW)) {
if (win.inner.kind === 2) {
const entity = win.inner.entity
const window = ext.windows.get(entity)
if (window) window.reassignment = true
}
}
}

const [ old_primary, old_displays ] = this.displays

const changes = new Map<number, number>()

// Records which display's windows were moved to what new display's ID
for (const [entity, w] of this.windows.iter()) {
if (!w.actor_exists()) continue

this.monitors.with(entity, ([mon,]) => {
changes.set(mon, w.meta.get_monitor())
const assignment = mon === old_primary ? primary_display : w.meta.get_monitor()
changes.set(mon, assignment)
})
}

// Fetch a new list of monitors
const updated = new Map()

for (const monitor of layoutManager.monitors) {
const mon = monitor as Monitor

Expand All @@ -1845,44 +1961,39 @@ export class Ext extends Ecs.System<ExtEvent> {
updated.set(mon.index, { area, ws })
}

function compare_maps<K, V>(map1: Map<K, V>, map2: Map<K, V>) {
if (map1.size !== map2.size) {
return false
}
const forest = this.auto_tiler.forest

let cmp
if (old_displays.size === updated.size) {
update_tiling()

for (let [key, val] of map1) {
cmp = map2.get(key)
if (cmp !== val || (cmp === undefined && !map2.has(key))) {
return false
}
}
this.displays = [primary_display, updated]

return
}

return true
this.displays = [primary_display, updated]

if (utils.map_eq(old_displays, updated)) {
return
}

if (this.displays_updating !== null) GLib.source_remove(this.displays_updating)

if (this.workareas_update !== null) {
GLib.source_remove(this.workareas_update)
this.workareas_update = null
}

// Delay actions until 3 seconds later, in case of temporary connection loss
// Delay actions in case of temporary connection loss
this.displays_updating = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 2000, () => {
(() => {
if (!this.auto_tiler) return

if (compare_maps(this.displays, updated)) {
return
}

this.displays = updated

const forest = this.auto_tiler.forest

let migrations: Array<[Fork, number, Rectangle, boolean]> = new Array()
let toplevels = new Array()
let assigned_monitors = new Set()
const toplevels = new Array()
const assigned_monitors = new Set<number>()

for (const [old_mon, new_mon] of changes) {
if (old_mon === new_mon) {
assigned_monitors.add(new_mon)
}
if (old_mon === new_mon) assigned_monitors.add(new_mon)
}

for (const f of forest.forks.values()) {
Expand All @@ -1891,61 +2002,35 @@ export class Ext extends Ecs.System<ExtEvent> {

let migration: null | [Fork, number, Rectangle, boolean] = null;

for (const [old_monitor, new_monitor] of changes) {
if (old_monitor === new_monitor) continue
const displays = this.displays[1]

if (f.monitor === old_monitor) {
const display = this.displays.get(new_monitor)
for (const [old_monitor, new_monitor] of changes) {
const display = displays.get(new_monitor)

if (display) {
f.monitor = new_monitor
f.workspace = 0
migration = [f, new_monitor, display.ws, true]
}
if (!display) continue

break
if (f.monitor === old_monitor) {
f.monitor = new_monitor
f.workspace = 0
migration = [f, new_monitor, display.ws, true]
}
}

if (!migration) {
const display = this.displays.get(f.monitor)
const display = displays.get(f.monitor)
if (display) {
migration = [f, f.monitor, display.ws, false]
}
}

if (migration) migrations.push(migration)
}
}

let iterator = migrations[Symbol.iterator]()

GLib.timeout_add(GLib.PRIORITY_LOW, 500, () => {
let next: null | [Fork, number, Rectangle, boolean] = iterator.next().value;

if (next) {
const [fork, new_monitor, workspace, find_workspace] = next
let new_workspace

if (find_workspace) {
if (assigned_monitors.has(new_monitor)) {
[new_workspace] = this.find_unused_workspace(new_monitor)
} else {
assigned_monitors.add(new_monitor)
new_workspace = 0
}
} else {
new_workspace = fork.workspace
if (migration) {
mark_for_reassignment(this, migration[0].entity)
migrations.push(migration)
}

fork.migrate(this, forest, workspace, new_monitor, new_workspace);
return true
}
}

update_tiling()

return false
})
apply_migrations(assigned_monitors)

return
})()
Expand Down Expand Up @@ -2053,7 +2138,7 @@ export class Ext extends Ecs.System<ExtEvent> {

/** Fetch a workspace by its index */
workspace_by_id(id: number): Meta.Workspace | null {
return display.get_workspace_manager().get_workspace_by_index(id);
return wom.get_workspace_by_index(id);
}

workspace_id(window: Window.ShellWindow | null = null): [number, number] {
Expand Down
1 change: 1 addition & 0 deletions src/fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export class Fork {
let window = ext.windows.get(child.inner.entity);
if (window) {
ext.size_signals_block(window);
window.reassignment = false
window.known_workspace = workspace
window.meta.change_workspace_by_index(workspace, true)
ext.monitors.insert(window.entity, [monitor, workspace])
Expand Down

0 comments on commit 90585cb

Please sign in to comment.