import Quickshell import Quickshell.Io import Quickshell.Services.SystemTray import Quickshell.Widgets import Custom as Custom import QtQuick import qs.Services PanelWindow { id: bar property var modelData anchors { top: true left: true right: true } margins { left: 26 + 8 right: 26 + 8 } screen: modelData implicitHeight: 21 color: "transparent" Rectangle { color: Qt.rgba(0, 0, 0, 0.66) anchors.fill: parent // bottomLeftRadius: 8 // bottomRightRadius: 8 } Row { id: left height: parent.height width: childrenRect.width anchors.left: parent.left anchors.leftMargin: 8 anchors.verticalCenter: parent.verticalCenter spacing: 8 Row { id: workspaces property var ignoreWorkspaces: @ignore_workspaces@ height: parent.height anchors.verticalCenter: parent.verticalCenter spacing: 0 Repeater { model: { let currWorkspaces = NiriService.workspaces; const ignoreWorkspaces = Array.from(workspaces.ignoreWorkspaces); currWorkspaces = currWorkspaces.filter(ws => ws.is_active || ignoreWorkspaces.every(iws => iws !== ws.name)); currWorkspaces.sort((a, b) => { if (NiriService.outputs?.[a.output]?.logical?.x !== NiriService.outputs?.[b.output]?.logical?.x) return NiriService.outputs?.[a.output]?.logical?.x - NiriService.outputs?.[b.output]?.logical?.x if (NiriService.outputs?.[a.output]?.logical?.y !== NiriService.outputs?.[b.output]?.logical?.y) return NiriService.outputs?.[a.output]?.logical?.y - NiriService.outputs?.[b.output]?.logical?.y return a.idx - b.idx; }); return currWorkspaces; } Rectangle { property var workspaceData: modelData width: wsLabel.contentWidth + 8 color: { if (mouseArea.containsMouse) { return "#33808080"; } return "transparent"; } height: parent.height anchors.verticalCenter: parent.verticalCenter MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor enabled: true onClicked: { NiriService.sendCommand({ "Action": { "FocusWorkspace": { "reference": { "Id": workspaceData.id } } } }, _ => {}) } } Text { id: wsLabel font.pointSize: 10 font.family: "Fira Sans" color: { if (workspaceData.is_active) return "#23fd00"; if (workspaceData.active_window_id === null) return "#555"; return "white"; } anchors.centerIn: parent text: workspaceData.name ? workspaceData.name : workspaceData.idx } } } } } Row { id: center height: parent.height width: childrenRect.width anchors.centerIn: parent spacing: 5 Item { id: activeWindowDisplay property var activeWindow: { let currWindowId = Array.from(NiriService.workspaces).find(ws => { return ws.output === bar.screen.name && ws.is_active; })?.active_window_id; return currWindowId ? Array.from(NiriService.windows).find(win => win.id == currWindowId) : null; } property var windowEntry: activeWindow ? DesktopEntries.heuristicLookup(activeWindow.app_id) : null anchors.verticalCenter: parent.verticalCenter width: activeWindowDisplayContent.width height: parent.height Row { id: activeWindowDisplayContent width: childrenRect.width height: parent.height anchors.verticalCenter: parent.verticalCenter spacing: 8 IconImage { id: activeWindowIcon height: 14 width: 14 anchors.verticalCenter: parent.verticalCenter source: { let icon = activeWindowDisplay.windowEntry?.icon if (typeof icon === 'string' || icon instanceof String) { if (icon.includes("?path=")) { const split = icon.split("?path=") if (split.length !== 2) return icon const name = split[0] const path = split[1] const fileName = name.substring( name.lastIndexOf("/") + 1) return `file://${path}/${fileName}` } else icon = Quickshell.iconPath(icon); return icon } return "" } asynchronous: true smooth: true mipmap: true } Text { id: windowTitle width: Math.min(implicitWidth, bar.width - 2*Math.max(left.width, right.width) - 2*8 - activeWindowIcon.width - activeWindowDisplayContent.spacing) property var appAliases: { "Firefox": "Mozilla Firefox", "mpv Media Player": "mpv", "Thunderbird": "Mozilla Thunderbird", "Thunderbird (LMU)": "Mozilla Thunderbird" } elide: Text.ElideRight maximumLineCount: 1 color: "white" anchors.verticalCenter: parent.verticalCenter text: { if (!activeWindowDisplay.activeWindow) return ""; var title = activeWindowDisplay.activeWindow.title; var appName = activeWindowDisplay.windowEntry?.name; if (appAliases[appName]) appName = appAliases[appName]; if (appName && title.endsWith(appName)) { const oldTitle = title; title = title.substring(0, title.length - appName.length); title = title.replace(/\s*(—|-)\s*$/, ""); if (!title) title = oldTitle; } return title; } } } } } Row { id: right height: parent.height width: childrenRect.width anchors.right: parent.right anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter spacing: 0 Item { anchors.verticalCenter: parent.verticalCenter width: systemTrayRow.childrenRect.width height: parent.height clip: true Row { id: systemTrayRow anchors.centerIn: parent width: childrenRect.width height: parent.height spacing: 0 Repeater { model: SystemTray.items.values delegate: Item { property var trayItem: modelData property string iconSource: { let icon = trayItem && trayItem.icon if (typeof icon === 'string' || icon instanceof String) { if (icon.includes("?path=")) { const split = icon.split("?path=") if (split.length !== 2) return icon const name = split[0] const path = split[1] const fileName = name.substring( name.lastIndexOf("/") + 1) return `file://${path}/${fileName}` } return icon } return "" } width: 16 height: parent.height anchors.verticalCenter: parent.verticalCenter IconImage { anchors.centerIn: parent width: parent.width height: parent.width source: parent.iconSource asynchronous: true smooth: true mipmap: true } MouseArea { id: trayItemArea anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: mouse => { if (!trayItem) return if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) { trayItem.activate() return } if (trayItem.hasMenu) { var globalPos = mapToGlobal(0, 0) var currentScreen = screen || Screen var screenX = currentScreen.x || 0 var relativeX = globalPos.x - screenX menuAnchor.menu = trayItem.menu menuAnchor.anchor.window = bar menuAnchor.anchor.rect = Qt.rect( relativeX, 21, parent.width, 1) menuAnchor.open() } } } } } } QsMenuAnchor { id: menuAnchor } } Rectangle { height: parent.height width: 4 color: "transparent" } Rectangle { id: kbdWidget property var keyboardAbbrev: { "English (programmer Dvorak)": "dvp", "English (US)": "us" } width: kbdLabel.contentWidth + 8 color: { if (kbdMouseArea.containsMouse) { return "#33808080"; } return "transparent"; } height: parent.height anchors.verticalCenter: parent.verticalCenter MouseArea { id: kbdMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor enabled: true onClicked: { NiriService.sendCommand({ "Action": { "SwitchLayout": { "layout": "Next" } } }, _ => {}) } } Text { id: kbdLabel font.pointSize: 10 font.family: "Fira Sans" color: { if (NiriService.keyboardLayouts?.current_idx === 0) return "#555"; return "white"; } anchors.centerIn: parent text: { const currentLayout = NiriService.keyboardLayouts?.names?.[NiriService.keyboardLayouts.current_idx]; return kbdWidget.keyboardAbbrev[currentLayout] ? kbdWidget.keyboardAbbrev[currentLayout] : currentLayout; } } } Rectangle { height: parent.height width: 4 color: "transparent" } Text { id: clock color: "white" anchors.verticalCenter: parent.verticalCenter Custom.Chrono { id: chrono format: "W{0:%V-%u} {0:%F} {0:%H:%M:%S%Ez}" } text: chrono.date font.pointSize: 10 font.family: "Fira Sans" font.features: { "tnum": 1 } } } }