mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
164 lines
6.3 KiB
QML
164 lines
6.3 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import qs.Commons
|
|
import qs.Services.Compositor
|
|
|
|
/**
|
|
* Detects which screen the cursor is currently on by creating a temporary
|
|
* invisible PanelWindow. Use withCurrentScreen() to get the screen asynchronously.
|
|
*
|
|
* Usage:
|
|
* CurrentScreenDetector {
|
|
* id: screenDetector
|
|
* }
|
|
*
|
|
* function doSomething() {
|
|
* screenDetector.withCurrentScreen(function(screen) {
|
|
* // screen is the ShellScreen where cursor is
|
|
* })
|
|
* }
|
|
*/
|
|
Item {
|
|
id: root
|
|
|
|
// Pending callback to execute once screen is detected
|
|
property var pendingCallback: null
|
|
property bool pendingSkipBarCheck: false
|
|
|
|
// Detected screen
|
|
property var detectedScreen: null
|
|
|
|
// Signal emitted when screen is detected from the PanelWindow
|
|
signal screenDetected(var detectedScreen)
|
|
|
|
onScreenDetected: function (detectedScreen) {
|
|
root.detectedScreen = detectedScreen;
|
|
screenDetectorDebounce.restart();
|
|
}
|
|
|
|
/**
|
|
* Find a fallback screen that has a bar configured.
|
|
* Prioritizes the screen at position 0x0 (likely the primary screen).
|
|
*/
|
|
function findScreenWithBar(): var {
|
|
const monitors = Settings.data.bar.monitors || [];
|
|
let primaryCandidate = null;
|
|
let firstWithBar = null;
|
|
|
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
|
const s = Quickshell.screens[i];
|
|
const hasBar = monitors.length === 0 || monitors.includes(s.name);
|
|
|
|
if (hasBar) {
|
|
// Check if this is at 0x0 (primary position)
|
|
if (s.x === 0 && s.y === 0) {
|
|
primaryCandidate = s;
|
|
}
|
|
// Track first screen with bar as fallback
|
|
if (!firstWithBar) {
|
|
firstWithBar = s;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prefer primary (0x0), then first with bar, then just first screen
|
|
return primaryCandidate || firstWithBar || Quickshell.screens[0];
|
|
}
|
|
|
|
/**
|
|
* Execute callback with the screen where the cursor currently is.
|
|
* On single-monitor setups, executes immediately.
|
|
* On multi-monitor setups, briefly opens an invisible window to detect the screen.
|
|
*/
|
|
function withCurrentScreen(callback: var, skipBarCheck: bool) {
|
|
if (root.pendingCallback) {
|
|
Logger.w("CurrentScreenDetector", "Another detection is pending, ignoring new call");
|
|
return;
|
|
}
|
|
|
|
// Single monitor setup can execute immediately
|
|
if (Quickshell.screens.length === 1) {
|
|
callback(Quickshell.screens[0]);
|
|
return;
|
|
}
|
|
|
|
// Try compositor-specific focused monitor detection first
|
|
let screen = CompositorService.getFocusedScreen();
|
|
|
|
if (screen) {
|
|
// Apply the bar check if configured (skip for overlay launcher etc.)
|
|
if (!skipBarCheck && !Settings.data.general.allowPanelsOnScreenWithoutBar) {
|
|
const monitors = Settings.data.bar.monitors || [];
|
|
const hasBar = monitors.length === 0 || monitors.includes(screen.name);
|
|
if (!hasBar) {
|
|
screen = findScreenWithBar();
|
|
}
|
|
}
|
|
Logger.d("CurrentScreenDetector", "Using compositor-detected screen:", screen.name);
|
|
callback(screen);
|
|
return;
|
|
}
|
|
|
|
// Fallback: Multi-monitor setup needs async detection via invisible PanelWindow
|
|
root.detectedScreen = null;
|
|
root.pendingCallback = callback;
|
|
root.pendingSkipBarCheck = !!skipBarCheck;
|
|
screenDetectorLoader.active = true;
|
|
}
|
|
|
|
Timer {
|
|
id: screenDetectorDebounce
|
|
running: false
|
|
interval: 40
|
|
onTriggered: {
|
|
Logger.d("CurrentScreenDetector", "Screen debounced to:", root.detectedScreen?.name || "null");
|
|
|
|
// Execute pending callback if any
|
|
if (root.pendingCallback) {
|
|
if (!root.pendingSkipBarCheck && !Settings.data.general.allowPanelsOnScreenWithoutBar) {
|
|
// If we explicitly disabled panels on screen without bar, check if bar is configured
|
|
// for this screen, and fallback to primary screen if necessary
|
|
var monitors = Settings.data.bar.monitors || [];
|
|
const hasBar = monitors.length === 0 || monitors.includes(root.detectedScreen?.name);
|
|
if (!hasBar) {
|
|
root.detectedScreen = findScreenWithBar();
|
|
}
|
|
}
|
|
|
|
Logger.d("CurrentScreenDetector", "Executing callback on screen:", root.detectedScreen.name);
|
|
// Store callback locally and clear pendingCallback first to prevent deadlock
|
|
// if the callback throws an error
|
|
var callback = root.pendingCallback;
|
|
root.pendingCallback = null;
|
|
root.pendingSkipBarCheck = false;
|
|
try {
|
|
callback(root.detectedScreen);
|
|
} catch (e) {
|
|
Logger.e("CurrentScreenDetector", "Callback failed:", e);
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
screenDetectorLoader.active = false;
|
|
}
|
|
}
|
|
|
|
// Invisible dummy PanelWindow to detect which screen should receive the action
|
|
Loader {
|
|
id: screenDetectorLoader
|
|
active: false
|
|
|
|
sourceComponent: PanelWindow {
|
|
implicitWidth: 0
|
|
implicitHeight: 0
|
|
color: "transparent"
|
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
|
WlrLayershell.namespace: "noctalia-screen-detector"
|
|
mask: Region {}
|
|
|
|
onScreenChanged: root.screenDetected(screen)
|
|
}
|
|
}
|
|
}
|