quickshell and hyprland additions

This commit is contained in:
2026-03-15 13:56:00 +02:00
parent c9c27d1554
commit 1ad06b82a6
509 changed files with 68371 additions and 19 deletions

View File

@@ -0,0 +1,86 @@
import qs.services
import qs.config
import qs.modules.osd as Osd
import qs.modules.notifications as Notifications
import qs.modules.session as Session
import qs.modules.launcher as Launcher
import qs.modules.dashboard as Dashboard
import qs.modules.bar.popouts as BarPopouts
import qs.modules.utilities as Utilities
import qs.modules.sidebar as Sidebar
import QtQuick
import QtQuick.Shapes
Shape {
id: root
required property Panels panels
required property Item bar
anchors.fill: parent
anchors.margins: Config.border.thickness
anchors.leftMargin: bar.implicitWidth
preferredRendererType: Shape.CurveRenderer
Osd.Background {
wrapper: root.panels.osd
startX: root.width - root.panels.session.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding
}
Notifications.Background {
wrapper: root.panels.notifications
sidebar: sidebar
startX: root.width
startY: 0
}
Session.Background {
wrapper: root.panels.session
startX: root.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding
}
Launcher.Background {
wrapper: root.panels.launcher
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
}
Dashboard.Background {
wrapper: root.panels.dashboard
startX: (root.width - wrapper.width) / 2 - rounding
startY: 0
}
BarPopouts.Background {
wrapper: root.panels.popouts
invertBottomRounding: wrapper.y + wrapper.height + 1 >= root.height
startX: wrapper.x
startY: wrapper.y - rounding * sideRounding
}
Utilities.Background {
wrapper: root.panels.utilities
sidebar: sidebar
startX: root.width
startY: root.height
}
Sidebar.Background {
id: sidebar
wrapper: root.panels.sidebar
panels: root.panels
startX: root.width
startY: root.panels.notifications.height
}
}

View File

@@ -0,0 +1,44 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.services
import qs.config
import QtQuick
import QtQuick.Effects
Item {
id: root
required property Item bar
anchors.fill: parent
StyledRect {
anchors.fill: parent
color: Colours.palette.m3surface
layer.enabled: true
layer.effect: MultiEffect {
maskSource: mask
maskEnabled: true
maskInverted: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
Item {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors.fill: parent
anchors.margins: Config.border.thickness
anchors.leftMargin: root.bar.implicitWidth
radius: Config.border.rounding
}
}
}

View File

@@ -0,0 +1,181 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.components.containers
import qs.services
import qs.config
import qs.utils
import qs.modules.bar
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick
import QtQuick.Effects
Variants {
model: Quickshell.screens
Scope {
id: scope
required property ShellScreen modelData
readonly property bool barDisabled: Strings.testRegexList(Config.bar.excludedScreens, modelData.name)
Exclusions {
screen: scope.modelData
bar: bar
}
StyledWindow {
id: win
readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) ?? false
readonly property int dragMaskPadding: {
if (focusGrab.active || panels.popouts.isDetached)
return 0;
const mon = Hypr.monitorFor(screen);
if (mon?.lastIpcObject?.specialWorkspace?.name || mon?.activeWorkspace?.lastIpcObject?.windows > 0)
return 0;
const thresholds = [];
for (const panel of ["dashboard", "launcher", "session", "sidebar"])
if (Config[panel].enabled)
thresholds.push(Config[panel].dragThreshold);
return Math.max(...thresholds);
}
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.session = false;
visibilities.dashboard = false;
}
screen: scope.modelData
name: "drawers"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.session ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
mask: Region {
x: bar.implicitWidth + win.dragMaskPadding
y: Config.border.thickness + win.dragMaskPadding
width: win.width - bar.implicitWidth - Config.border.thickness - win.dragMaskPadding * 2
height: win.height - Config.border.thickness * 2 - win.dragMaskPadding * 2
intersection: Intersection.Xor
regions: regions.instances
}
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
Variants {
id: regions
model: panels.children
Region {
required property Item modelData
x: modelData.x + bar.implicitWidth
y: modelData.y + Config.border.thickness
width: modelData.width
height: modelData.height
intersection: Intersection.Subtract
}
}
HyprlandFocusGrab {
id: focusGrab
active: (visibilities.launcher && Config.launcher.enabled) || (visibilities.session && Config.session.enabled) || (visibilities.sidebar && Config.sidebar.enabled) || (!Config.dashboard.showOnHover && visibilities.dashboard && Config.dashboard.enabled) || (panels.popouts.currentName.startsWith("traymenu") && panels.popouts.current?.depth > 1)
windows: [win]
onCleared: {
visibilities.launcher = false;
visibilities.session = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
panels.popouts.hasCurrent = false;
bar.closeTray();
}
}
StyledRect {
anchors.fill: parent
opacity: visibilities.session && Config.session.enabled ? 0.5 : 0
color: Colours.palette.m3scrim
Behavior on opacity {
Anim {}
}
}
Item {
anchors.fill: parent
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 15
shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7)
}
Border {
bar: bar
}
Backgrounds {
panels: panels
bar: bar
}
}
PersistentProperties {
id: visibilities
property bool bar
property bool osd
property bool session
property bool launcher
property bool dashboard
property bool utilities
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
Interactions {
screen: scope.modelData
popouts: panels.popouts
visibilities: visibilities
panels: panels
bar: bar
Panels {
id: panels
screen: scope.modelData
visibilities: visibilities
bar: bar
}
BarWrapper {
id: bar
anchors.top: parent.top
anchors.bottom: parent.bottom
screen: scope.modelData
visibilities: visibilities
popouts: panels.popouts
disabled: scope.barDisabled
Component.onCompleted: Visibilities.bars.set(scope.modelData, this)
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
pragma ComponentBehavior: Bound
import qs.components.containers
import qs.config
import Quickshell
import QtQuick
Scope {
id: root
required property ShellScreen screen
required property Item bar
ExclusionZone {
anchors.left: true
exclusiveZone: root.bar.exclusiveZone
}
ExclusionZone {
anchors.top: true
}
ExclusionZone {
anchors.right: true
}
ExclusionZone {
anchors.bottom: true
}
component ExclusionZone: StyledWindow {
screen: root.screen
name: "border-exclusion"
exclusiveZone: Config.border.thickness
mask: Region {}
implicitWidth: 1
implicitHeight: 1
}
}

View File

@@ -0,0 +1,274 @@
import qs.components.controls
import qs.config
import qs.modules.bar.popouts as BarPopouts
import Quickshell
import QtQuick
CustomMouseArea {
id: root
required property ShellScreen screen
required property BarPopouts.Wrapper popouts
required property PersistentProperties visibilities
required property Panels panels
required property Item bar
property point dragStart
property bool dashboardShortcutActive
property bool osdShortcutActive
property bool utilitiesShortcutActive
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = Config.border.thickness + panel.y;
return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
}
function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = bar.implicitWidth + panel.x;
return x >= panelX - Config.border.rounding && x <= panelX + panel.width + Config.border.rounding;
}
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < bar.implicitWidth + panel.x + panel.width && withinPanelHeight(panel, x, y);
}
function inRightPanel(panel: Item, x: real, y: real): bool {
return x > bar.implicitWidth + panel.x && withinPanelHeight(panel, x, y);
}
function inTopPanel(panel: Item, x: real, y: real): bool {
return y < Config.border.thickness + panel.y + panel.height && withinPanelWidth(panel, x, y);
}
function inBottomPanel(panel: Item, x: real, y: real): bool {
return y > root.height - Config.border.thickness - panel.height - Config.border.rounding && withinPanelWidth(panel, x, y);
}
function onWheel(event: WheelEvent): void {
if (event.x < bar.implicitWidth) {
bar.handleWheel(event.y, event.angleDelta);
}
}
anchors.fill: parent
hoverEnabled: true
onPressed: event => dragStart = Qt.point(event.x, event.y)
onContainsMouseChanged: {
if (!containsMouse) {
// Only hide if not activated by shortcut
if (!osdShortcutActive) {
visibilities.osd = false;
root.panels.osd.hovered = false;
}
if (!dashboardShortcutActive)
visibilities.dashboard = false;
if (!utilitiesShortcutActive)
visibilities.utilities = false;
if (!popouts.currentName.startsWith("traymenu") || (popouts.current?.depth ?? 0) <= 1) {
popouts.hasCurrent = false;
bar.closeTray();
}
if (Config.bar.showOnHover)
bar.isHovered = false;
}
}
onPositionChanged: event => {
if (popouts.isDetached)
return;
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
// Show bar in non-exclusive mode on hover
if (!visibilities.bar && Config.bar.showOnHover && x < bar.implicitWidth)
bar.isHovered = true;
// Show/hide bar on drag
if (pressed && dragStart.x < bar.implicitWidth) {
if (dragX > Config.bar.dragThreshold)
visibilities.bar = true;
else if (dragX < -Config.bar.dragThreshold)
visibilities.bar = false;
}
if (panels.sidebar.width === 0) {
// Show osd on hover
const showOsd = inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x;
// Show/hide session on drag
if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
if (dragX < -Config.session.dragThreshold)
visibilities.session = true;
else if (dragX > Config.session.dragThreshold)
visibilities.session = false;
// Show sidebar on drag if in session area and session is nearly fully visible
if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
visibilities.sidebar = true;
} else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// Show sidebar on drag if not in session area
visibilities.sidebar = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
// Show/hide session on drag
if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
if (dragX < -Config.session.dragThreshold)
visibilities.session = true;
else if (dragX > Config.session.dragThreshold)
visibilities.session = false;
}
// Hide sidebar on drag
if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
visibilities.sidebar = false;
}
// Show launcher on hover, or show/hide on drag if hover is disabled
if (Config.launcher.showOnHover) {
if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y))
visibilities.launcher = true;
} else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
if (dragY < -Config.launcher.dragThreshold)
visibilities.launcher = true;
else if (dragY > Config.launcher.dragThreshold)
visibilities.launcher = false;
}
// Show dashboard on hover
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!dashboardShortcutActive) {
visibilities.dashboard = showDashboard;
} else if (showDashboard) {
// If hovering over dashboard area while in shortcut mode, transition to hover control
dashboardShortcutActive = false;
}
// Show/hide dashboard on drag (for touchscreen devices)
if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
if (dragY > Config.dashboard.dragThreshold)
visibilities.dashboard = true;
else if (dragY < -Config.dashboard.dragThreshold)
visibilities.dashboard = false;
}
// Show utilities on hover
const showUtilities = inBottomPanel(panels.utilities, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!utilitiesShortcutActive) {
visibilities.utilities = showUtilities;
} else if (showUtilities) {
// If hovering over utilities area while in shortcut mode, transition to hover control
utilitiesShortcutActive = false;
}
// Show popouts on hover
if (x < bar.implicitWidth) {
bar.checkPopout(y);
} else if ((!popouts.currentName.startsWith("traymenu") || (popouts.current?.depth ?? 0) <= 1) && !inLeftPanel(panels.popouts, x, y)) {
popouts.hasCurrent = false;
bar.closeTray();
}
}
// Monitor individual visibility changes
Connections {
target: root.visibilities
function onLauncherChanged() {
// If launcher is hidden, clear shortcut flags for dashboard and OSD
if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false;
root.osdShortcutActive = false;
root.utilitiesShortcutActive = false;
// Also hide dashboard and OSD if they're not being hovered
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.visibilities.dashboard = false;
}
if (!inOsdArea) {
root.visibilities.osd = false;
root.panels.osd.hovered = false;
}
}
}
function onDashboardChanged() {
if (root.visibilities.dashboard) {
// Dashboard became visible, immediately check if this should be shortcut mode
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
} else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false;
}
}
function onOsdChanged() {
if (root.visibilities.osd) {
// OSD became visible, immediately check if this should be shortcut mode
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false;
}
}
function onUtilitiesChanged() {
if (root.visibilities.utilities) {
// Utilities became visible, immediately check if this should be shortcut mode
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true;
}
} else {
// Utilities hidden, clear shortcut flag
root.utilitiesShortcutActive = false;
}
}
}
}

View File

@@ -0,0 +1,136 @@
import qs.config
import qs.modules.osd as Osd
import qs.modules.notifications as Notifications
import qs.modules.session as Session
import qs.modules.launcher as Launcher
import qs.modules.dashboard as Dashboard
import qs.modules.bar.popouts as BarPopouts
import qs.modules.utilities as Utilities
import qs.modules.utilities.toasts as Toasts
import qs.modules.sidebar as Sidebar
import Quickshell
import QtQuick
Item {
id: root
required property ShellScreen screen
required property PersistentProperties visibilities
required property Item bar
readonly property alias osd: osd
readonly property alias notifications: notifications
readonly property alias session: session
readonly property alias launcher: launcher
readonly property alias dashboard: dashboard
readonly property alias popouts: popouts
readonly property alias utilities: utilities
readonly property alias toasts: toasts
readonly property alias sidebar: sidebar
anchors.fill: parent
anchors.margins: Config.border.thickness
anchors.leftMargin: bar.implicitWidth
Osd.Wrapper {
id: osd
clip: session.width > 0 || sidebar.width > 0
screen: root.screen
visibilities: root.visibilities
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: session.width + sidebar.width
}
Notifications.Wrapper {
id: notifications
visibilities: root.visibilities
panels: root
anchors.top: parent.top
anchors.right: parent.right
}
Session.Wrapper {
id: session
clip: sidebar.width > 0
visibilities: root.visibilities
panels: root
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: sidebar.width
}
Launcher.Wrapper {
id: launcher
screen: root.screen
visibilities: root.visibilities
panels: root
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
}
Dashboard.Wrapper {
id: dashboard
visibilities: root.visibilities
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
}
BarPopouts.Wrapper {
id: popouts
screen: root.screen
x: isDetached ? (root.width - nonAnimWidth) / 2 : 0
y: {
if (isDetached)
return (root.height - nonAnimHeight) / 2;
const off = currentCenter - Config.border.thickness - nonAnimHeight / 2;
const diff = root.height - Math.floor(off + nonAnimHeight);
if (diff < 0)
return off + diff;
return Math.max(off, 0);
}
}
Utilities.Wrapper {
id: utilities
visibilities: root.visibilities
sidebar: sidebar
popouts: popouts
anchors.bottom: parent.bottom
anchors.right: parent.right
}
Toasts.Toasts {
id: toasts
anchors.bottom: sidebar.visible ? parent.bottom : utilities.top
anchors.right: sidebar.left
anchors.margins: Appearance.padding.normal
}
Sidebar.Wrapper {
id: sidebar
visibilities: root.visibilities
panels: root
anchors.top: notifications.bottom
anchors.bottom: utilities.top
anchors.right: parent.right
}
}