Popup auf SNI-Desktops: Activate-Koordinaten (Anchor::Click) + Cinnamon-Extension
- SNI-Activate-Koordinaten gehen als Anchor::Click durch; Cinnamon liefert echte Werte, bei (0,0) bleibt der Cursor-Fallback (KDE/XFCE). - Neue Cinnamon-Extension ai-control-popup@local: platziert das Popup unter Wayland am Zeiger (Work-Area-geklemmt, set_position ist dort wirkungslos) und gibt ihm unter X11 den initialen Fokus (Muffins Focus-Stealing- Prevention verwirft das present() der App). - Extension in deb/rpm nach /usr/share/cinnamon/extensions paketiert. - README: Cinnamon-Abschnitte und Installer-Teststand aktualisiert. Verifiziert in der VM-Flotte: Cinnamon Wayland + X11, XFCE, KDE (Wayland), GNOME (Wayland).
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/* Cinnamon-Extension (CJS, Muffin/Meta-0)
|
||||
* ai-control-popup@local
|
||||
*
|
||||
* Das Tray-Icon kommt unter Cinnamon über SNI (xapp), die App zeigt das
|
||||
* Popup selbst. Die Extension ergänzt, was der Client nicht kann:
|
||||
* - Wayland: set_position aus dem Client ist wirkungslos — die Extension
|
||||
* schiebt das frisch gemappte Popup an den Mauszeiger (der beim Klick auf
|
||||
* dem Icon steht), mit derselben Klemm-Logik wie position_popup in app.rs.
|
||||
* - X11: Muffins Focus-Stealing-Prevention verwirft das present() der App —
|
||||
* ohne initialen Fokus schließt das Blur-Verstecken nie. Die Extension
|
||||
* fokussiert das Popup beim Map aus dem Compositor heraus (platziert wird
|
||||
* client-seitig über die SNI-Activate-Koordinaten).
|
||||
*/
|
||||
|
||||
const Meta = imports.gi.Meta;
|
||||
|
||||
const WIN_TITLE = 'ai-control-popup';
|
||||
|
||||
let mapId = 0;
|
||||
|
||||
function _place(win) {
|
||||
const [px, py] = global.get_pointer();
|
||||
const rect = win.get_frame_rect();
|
||||
// Work-Area statt Monitor: die Panel-Struts bleiben frei, das Popup sitzt
|
||||
// über der Leiste statt darauf.
|
||||
const wa = win.get_work_area_current_monitor();
|
||||
const maxX = wa.x + wa.width - rect.width;
|
||||
const maxY = wa.y + wa.height - rect.height;
|
||||
const x = Math.min(Math.max(px - rect.width, wa.x), Math.max(maxX, wa.x));
|
||||
const y = Math.min(Math.max(py, wa.y), Math.max(maxY, wa.y));
|
||||
win.move_frame(true, x, y);
|
||||
}
|
||||
|
||||
// Beim Mappen unsichtbar schalten und vor dem nächsten Redraw positionieren —
|
||||
// der erste sichtbare Frame sitzt schon am Zielort (Muster wie in der
|
||||
// GNOME-Extension, dort über global.compositor.get_laters()).
|
||||
function _placeBeforeRedraw(actor, win) {
|
||||
actor.remove_all_transitions();
|
||||
actor.opacity = 0;
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
||||
const rect = win.get_frame_rect();
|
||||
// Noch keine echte Größe -> nächster Redraw, bis positionierbar.
|
||||
if (!rect || rect.width <= 1) return true;
|
||||
_place(win);
|
||||
actor.opacity = 255;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function init(_metadata) {}
|
||||
|
||||
function enable() {
|
||||
mapId = global.window_manager.connect('map', (_wm, actor) => {
|
||||
const win = actor.meta_window;
|
||||
if (!win || win.get_title() !== WIN_TITLE) return;
|
||||
if (Meta.is_wayland_compositor()) {
|
||||
_placeBeforeRedraw(actor, win);
|
||||
} else {
|
||||
win.activate(global.get_current_time());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function disable() {
|
||||
if (mapId) {
|
||||
global.window_manager.disconnect(mapId);
|
||||
mapId = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user