257 lines
8.8 KiB
JavaScript
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;
|
|
}
|
|
} |