Files
noctalia-shell/Modules/MainScreen/MainScreen.qml
T

564 lines
17 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import "Backgrounds" as Backgrounds
import qs.Commons
// All panels
import qs.Modules.Bar
import qs.Modules.Bar.Extras
import qs.Modules.Panels.Audio
import qs.Modules.Panels.Battery
import qs.Modules.Panels.Bluetooth
import qs.Modules.Panels.Brightness
import qs.Modules.Panels.Changelog
import qs.Modules.Panels.Clock
import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.Launcher
import qs.Modules.Panels.NotificationHistory
import qs.Modules.Panels.Plugins
import qs.Modules.Panels.SessionMenu
import qs.Modules.Panels.Settings
import qs.Modules.Panels.SetupWizard
import qs.Modules.Panels.SystemStats
import qs.Modules.Panels.Tray
import qs.Modules.Panels.Wallpaper
import qs.Modules.Panels.WiFi
import qs.Services.Compositor
import qs.Services.UI
/**
* MainScreen - Single PanelWindow per screen that manages all panels and the bar
*/
PanelWindow {
id: root
Component.onCompleted: {
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y);
}
// Wayland
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "noctalia-background-" + (screen?.name || "unknown")
WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone handles that
WlrLayershell.keyboardFocus: {
// No panel open anywhere: no keyboard focus needed
if (!root.isAnyPanelOpen) {
return WlrKeyboardFocus.None;
}
// Panel open on THIS screen: use panel's preferred focus mode
if (root.isPanelOpen) {
// Hyprland's Exclusive captures ALL input globally (including pointer),
// preventing click-to-close from working on other monitors.
// Workaround: briefly use Exclusive when panel opens (for text input focus),
// then switch to OnDemand (for click-to-close on other screens).
if (CompositorService.isHyprland) {
return PanelService.isInitializingKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
}
return PanelService.openedPanel.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
}
// Panel open on ANOTHER screen: OnDemand allows receiving pointer events for click-to-close
return WlrKeyboardFocus.OnDemand;
}
anchors {
top: true
bottom: true
left: true
right: true
}
// Desktop dimming when panels are open
property real dimmerOpacity: Settings.data.general.dimmerOpacity ?? 0.8
property bool isPanelOpen: (PanelService.openedPanel !== null) && (PanelService.openedPanel.screen === screen)
property bool isPanelClosing: (PanelService.openedPanel !== null) && PanelService.openedPanel.isClosing
property bool isAnyPanelOpen: PanelService.openedPanel !== null
color: {
if (dimmerOpacity > 0 && isPanelOpen && !isPanelClosing) {
return Qt.alpha(Color.mShadow, dimmerOpacity);
}
return "transparent";
}
Behavior on color {
enabled: !PanelService.closedImmediately
ColorAnimation {
duration: isPanelClosing ? Style.animationFaster : Style.animationNormal
easing.type: Easing.OutQuad
}
}
// Reset closedImmediately flag after color change is applied
onColorChanged: {
if (PanelService.closedImmediately) {
PanelService.closedImmediately = false;
}
}
// Check if bar should be visible on this screen
readonly property bool barShouldShow: {
// Check global bar visibility
if (!BarService.isVisible)
return false;
// Check screen-specific configuration
var monitors = Settings.data.bar.monitors || [];
var screenName = screen?.name || "";
// If no monitors specified, show on all screens
// If monitors specified, only show if this screen is in the list
return monitors.length === 0 || monitors.includes(screenName);
}
// Make everything click-through except bar
mask: Region {
id: clickableMask
// Cover entire window (everything is masked/click-through)
x: 0
y: 0
width: root.width
height: root.height
intersection: Intersection.Xor
// Only include regions that are actually needed
// panelRegions is handled by PanelService, bar is local to this screen
regions: [barMaskRegion, backgroundMaskRegion]
// Bar region - subtract bar area from mask (only if bar should be shown on this screen)
Region {
id: barMaskRegion
x: barPlaceholder.x
y: barPlaceholder.y
// Set width/height to 0 if bar shouldn't show on this screen (makes region empty)
width: root.barShouldShow ? barPlaceholder.width : 0
height: root.barShouldShow ? barPlaceholder.height : 0
intersection: Intersection.Subtract
}
// Background region for click-to-close - reactive sizing
// Uses isAnyPanelOpen so clicking on any screen's background closes the panel
Region {
id: backgroundMaskRegion
x: 0
y: 0
width: root.isAnyPanelOpen ? root.width : 0
height: root.isAnyPanelOpen ? root.height : 0
intersection: Intersection.Subtract
}
}
// --------------------------------------
// Container for all UI elements
Item {
id: container
width: root.width
height: root.height
// Unified backgrounds container / unified shadow system
// Renders all bar and panel backgrounds as ShapePaths within a single Shape
// This allows the shadow effect to apply to all backgrounds in one render pass
Backgrounds.AllBackgrounds {
id: unifiedBackgrounds
anchors.fill: parent
bar: barPlaceholder.barItem || null
windowRoot: root
z: 0 // Behind all content
}
// Background MouseArea for closing panels when clicking outside
// Uses isAnyPanelOpen so clicking on any screen's background closes the panel
MouseArea {
anchors.fill: parent
enabled: root.isAnyPanelOpen
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (PanelService.openedPanel) {
PanelService.openedPanel.close();
}
}
z: 0 // Behind panels and bar
}
// ---------------------------------------
// All panels always exist
// ---------------------------------------
AudioPanel {
id: audioPanel
objectName: "audioPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
BatteryPanel {
id: batteryPanel
objectName: "batteryPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
BluetoothPanel {
id: bluetoothPanel
objectName: "bluetoothPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
BrightnessPanel {
id: brightnessPanel
objectName: "brightnessPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
ControlCenterPanel {
id: controlCenterPanel
objectName: "controlCenterPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
ChangelogPanel {
id: changelogPanel
objectName: "changelogPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
ClockPanel {
id: clockPanel
objectName: "clockPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
Launcher {
id: launcherPanel
objectName: "launcherPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
NotificationHistoryPanel {
id: notificationHistoryPanel
objectName: "notificationHistoryPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
SessionMenu {
id: sessionMenuPanel
objectName: "sessionMenuPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
SettingsPanel {
id: settingsPanel
objectName: "settingsPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
SetupWizard {
id: setupWizardPanel
objectName: "setupWizardPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
TrayDrawerPanel {
id: trayDrawerPanel
objectName: "trayDrawerPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
WallpaperPanel {
id: wallpaperPanel
objectName: "wallpaperPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
WiFiPanel {
id: wifiPanel
objectName: "wifiPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
SystemStatsPanel {
id: systemStatsPanel
objectName: "systemStatsPanel-" + (root.screen?.name || "unknown")
screen: root.screen
}
// ----------------------------------------------
// Plugin panel slots
// ----------------------------------------------
PluginPanelSlot {
id: pluginPanel1
objectName: "pluginPanel1-" + (root.screen?.name || "unknown")
screen: root.screen
slotNumber: 1
}
PluginPanelSlot {
id: pluginPanel2
objectName: "pluginPanel2-" + (root.screen?.name || "unknown")
screen: root.screen
slotNumber: 2
}
// ----------------------------------------------
// Bar background placeholder - just for background positioning (actual bar content is in BarContentWindow)
Item {
id: barPlaceholder
// Expose self as barItem for AllBackgrounds compatibility
readonly property var barItem: barPlaceholder
// Screen reference
property ShellScreen screen: root.screen
// Bar background positioning properties
readonly property string barPosition: Settings.data.bar.position || "top"
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property bool barFloating: Settings.data.bar.floating || false
readonly property real barMarginH: barFloating ? Math.floor(Settings.data.bar.marginHorizontal * Style.marginXL) : 0
readonly property real barMarginV: barFloating ? Math.floor(Settings.data.bar.marginVertical * Style.marginXL) : 0
// Expose bar dimensions directly on this Item for BarBackground
// Use screen dimensions directly
x: {
if (barPosition === "right")
return screen.width - Style.barHeight - barMarginH;
return barMarginH;
}
y: {
if (barPosition === "bottom")
return screen.height - Style.barHeight - barMarginV;
return barMarginV;
}
width: {
if (barIsVertical) {
return Style.barHeight;
}
return screen.width - barMarginH * 2;
}
height: {
if (barIsVertical) {
return screen.height - barMarginV * 2;
}
return Style.barHeight;
}
// Corner states (same as Bar.qml)
readonly property int topLeftCornerState: {
if (barFloating)
return 0;
if (barPosition === "top")
return -1;
if (barPosition === "left")
return -1;
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) {
return barIsVertical ? 1 : 2;
}
return -1;
}
readonly property int topRightCornerState: {
if (barFloating)
return 0;
if (barPosition === "top")
return -1;
if (barPosition === "right")
return -1;
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
return barIsVertical ? 1 : 2;
}
return -1;
}
readonly property int bottomLeftCornerState: {
if (barFloating)
return 0;
if (barPosition === "bottom")
return -1;
if (barPosition === "left")
return -1;
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
return barIsVertical ? 1 : 2;
}
return -1;
}
readonly property int bottomRightCornerState: {
if (barFloating)
return 0;
if (barPosition === "bottom")
return -1;
if (barPosition === "right")
return -1;
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
return barIsVertical ? 1 : 2;
}
return -1;
}
}
/**
* Screen Corners
*/
ScreenCorners {}
}
// ========================================
// Centralized Keyboard Shortcuts
// ========================================
// These shortcuts delegate to the opened panel's handler functions
// Panels can implement: onEscapePressed, onTabPressed, onBackTabPressed,
// onUpPressed, onDownPressed, onReturnPressed, etc...
Shortcut {
sequence: "Escape"
enabled: root.isPanelOpen && (PanelService.openedPanel.onEscapePressed !== undefined)
onActivated: PanelService.openedPanel.onEscapePressed()
}
Shortcut {
sequence: "Tab"
enabled: root.isPanelOpen && (PanelService.openedPanel.onTabPressed !== undefined)
onActivated: PanelService.openedPanel.onTabPressed()
}
Shortcut {
sequence: "Backtab"
enabled: root.isPanelOpen && (PanelService.openedPanel.onBackTabPressed !== undefined)
onActivated: PanelService.openedPanel.onBackTabPressed()
}
Shortcut {
sequence: "Up"
enabled: root.isPanelOpen && (PanelService.openedPanel.onUpPressed !== undefined)
onActivated: PanelService.openedPanel.onUpPressed()
}
Shortcut {
sequence: "Down"
enabled: root.isPanelOpen && (PanelService.openedPanel.onDownPressed !== undefined)
onActivated: PanelService.openedPanel.onDownPressed()
}
Shortcut {
sequence: "Return"
enabled: root.isPanelOpen && (PanelService.openedPanel.onReturnPressed !== undefined)
onActivated: PanelService.openedPanel.onReturnPressed()
}
Shortcut {
sequence: "Left"
enabled: root.isPanelOpen && (PanelService.openedPanel.onLeftPressed !== undefined)
onActivated: PanelService.openedPanel.onLeftPressed()
}
Shortcut {
sequence: "Right"
enabled: root.isPanelOpen && (PanelService.openedPanel.onRightPressed !== undefined)
onActivated: PanelService.openedPanel.onRightPressed()
}
Shortcut {
sequence: "Home"
enabled: root.isPanelOpen && (PanelService.openedPanel.onHomePressed !== undefined)
onActivated: PanelService.openedPanel.onHomePressed()
}
Shortcut {
sequence: "End"
enabled: root.isPanelOpen && (PanelService.openedPanel.onEndPressed !== undefined)
onActivated: PanelService.openedPanel.onEndPressed()
}
Shortcut {
sequence: "PgUp"
enabled: root.isPanelOpen && (PanelService.openedPanel.onPageUpPressed !== undefined)
onActivated: PanelService.openedPanel.onPageUpPressed()
}
Shortcut {
sequence: "PgDown"
enabled: root.isPanelOpen && (PanelService.openedPanel.onPageDownPressed !== undefined)
onActivated: PanelService.openedPanel.onPageDownPressed()
}
Shortcut {
sequence: "Ctrl+J"
enabled: root.isPanelOpen && (PanelService.openedPanel.onCtrlJPressed !== undefined)
onActivated: PanelService.openedPanel.onCtrlJPressed()
}
Shortcut {
sequence: "Ctrl+K"
enabled: root.isPanelOpen && (PanelService.openedPanel.onCtrlKPressed !== undefined)
onActivated: PanelService.openedPanel.onCtrlKPressed()
}
Shortcut {
sequence: "Ctrl+N"
enabled: root.isPanelOpen && (PanelService.openedPanel.onCtrlNPressed !== undefined)
onActivated: PanelService.openedPanel.onCtrlNPressed()
}
Shortcut {
sequence: "Ctrl+P"
enabled: root.isPanelOpen && (PanelService.openedPanel.onCtrlPPressed !== undefined)
onActivated: PanelService.openedPanel.onCtrlPPressed()
}
Shortcut {
sequence: "1"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(1)
}
Shortcut {
sequence: "2"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(2)
}
Shortcut {
sequence: "3"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(3)
}
Shortcut {
sequence: "4"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(4)
}
Shortcut {
sequence: "5"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(5)
}
Shortcut {
sequence: "6"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(6)
}
Shortcut {
sequence: "7"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(7)
}
Shortcut {
sequence: "8"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(8)
}
Shortcut {
sequence: "9"
enabled: root.isPanelOpen && (PanelService.openedPanel.onNumberPressed !== undefined)
onActivated: PanelService.openedPanel.onNumberPressed(9)
}
}