Fix memory consumption issue on window resize, update cursor cloning approach, fix ability to drag windows between workspaces on the launcher view

This commit is contained in:
wheaney 2025-02-27 13:58:54 -08:00
parent 9dad741cc8
commit 4d85db204b
4 changed files with 108 additions and 58 deletions

View File

@ -6,8 +6,7 @@ import Globals from './globals.js';
// Taken from https://github.com/jkitching/soft-brightness-plus
export class CursorManager {
constructor(mainActor, targetMonitors, refreshRate) {
this._mainActor = mainActor;
constructor(targetMonitors, refreshRate) {
this._targetMonitors = targetMonitors;
this._refreshRate = refreshRate;
@ -111,7 +110,6 @@ export class CursorManager {
// prereqs: setup in _enableCloningMouse
_startCloningMouse() {
Globals.logger.log_debug('CursorManager _startCloningMouse');
this._mainActor.add_child(this._cursorRoot);
this._updateMouseSprite();
this._cursorTracker.connectObject('cursor-changed', this._updateMouseSprite.bind(this), this);
@ -122,10 +120,6 @@ export class CursorManager {
this._cursorWatch = PointerWatcher.getPointerWatcher().addWatch(interval, this._updateMousePosition.bind(this));
this._updateMousePosition();
const [xMouse, yMouse] = global.get_pointer();
if (this._targetMonitors.some(monitor => this._isWithinMonitorBounds(xMouse, yMouse, monitor))) this._hideSystemCursor();
}
// After this:
@ -145,7 +139,6 @@ export class CursorManager {
if (this._mouseSprite?.content?.texture) this._mouseSprite.content.texture = null;
Meta.enable_unredirect_for_display(global.display);
if (this._cursorRoot) this._mainActor.remove_child(this._cursorRoot);
if (!this._systemCursorShown) this._showSystemCursor();
}
@ -169,23 +162,54 @@ export class CursorManager {
_updateMousePosition(...args) {
const [xMouse, yMouse] = args.length ? args : global.get_pointer();
const inBounds = this._targetMonitors.some(monitor => this._isWithinMonitorBounds(xMouse, yMouse, monitor));
let onMonitorIndex;
let xRel;
let yRel;
if (xMouse === this.xMouse && yMouse === this.yMouse)
return;
const inBoundsCheck = (monitorObj, index) => {
const inBoundsCoordinates = this._getInBoundsCoordinates(xMouse, yMouse, monitorObj.monitor);
if (inBoundsCoordinates) {
onMonitorIndex = index;
xRel = inBoundsCoordinates.xRel;
yRel = inBoundsCoordinates.yRel;
return true;
}
return false;
}
if (inBounds) {
if (this._cursorRoot && this._mainActor.get_last_child() !== this._cursorRoot)
this._mainActor.set_child_above_sibling(this._cursorRoot, null);
// check the previously in-bounds monitor first to avoid iterating over the whole list in the likely case that the cursor
// is still on the same monitor
if (this.onMonitorIndex === undefined || !inBoundsCheck(this._targetMonitors[this.onMonitorIndex], this.onMonitorIndex)) {
for (let i = 0; i < this._targetMonitors.length; i++) {
if (this.onMonitorIndex === i) continue;
if (inBoundsCheck(this._targetMonitors[i], i)) break;
}
}
if (this.onMonitorIndex !== onMonitorIndex) {
try {
if (this.onMonitorIndex !== undefined) this._targetMonitors[this.onMonitorIndex].actor.remove_child(this._cursorRoot);
this.onMonitorIndex = onMonitorIndex;
if (this.onMonitorIndex !== undefined) {
const actor = this._targetMonitors[this.onMonitorIndex].actor;
actor.add_child(this._cursorRoot);
actor.set_child_above_sibling(this._cursorRoot, null);
}
} catch (e) {
Globals.logger.log_debug(e);
}
}
if (this.onMonitorIndex !== undefined) {
if (this._systemCursorShown) this._hideSystemCursor();
this._cursorRoot.set_position(xMouse, yMouse);
} else if (!this._systemCursorShown && !inBounds) {
this._cursorRoot.set_position(xRel, yRel);
} else if (!this._systemCursorShown) {
this._showSystemCursor();
}
this.xMouse = xMouse;
this.yMouse = yMouse;
this.xRel = xRel;
this.xRel = xRel;
const seat = Clutter.get_default_backend().get_default_seat();
if (this._cursorUnfocusInhibited && !seat.is_unfocus_inhibited()) {
@ -214,8 +238,16 @@ export class CursorManager {
}
}
_isWithinMonitorBounds(x, y, monitor) {
return x >= monitor.x && x < monitor.x + monitor.width &&
y >= monitor.y && y < monitor.y + monitor.height;
_getInBoundsCoordinates(x, y, monitor) {
const xRel = x - monitor.x;
const yRel = y - monitor.y;
if (xRel >= 0 && xRel < monitor.width && yRel >= 0 && yRel < monitor.height) {
return {
xRel,
yRel,
}
}
return null;
}
}

View File

@ -250,9 +250,6 @@ export default class BreezyDesktopExtension extends Extension {
const virtualMonitors = this._find_virtual_monitors();
const refreshRate = targetMonitor.refreshRate ?? 60;
this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup, [targetMonitor, ...virtualMonitors], refreshRate);
this._cursor_manager.enable();
// use rgba(255, 4, 144, 1) for chroma key background
this._overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);', clip_to_allocation: true });
this._overlay.opacity = 255;
@ -285,16 +282,15 @@ export default class BreezyDesktopExtension extends Extension {
Shell.util_set_hidden_from_pick(this._overlay, true);
global.stage.add_child(this._overlay);
// In GS 45, use of "actor" was renamed to "child".
const clutterContainer = Clutter.Container !== undefined;
this._actor_added_connection = global.stage.connect(
clutterContainer ? 'actor-added' : 'child-added',
this._handle_sibling_update.bind(this),
);
this._actor_removed_connection = global.stage.connect(
clutterContainer ? 'actor-removed' : 'child-removed',
this._handle_sibling_update.bind(this),
);
const cursor_manager_monitor_objs = this._overlay_content.monitor_actors.map(monitor => {
return {
monitor: monitor.monitorDetails,
actor: monitor.containerActor
};
});
this._cursor_manager = new CursorManager(cursor_manager_monitor_objs, refreshRate);
this._cursor_manager.enable();
this._update_follow_threshold(this.settings);
@ -342,11 +338,6 @@ export default class BreezyDesktopExtension extends Extension {
}
}
_handle_sibling_update() {
Globals.logger.log_debug('BreezyDesktopExtension _handle_sibling_update()');
global.stage.set_child_above_sibling(this._overlay, null);
}
_add_settings_keybinding(settings_key, bind_to_function) {
try {
Main.wm.addKeybinding(

View File

@ -5,6 +5,7 @@ import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Mtk from 'gi://Mtk';
import Shell from 'gi://Shell';
import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import Globals from './globals.js';
@ -339,6 +340,12 @@ export const VirtualMonitorEffect = GObject.registerClass({
'Target and virtual monitor placement details, as relevant to rendering',
GObject.ParamFlags.READWRITE
),
'target-monitor': GObject.ParamSpec.jsobject(
'target-monitor',
'Target Monitor',
'Details about the monitor being used as a viewport',
GObject.ParamFlags.READWRITE
),
'imu-snapshots': GObject.ParamSpec.jsobject(
'imu-snapshots',
'IMU Snapshots',
@ -631,7 +638,11 @@ export const VirtualMonitorEffect = GObject.registerClass({
float cogl_position_width = cogl_position_mystery_factor * aspect_ratio / u_actor_to_display_ratios.y;
float cogl_position_height = cogl_position_width / aspect_ratio;
vec3 pos_factors = vec3(cogl_position_width / u_display_resolution.x, cogl_position_height / u_display_resolution.y, cogl_position_mystery_factor / u_display_resolution.x);
vec3 pos_factors = vec3(
cogl_position_width / u_display_resolution.x,
cogl_position_height / u_display_resolution.y,
cogl_position_mystery_factor / u_display_resolution.x
);
world_pos.x -= u_display_position.x * pos_factors.x;
world_pos.y -= u_display_position.y * pos_factors.y;
world_pos.z = u_display_position.z * pos_factors.z;
@ -697,7 +708,7 @@ export const VirtualMonitorEffect = GObject.registerClass({
);
this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix);
this.set_uniform_float(this.get_uniform_location("u_fov_vertical_radians"), 1, [fovVerticalRadians]);
this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.get_actor().width, this.get_actor().height]);
this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.target_monitor.width, this.target_monitor.height]);
this.set_uniform_float(this.get_uniform_location("u_look_ahead_cfg"), 4, Globals.data_stream.device_data.lookAheadCfg);
this.set_uniform_float(this.get_uniform_location("u_actor_to_display_ratios"), 2, this.actor_to_display_ratios);
this.set_uniform_float(this.get_uniform_location("u_actor_to_display_offsets"), 2, this.actor_to_display_offsets);
@ -771,6 +782,12 @@ export const VirtualMonitorsActor = GObject.registerClass({
'Target and virtual monitor placement details, as relevant to rendering',
GObject.ParamFlags.READWRITE
),
'monitor-actors': GObject.ParamSpec.jsobject(
'monitor-actors',
'Monitor Actors',
'Tracking actors and details for each monitor',
GObject.ParamFlags.READWRITE
),
'imu-snapshots': GObject.ParamSpec.jsobject(
'imu-snapshots',
'IMU Snapshots',
@ -910,13 +927,14 @@ export const VirtualMonitorsActor = GObject.registerClass({
);
this.bannerActor.set_content(this.custom_banner_enabled ? this.customBannerContent : this.bannerContent);
this.bannerActor.hide();
this.monitor_actors = [];
}
renderMonitors() {
// collect bindings and connections to clean up on dispose
this._property_bindings = [];
this._property_connections = [];
this._monitor_actors = [];
const notifyToFunction = ((property, fn) => {
this._property_connections.push(this.connect(`notify::${property}`, fn.bind(this)));
@ -956,17 +974,21 @@ export const VirtualMonitorsActor = GObject.registerClass({
Globals.logger.log_debug(`\t\t\tMonitor ${index}: ${monitor.x}, ${monitor.y}, ${monitor.width}, ${monitor.height}`);
const containerActor = new Clutter.Actor({
width: this.target_monitor.width,
height: this.target_monitor.height
clip_to_allocation: true
});
const viewport = new St.Bin({
child: containerActor,
width: monitor.width,
height: monitor.height
});
// Create a clone of the stage content for this monitor
const monitorClone = new Clutter.Clone({
source: Main.layoutManager.uiGroup,
clip_to_allocation: true,
x: -monitor.x,
y: -monitor.y
});
monitorClone.set_clip(monitor.x, monitor.y, monitor.width, monitor.height);
// Add the monitor actor to the scene
containerActor.add_child(monitorClone);
@ -975,6 +997,7 @@ export const VirtualMonitorsActor = GObject.registerClass({
imu_snapshots: this.imu_snapshots,
monitor_index: index,
monitor_placements: this.monitor_placements,
target_monitor: this.target_monitor,
display_distance: this.display_distance,
display_distance_default: this._display_distance_default(),
actor_to_display_ratios: actorToDisplayRatios,
@ -982,17 +1005,20 @@ export const VirtualMonitorsActor = GObject.registerClass({
lens_vector: this.lens_vector,
show_banner: this.show_banner
});
containerActor.add_effect_with_name('viewport-effect', effect);
this.add_child(containerActor);
viewport.add_effect_with_name('viewport-effect', effect);
this.add_child(viewport);
Shell.util_set_hidden_from_pick(viewport, true);
this._monitor_actors.push({
this.monitor_actors.push({
viewport,
containerActor,
monitorClone,
effect
effect,
monitorDetails: monitor
});
// do this so the primary monitor is always on top at first, before the focused monitor logic comes into play
this.set_child_below_sibling(containerActor, null);
this.set_child_below_sibling(viewport, null);
[
'monitor-placements',
@ -1018,7 +1044,7 @@ export const VirtualMonitorsActor = GObject.registerClass({
// in addition to rendering distance properly in the shader, the parent actor determines overlap based on child ordering
effect.connect('notify::is-closest', ((actor, _pspec) => {
if (!this._is_disposed && actor.is_closest) {
this.set_child_above_sibling(containerActor, null);
this.set_child_above_sibling(viewport, null);
if (this.show_banner) this.set_child_above_sibling(this.bannerActor, null);
}
}).bind(this));
@ -1063,7 +1089,7 @@ export const VirtualMonitorsActor = GObject.registerClass({
Globals.data_stream.refresh_data();
this.imu_snapshots = Globals.data_stream.imu_snapshots;
this.queue_redraw();
this.monitor_actors.forEach(({ monitorClone }) => monitorClone.queue_redraw());
this._last_redraw = Date.now();
}).bind(this));
this._redraw_timeline.set_repeat_count(-1);
@ -1080,7 +1106,7 @@ export const VirtualMonitorsActor = GObject.registerClass({
const fovVerticalRadians = Math.atan(Math.tan(fovVerticalRadiansInitial) / this._display_distance_default());
// distance needed for the FOV-sized monitor to fill up the screen
const fullScreenDistance = this.target_monitor.height / 2 / Math.sin(fovVerticalRadians / 2);
const fullScreenDistance = this.target_monitor.height / 2 / Math.tan(fovVerticalRadians / 2);
return {
widthPixels: this.target_monitor.width,
@ -1172,12 +1198,13 @@ export const VirtualMonitorsActor = GObject.registerClass({
this._redraw_timeline = null;
}
this._monitor_actors.forEach(({ containerActor, monitorClone, effect }) => {
containerActor.remove_effect(effect);
this.monitor_actors.forEach(({ viewport, containerActor, monitorClone, effect }) => {
viewport.remove_effect(effect);
containerActor.remove_child(monitorClone);
this.remove_child(containerActor);
viewport.remove_child(containerActor);
this.remove_child(viewport);
});
this._monitor_actors = [];
this.monitor_actors = [];
this._property_bindings.forEach(binding => binding.unbind());
this._property_bindings = [];

@ -1 +1 @@
Subproject commit 4f299ecc7da6d4cdfb164cbcb22f60580fb2295a
Subproject commit 64c31bb7f40f911130d69a7ca83ef9617c3d0e07