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,136 @@
import qs.config
import qs.modules.components
import qs.modules.functions
import qs.services
import QtQuick
import Quickshell
import Quickshell.Wayland
import QtQuick.Layouts
Item {
id: container
property string displayName: screen?.name ?? ""
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
property Toplevel activeToplevel: Compositor.isWorkspaceOccupied(Compositor.focusedWorkspaceId)
? Compositor.activeToplevel
: null
implicitWidth: row.implicitWidth + 30
implicitHeight: ConfigResolver.bar(displayName).modules.height
function simplifyTitle(title) {
if (!title)
return ""
title = title.replace(/[●⬤○◉◌◎]/g, "") // Symbols to remove
// Normalize separators
title = title
.replace(/\s*[|—]\s*/g, " - ")
.replace(/\s+/g, " ")
.trim()
const parts = title.split(" - ").map(p => p.trim()).filter(Boolean)
if (parts.length === 1)
return parts[0]
// Known app names (extend freely my fellow contributors)
const apps = [
"Firefox", "Mozilla Firefox",
"Chromium", "Google Chrome",
"Neovim", "VS Code", "Code",
"Kitty", "Alacritty", "Terminal",
"Discord", "Spotify", "Steam",
"Settings - Nucleus", "Settings"
]
let app = ""
for (let i = parts.length - 1; i >= 0; i--) { // loop over
for (let a of apps) {
if (parts[i].includes(a)) {
app = a
break
}
}
if (app) break
}
if (!app)
app = parts[parts.length - 1]
const context = parts.find(p => p !== app)
return context ? `${app} · ${context}` : app
}
function formatAppId(appId) { // Random ass function to make it look good
if (!appId || appId.length === 0)
return "";
// split on dashes/underscores
const parts = appId.split(/[-_]/);
// capitalize each segment
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
parts[i] = p.charAt(0).toUpperCase() + p.slice(1);
}
return parts.join("-");
}
/* Column {
id: col
anchors.centerIn: parent
StyledText {
id: workspaceText
font.pixelSize: Metrics.fontSize("smallie")
text: {
if (!activeToplevel)
return "Desktop"
const id = activeToplevel.appId || ""
return id // Just for aesthetics
}
horizontalAlignment: Text.AlignHCenter
}
StyledText {
id: titleText
text: StringUtils.shortText(simplifyTitle(activeToplevel?.title, 24) || `Workspace ${Hyprland.focusedWorkspaceId}`)
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Metrics.fontSize("smalle")
}
} */
Rectangle {
visible: (ConfigResolver.bar(displayName).position === "top" || ConfigResolver.bar(displayName).position === "bottom")
color: Appearance.m3colors.m3paddingContainer
anchors.fill: parent
height: 34
width: row.height + 30
radius: ConfigResolver.bar(displayName).modules.radius
}
RowLayout {
id: row
spacing: 12
anchors.centerIn: parent
MaterialSymbol {
icon: "desktop_windows"
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
}
StyledText {
text: StringUtils.shortText(simplifyTitle(activeToplevel?.title), 24) || `Workspace ${Hyprland.focusedWorkspaceId}`
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance.font.size.small
}
}
}

View File

@@ -0,0 +1,57 @@
import QtQuick
import QtQuick.Layouts
import qs.config
import qs.modules.components
import qs.services
Item {
id: batteryIndicatorModuleContainer
visible: UPower.batteryPresent
Layout.alignment: Qt.AlignVCenter
// Determine if bar is isVertical
property bool isVertical: ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right"
implicitWidth: bgRect.implicitWidth
implicitHeight: bgRect.implicitHeight
Rectangle {
id: bgRect
color: isVertical ? Appearance.m3colors.m3primary : Appearance.m3colors.m3paddingContainer
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius * Config.runtime.appearance.rounding.factor // No need to use metrics here...
implicitWidth: child.implicitWidth + Appearance.margin.large - (isVertical ? 10 : 0)
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
}
RowLayout {
id: child
anchors.centerIn: parent
spacing: isVertical ? 0 : Metrics.spacing(8)
// Icon for isVertical bars
MaterialSymbol {
visible: isVertical
icon: UPower.battIcon
iconSize: Metrics.iconSize(20)
}
// Battery percentage text
StyledText {
animate: false
font.pixelSize: Metrics.fontSize(16)
rotation: isVertical ? 270 : 0
text: (isVertical ? UPower.percentage : UPower.percentage + "%")
}
// Circular progress for horizontal bars
CircularProgressBar {
visible: !isVertical
value: UPower.percentage / 100
icon: UPower.battIcon
iconSize: Metrics.iconSize(18)
Layout.bottomMargin: Metrics.margin(2)
}
}
}

View File

@@ -0,0 +1,27 @@
import QtQuick
import QtQuick.Layouts
import qs.services
import qs.config
import qs.modules.components
Item {
id: clockContainer
property string format: isVertical ? "hh\nmm\nAP" : "hh:mm • dd/MM"
property bool isVertical: (ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right")
Layout.alignment: Qt.AlignVCenter
implicitWidth: 37
implicitHeight: 30
AnimatedImage {
id: art
anchors.fill: parent
source: Directories.assetsPath + "/gifs/bongo-cat.gif"
cache: false // this is important
smooth: true // smooooooth
rotation: isVertical ? 270 : 0
}
}

View File

@@ -0,0 +1,36 @@
import QtQuick
import QtQuick.Layouts
import qs.services
import qs.config
import qs.modules.components
Item {
id: clockContainer
property string format: isVertical ? "hh\nmm\nAP" : "hh:mm • dd/MM"
property bool isVertical: (ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right")
Layout.alignment: Qt.AlignVCenter
implicitWidth: bgRect.implicitWidth
implicitHeight: bgRect.implicitHeight
// Let the layout compute size automatically
Rectangle {
id: bgRect
color: isVertical ? "transparent" : Appearance.m3colors.m3paddingContainer
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius * Config.runtime.appearance.rounding.factor
// Padding around the text
implicitWidth: isVertical ? textItem.implicitWidth + 40 : textItem.implicitWidth + Metrics.margin("large")
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
}
StyledText {
id: textItem
anchors.centerIn: parent
animate: false
text: Time.format(clockContainer.format)
}
}

View File

@@ -0,0 +1,127 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import qs.config
import qs.modules.functions
import qs.modules.components
import qs.services
Item {
id: mediaPlayer
property bool isVertical: (
ConfigResolver.bar(screen?.name ?? "").position === "left" ||
ConfigResolver.bar(screen?.name ?? "").position === "right"
)
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
implicitWidth: bgRect.implicitWidth
implicitHeight: bgRect.implicitHeight
Rectangle {
id: bgRect
color: Appearance.m3colors.m3paddingContainer
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius *
Config.runtime.appearance.rounding.factor
implicitWidth: isVertical
? row.implicitWidth + Metrics.margin("large") - 10
: row.implicitWidth + Metrics.margin("large")
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
}
Row {
id: row
anchors.centerIn: parent
spacing: Metrics.margin("small")
ClippingRectangle {
id: iconButton
width: 24
height: 24
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius / 1.2
color: Appearance.colors.colLayer1Hover
opacity: 0.9
clip: true
layer.enabled: true
Item {
anchors.fill: parent
Image {
id: art
anchors.fill: parent
visible: Mpris.artUrl !== ""
source: Mpris.artUrl
fillMode: Image.PreserveAspectCrop
smooth: true
mipmap: true
}
MaterialSymbol {
anchors.centerIn: parent
visible: Mpris.artUrl === ""
icon: "music_note"
iconSize: 18
color: Config.runtime.appearance.theme === "dark"
? "#b1a4a4"
: "grey"
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: Mpris.playPause()
onEntered: iconButton.opacity = 1
onExited: iconButton.opacity = 0.9
}
RotationAnimation on rotation {
from: 0
to: 360
duration: Metrics.chronoDuration(4000)
loops: Animation.Infinite
running: Mpris.isPlaying &&
Config.runtime.appearance.animations.enabled
}
}
StyledText {
id: textItem
anchors.verticalCenter: parent.verticalCenter
text: StringUtils.shortText(Mpris.title, 16)
visible: !mediaPlayer.isVertical
}
}
}

View File

@@ -0,0 +1,65 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.config
import qs.modules.components
import qs.services
Item {
id: statusIconsContainer
property bool isVertical: (ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right")
Layout.alignment: Qt.AlignVCenter
visible: ConfigResolver.bar(screen?.name ?? "").modules.statusIcons.enabled
implicitWidth: bgRect.implicitWidth
implicitHeight: bgRect.implicitHeight
StyledRect {
id: bgRect
color: Globals.visiblility.sidebarRight ? Appearance.m3colors.m3paddingContainer : "transparent"
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius * Config.runtime.appearance.rounding.factor
implicitWidth: isVertical ? contentRow.implicitWidth + Metrics.margin("large") - 8 : contentRow.implicitWidth + Metrics.margin("large")
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
RowLayout {
id: contentRow
anchors.centerIn: parent
spacing: isVertical ? Metrics.spacing(8) : Metrics.spacing(16)
MaterialSymbol {
id: wifi
animate: false
visible: ConfigResolver.bar(screen?.name ?? "").modules.statusIcons.networkStatusEnabled
rotation: isVertical ? 270 : 0
icon: Network.icon
iconSize: Metrics.fontSize("huge")
}
MaterialSymbol {
id: btIcon
animate: false
visible: ConfigResolver.bar(screen?.name ?? "").modules.statusIcons.bluetoothStatusEnabled
rotation: isVertical ? 270 : 0
icon: Bluetooth.icon
iconSize: Metrics.fontSize("huge")
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (Globals.visiblility.sidebarLeft)
return
Globals.visiblility.sidebarRight = !Globals.visiblility.sidebarRight
}
}
}
}

View File

@@ -0,0 +1,165 @@
import qs.modules.components
import qs.config
import qs.services
import Quickshell.Services.SystemTray
import QtQuick
import Quickshell
import Quickshell.Widgets
import QtQuick.Layouts
Item {
id: root
readonly property Repeater items: items
property bool horizontalMode: (ConfigResolver.bar(screen?.name ?? "").position === "top" || ConfigResolver.bar(screen?.name ?? "").position === "bottom")
clip: true
implicitWidth: layout.implicitWidth + Metrics.margin("verylarge")
implicitHeight: 34
Rectangle {
visible: (items.count > 0) ? 1 : 0
id: padding
implicitHeight: padding.height
anchors.fill: parent
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius
color: "transparent"
}
GridLayout {
id: layout
anchors.centerIn: parent
rows: 1
columns: items.count
rowSpacing: Metrics.spacing(10)
columnSpacing: Metrics.spacing(10)
Repeater {
id: items
model: SystemTray.items
delegate: Item {
id: trayItemRoot
required property SystemTrayItem modelData
implicitWidth: 20
implicitHeight: 20
IconImage {
visible: trayItemRoot.modelData.icon !== ""
source: trayItemRoot.modelData.icon
asynchronous: true
anchors.fill: parent
rotation: root.horizontalMode ? 0 : 270
}
HoverHandler {
id: hover
}
QsMenuOpener {
id: menuOpener
menu: trayItemRoot.modelData.menu
}
StyledPopout {
id: popout
hoverTarget: hover
interactable: true
hCenterOnItem: true
requiresHover: false
Component {
Item {
width: childColumn.implicitWidth
height: childColumn.height
ColumnLayout {
id: childColumn
spacing: Metrics.spacing(5)
Repeater {
model: menuOpener.children
delegate: TrayMenuItem {
parentColumn: childColumn
Layout.preferredWidth: childColumn.width > 0 ? childColumn.width : implicitWidth
}
}
}
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
hoverEnabled: true
onClicked: {
if (popout.isVisible)
popout.hide();
else
popout.show();
}
}
}
}
}
component TrayMenuItem: Item {
id: itemRoot
required property QsMenuEntry modelData
required property ColumnLayout parentColumn
Layout.fillWidth: true
implicitWidth: rowLayout.implicitWidth + 10
implicitHeight: !itemRoot.modelData.isSeparator ? rowLayout.implicitHeight + 10 : 1
MouseArea {
id: hover
hoverEnabled: itemRoot.modelData.enabled
anchors.fill: parent
onClicked: {
if (!itemRoot.modelData.hasChildren)
itemRoot.modelData.triggered();
}
}
Rectangle {
id: itemBg
anchors.fill: parent
opacity: itemRoot.modelData.isSeparator ? 0.5 : 1
color: itemRoot.modelData.isSeparator ? Appearance.m3colors.m3outline : hover.containsMouse ? Appearance.m3colors.m3surfaceContainer : Appearance.m3colors.m3surface
}
RowLayout {
id: rowLayout
visible: !itemRoot.modelData.isSeparator
opacity: itemRoot.modelData.isSeparator ? 0.5 : 1
spacing: Metrics.spacing(5)
anchors {
left: itemBg.left
leftMargin: Metrics.margin(5)
top: itemBg.top
topMargin:Metrics.margin(5)
}
IconImage {
visible: itemRoot.modelData.icon !== ""
source: itemRoot.modelData.icon
width: 15
height: 15
}
StyledText {
text: itemRoot.modelData.text
font.pixelSize: Metrics.fontSize(14)
color: Appearance.m3colors.m3onSurface
}
MaterialSymbol {
visible: itemRoot.modelData.hasChildren
icon: "chevron_right"
iconSize: Metrics.iconSize(16)
color: Appearance.m3colors.m3onSurface
}
}
}
}

View File

@@ -0,0 +1,99 @@
import QtQuick
import QtQuick.Layouts
import qs.config
import qs.modules.components
import qs.services
Item {
id: systemUsageContainer
property string displayName: screen?.name ?? ""
property bool isHorizontal: (ConfigResolver.bar(displayName).position === "top" || ConfigResolver.bar(displayName).position === "bottom")
visible: ConfigResolver.bar(displayName).modules.systemUsage.enabled && haveWidth
Layout.alignment: Qt.AlignVCenter
implicitWidth: bgRect.implicitWidth
implicitHeight: bgRect.implicitHeight
property bool haveWidth:
ConfigResolver.bar(displayName).modules.systemUsage.tempStatsEnabled ||
ConfigResolver.bar(displayName).modules.systemUsage.cpuStatsEnabled ||
ConfigResolver.bar(displayName).modules.systemUsage.memoryStatsEnabled
// Normalize values so UI always receives correct ranges
function normalize(v) {
if (v > 1) return v / 100
return v
}
function percent(v) {
if (v <= 1) return Math.round(v * 100)
return Math.round(v)
}
Rectangle {
id: bgRect
color: Appearance.m3colors.m3paddingContainer
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
implicitWidth: child.implicitWidth + Metrics.margin("large")
implicitHeight: ConfigResolver.bar(displayName).modules.height
}
RowLayout {
id: child
anchors.centerIn: parent
spacing: Metrics.spacing(4)
// CPU
CircularProgressBar {
rotation: !isHorizontal ? 270 : 0
icon: "developer_board"
visible: ConfigResolver.bar(displayName).modules.systemUsage.cpuStatsEnabled
iconSize: Metrics.iconSize(14)
value: normalize(SystemDetails.cpuPercent)
Layout.bottomMargin: Metrics.margin(2)
}
StyledText {
visible: ConfigResolver.bar(displayName).modules.systemUsage.cpuStatsEnabled && isHorizontal
animate: false
text: percent(SystemDetails.cpuPercent) + "%"
}
// RAM
CircularProgressBar {
rotation: !isHorizontal ? 270 : 0
Layout.leftMargin: Metrics.margin(4)
icon: "memory_alt"
visible: ConfigResolver.bar(displayName).modules.systemUsage.memoryStatsEnabled
iconSize: Metrics.iconSize(14)
value: normalize(SystemDetails.ramPercent)
Layout.bottomMargin: Metrics.margin(2)
}
StyledText {
visible: ConfigResolver.bar(displayName).modules.systemUsage.memoryStatsEnabled && isHorizontal
animate: false
text: percent(SystemDetails.ramPercent) + "%"
}
// Temperature
CircularProgressBar {
rotation: !isHorizontal ? 270 : 0
visible: ConfigResolver.bar(displayName).modules.systemUsage.tempStatsEnabled
Layout.leftMargin: Metrics.margin(4)
icon: "device_thermostat"
iconSize: Metrics.iconSize(14)
value: normalize(SystemDetails.cpuTempPercent)
Layout.bottomMargin: Metrics.margin(2)
}
StyledText {
visible: ConfigResolver.bar(displayName).modules.systemUsage.tempStatsEnabled && isHorizontal
animate: false
text: percent(SystemDetails.cpuTempPercent) + "%"
}
}
}

View File

@@ -0,0 +1,43 @@
import qs.config
import qs.modules.components
import QtQuick
import Quickshell
import QtQuick.Layouts
StyledRect {
id: bg
property string icon
property color iconColor: Appearance.syntaxHighlightingTheme
property int iconSize
property bool toggle
property bool transparentBg: false
signal toggled(bool value)
color: (ma.containsMouse && !transparentBg)
? Appearance.m3colors.m3paddingContainer
: "transparent"
radius: Metrics.radius("childish")
implicitWidth: textItem.implicitWidth + 12
implicitHeight: textItem.implicitHeight + 6
MaterialSymbol {
id: textItem
anchors.centerIn: parent
anchors.verticalCenterOffset: 0.4
anchors.horizontalCenterOffset: 0.499
iconSize: bg.iconSize
icon: bg.icon
color: bg.iconColor
}
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
onClicked: bg.toggled(!bg.toggle)
}
}

View File

@@ -0,0 +1,254 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.modules.functions
import qs.services
Item {
id: workspaceContainer
property string displayName: screen?.name ?? ""
property int numWorkspaces: ConfigResolver.bar(displayName).modules.workspaces.workspaceIndicators
property var workspaceOccupied: []
property var occupiedRanges: []
function japaneseNumber(num) {
var kanjiMap = {
"0": "零",
"1": "一",
"2": "二",
"3": "三",
"4": "四",
"5": "五",
"6": "六",
"7": "七",
"8": "八",
"9": "九",
"10": "十"
};
return kanjiMap[num] !== undefined ? kanjiMap[num] : "Number out of range";
}
function updateWorkspaceOccupied() {
const offset = 1;
workspaceOccupied = Array.from({
"length": numWorkspaces
}, (_, i) => {
return Compositor.isWorkspaceOccupied(i + 1);
});
const ranges = [];
let start = -1;
for (let i = 0; i < workspaceOccupied.length; i++) {
if (workspaceOccupied[i]) {
if (start === -1)
start = i;
} else if (start !== -1) {
ranges.push({
"start": start,
"end": i - 1
});
start = -1;
}
}
if (start !== -1)
ranges.push({
"start": start,
"end": workspaceOccupied.length - 1
});
occupiedRanges = ranges;
}
visible: ConfigResolver.bar(displayName).modules.workspaces.enabled
implicitWidth: bg.implicitWidth
implicitHeight: ConfigResolver.bar(displayName).modules.height
Component.onCompleted: updateWorkspaceOccupied()
Connections {
function onStateChanged() {
updateWorkspaceOccupied();
}
target: Compositor
}
Rectangle {
id: bg
color: Appearance.m3colors.m3paddingContainer
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
implicitWidth: workspaceRow.implicitWidth + Metrics.margin("large") - 8
implicitHeight: ConfigResolver.bar(displayName).modules.height
// occupied background highlight
Item {
id: occupiedStretchLayer
anchors.centerIn: workspaceRow
width: workspaceRow.width
height: 26
z: 0
visible: Compositor.require("hyprland") // Hyprland only
Repeater {
model: occupiedRanges
Rectangle {
height: 26
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
color: ColorUtils.mix(Appearance.m3colors.m3tertiary, Appearance.m3colors.m3surfaceContainerLowest)
opacity: 0.8
x: modelData.start * (26 + workspaceRow.spacing)
width: (modelData.end - modelData.start + 1) * 26 + (modelData.end - modelData.start) * workspaceRow.spacing
}
}
}
// workspace highlight
Rectangle {
id: highlight
property int offset: Compositor.require("hyprland") ? 1 : 0
property int index: Math.max(0, Compositor.focusedWorkspaceId - 1 - offset)
property real itemWidth: 26
property real spacing: workspaceRow.spacing
property int highlightIndex: {
if (!Compositor.focusedWorkspaceId)
return 0;
if (Compositor.require("hyprland"))
return Compositor.focusedWorkspaceId - 1;
// Hyprland starts at 2 internally
return Compositor.focusedWorkspaceId - 2; // Niri or default
}
property real targetX: Math.min(highlightIndex, numWorkspaces - 1) * (itemWidth + spacing) + 7.3
property real animatedX1: targetX
property real animatedX2: targetX
x: Math.min(animatedX1, animatedX2)
anchors.verticalCenter: parent.verticalCenter
width: Math.abs(animatedX2 - animatedX1) + itemWidth - 1
height: 24
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
color: Appearance.m3colors.m3tertiary
onTargetXChanged: {
animatedX1 = targetX;
animatedX2 = targetX;
}
Behavior on animatedX1 {
enabled: Config.runtime.appearance.animations.enabled
NumberAnimation {
duration: Metrics.chronoDuration(400)
easing.type: Easing.OutSine
}
}
Behavior on animatedX2 {
enabled: Config.runtime.appearance.animations.enabled
NumberAnimation {
duration: Metrics.chronoDuration(133)
easing.type: Easing.OutSine
}
}
}
RowLayout {
id: workspaceRow
anchors.centerIn: parent
spacing: Metrics.spacing(10)
Repeater {
model: numWorkspaces
Item {
property int wsIndex: index + 1
property bool occupied: Compositor.isWorkspaceOccupied(wsIndex)
property bool focused: wsIndex === Compositor.focusedWorkspaceId
width: 26
height: 26
// Icon container — only used on Hyprland
ClippingRectangle {
id: iconContainer
anchors.centerIn: parent
width: 20
height: 20
color: "transparent"
radius: Appearance.rounding.small
clip: true
IconImage {
id: appIcon
anchors.fill: parent
visible: Compositor.require("hyprland") && ConfigResolver.bar(displayName).modules.workspaces.showAppIcons && occupied
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
source: {
const win = Compositor.focusedWindowForWorkspace(wsIndex);
return win ? AppRegistry.iconForClass(win.class) : "";
}
layer.enabled: true
layer.effect: MultiEffect {
saturation: (Config.runtime.appearance.tintIcons || (Config.runtime.appearance.colors.matugenScheme === "scheme-monochrome" && Config.runtime.appearance.colors.autogenerated) || Config.runtime.appearance.colors.scheme.toLowerCase() === "monochrome") ? -1.0 : 1.0
}
}
}
// Kanji mode — only if not Hyprland
StyledText {
anchors.centerIn: parent
visible: ConfigResolver.bar(displayName).modules.workspaces.showJapaneseNumbers && !ConfigResolver.bar(displayName).modules.workspaces.showAppIcons
text: japaneseNumber(index + 1)
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
}
// Numbers mode — only if not Hyprland
StyledText {
anchors.centerIn: parent
visible: !ConfigResolver.bar(displayName).modules.workspaces.showJapaneseNumbers && !ConfigResolver.bar(displayName).modules.workspaces.showAppIcons
text: index + 1
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
}
// Symbols for unoccupied workspaces — only for Hyprland icons
MaterialSymbol {
property string displayText: Config.runtime.appearance.rounding.factor === 0 ? "crop_square" : "fiber_manual_record"
anchors.centerIn: parent
visible: Compositor.require("hyprland") && ConfigResolver.bar(displayName).modules.workspaces.showAppIcons && !occupied
text: displayText
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
font.pixelSize: Metrics.iconSize(10)
fill: 1
}
MouseArea {
anchors.fill: parent
onClicked: Compositor.changeWorkspace(wsIndex)
}
}
}
}
}
}