diff --git a/gnome/src/cursormanager.js b/gnome/src/cursormanager.js index 477432f..d037c01 100644 --- a/gnome/src/cursormanager.js +++ b/gnome/src/cursormanager.js @@ -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; } } \ No newline at end of file diff --git a/gnome/src/extension.js b/gnome/src/extension.js index ac1ed4d..1383d5c 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -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( diff --git a/gnome/src/virtualmonitorsactor.js b/gnome/src/virtualmonitorsactor.js index d7b3327..5a76ebb 100644 --- a/gnome/src/virtualmonitorsactor.js +++ b/gnome/src/virtualmonitorsactor.js @@ -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 = []; diff --git a/modules/XRLinuxDriver b/modules/XRLinuxDriver index 4f299ec..64c31bb 160000 --- a/modules/XRLinuxDriver +++ b/modules/XRLinuxDriver @@ -1 +1 @@ -Subproject commit 4f299ecc7da6d4cdfb164cbcb22f60580fb2295a +Subproject commit 64c31bb7f40f911130d69a7ca83ef9617c3d0e07