Bring backs most services
This commit is contained in:
parent
71cb79b08b
commit
ec3bff68ac
16 changed files with 1038 additions and 13 deletions
0
Modules/Background/Background.qml
Normal file
0
Modules/Background/Background.qml
Normal file
0
Modules/Background/Overview.qml
Normal file
0
Modules/Background/Overview.qml
Normal file
0
Modules/Background/WallpaperPicker.qml
Normal file
0
Modules/Background/WallpaperPicker.qml
Normal file
|
|
@ -25,7 +25,9 @@ PanelWindow {
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
// Background fill
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bar
|
id: bar
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -35,9 +37,10 @@ PanelWindow {
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: leftSection
|
id: leftSection
|
||||||
anchors.left: bar.left
|
height: parent.height
|
||||||
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Style.marginMedium * scaling
|
anchors.leftMargin: Style.marginMedium * scaling
|
||||||
anchors.verticalCenter: bar.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Style.marginMedium * scaling
|
spacing: Style.marginMedium * scaling
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
|
|
@ -47,9 +50,11 @@ PanelWindow {
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: centerSection
|
id: centerSection
|
||||||
|
height: parent.height
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.verticalCenter: bar.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Style.marginMedium * scaling
|
spacing: Style.marginMedium * scaling
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: "Center"
|
text: "Center"
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +62,7 @@ PanelWindow {
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: rightSection
|
id: rightSection
|
||||||
|
height: parent.height
|
||||||
anchors.right: bar.right
|
anchors.right: bar.right
|
||||||
anchors.rightMargin: Style.marginMedium * scaling
|
anchors.rightMargin: Style.marginMedium * scaling
|
||||||
anchors.verticalCenter: bar.verticalCenter
|
anchors.verticalCenter: bar.verticalCenter
|
||||||
|
|
@ -65,6 +70,7 @@ PanelWindow {
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: "Right"
|
text: "Right"
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Clock {}
|
Clock {}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ NPanel {
|
||||||
border.color: Colors.backgroundTertiary
|
border.color: Colors.backgroundTertiary
|
||||||
border.width: Math.min(1, Style.borderMedium * scaling)
|
border.width: Math.min(1, Style.borderMedium * scaling)
|
||||||
width: 500 * scaling
|
width: 500 * scaling
|
||||||
height: 300
|
height: 400
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,15 +33,15 @@ NPanel {
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 16 * scaling
|
anchors.margins: Style.marginXL * scaling
|
||||||
spacing: 12 * scaling
|
spacing: Style.marginSmall * scaling
|
||||||
|
|
||||||
|
|
||||||
// NIconButton
|
// NIconButton
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 16 * scaling
|
spacing: 16 * scaling
|
||||||
NText {
|
NText {
|
||||||
text: "NIconButton"
|
text: "NIconButton"
|
||||||
|
color: Colors.accentSecondary
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
|
|
@ -54,15 +54,18 @@ NPanel {
|
||||||
myTooltip.hide();
|
myTooltip.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NDivider {Layout.fillWidth: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// NToggle
|
// NToggle
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 16 * scaling
|
spacing: Style.marginLarge * scaling
|
||||||
uniformCellSizes: true
|
uniformCellSizes: true
|
||||||
NText {
|
NText {
|
||||||
text: "NToggle + NTooltip"
|
text: "NToggle + NTooltip"
|
||||||
|
color: Colors.accentSecondary
|
||||||
}
|
}
|
||||||
|
|
||||||
NToggle {
|
NToggle {
|
||||||
|
|
@ -79,16 +82,24 @@ NPanel {
|
||||||
positionAbove: false
|
positionAbove: false
|
||||||
text: "Hello world"
|
text: "Hello world"
|
||||||
}
|
}
|
||||||
|
NDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NSlider
|
// NSlider
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 16 * scaling
|
spacing: 16 * scaling
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: "NSlider"
|
text: "NSlider"
|
||||||
|
color: Colors.accentSecondary
|
||||||
}
|
}
|
||||||
|
|
||||||
NSlider {}
|
NSlider {}
|
||||||
|
NDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
Services/Location.qml
Normal file
2
Services/Location.qml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Weather logic and caching
|
||||||
|
// Calendar Hollidays logic and caching
|
||||||
169
Services/MediaPlayer.qml
Normal file
169
Services/MediaPlayer.qml
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
// pragma Singleton
|
||||||
|
|
||||||
|
// import QtQuick
|
||||||
|
// import Quickshell
|
||||||
|
// import Quickshell.Services.Mpris
|
||||||
|
// import qs.Services
|
||||||
|
|
||||||
|
// Singleton {
|
||||||
|
// id: manager
|
||||||
|
|
||||||
|
// property var currentPlayer: null
|
||||||
|
// property real currentPosition: 0
|
||||||
|
// property int selectedPlayerIndex: 0
|
||||||
|
// property bool isPlaying: currentPlayer ? currentPlayer.isPlaying : false
|
||||||
|
// property string trackTitle: currentPlayer ? (currentPlayer.trackTitle
|
||||||
|
// || "Unknown Track") : ""
|
||||||
|
// property string trackArtist: currentPlayer ? (currentPlayer.trackArtist
|
||||||
|
// || "Unknown Artist") : ""
|
||||||
|
// property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum
|
||||||
|
// || "Unknown Album") : ""
|
||||||
|
// property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl
|
||||||
|
// || "") : ""
|
||||||
|
// property real trackLength: currentPlayer ? currentPlayer.length : 0
|
||||||
|
// property bool canPlay: currentPlayer ? currentPlayer.canPlay : false
|
||||||
|
// property bool canPause: currentPlayer ? currentPlayer.canPause : false
|
||||||
|
// property bool canGoNext: currentPlayer ? currentPlayer.canGoNext : false
|
||||||
|
// property bool canGoPrevious: currentPlayer ? currentPlayer.canGoPrevious : false
|
||||||
|
// property bool canSeek: currentPlayer ? currentPlayer.canSeek : false
|
||||||
|
// property bool hasPlayer: getAvailablePlayers().length > 0
|
||||||
|
|
||||||
|
// Item {
|
||||||
|
// Component.onCompleted: {
|
||||||
|
// updateCurrentPlayer()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function getAvailablePlayers() {
|
||||||
|
// if (!Mpris.players || !Mpris.players.values) {
|
||||||
|
// return []
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let allPlayers = Mpris.players.values
|
||||||
|
// let controllablePlayers = []
|
||||||
|
|
||||||
|
// for (var i = 0; i < allPlayers.length; i++) {
|
||||||
|
// let player = allPlayers[i]
|
||||||
|
// if (player && player.canControl) {
|
||||||
|
// controllablePlayers.push(player)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return controllablePlayers
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function findActivePlayer() {
|
||||||
|
// let availablePlayers = getAvailablePlayers()
|
||||||
|
// if (availablePlayers.length === 0) {
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (selectedPlayerIndex < availablePlayers.length) {
|
||||||
|
// return availablePlayers[selectedPlayerIndex]
|
||||||
|
// } else {
|
||||||
|
// selectedPlayerIndex = 0
|
||||||
|
// return availablePlayers[0]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Switch to the most recently active player
|
||||||
|
// function updateCurrentPlayer() {
|
||||||
|
// let newPlayer = findActivePlayer()
|
||||||
|
// if (newPlayer !== currentPlayer) {
|
||||||
|
// currentPlayer = newPlayer
|
||||||
|
// currentPosition = currentPlayer ? currentPlayer.position : 0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function playPause() {
|
||||||
|
// if (currentPlayer) {
|
||||||
|
// if (currentPlayer.isPlaying) {
|
||||||
|
// currentPlayer.pause()
|
||||||
|
// } else {
|
||||||
|
// currentPlayer.play()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function play() {
|
||||||
|
// if (currentPlayer && currentPlayer.canPlay) {
|
||||||
|
// currentPlayer.play()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function pause() {
|
||||||
|
// if (currentPlayer && currentPlayer.canPause) {
|
||||||
|
// currentPlayer.pause()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function next() {
|
||||||
|
// if (currentPlayer && currentPlayer.canGoNext) {
|
||||||
|
// currentPlayer.next()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function previous() {
|
||||||
|
// if (currentPlayer && currentPlayer.canGoPrevious) {
|
||||||
|
// currentPlayer.previous()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function seek(position) {
|
||||||
|
// if (currentPlayer && currentPlayer.canSeek) {
|
||||||
|
// currentPlayer.position = position
|
||||||
|
// currentPosition = position
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Seek to position based on ratio (0.0 to 1.0)
|
||||||
|
// function seekByRatio(ratio) {
|
||||||
|
// if (currentPlayer && currentPlayer.canSeek && currentPlayer.length > 0) {
|
||||||
|
// let seekPosition = ratio * currentPlayer.length
|
||||||
|
// currentPlayer.position = seekPosition
|
||||||
|
// currentPosition = seekPosition
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Update progress bar every second while playing
|
||||||
|
// Timer {
|
||||||
|
// id: positionTimer
|
||||||
|
// interval: 1000
|
||||||
|
// running: currentPlayer && currentPlayer.isPlaying
|
||||||
|
// && currentPlayer.length > 0
|
||||||
|
// && currentPlayer.playbackState === MprisPlaybackState.Playing
|
||||||
|
// repeat: true
|
||||||
|
// onTriggered: {
|
||||||
|
// if (currentPlayer && currentPlayer.isPlaying
|
||||||
|
// && currentPlayer.playbackState === MprisPlaybackState.Playing) {
|
||||||
|
// currentPosition = currentPlayer.position
|
||||||
|
// } else {
|
||||||
|
// running = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Reset position when switching to inactive player
|
||||||
|
// onCurrentPlayerChanged: {
|
||||||
|
// if (!currentPlayer || !currentPlayer.isPlaying
|
||||||
|
// || currentPlayer.playbackState !== MprisPlaybackState.Playing) {
|
||||||
|
// currentPosition = 0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Update current player when available players change
|
||||||
|
// Connections {
|
||||||
|
// target: Mpris.players
|
||||||
|
// function onValuesChanged() {
|
||||||
|
// updateCurrentPlayer()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Cava {
|
||||||
|
// id: cava
|
||||||
|
// count: 44
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Expose cava values
|
||||||
|
// property alias cavaValues: cava.values
|
||||||
|
// }
|
||||||
345
Services/Network.qml
Normal file
345
Services/Network.qml
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var networks: ({})
|
||||||
|
property string connectingSsid: ""
|
||||||
|
property string connectStatus: ""
|
||||||
|
property string connectStatusSsid: ""
|
||||||
|
property string connectError: ""
|
||||||
|
property string detectedInterface: ""
|
||||||
|
|
||||||
|
function signalIcon(signal) {
|
||||||
|
if (signal >= 80)
|
||||||
|
return "network_wifi"
|
||||||
|
if (signal >= 60)
|
||||||
|
return "network_wifi_3_bar"
|
||||||
|
if (signal >= 40)
|
||||||
|
return "network_wifi_2_bar"
|
||||||
|
if (signal >= 20)
|
||||||
|
return "network_wifi_1_bar"
|
||||||
|
return "wifi_0_bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSecured(security) {
|
||||||
|
return security && security.trim() !== "" && security.trim() !== "--"
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshNetworks() {
|
||||||
|
existingNetwork.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectNetwork(ssid, security) {
|
||||||
|
pendingConnect = {
|
||||||
|
"ssid": ssid,
|
||||||
|
"security": security,
|
||||||
|
"password": ""
|
||||||
|
}
|
||||||
|
doConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitPassword(ssid, password) {
|
||||||
|
pendingConnect = {
|
||||||
|
"ssid": ssid,
|
||||||
|
"security": networks[ssid].security,
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
doConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnectNetwork(ssid) {
|
||||||
|
disconnectProfileProcess.connectionName = ssid
|
||||||
|
disconnectProfileProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
property var pendingConnect: null
|
||||||
|
|
||||||
|
function doConnect() {
|
||||||
|
const params = pendingConnect
|
||||||
|
if (!params)
|
||||||
|
return
|
||||||
|
|
||||||
|
connectingSsid = params.ssid
|
||||||
|
connectStatus = ""
|
||||||
|
connectStatusSsid = params.ssid
|
||||||
|
|
||||||
|
const targetNetwork = networks[params.ssid]
|
||||||
|
|
||||||
|
if (targetNetwork && targetNetwork.existing) {
|
||||||
|
upConnectionProcess.profileName = params.ssid
|
||||||
|
upConnectionProcess.running = true
|
||||||
|
pendingConnect = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.security && params.security !== "--") {
|
||||||
|
getInterfaceProcess.running = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
connectProcess.security = params.security
|
||||||
|
connectProcess.ssid = params.ssid
|
||||||
|
connectProcess.password = params.password
|
||||||
|
connectProcess.running = true
|
||||||
|
pendingConnect = null
|
||||||
|
}
|
||||||
|
|
||||||
|
property int refreshInterval: 25000
|
||||||
|
|
||||||
|
// Only refresh when we have an active connection
|
||||||
|
property bool hasActiveConnection: {
|
||||||
|
for (const net in networks) {
|
||||||
|
if (networks[net].connected) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
property Timer refreshTimer: Timer {
|
||||||
|
interval: root.refreshInterval
|
||||||
|
// Only run timer when we're connected to a network
|
||||||
|
running: root.hasActiveConnection
|
||||||
|
repeat: true
|
||||||
|
onTriggered: root.refreshNetworks()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force a refresh when menu is opened
|
||||||
|
function onMenuOpened() {
|
||||||
|
refreshNetworks()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMenuClosed() {// No need to do anything special on close
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process disconnectProfileProcess: Process {
|
||||||
|
id: disconnectProfileProcess
|
||||||
|
property string connectionName: ""
|
||||||
|
running: false
|
||||||
|
command: ["nmcli", "connection", "down", connectionName]
|
||||||
|
onRunningChanged: {
|
||||||
|
if (!running) {
|
||||||
|
root.refreshNetworks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process existingNetwork: Process {
|
||||||
|
id: existingNetwork
|
||||||
|
running: false
|
||||||
|
command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const lines = text.split("\n")
|
||||||
|
const networksMap = {}
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length; ++i) {
|
||||||
|
const line = lines[i].trim()
|
||||||
|
if (!line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
const parts = line.split(":")
|
||||||
|
if (parts.length < 2) {
|
||||||
|
console.warn("Malformed nmcli output line:", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssid = parts[0]
|
||||||
|
const type = parts[1]
|
||||||
|
|
||||||
|
if (ssid) {
|
||||||
|
networksMap[ssid] = {
|
||||||
|
"ssid": ssid,
|
||||||
|
"type": type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scanProcess.existingNetwork = networksMap
|
||||||
|
scanProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process scanProcess: Process {
|
||||||
|
id: scanProcess
|
||||||
|
running: false
|
||||||
|
command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"]
|
||||||
|
|
||||||
|
property var existingNetwork
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const lines = text.split("\n")
|
||||||
|
const networksMap = {}
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length; ++i) {
|
||||||
|
const line = lines[i].trim()
|
||||||
|
if (!line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
const parts = line.split(":")
|
||||||
|
if (parts.length < 4) {
|
||||||
|
console.warn("Malformed nmcli output line:", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const ssid = parts[0]
|
||||||
|
const security = parts[1]
|
||||||
|
const signal = parseInt(parts[2])
|
||||||
|
const inUse = parts[3] === "*"
|
||||||
|
|
||||||
|
if (ssid) {
|
||||||
|
if (!networksMap[ssid]) {
|
||||||
|
networksMap[ssid] = {
|
||||||
|
"ssid": ssid,
|
||||||
|
"security": security,
|
||||||
|
"signal": signal,
|
||||||
|
"connected": inUse,
|
||||||
|
"existing": ssid in scanProcess.existingNetwork
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const existingNet = networksMap[ssid]
|
||||||
|
if (inUse) {
|
||||||
|
existingNet.connected = true
|
||||||
|
}
|
||||||
|
if (signal > existingNet.signal) {
|
||||||
|
existingNet.signal = signal
|
||||||
|
existingNet.security = security
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root.networks = networksMap
|
||||||
|
scanProcess.existingNetwork = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process connectProcess: Process {
|
||||||
|
id: connectProcess
|
||||||
|
property string ssid: ""
|
||||||
|
property string password: ""
|
||||||
|
property string security: ""
|
||||||
|
running: false
|
||||||
|
command: {
|
||||||
|
if (password) {
|
||||||
|
return ["nmcli", "device", "wifi", "connect", `'${ssid}'`, "password", password]
|
||||||
|
} else {
|
||||||
|
return ["nmcli", "device", "wifi", "connect", `'${ssid}'`]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.connectingSsid = ""
|
||||||
|
root.connectStatus = "success"
|
||||||
|
root.connectStatusSsid = connectProcess.ssid
|
||||||
|
root.connectError = ""
|
||||||
|
root.refreshNetworks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stderr: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.connectingSsid = ""
|
||||||
|
root.connectStatus = "error"
|
||||||
|
root.connectStatusSsid = connectProcess.ssid
|
||||||
|
root.connectError = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process getInterfaceProcess: Process {
|
||||||
|
id: getInterfaceProcess
|
||||||
|
running: false
|
||||||
|
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
var lines = text.split("\n")
|
||||||
|
for (var i = 0; i < lines.length; ++i) {
|
||||||
|
var parts = lines[i].split(":")
|
||||||
|
if (parts[1] === "wifi" && parts[2] !== "unavailable") {
|
||||||
|
root.detectedInterface = parts[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (root.detectedInterface) {
|
||||||
|
var params = root.pendingConnect
|
||||||
|
addConnectionProcess.ifname = root.detectedInterface
|
||||||
|
addConnectionProcess.ssid = params.ssid
|
||||||
|
addConnectionProcess.password = params.password
|
||||||
|
addConnectionProcess.profileName = params.ssid
|
||||||
|
addConnectionProcess.security = params.security
|
||||||
|
addConnectionProcess.running = true
|
||||||
|
} else {
|
||||||
|
root.connectStatus = "error"
|
||||||
|
root.connectStatusSsid = root.pendingConnect.ssid
|
||||||
|
root.connectError = "No Wi-Fi interface found."
|
||||||
|
root.connectingSsid = ""
|
||||||
|
root.pendingConnect = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process addConnectionProcess: Process {
|
||||||
|
id: addConnectionProcess
|
||||||
|
property string ifname: ""
|
||||||
|
property string ssid: ""
|
||||||
|
property string password: ""
|
||||||
|
property string profileName: ""
|
||||||
|
property string security: ""
|
||||||
|
running: false
|
||||||
|
command: {
|
||||||
|
var cmd = ["nmcli", "connection", "add", "type", "wifi", "ifname", ifname, "con-name", profileName, "ssid", ssid]
|
||||||
|
if (security && security !== "--") {
|
||||||
|
cmd.push("wifi-sec.key-mgmt")
|
||||||
|
cmd.push("wpa-psk")
|
||||||
|
cmd.push("wifi-sec.psk")
|
||||||
|
cmd.push(password)
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
upConnectionProcess.profileName = addConnectionProcess.profileName
|
||||||
|
upConnectionProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stderr: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
upConnectionProcess.profileName = addConnectionProcess.profileName
|
||||||
|
upConnectionProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process upConnectionProcess: Process {
|
||||||
|
id: upConnectionProcess
|
||||||
|
property string profileName: ""
|
||||||
|
running: false
|
||||||
|
command: ["nmcli", "connection", "up", "id", profileName]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.connectingSsid = ""
|
||||||
|
root.connectStatus = "success"
|
||||||
|
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : ""
|
||||||
|
root.connectError = ""
|
||||||
|
root.pendingConnect = null
|
||||||
|
root.refreshNetworks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stderr: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.connectingSsid = ""
|
||||||
|
root.connectStatus = "error"
|
||||||
|
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : ""
|
||||||
|
root.connectError = text
|
||||||
|
root.pendingConnect = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
refreshNetworks()
|
||||||
|
}
|
||||||
|
}
|
||||||
140
Services/Niri.qml
Normal file
140
Services/Niri.qml
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var workspaces: []
|
||||||
|
property var windows: []
|
||||||
|
property int focusedWindowIndex: -1
|
||||||
|
property bool inOverview: false
|
||||||
|
property string focusedWindowTitle: "(No active window)"
|
||||||
|
|
||||||
|
function updateFocusedWindowTitle() {
|
||||||
|
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) {
|
||||||
|
focusedWindowTitle = windows[focusedWindowIndex].title
|
||||||
|
|| "(Unnamed window)"
|
||||||
|
} else {
|
||||||
|
focusedWindowTitle = "(No active window)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowsChanged: updateFocusedWindowTitle()
|
||||||
|
onFocusedWindowIndexChanged: updateFocusedWindowTitle()
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
eventStream.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: workspaceProcess
|
||||||
|
running: false
|
||||||
|
command: ["niri", "msg", "--json", "workspaces"]
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: function (line) {
|
||||||
|
try {
|
||||||
|
const workspacesData = JSON.parse(line)
|
||||||
|
const workspacesList = []
|
||||||
|
|
||||||
|
for (const ws of workspacesData) {
|
||||||
|
workspacesList.push({
|
||||||
|
"id": ws.id,
|
||||||
|
"idx": ws.idx,
|
||||||
|
"name": ws.name || "",
|
||||||
|
"output": ws.output || "",
|
||||||
|
"isFocused": ws.is_focused === true,
|
||||||
|
"isActive": ws.is_active === true,
|
||||||
|
"isUrgent": ws.is_urgent === true,
|
||||||
|
"isOccupied": ws.active_window_id ? true : false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
workspacesList.sort((a, b) => {
|
||||||
|
if (a.output !== b.output) {
|
||||||
|
return a.output.localeCompare(b.output)
|
||||||
|
}
|
||||||
|
return a.id - b.id
|
||||||
|
})
|
||||||
|
|
||||||
|
root.workspaces = workspacesList
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse workspaces:", e, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: eventStream
|
||||||
|
running: false
|
||||||
|
command: ["niri", "msg", "--json", "event-stream"]
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: data => {
|
||||||
|
try {
|
||||||
|
const event = JSON.parse(data.trim())
|
||||||
|
|
||||||
|
if (event.WorkspacesChanged) {
|
||||||
|
workspaceProcess.running = true
|
||||||
|
} else if (event.WindowsChanged) {
|
||||||
|
try {
|
||||||
|
const windowsData = event.WindowsChanged.windows
|
||||||
|
const windowsList = []
|
||||||
|
for (const win of windowsData) {
|
||||||
|
windowsList.push({
|
||||||
|
"id": win.id,
|
||||||
|
"title": win.title || "",
|
||||||
|
"appId": win.app_id || "",
|
||||||
|
"workspaceId": win.workspace_id || null,
|
||||||
|
"isFocused": win.is_focused === true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
windowsList.sort((a, b) => a.id - b.id)
|
||||||
|
root.windows = windowsList
|
||||||
|
for (var i = 0; i < windowsList.length; i++) {
|
||||||
|
if (windowsList[i].isFocused) {
|
||||||
|
root.focusedWindowIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing windows event:", e)
|
||||||
|
}
|
||||||
|
} else if (event.WorkspaceActivated) {
|
||||||
|
workspaceProcess.running = true
|
||||||
|
} else if (event.WindowFocusChanged) {
|
||||||
|
try {
|
||||||
|
const focusedId = event.WindowFocusChanged.id
|
||||||
|
if (focusedId) {
|
||||||
|
root.focusedWindowIndex = root.windows.findIndex(
|
||||||
|
w => w.id === focusedId)
|
||||||
|
if (root.focusedWindowIndex < 0) {
|
||||||
|
root.focusedWindowIndex = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root.focusedWindowIndex = -1
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing window focus event:", e)
|
||||||
|
}
|
||||||
|
} else if (event.OverviewOpenedOrClosed) {
|
||||||
|
try {
|
||||||
|
root.inOverview = event.OverviewOpenedOrClosed.is_open === true
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing overview state:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing event stream:", e, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,8 +55,8 @@ Singleton {
|
||||||
property int marginTiny: 4
|
property int marginTiny: 4
|
||||||
property int marginSmall: 8
|
property int marginSmall: 8
|
||||||
property int marginMedium: 12
|
property int marginMedium: 12
|
||||||
property int marginLarge: 18
|
property int marginLarge: 16
|
||||||
property int marginXL: 24
|
property int marginXL: 20
|
||||||
|
|
||||||
// Opacity
|
// Opacity
|
||||||
property real opacityLight: 0.25
|
property real opacityLight: 0.25
|
||||||
|
|
|
||||||
47
Services/SysInfo.qml
Normal file
47
Services/SysInfo.qml
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Qt.labs.folderlistmodel
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: manager //TBC
|
||||||
|
|
||||||
|
property string updateInterval: "2s"
|
||||||
|
property string cpuUsageStr: ""
|
||||||
|
property string cpuTempStr: ""
|
||||||
|
property string memoryUsageStr: ""
|
||||||
|
property string memoryUsagePerStr: ""
|
||||||
|
property real cpuUsage: 0
|
||||||
|
property real memoryUsage: 0
|
||||||
|
property real cpuTemp: 0
|
||||||
|
property real diskUsage: 0
|
||||||
|
property real memoryUsagePer: 0
|
||||||
|
property string diskUsageStr: ""
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: zigstatProcess
|
||||||
|
running: true
|
||||||
|
command: [Quickshell.shellDir + "/Programs/zigstat", updateInterval]
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: function (line) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(line)
|
||||||
|
cpuUsage = +data.cpu
|
||||||
|
cpuTemp = +data.cputemp
|
||||||
|
memoryUsage = +data.mem
|
||||||
|
memoryUsagePer = +data.memper
|
||||||
|
diskUsage = +data.diskper
|
||||||
|
cpuUsageStr = data.cpu + "%"
|
||||||
|
cpuTempStr = data.cputemp + "°C"
|
||||||
|
memoryUsageStr = data.mem + "G"
|
||||||
|
memoryUsagePerStr = data.memper + "%"
|
||||||
|
diskUsageStr = data.diskper + "%"
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse zigstat output:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Services/Wallpapers.qml
Normal file
136
Services/Wallpapers.qml
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Qt.labs.folderlistmodel
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: manager //TBC
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Component.onCompleted: {
|
||||||
|
loadWallpapers()
|
||||||
|
setCurrentWallpaper(currentWallpaper, true)
|
||||||
|
toggleRandomWallpaper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var wallpaperList: []
|
||||||
|
property string currentWallpaper: Settings.settings.currentWallpaper
|
||||||
|
property bool scanning: false
|
||||||
|
property string transitionType: Settings.settings.transitionType
|
||||||
|
property var randomChoices: ["fade", "left", "right", "top", "bottom", "wipe", "wave", "grow", "center", "any", "outer"]
|
||||||
|
|
||||||
|
function loadWallpapers() {
|
||||||
|
scanning = true
|
||||||
|
wallpaperList = []
|
||||||
|
folderModel.folder = ""
|
||||||
|
folderModel.folder = "file://" + (Settings.settings.wallpaperFolder
|
||||||
|
!== undefined ? Settings.settings.wallpaperFolder : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeWallpaper(path) {
|
||||||
|
setCurrentWallpaper(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCurrentWallpaper(path, isInitial) {
|
||||||
|
currentWallpaper = path
|
||||||
|
if (!isInitial) {
|
||||||
|
Settings.settings.currentWallpaper = path
|
||||||
|
}
|
||||||
|
if (Settings.settings.useSWWW) {
|
||||||
|
if (Settings.settings.transitionType === "random") {
|
||||||
|
transitionType = randomChoices[Math.floor(Math.random(
|
||||||
|
) * randomChoices.length)]
|
||||||
|
} else {
|
||||||
|
transitionType = Settings.settings.transitionType
|
||||||
|
}
|
||||||
|
changeWallpaperProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (randomWallpaperTimer.running) {
|
||||||
|
randomWallpaperTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRandomWallpaper() {
|
||||||
|
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
|
||||||
|
var randomPath = wallpaperList[randomIndex]
|
||||||
|
if (!randomPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setCurrentWallpaper(randomPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRandomWallpaper() {
|
||||||
|
if (Settings.settings.randomWallpaper && !randomWallpaperTimer.running) {
|
||||||
|
randomWallpaperTimer.start()
|
||||||
|
setRandomWallpaper()
|
||||||
|
} else if (!Settings.settings.randomWallpaper
|
||||||
|
&& randomWallpaperTimer.running) {
|
||||||
|
randomWallpaperTimer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartRandomWallpaperTimer() {
|
||||||
|
if (Settings.settings.randomWallpaper) {
|
||||||
|
randomWallpaperTimer.stop()
|
||||||
|
randomWallpaperTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTheme() {
|
||||||
|
if (Settings.settings.useWallpaperTheme) {
|
||||||
|
generateThemeProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: randomWallpaperTimer
|
||||||
|
interval: Settings.settings.wallpaperInterval * 1000
|
||||||
|
running: false
|
||||||
|
repeat: true
|
||||||
|
onTriggered: setRandomWallpaper()
|
||||||
|
triggeredOnStart: false
|
||||||
|
}
|
||||||
|
|
||||||
|
FolderListModel {
|
||||||
|
id: folderModel
|
||||||
|
// Swww supports many images format but Quickshell only support a subset of those.
|
||||||
|
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
|
||||||
|
showDirs: false
|
||||||
|
sortField: FolderListModel.Name
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === FolderListModel.Ready) {
|
||||||
|
var files = []
|
||||||
|
var filesSwww = []
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
var filepath = (Settings.settings.wallpaperFolder
|
||||||
|
!== undefined ? Settings.settings.wallpaperFolder : "") + "/" + get(
|
||||||
|
i, "fileName")
|
||||||
|
files.push(filepath)
|
||||||
|
}
|
||||||
|
wallpaperList = files
|
||||||
|
scanning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: changeWallpaperProcess
|
||||||
|
command: ["swww", "img", "--resize", Settings.settings.wallpaperResize, "--transition-fps", Settings.settings.transitionFps.toString(
|
||||||
|
), "--transition-type", transitionType, "--transition-duration", Settings.settings.transitionDuration.toString(
|
||||||
|
), currentWallpaper]
|
||||||
|
running: false
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: generateThemeProcess
|
||||||
|
command: ["wallust", "run", currentWallpaper, "-u", "-k", "-d", "Templates"]
|
||||||
|
workingDirectory: Quickshell.shellDir
|
||||||
|
running: false
|
||||||
|
}
|
||||||
|
}
|
||||||
156
Services/Workspaces.qml
Normal file
156
Services/Workspaces.qml
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property ListModel workspaces: ListModel {}
|
||||||
|
property bool isHyprland: false
|
||||||
|
property bool isNiri: false
|
||||||
|
property var hlWorkspaces: Hyprland.workspaces.values
|
||||||
|
// Detect which compositor we're using
|
||||||
|
Component.onCompleted: {
|
||||||
|
console.log("WorkspaceManager initializing...")
|
||||||
|
detectCompositor()
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectCompositor() {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
if (Hyprland.eventSocketPath) {
|
||||||
|
console.log("Detected Hyprland compositor")
|
||||||
|
isHyprland = true
|
||||||
|
isNiri = false
|
||||||
|
initHyprland()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Hyprland not available:", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Niri !== "undefined") {
|
||||||
|
console.log("Detected Niri service")
|
||||||
|
isHyprland = false
|
||||||
|
isNiri = true
|
||||||
|
initNiri()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("No supported compositor detected")
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error detecting compositor:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Hyprland integration
|
||||||
|
function initHyprland() {
|
||||||
|
try {
|
||||||
|
// Fixes the odd workspace issue.
|
||||||
|
Hyprland.refreshWorkspaces()
|
||||||
|
// hlWorkspaces = Hyprland.workspaces.values;
|
||||||
|
// updateHyprlandWorkspaces();
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error initializing Hyprland:", e)
|
||||||
|
isHyprland = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onHlWorkspacesChanged: {
|
||||||
|
updateHyprlandWorkspaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Hyprland.workspaces
|
||||||
|
function onValuesChanged() {
|
||||||
|
updateHyprlandWorkspaces()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Hyprland
|
||||||
|
function onRawEvent(event) {
|
||||||
|
updateHyprlandWorkspaces()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHyprlandWorkspaces() {
|
||||||
|
workspaces.clear()
|
||||||
|
try {
|
||||||
|
for (var i = 0; i < hlWorkspaces.length; i++) {
|
||||||
|
const ws = hlWorkspaces[i]
|
||||||
|
workspaces.append({
|
||||||
|
"id": i,
|
||||||
|
"idx": ws.id,
|
||||||
|
"name": ws.name || "",
|
||||||
|
"output": ws.monitor?.name || "",
|
||||||
|
"isActive": ws.active === true,
|
||||||
|
"isFocused": ws.focused === true,
|
||||||
|
"isUrgent": ws.urgent === true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
workspacesChanged()
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error updating Hyprland workspaces:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initNiri() {
|
||||||
|
updateNiriWorkspaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Niri
|
||||||
|
function onWorkspacesChanged() {
|
||||||
|
updateNiriWorkspaces()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNiriWorkspaces() {
|
||||||
|
const niriWorkspaces = Niri.workspaces || []
|
||||||
|
workspaces.clear()
|
||||||
|
for (var i = 0; i < niriWorkspaces.length; i++) {
|
||||||
|
const ws = niriWorkspaces[i]
|
||||||
|
workspaces.append({
|
||||||
|
"id": ws.id,
|
||||||
|
"idx": ws.idx || 1,
|
||||||
|
"name": ws.name || "",
|
||||||
|
"output": ws.output || "",
|
||||||
|
"isFocused": ws.isFocused === true,
|
||||||
|
"isActive": ws.isActive === true,
|
||||||
|
"isUrgent": ws.isUrgent === true,
|
||||||
|
"isOccupied": ws.isOccupied === true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
workspacesChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToWorkspace(workspaceId) {
|
||||||
|
if (isHyprland) {
|
||||||
|
try {
|
||||||
|
Hyprland.dispatch(`workspace ${workspaceId}`)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error switching Hyprland workspace:", e)
|
||||||
|
}
|
||||||
|
} else if (isNiri) {
|
||||||
|
try {
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["niri", "msg", "action", "focus-workspace", workspaceId.toString(
|
||||||
|
)])
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error switching Niri workspace:", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("No supported compositor detected for workspace switching")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ NPanel {
|
||||||
border.color: Colors.backgroundTertiary
|
border.color: Colors.backgroundTertiary
|
||||||
border.width: Math.min(1, Style.borderMedium * scaling)
|
border.width: Math.min(1, Style.borderMedium * scaling)
|
||||||
width: 340 * scaling
|
width: 340 * scaling
|
||||||
height: 300
|
height: 320 // TBC
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: Style.marginTiny * scaling
|
anchors.topMargin: Style.marginTiny * scaling
|
||||||
|
|
@ -66,6 +66,10 @@ NPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
DayOfWeekRow {
|
DayOfWeekRow {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
|
||||||
10
Widgets/NDivider.qml
Normal file
10
Widgets/NDivider.qml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: Math.max(1, Style.borderThin * scaling)
|
||||||
|
color: Colors.outline
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,6 @@ Rectangle {
|
||||||
color: root.hovering ? Colors.accentPrimary : "transparent"
|
color: root.hovering ? Colors.accentPrimary : "transparent"
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: iconText
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.icon
|
text: root.icon
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue