diff --git a/Settings.ui b/Settings.ui index 1719fefb1..65aa3e641 100644 --- a/Settings.ui +++ b/Settings.ui @@ -277,6 +277,43 @@ + + + False + False + + + False + Monitor focus/activation follows mouse movement + 12 + 12 + 6 + 6 + 32 + + + False + 1 + Monitor focus follows mouse + 0 + + 0 + 0 + + + + + + + 1 + 0 + + + + + + + diff --git a/grab.js b/grab.js index 977f3bf37..f425267ca 100644 --- a/grab.js +++ b/grab.js @@ -8,23 +8,6 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import { Settings, Utils, Tiling, Navigator, Scratch } from './imports.js'; import { Easer } from './utils.js'; -/** - * Returns a virtual pointer (i.e. mouse) device that can be used to - * "clickout" of a drag operation when `grab_end_op` is unavailable - * (i.e. as of Gnome 44 where `grab_end_op` was removed). - * @returns Clutter.VirtualInputDevice -*/ -let virtualPointer; -export function getVirtualPointer() { - if (!virtualPointer) { - virtualPointer = Clutter.get_default_backend() - .get_default_seat() - .create_virtual_device(Clutter.InputDeviceType.POINTER_DEVICE); - } - - return virtualPointer; -} - export class MoveGrab { constructor(metaWindow, type, space) { this.window = metaWindow; @@ -38,6 +21,8 @@ export class MoveGrab { // save whether this was tiled window at start of grab this.wasTiled = !(this.initialSpace.isFloating(metaWindow) || Scratch.isScratchWindow(metaWindow)); + + this.dndTargets = []; } begin({ center } = {}) { @@ -286,11 +271,12 @@ export class MoveGrab { }; if (!sameTarget(target, this.dndTarget)) { - // deactivate only if target exists + // has a new zone target if (target) { - this.deactivateDndTarget(this.dndTarget); - this.activateDndTarget(target, initial); + this.dndTargets.push(target); } + this.dndTarget = null; + this.activateDndTarget(target, initial); } } @@ -369,7 +355,7 @@ export class MoveGrab { if (dndTarget) { let space = dndTarget.space; - space.selection.show(); + space.showSelection(); if (Scratch.isScratchWindow(metaWindow)) Scratch.unmakeScratch(metaWindow); @@ -411,6 +397,7 @@ export class MoveGrab { metaWindow.move_frame(true, clone.x, clone.y); Scratch.makeScratch(metaWindow); this.initialSpace.moveDone(); + this.initialSpace.showSelection(); actor.set_scale(clone.scale_x, clone.scale_y); actor.opacity = clone.opacity; @@ -480,21 +467,15 @@ export class MoveGrab { */ Utils.later_add(Meta.LaterType.IDLE, () => { if (!global.display.end_grab_op && this.wasTiled) { - // move to current cursor position - let [x, y, _mods] = global.get_pointer(); - getVirtualPointer().notify_absolute_motion( - Clutter.get_current_event_time(), - x, y); - - getVirtualPointer().notify_button(Clutter.get_current_event_time(), - Clutter.BUTTON_PRIMARY, Clutter.ButtonState.PRESSED); - getVirtualPointer().notify_button(Clutter.get_current_event_time(), - Clutter.BUTTON_PRIMARY, Clutter.ButtonState.RELEASED); + Utils.clickAtCursorPoint(); } }); } activateDndTarget(zone, first) { + if (!zone) { + return; + } const mkZoneActor = props => { let actor = new St.Widget({ style_class: "tile-preview" }); actor.x = props.x ?? 0; @@ -506,6 +487,10 @@ export class MoveGrab { zone.actor = mkZoneActor({ ...zone.actorParams }); + // deactivate previous target + this.dndTargets.filter(t => t !== zone).forEach(t => this.deactivateDndTarget(t)); + this.dndTargets = [zone]; + this.dndTarget = zone; this.zoneActors.add(zone.actor); const raise = () => Utils.actor_raise(zone.actor); @@ -532,7 +517,7 @@ export class MoveGrab { } zone.space.cloneContainer.add_child(zone.actor); - zone.space.selection.hide(); + zone.space.hideSelection(); zone.actor.show(); raise(); Easer.addEase(zone.actor, params); @@ -540,18 +525,17 @@ export class MoveGrab { deactivateDndTarget(zone) { if (zone) { - zone.space.selection.show(); + zone.space.showSelection(); Easer.addEase(zone.actor, { time: Settings.prefs.animation_time, [zone.originProp]: zone.center, [zone.sizeProp]: 0, - onComplete: () => { zone.actor.destroy(); + onComplete: () => { + zone.actor.destroy(); this.zoneActors.delete(zone.actor); }, }); } - - this.dndTarget = null; } } diff --git a/minimap.js b/minimap.js index 24ab7014f..71db1263e 100644 --- a/minimap.js +++ b/minimap.js @@ -42,13 +42,13 @@ export class Minimap extends Array { let container = new St.Widget({ name: 'minimap-container' }); this.container = container; - actor.add_actor(highlight); + actor.add_child(highlight); actor.add_actor(label); - actor.add_actor(clip); - clip.add_actor(container); + actor.add_child(clip); + clip.add_child(container); clip.set_position(12 + Settings.prefs.window_gap, 12 + Math.round(1.5 * Settings.prefs.window_gap)); highlight.y = clip.y - 10; - Main.uiGroup.add_actor(this.actor); + Main.uiGroup.add_child(this.actor); this.actor.opacity = 0; this.createClones(); @@ -143,8 +143,8 @@ export class Minimap extends Array { clone.meta_window = mw; container.clone = clone; container.meta_window = mw; - container.add_actor(clone); - this.container.add_actor(container); + container.add_child(clone); + this.container.add_child(container); return container; } diff --git a/patches.js b/patches.js index 915d24d30..bfbf635ee 100644 --- a/patches.js +++ b/patches.js @@ -140,21 +140,47 @@ export function setupOverrides() { // WorkspaceAnimation.WorkspaceAnimationController.animateSwitch // Disable the workspace switching animation in Gnome 40+ function (_from, _to, _direction, onComplete) { + // ensure swipeTrackers are disabled after this + const reset = () => { + // gnome windows switch animation time = 250, do that plus a little more + pillSwipeTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { + swipeTrackers.forEach(t => { + t.enabled = false; + }); + pillSwipeTimer = null; + return false; // on return false destroys timeout + }); + }; + + if (Tiling.inPreview) { + onComplete(); + reset(); + return; + } + // if using PaperWM workspace switch animation, just do complete here - if (Tiling.inPreview || !Tiling.spaces.space_defaultAnimation) { + if (!Tiling.spaces.space_defaultAnimation) { onComplete(); + reset(); + return; } - else { - const saved = getSavedPrototype(WorkspaceAnimation.WorkspaceAnimationController, 'animateSwitch'); - saved.call(this, _from, _to, _direction, onComplete); + + // if switching to a paperwm space that is already shown on a monitor + // from / to are workspace indices + const toSpace = Tiling.spaces.spaceOfIndex(_to); + + const spaces = Array.from(Tiling.spaces.monitors.values()); + const toOnMonitor = spaces.some(space => space === toSpace); + if (toOnMonitor) { + onComplete(); + reset(); + return; } - // ensure swipeTrackers are disabled after this - pillSwipeTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => { - swipeTrackers.forEach(t => t.enabled = false); - pillSwipeTimer = null; - return false; // on return false destroys timeout - }); + // standard gnome switch animation + const saved = getSavedPrototype(WorkspaceAnimation.WorkspaceAnimationController, 'animateSwitch'); + saved.call(this, _from, _to, _direction, onComplete); + reset(); }); registerOverridePrototype(WorkspaceAnimation.WorkspaceAnimationController, '_prepareWorkspaceSwitch', @@ -321,21 +347,21 @@ export function setupOverrides() { } switch (mode) { case AppIconMode.THUMBNAIL_ONLY: - this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor)); + this._icon.add_child(_createWindowClone(mutterWindow, size * scaleFactor)); break; case AppIconMode.BOTH: - this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor)); + this._icon.add_child(_createWindowClone(mutterWindow, size * scaleFactor)); if (this.app) { - this._icon.add_actor( + this._icon.add_child( this._createAppIcon(this.app, APP_ICON_SIZE_SMALL)); } break; case AppIconMode.APP_ICON_ONLY: size = APP_ICON_SIZE; - this._icon.add_actor(this._createAppIcon(this.app, size)); + this._icon.add_child(this._createAppIcon(this.app, size)); } this._icon.set_size(size * scaleFactor, size * scaleFactor); diff --git a/prefs.js b/prefs.js index f3d3044e9..f6fc697d3 100644 --- a/prefs.js +++ b/prefs.js @@ -126,25 +126,6 @@ class SettingsWidget { }); }; - const gestureFingersChanged = key => { - const builder = this.builder.get_object(key); - const setting = this._settings.get_int(key); - const valueToFingers = { - 0: 'fingers-disabled', - 3: 'three-fingers', - 4: 'four-fingers', - }; - const fingersToValue = Object.fromEntries( - Object.entries(valueToFingers).map(a => a.reverse()) - ); - - builder.set_active_id(valueToFingers[setting] ?? 'fingers-disable'); - builder.connect('changed', obj => { - const value = fingersToValue[obj.get_active_id()] ?? 0; - this._settings.set_int(key, value); - }); - }; - // General intValueChanged('window_gap_spin', 'window-gap'); intValueChanged('hmargin_spinner', 'horizontal-margin'); @@ -299,6 +280,8 @@ class SettingsWidget { this._settings.set_boolean('show-workspace-indicator', !state); }); + booleanStateChanged('monitor-focus-follows-mouse'); + // Workspaces booleanStateChanged('use-default-background'); diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index d42b14fac..6d8f7f6de 100644 Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ diff --git a/schemas/org.gnome.shell.extensions.paperwm.gschema.xml b/schemas/org.gnome.shell.extensions.paperwm.gschema.xml index e12822f0c..b89b90c4a 100644 --- a/schemas/org.gnome.shell.extensions.paperwm.gschema.xml +++ b/schemas/org.gnome.shell.extensions.paperwm.gschema.xml @@ -444,6 +444,11 @@ Show the focus mode icon in the topbar + + true + Sets whether monitor focus / activation follows mouse movement + + 0.25 Duration of animations in seconds diff --git a/settings.js b/settings.js index 6cbcf9892..2082433ae 100644 --- a/settings.js +++ b/settings.js @@ -40,7 +40,8 @@ export function enable(extension) { 'show-window-position-bar', 'show-focus-mode-icon', 'disable-topbar-styling', 'default-focus-mode', 'gesture-enabled', 'gesture-horizontal-fingers', 'gesture-workspace-fingers', 'open-window-position', - 'overview-ensure-viewport-animation'] + 'overview-ensure-viewport-animation', 'monitor-focus-follows-mouse', + ] .forEach(k => setState(null, k)); prefs.__defineGetter__("minimum_margin", () => { return Math.min(15, prefs.horizontal_margin); diff --git a/stackoverlay.js b/stackoverlay.js index b01cb6e72..70806d34c 100644 --- a/stackoverlay.js +++ b/stackoverlay.js @@ -43,14 +43,13 @@ import { Settings, Utils, Tiling, Navigator, Grab, Scratch } from './imports.js' restack loops) */ -let pointerWatch, lastSpace; +let pointerWatch; export function enable(extension) { } export function disable() { disableMultimonitorSupport(); - lastSpace = null; } /** @@ -74,11 +73,9 @@ export function enableMultimonitorSupport() { const space = Tiling.spaces.monitors.get(monitor); // same space - if (space === lastSpace) { + if (space === Tiling.spaces.activeSpace) { return; } - // update to space - lastSpace = space; // check if in the midst of a window resize action if (Tiling.inGrab && @@ -90,14 +87,17 @@ export function enableMultimonitorSupport() { return; } - // if drag/grabbing window, do simple activate + // if grab/dragging window if (Tiling.inGrab) { - space?.activate(false, false); + space.activate(false, false); return; } - const selected = space?.selectedWindow; - space?.activateWithFocus(selected, false, false); + // if monitor focus follow mouse ==> activate space on mouse + if (Settings.prefs.monitor_focus_follows_mouse) { + Tiling.setAllWorkspacesInactive(); + space.activateWithFocus(space.selectedWindow, false, false); + } }); console.debug('paperwm multimonitor support is ENABLED'); } @@ -265,7 +265,7 @@ export class StackOverlay { clone.opacity = 255 * 0.95; clone.set_scale(scale, scale); - Main.uiGroup.add_actor(clone); + Main.uiGroup.add_child(clone); let monitor = this.monitor; let scaleWidth = scale * clone.width; diff --git a/tiling.js b/tiling.js index cf7577934..299d9d0ea 100644 --- a/tiling.js +++ b/tiling.js @@ -89,7 +89,7 @@ let signals, backgroundGroup, grabSignals; let gsettings, backgroundSettings, interfaceSettings; let displayConfig; let saveState; -let startupTimeoutId, timerId, fullscrenStartTimeout; +let startupTimeoutId, timerId, fullscrenStartTimeout, backgroundClickTimout; let workspaceSettings; export let inGrab; export function enable(extension) { @@ -191,6 +191,8 @@ export function disable () { timerId = null; Utils.timeout_remove(fullscrenStartTimeout); fullscrenStartTimeout = null; + Utils.timeout_remove(backgroundClickTimout); + backgroundClickTimout = null; grabSignals.destroy(); grabSignals = null; @@ -268,7 +270,7 @@ export class Space extends Array { workspaceIndicator.connect('button-press-event', () => Main.overview.toggle()); this.workspaceIndicator = workspaceIndicator; let workspaceLabel = new St.Label(); - workspaceIndicator.add_actor(workspaceLabel); + workspaceIndicator.add_child(workspaceLabel); this.workspaceLabel = workspaceLabel; workspaceLabel.hide(); @@ -281,15 +283,15 @@ export class Space extends Array { clip.space = this; cloneContainer.space = this; - container.add_actor(clip); - clip.add_actor(actor); - actor.add_actor(workspaceIndicator); + container.add_child(clip); + clip.add_child(actor); + actor.add_child(workspaceIndicator); actor.add_child(this.focusModeIcon); - actor.add_actor(cloneClip); - cloneClip.add_actor(cloneContainer); + actor.add_child(cloneClip); + cloneClip.add_child(cloneContainer); this.border = new St.Widget({ name: "border" }); - this.actor.add_actor(this.border); + this.actor.add_child(this.border); this.border.hide(); let monitor = Main.layoutManager.primaryMonitor; @@ -891,10 +893,10 @@ export class Space extends Array { this.visible.splice(this.visible.indexOf(metaWindow), 1); let clone = metaWindow.clone; - this.cloneContainer.remove_actor(clone); + this.cloneContainer.remove_child(clone); // Don't destroy the selection highlight widget if (clone.first_child.name === 'selection') - clone.remove_actor(clone.first_child); + clone.remove_child(clone.first_child); let actor = metaWindow.get_compositor_private(); if (actor) actor.remove_clip(); @@ -930,7 +932,7 @@ export class Space extends Array { if (i === -1) return false; this._floating.splice(i, 1); - this.actor.remove_actor(metaWindow.clone); + this.actor.remove_child(metaWindow.clone); return true; } @@ -1313,11 +1315,11 @@ export class Space extends Array { let showTopBar = this.getShowTopBarSetting(); // remove window position bar actors - this.actor.remove_actor(this.windowPositionBarBackdrop); - this.actor.remove_actor(this.windowPositionBar); + this.actor.remove_child(this.windowPositionBarBackdrop); + this.actor.remove_child(this.windowPositionBar); if (showTopBar) { - this.actor.add_actor(this.windowPositionBarBackdrop); - this.actor.add_actor(this.windowPositionBar); + this.actor.add_child(this.windowPositionBarBackdrop); + this.actor.add_child(this.windowPositionBar); } this.updateShowTopBar(); @@ -1612,10 +1614,17 @@ border-radius: ${borderWidth}px; this.signals.connect(this.background, 'button-press-event', (actor, event) => { + // ensure this space is active if clicked + this.activateWithFocus(this.selectedWindow, false, false); + if (inGrab) { return; } + // update selection on spaces + setAllWorkspacesInactive(); + this.setSelectionActive(); + /** * if user clicks on window, then ensureViewport on that window before exiting */ @@ -1626,12 +1635,29 @@ border-radius: ${borderWidth}px; ensureViewport(windowAtPoint, this); } + /** + * if not monitor focus follows, then do a virtual click (first click + * activated space). + */ + if (!Settings.prefs.monitor_focus_follows_mouse) { + backgroundClickTimout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, () => { + Utils.clickAtCursorPoint(); + backgroundClickTimout = null; + return false; + }); + } + spaces.selectedSpace = this; Navigator.finishNavigation(); }); - this.signals.connect( - this.background, 'scroll-event', + // ensure this space is active if touched + this.signals.connect(this.background, 'touch-event', + (actor, event) => { + this.activateWithFocus(this.selectedWindow, false, false); + }); + + this.signals.connect(this.background, 'scroll-event', (actor, event) => { if (!inGrab && !Navigator.navigating) return; @@ -1712,7 +1738,6 @@ border-radius: ${borderWidth}px; // transforms break if there's no height this.cloneContainer.height = this.monitor.height; - this.layout(true, { centerIfOne: false }); this.emit('monitor-changed'); } @@ -3001,7 +3026,7 @@ export function registerWindow(metaWindow) { let cloneActor = new Clutter.Clone({ source: actor }); let clone = new Clutter.Actor(); - clone.add_actor(cloneActor); + clone.add_child(cloneActor); clone.targetX = 0; clone.meta_window = metaWindow; @@ -4639,7 +4664,7 @@ export function takeWindow(metaWindow, space, { navigator }) { navigator._moving.push(metaWindow); let parent = backgroundGroup; - parent.add_actor(metaWindow.clone); + parent.add_child(metaWindow.clone); let lowest = navigator._moving[navigator._moving.length - 2]; lowest && parent.set_child_below_sibling(metaWindow.clone, lowest.clone); let point = space.cloneContainer.apply_relative_transform_to_point( diff --git a/topbar.js b/topbar.js index b729ffebf..d8644b9a5 100644 --- a/topbar.js +++ b/topbar.js @@ -177,9 +177,9 @@ export function popupMenuEntryHelper(text) { this.prevIcon.grab_key_focus(); }); - this.actor.add_actor(this.prevIcon); - this.actor.add_actor(this.label); - this.actor.add_actor(this.nextIcon); + this.actor.add_child(this.prevIcon); + this.actor.add_child(this.label); + this.actor.add_child(this.nextIcon); this.actor.label_actor = this.label; this.label.clutter_text.connect('activate', this.emit.bind(this, 'activate')); } @@ -213,7 +213,7 @@ class Color { this.color = color; this.actor = new St.Button(); let icon = new St.Widget(); - this.actor.add_actor(icon); + this.actor.add_child(icon); icon.set_style(`background: ${color}`); icon.set_size(20, 20); icon.set_position(4, 4); @@ -235,11 +235,11 @@ class ColorEntry { let flowbox = new St.Widget(); let flowLayout = new Clutter.FlowLayout(); let flow = new St.Widget(); - flowbox.add_actor(flow); + flowbox.add_child(flow); flow.layout_manager = flowLayout; flow.width = 24 * 16; for (let c of colors) { - flow.add_actor(new Color(c, this).actor); + flow.add_child(new Color(c, this).actor); } this.entry = new PopupMenuEntry(startColor, 'Set color'); @@ -251,8 +251,8 @@ class ColorEntry { this.entry.button.connect('clicked', this.clicked.bind(this)); - this.actor.add_actor(this.entry.actor); - this.actor.add_actor(flowbox); + this.actor.add_child(this.entry.actor); + this.actor.add_child(flowbox); } clicked() { @@ -429,7 +429,7 @@ export const WorkspaceMenu = GObject.registerClass( this.setName(Meta.prefs_get_workspace_name(workspaceManager.get_active_workspace_index())); - this.add_actor(this.label); + this.add_child(this.label); this.signals = new Utils.Signals(); this.signals.connect(global.window_manager, @@ -531,7 +531,7 @@ export const WorkspaceMenu = GObject.registerClass( this._navigator = Navigator.getNavigator(); Tiling.spaces.initWorkspaceSequence(); this._enterbox = new Clutter.Actor({ reactive: true }); - Main.uiGroup.add_actor(this._enterbox); + Main.uiGroup.add_child(this._enterbox); this._enterbox.set_position(panelBox.x, panelBox.y + panelBox.height + 20); this._enterbox.set_size(global.screen_width, global.screen_height); Main.layoutManager.trackChrome(this._enterbox); diff --git a/utils.js b/utils.js index 8b7aca617..389a31456 100644 --- a/utils.js +++ b/utils.js @@ -15,7 +15,7 @@ import { Lib } from './imports.js'; const Display = global.display; export let version = Config.PACKAGE_VERSION.split('.').map(Number); -let warpRipple; +let warpRipple, virtualPointer; export function enable() { warpRipple = new Ripples.Ripples(0.5, 0.5, 'ripple-pointer-location'); warpRipple.addTo(Main.uiGroup); @@ -117,7 +117,7 @@ export function toggleWindowBoxes(metaWindow) { boxes.push(makeFrameBox(actor, "yellow")); } - boxes.forEach(box => global.stage.add_actor(box)); + boxes.forEach(box => global.stage.add_child(box)); metaWindow._paperDebugBoxes = boxes; return boxes; @@ -217,6 +217,38 @@ export function warpPointer(x, y) { warpRipple.playAnimation(x, y); } +/** + * Returns a virtual pointer (i.e. mouse) device that can be used to + * "clickout" of a drag operation when `grab_end_op` is unavailable + * (i.e. as of Gnome 44 where `grab_end_op` was removed). + * @returns Clutter.VirtualInputDevice +*/ +export function getVirtualPointer() { + if (!virtualPointer) { + virtualPointer = Clutter.get_default_backend() + .get_default_seat() + .create_virtual_device(Clutter.InputDeviceType.POINTER_DEVICE); + } + + return virtualPointer; +} + +/** + * Clicks at cursor point (with virtual pointer). + */ +export function clickAtCursorPoint() { + // move to current cursor position + let [x, y, _mods] = global.get_pointer(); + getVirtualPointer().notify_absolute_motion( + Clutter.get_current_event_time(), + x, y); + + getVirtualPointer().notify_button(Clutter.get_current_event_time(), + Clutter.BUTTON_PRIMARY, Clutter.ButtonState.PRESSED); + getVirtualPointer().notify_button(Clutter.get_current_event_time(), + Clutter.BUTTON_PRIMARY, Clutter.ButtonState.RELEASED); +} + /** * Return current modifiers state (or'ed Clutter.ModifierType.*) */ diff --git a/virtTiling.js b/virtTiling.js index 494252682..6b61a31a1 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -55,13 +55,13 @@ export function repl() { let tilingStyle = `background-color: rgba(190, 190, 0, 0.3);`; let tilingContainer = new St.Widget({ name: "tiling", style: tilingStyle }); - global.stage.add_actor(virtStage); + global.stage.add_child(virtStage); virtStage.x = 3000; virtStage.y = 300; - virtStage.add_actor(monitor); - monitor.add_actor(panel); - monitor.add_actor(tilingContainer); + virtStage.add_child(monitor); + monitor.add_child(panel); + monitor.add_child(tilingContainer); function sync(space_ = space) { let columns = layout( @@ -143,7 +143,7 @@ export function render(columns, tiling) { for (let col of columns) { for (let window of col) { let windowActor = createWindowActor(window); - tiling.add_actor(windowActor); + tiling.add_child(windowActor); } } }