breezy-desktop/gnome/src/cursormanager.js

257 lines
8.8 KiB
JavaScript

import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta';
import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js';
import { MouseSpriteContent } from './cursor.js';
import Globals from './globals.js';
// Taken from https://github.com/jkitching/soft-brightness-plus
export class CursorManager {
constructor(targetMonitors, refreshRate) {
this._targetMonitors = targetMonitors;
this._refreshRate = refreshRate;
// Set/destroyed by _enableCloningMouse/_disableCloningMouse
this._cursorTracker = null;
this._mouseSprite = null;
this._cursorRoot = null;
this._cursorUnfocusInhibited = false;
// Set/destroyed by _startCloningMouse / _stopCloningMouse
this._cursorWatch = null;
this._cursorChangedConnection = null;
this._systemCursorShown = true;
}
enable() {
Globals.logger.log_debug('CursorManager enable');
this._enableCloningMouse();
this.startCloning();
}
disable() {
Globals.logger.log_debug('CursorManager disable');
this._disableCloningMouse();
}
startCloning() {
Globals.logger.log_debug('CursorManager startCloning');
this._startCloningMouse();
}
stopCloning() {
Globals.logger.log_debug('CursorManager stopCloning');
this._stopCloningMouse();
}
// After this:
// * real cursor is disabled
// * cloning is "on"
// * cloned cursor not visible, but ready for _startCloningMouse to make it visible
//
// okay if _startCloningMouse is not immediately called since set_pointer_visible is bound to our replacement function
// and will trigger _startCloningMouse when the cursor should be shown
_enableCloningMouse() {
Globals.logger.log_debug('CursorManager _enableCloningMouse');
this._cursorTracker = global.backend.get_cursor_tracker?.() ?? Meta.CursorTracker.get_for_display(global.display);
this._mouseSprite = new Clutter.Actor({ request_mode: Clutter.RequestMode.CONTENT_SIZE });
this._mouseSprite.content = new MouseSpriteContent();
this._cursorRoot = new Clutter.Actor();
this._cursorRoot.add_child(this._mouseSprite);
}
_backend() {
return global.stage.get_context?.().get_backend() ?? Clutter.get_default_backend();
}
_hideSystemCursor() {
this._systemCursorShown = false;
this._cursorRoot.show();
if (!this._cursorUnfocusInhibited) {
this._backend().get_default_seat().inhibit_unfocus();
this._cursorUnfocusInhibited = true;
}
if (!this._cursorVisibilityChangedId) {
this._cursorTracker.set_pointer_visible(false);
this._cursorVisibilityChangedId = this._cursorTracker.connect('visibility-changed', (() => {
if (this._cursorTracker.get_pointer_visible())
this._cursorTracker.set_pointer_visible(false);
}).bind(this));
}
}
// After this:
// * real cursor enabled, manages its own visibility
// * cloning is "off"
// * no cloned cursor
//
// completely reverts _enableCloningMouse
_disableCloningMouse() {
Globals.logger.log_debug('CursorManager _disableCloningMouse');
this._stopCloningMouse();
if (this._mouseSprite) {
this._mouseSprite.content = null;
if (this._cursorRoot) this._cursorRoot.remove_child(this._mouseSprite);
}
this._cursorTracker = null;
this._mouseSprite = null;
this._cursorRoot = null;
}
// After this:
// * real cursor is hidden
// * cloning is "on"
// * clone cursor is visible
//
// add the clone cursor actor, watch for pointer movement and cursor changes, reflect them in the cloned cursor
// prereqs: setup in _enableCloningMouse
_startCloningMouse() {
Globals.logger.log_debug('CursorManager _startCloningMouse');
this._updateMouseSprite();
this._cursorTracker.connectObject('cursor-changed', this._updateMouseSprite.bind(this), this);
Meta.Compositor?.disable_unredirect?.() ?? Meta.disable_unredirect_for_display(global.display);
// cap the refresh rate for performance reasons
const interval = 1000.0 / Math.min(this._refreshRate, 60);
this._cursorWatch = PointerWatcher.getPointerWatcher().addWatch(interval, this._updateMousePosition.bind(this));
this._updateMousePosition();
}
// After this:
// * real cursor is hidden
// * cloning is "on"
// * cloned cursor not visible, but ready for _startCloningMouse to make it visible
//
// completely reverts _startCloningMouse
_stopCloningMouse() {
Globals.logger.log_debug('CursorManager _stopCloningMouse');
if (this._cursorWatch != null) {
this._cursorWatch.remove();
this._cursorWatch = null;
}
if (this._cursorTracker) this._cursorTracker.disconnectObject(this);
if (this._mouseSprite?.content?.texture) this._mouseSprite.content.texture = null;
Meta.Compositor?.enable_unredirect?.() ?? Meta.enable_unredirect_for_display(global.display);
if (!this._systemCursorShown) this._showSystemCursor();
}
_showSystemCursor() {
this._systemCursorShown = true;
if (this._cursorRoot) this._cursorRoot.hide();
if (this._cursorUnfocusInhibited) {
this._backend().get_default_seat().uninhibit_unfocus();
this._cursorUnfocusInhibited = false;
}
if (this._cursorVisibilityChangedId) {
this._cursorTracker.disconnect(this._cursorVisibilityChangedId);
delete this._cursorVisibilityChangedId;
this._cursorTracker.set_pointer_visible(true);
}
}
_updateMousePosition(...args) {
const [xMouse, yMouse] = args.length ? args : global.get_pointer();
let onMonitorIndex;
let xRel;
let yRel;
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;
}
// 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(xRel, yRel);
} else if (!this._systemCursorShown) {
this._showSystemCursor();
}
this.xRel = xRel;
this.xRel = xRel;
const seat = this._backend().get_default_seat();
if (this._cursorUnfocusInhibited && !seat.is_unfocus_inhibited()) {
Globals.logger.log_debug('reinhibiting');
seat.inhibit_unfocus();
}
}
_updateMouseSprite() {
this._updateSpriteTexture();
let [xHot, yHot] = this._cursorTracker.get_hot();
this._mouseSprite.set({
translation_x: -xHot,
translation_y: -yHot,
});
}
_updateSpriteTexture() {
let sprite = this._cursorTracker.get_sprite();
if (sprite) {
this._mouseSprite.content.texture = sprite;
this._mouseSprite.show();
} else {
this._mouseSprite.hide();
}
}
_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;
}
}