Skip to content

Commit 687ca2c

Browse files
authored
feat: enhance clipboard panel positioning with focused window support (#65)
1 parent 5bb3b3a commit 687ca2c

3 files changed

Lines changed: 106 additions & 7 deletions

File tree

src/clipboard/clipboardPanel.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import { gettext as _ } from 'gettext';
44
import St from '@girs/st-18';
55
import GObject from '@girs/gobject-2.0';
66
import Clutter from '@girs/clutter-18';
7+
import type Meta from '@girs/meta-18';
78
import * as Main from '@girs/gnome-shell/ui/main';
89

910
import type { ClipboardEntry, ClipboardStore } from '~/clipboard/clipboardStore.ts';
1011
import { ClipboardList } from '~/clipboard/clipboardList.ts';
11-
import { placeClipboardPanelNearPointer } from '~/clipboard/clipboardPosition.ts';
12+
import {
13+
placeClipboardPanelNearPointer,
14+
resolveClipboardPanelAnchor,
15+
type ClipboardPanelWindowPlacement,
16+
} from '~/clipboard/clipboardPosition.ts';
1217

1318
const PANEL_WIDTH = 360;
1419
const PANEL_HEIGHT = 480;
@@ -99,7 +104,7 @@ export class ClipboardPanel extends St.BoxLayout {
99104
Main.uiGroup.add_child(this._overlay);
100105
Main.uiGroup.add_child(this); // panel sits above overlay
101106

102-
this._positionNearPointer();
107+
this._positionNearCurrentAnchor();
103108

104109
this.show();
105110
this._isOpen = true;
@@ -230,13 +235,22 @@ export class ClipboardPanel extends St.BoxLayout {
230235
}
231236
}
232237

233-
private _positionNearPointer(): void {
238+
private _positionNearCurrentAnchor(): void {
234239
const [pointerX, pointerY] = global.get_pointer();
235-
const monitorIndex = this._findMonitorIndexAt(pointerX, pointerY);
236-
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);
237-
const bounds = placeClipboardPanelNearPointer(
240+
const pointerMonitorIndex = this._findMonitorIndexAt(pointerX, pointerY);
241+
const anchor = resolveClipboardPanelAnchor(
238242
pointerX,
239243
pointerY,
244+
pointerMonitorIndex,
245+
this._getFocusedWindowPlacement(),
246+
);
247+
const monitorIndex = this._isMonitorValid(anchor.monitorIndex)
248+
? anchor.monitorIndex
249+
: pointerMonitorIndex;
250+
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);
251+
const bounds = placeClipboardPanelNearPointer(
252+
anchor.x,
253+
anchor.y,
240254
workArea,
241255
PANEL_WIDTH,
242256
PANEL_HEIGHT,
@@ -264,4 +278,28 @@ export class ClipboardPanel extends St.BoxLayout {
264278

265279
return Main.layoutManager.primaryIndex;
266280
}
281+
282+
private _getFocusedWindowPlacement(): ClipboardPanelWindowPlacement | null {
283+
const win = global.display.focus_window as Meta.Window | null;
284+
if (!win || win.minimized) return null;
285+
286+
const monitorIndex = win.get_monitor();
287+
if (!this._isMonitorValid(monitorIndex)) return null;
288+
289+
const frame = win.get_frame_rect();
290+
return {
291+
monitorIndex,
292+
frame: {
293+
x: frame.x,
294+
y: frame.y,
295+
width: frame.width,
296+
height: frame.height,
297+
},
298+
};
299+
}
300+
301+
private _isMonitorValid(monitorIndex: number): boolean {
302+
const monitors = Main.layoutManager.monitors ?? [];
303+
return monitorIndex >= 0 && monitorIndex < monitors.length;
304+
}
267305
}

src/clipboard/clipboardPosition.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ export type ClipboardPanelBounds = {
55
height: number;
66
};
77

8+
export type ClipboardPanelAnchor = {
9+
x: number;
10+
y: number;
11+
monitorIndex: number;
12+
};
13+
14+
export type ClipboardPanelWindowPlacement = {
15+
monitorIndex: number;
16+
frame: ClipboardPanelBounds;
17+
};
18+
19+
export function resolveClipboardPanelAnchor(
20+
pointerX: number,
21+
pointerY: number,
22+
pointerMonitorIndex: number,
23+
focusedWindow: ClipboardPanelWindowPlacement | null,
24+
): ClipboardPanelAnchor {
25+
if (focusedWindow && focusedWindow.monitorIndex >= 0 && hasUsableFrame(focusedWindow.frame)) {
26+
return {
27+
x: focusedWindow.frame.x + Math.floor(focusedWindow.frame.width / 2),
28+
y: focusedWindow.frame.y + Math.floor(focusedWindow.frame.height / 2),
29+
monitorIndex: focusedWindow.monitorIndex,
30+
};
31+
}
32+
33+
return { x: pointerX, y: pointerY, monitorIndex: pointerMonitorIndex };
34+
}
35+
836
export function placeClipboardPanelNearPointer(
937
pointerX: number,
1038
pointerY: number,
@@ -43,3 +71,7 @@ function clamp(value: number, min: number, max: number): number {
4371
if (max < min) return min;
4472
return Math.max(min, Math.min(max, value));
4573
}
74+
75+
function hasUsableFrame(frame: ClipboardPanelBounds): boolean {
76+
return frame.width > 0 && frame.height > 0;
77+
}

tests/unit/clipboardPosition.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import assert from 'node:assert/strict';
22
import test from 'node:test';
33

4-
import { placeClipboardPanelNearPointer } from '../../src/clipboard/clipboardPosition.ts';
4+
import {
5+
placeClipboardPanelNearPointer,
6+
resolveClipboardPanelAnchor,
7+
} from '../../src/clipboard/clipboardPosition.ts';
58

69
const AREA = { x: 0, y: 24, width: 1280, height: 696 };
710
const WIDTH = 380;
@@ -49,3 +52,29 @@ test('placeClipboardPanelNearPointer clamps and shrinks in narrow work areas', (
4952
height: 236,
5053
});
5154
});
55+
56+
test('resolveClipboardPanelAnchor prefers focused window center and monitor', () => {
57+
const anchor = resolveClipboardPanelAnchor(100, 120, 0, {
58+
monitorIndex: 1,
59+
frame: { x: 1920, y: 80, width: 1000, height: 700 },
60+
});
61+
62+
assert.deepEqual(anchor, {
63+
x: 2420,
64+
y: 430,
65+
monitorIndex: 1,
66+
});
67+
});
68+
69+
test('resolveClipboardPanelAnchor falls back to pointer for invalid focused window frame', () => {
70+
const anchor = resolveClipboardPanelAnchor(100, 120, 0, {
71+
monitorIndex: 1,
72+
frame: { x: 1920, y: 80, width: 0, height: 700 },
73+
});
74+
75+
assert.deepEqual(anchor, {
76+
x: 100,
77+
y: 120,
78+
monitorIndex: 0,
79+
});
80+
});

0 commit comments

Comments
 (0)