Redesign Quickshell control center
This commit is contained in:
@@ -14,7 +14,6 @@ Item {
|
|||||||
id: bar
|
id: bar
|
||||||
|
|
||||||
property date currentTime: new Date()
|
property date currentTime: new Date()
|
||||||
|
|
||||||
property var controlCenter
|
property var controlCenter
|
||||||
property var settings
|
property var settings
|
||||||
property var notificationCenter
|
property var notificationCenter
|
||||||
@@ -34,6 +33,11 @@ Item {
|
|||||||
property var audioSink: Pipewire.defaultAudioSink
|
property var audioSink: Pipewire.defaultAudioSink
|
||||||
property var mediaPlayer: Mpris.players.values.find(player => player.playbackState === MprisPlaybackState.Playing)
|
property var mediaPlayer: Mpris.players.values.find(player => player.playbackState === MprisPlaybackState.Playing)
|
||||||
|| Mpris.players.values[0]
|
|| Mpris.players.values[0]
|
||||||
|
readonly property var defaultBarModules: [
|
||||||
|
"weather", "mail", "media", "notifications", "gpu", "cpu", "memory",
|
||||||
|
"disk", "keyboard", "audio", "calendar", "settings", "tray"
|
||||||
|
]
|
||||||
|
property var activeModules: settings ? settings.visibleBarModules() : defaultBarModules
|
||||||
|
|
||||||
function start(process) {
|
function start(process) {
|
||||||
if (!process.running) process.running = true;
|
if (!process.running) process.running = true;
|
||||||
@@ -52,13 +56,28 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moduleComponent(name) {
|
||||||
|
switch (name) {
|
||||||
|
case "weather": return weatherModule;
|
||||||
|
case "mail": return mailModule;
|
||||||
|
case "media": return mediaModule;
|
||||||
|
case "notifications": return notificationsModule;
|
||||||
|
case "gpu": return gpuModule;
|
||||||
|
case "cpu": return cpuModule;
|
||||||
|
case "memory": return memoryModule;
|
||||||
|
case "disk": return diskModule;
|
||||||
|
case "keyboard": return keyboardModule;
|
||||||
|
case "audio": return audioModule;
|
||||||
|
case "calendar": return calendarModule;
|
||||||
|
case "settings": return settingsModule;
|
||||||
|
case "tray": return trayModule;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: barWindow
|
id: barWindow
|
||||||
anchors {
|
anchors { top: true; left: true; right: true }
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
implicitHeight: 40
|
implicitHeight: 40
|
||||||
exclusiveZone: 40
|
exclusiveZone: 40
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
@@ -71,7 +90,6 @@ Item {
|
|||||||
anchors.topMargin: 4
|
anchors.topMargin: 4
|
||||||
anchors.leftMargin: 4
|
anchors.leftMargin: 4
|
||||||
anchors.rightMargin: 4
|
anchors.rightMargin: 4
|
||||||
anchors.bottomMargin: 0
|
|
||||||
radius: 14
|
radius: 14
|
||||||
color: "#1e1e2e"
|
color: "#1e1e2e"
|
||||||
border.color: "#313244"
|
border.color: "#313244"
|
||||||
@@ -84,9 +102,7 @@ Item {
|
|||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: workspaceRow
|
|
||||||
spacing: 3
|
spacing: 3
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: bar.workspaces
|
model: bar.workspaces
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
@@ -95,26 +111,20 @@ Item {
|
|||||||
implicitHeight: 26
|
implicitHeight: 26
|
||||||
radius: 10
|
radius: 10
|
||||||
color: modelData.is_focused ? "#89b4fa" : (modelData.is_active ? "#45475a" : "#313244")
|
color: modelData.is_focused ? "#89b4fa" : (modelData.is_active ? "#45475a" : "#313244")
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: workspaceLabel
|
id: workspaceLabel
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: modelData.name || String(modelData.idx)
|
text: modelData.name || String(modelData.idx)
|
||||||
color: modelData.is_focused ? "#1e1e2e" : "#cdd6f4"
|
color: modelData.is_focused ? "#11111b" : "#cdd6f4"
|
||||||
font.pixelSize: 15
|
font.pixelSize: 15
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: bar.focusWorkspace(modelData.idx) }
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: bar.focusWorkspace(modelData.idx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: bar.windowTitle
|
text: bar.windowTitle
|
||||||
color: "#cdd6f4"
|
color: "#cdd6f4"
|
||||||
@@ -124,230 +134,27 @@ Item {
|
|||||||
Layout.maximumWidth: 420
|
Layout.maximumWidth: 420
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.weather
|
|
||||||
color: "#89dceb"
|
|
||||||
font.pixelSize: 15
|
|
||||||
ToolTip.visible: weatherMouse.containsMouse
|
|
||||||
ToolTip.text: "Weather"
|
|
||||||
MouseArea {
|
|
||||||
id: weatherMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
visible: bar.mail.length > 0
|
|
||||||
text: bar.mail
|
|
||||||
color: "#b4befe"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: mailMouse.containsMouse
|
|
||||||
ToolTip.text: "Unread mail"
|
|
||||||
MouseArea {
|
|
||||||
id: mailMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: Quickshell.execDetached(["thunderbird"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
visible: bar.mediaPlayer !== null
|
|
||||||
text: bar.mediaPlayer ? (bar.mediaPlayer.trackTitle || bar.mediaPlayer.identity) : ""
|
|
||||||
color: "#f5c2e7"
|
|
||||||
leftPadding: 19
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.maximumWidth: 190
|
|
||||||
BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: mediaMouse.containsMouse
|
|
||||||
ToolTip.text: "Open media controls"
|
|
||||||
MouseArea {
|
|
||||||
id: mediaMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
if (bar.toggleMedia) bar.toggleMedia();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
visible: !!bar.notificationCenter && bar.notificationCenter.count > 0
|
|
||||||
text: bar.notificationCenter ? String(bar.notificationCenter.count) : ""
|
|
||||||
color: "#f38ba8"
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: notificationMouse.containsMouse
|
|
||||||
ToolTip.text: "Open notifications"
|
|
||||||
MouseArea {
|
|
||||||
id: notificationMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
if (bar.notificationCenter) bar.notificationCenter.open = !bar.notificationCenter.open;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.gpu
|
|
||||||
color: "#a6e3a1"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "gpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: gpuMouse.containsMouse
|
|
||||||
ToolTip.text: "GPU usage"
|
|
||||||
MouseArea {
|
|
||||||
id: gpuMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.cpuUsage + "%"
|
|
||||||
color: "#a6e3a1"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "cpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: cpuMouse.containsMouse
|
|
||||||
ToolTip.text: "CPU usage"
|
|
||||||
MouseArea { id: cpuMouse; anchors.fill: parent }
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.memoryUsage + "%"
|
|
||||||
color: "#cba6f7"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "memory"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: memoryMouse.containsMouse
|
|
||||||
ToolTip.text: "Memory usage"
|
|
||||||
MouseArea { id: memoryMouse; anchors.fill: parent }
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.diskUsage
|
|
||||||
color: "#f38ba8"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "disk"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: diskMouse.containsMouse
|
|
||||||
ToolTip.text: "Root filesystem"
|
|
||||||
MouseArea { id: diskMouse; anchors.fill: parent }
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.language
|
|
||||||
color: "#94e2d5"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "language"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: languageMouse.containsMouse
|
|
||||||
ToolTip.text: "Keyboard layout"
|
|
||||||
MouseArea { id: languageMouse; anchors.fill: parent }
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: bar.audioSink && bar.audioSink.audio
|
|
||||||
? Math.round(bar.audioSink.audio.volume * 100) + "%"
|
|
||||||
: "--"
|
|
||||||
color: "#f9e2af"
|
|
||||||
font.pixelSize: 15
|
|
||||||
leftPadding: 19
|
|
||||||
BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
|
||||||
ToolTip.visible: audioMouse.containsMouse
|
|
||||||
ToolTip.text: "Audio output"
|
|
||||||
MouseArea {
|
|
||||||
id: audioMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
if (bar.audioSink && bar.audioSink.audio) bar.audioSink.audio.muted = !bar.audioSink.audio.muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: Qt.formatDateTime(bar.currentTime, "dd.MM HH:mm")
|
|
||||||
color: "#cdd6f4"
|
|
||||||
font.pixelSize: 15
|
|
||||||
ToolTip.visible: clockMouse.containsMouse
|
|
||||||
ToolTip.text: "Open calendar"
|
|
||||||
MouseArea {
|
|
||||||
id: clockMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: Quickshell.execDetached(["sh", "-c", "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: ""
|
|
||||||
color: "#89b4fa"
|
|
||||||
Layout.preferredWidth: 18
|
|
||||||
Layout.minimumWidth: 18
|
|
||||||
Layout.maximumWidth: 18
|
|
||||||
BarIcon { name: "gear"; size: 18; anchors.centerIn: parent }
|
|
||||||
ToolTip.visible: settingsMouse.containsMouse
|
|
||||||
ToolTip.text: "Quick settings"
|
|
||||||
MouseArea {
|
|
||||||
id: settingsMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
if (bar.controlCenter) bar.controlCenter.togglePanel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SystemTray.items
|
model: bar.activeModules
|
||||||
delegate: Item {
|
delegate: Rectangle {
|
||||||
required property var modelData
|
required property string modelData
|
||||||
property bool hiddenTrayItem: !modelData
|
property bool cardStyle: !bar.settings || bar.settings.panelStyle !== "indicators"
|
||||||
|| /wayland to x11 video bridge|screen sharing|screen share/i.test(String(modelData.title || ""))
|
implicitWidth: moduleLoader.implicitWidth + (cardStyle ? 10 : 0)
|
||||||
|| /video-display|screen-share|screen-sharing/i.test(String(modelData.icon || ""))
|
implicitHeight: 28
|
||||||
visible: !hiddenTrayItem
|
radius: 8
|
||||||
implicitWidth: hiddenTrayItem ? 0 : 20
|
color: cardStyle ? "#24243a" : "transparent"
|
||||||
implicitHeight: 22
|
border.color: cardStyle ? "#313244" : "transparent"
|
||||||
|
border.width: cardStyle ? 1 : 0
|
||||||
|
Layout.preferredWidth: implicitWidth
|
||||||
|
Layout.preferredHeight: implicitHeight
|
||||||
Loader {
|
Loader {
|
||||||
id: trayIcon
|
id: moduleLoader
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
active: !hiddenTrayItem && modelData
|
sourceComponent: bar.moduleComponent(modelData)
|
||||||
sourceComponent: Component {
|
|
||||||
IconImage {
|
|
||||||
anchors.fill: parent
|
|
||||||
implicitSize: 18
|
|
||||||
source: modelData && modelData.icon && modelData.icon.startsWith("image://")
|
|
||||||
? modelData.icon
|
|
||||||
: (modelData ? Quickshell.iconPath(modelData.icon) : "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BarIcon {
|
|
||||||
visible: !hiddenTrayItem && !trayIcon.active
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: !hiddenTrayItem
|
|
||||||
name: "video"
|
|
||||||
size: 15
|
|
||||||
anchors.margins: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: function(mouse) {
|
|
||||||
if (!modelData) return;
|
|
||||||
if (mouse.button === Qt.RightButton && modelData.hasMenu) {
|
|
||||||
modelData.display(barWindow, mouse.x, mouse.y + 20);
|
|
||||||
} else {
|
|
||||||
modelData.activate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,10 +163,211 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PwObjectTracker {
|
Component {
|
||||||
objects: [bar.audioSink]
|
id: weatherModule
|
||||||
|
Label {
|
||||||
|
text: bar.weather
|
||||||
|
color: "#89dceb"
|
||||||
|
font.pixelSize: 15
|
||||||
|
ToolTip.visible: mouse.containsMouse
|
||||||
|
ToolTip.text: "Weather"
|
||||||
|
MouseArea { id: mouse; anchors.fill: parent; hoverEnabled: true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: mailModule
|
||||||
|
Label {
|
||||||
|
text: bar.mail
|
||||||
|
visible: bar.mail.length > 0
|
||||||
|
color: "#b4befe"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: Quickshell.execDetached(["thunderbird"]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: mediaModule
|
||||||
|
Label {
|
||||||
|
text: bar.mediaPlayer ? (bar.mediaPlayer.trackTitle || bar.mediaPlayer.identity) : ""
|
||||||
|
visible: bar.mediaPlayer !== null
|
||||||
|
color: "#cba6f7"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.maximumWidth: 190
|
||||||
|
BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: if (bar.toggleMedia) bar.toggleMedia() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: notificationsModule
|
||||||
|
Label {
|
||||||
|
text: bar.notificationCenter ? String(bar.notificationCenter.count) : ""
|
||||||
|
visible: !!bar.settings && bar.settings.notificationBadge && !!bar.notificationCenter && bar.notificationCenter.count > 0
|
||||||
|
color: "#f38ba8"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: if (bar.notificationCenter) bar.notificationCenter.open = !bar.notificationCenter.open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: gpuModule
|
||||||
|
Label {
|
||||||
|
text: bar.gpu
|
||||||
|
color: "#a6e3a1"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "gpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: cpuModule
|
||||||
|
Label {
|
||||||
|
text: bar.cpuUsage + "%"
|
||||||
|
color: "#a6e3a1"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "cpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: memoryModule
|
||||||
|
Label {
|
||||||
|
text: bar.memoryUsage + "%"
|
||||||
|
color: "#cba6f7"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "memory"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: diskModule
|
||||||
|
Label {
|
||||||
|
text: bar.diskUsage
|
||||||
|
color: "#f38ba8"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "disk"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: keyboardModule
|
||||||
|
Label {
|
||||||
|
text: bar.language
|
||||||
|
color: "#94e2d5"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "language"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioModule
|
||||||
|
Label {
|
||||||
|
text: bar.audioSink && bar.audioSink.audio ? Math.round(bar.audioSink.audio.volume * 100) + "%" : "--"
|
||||||
|
color: "#f9e2af"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 19
|
||||||
|
BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter }
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: if (bar.audioSink && bar.audioSink.audio) bar.audioSink.audio.muted = !bar.audioSink.audio.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: calendarModule
|
||||||
|
Label {
|
||||||
|
text: Qt.formatDateTime(bar.currentTime, "dd.MM HH:mm")
|
||||||
|
color: "#cdd6f4"
|
||||||
|
font.pixelSize: 15
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: Quickshell.execDetached(["sh", "-c", "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: settingsModule
|
||||||
|
Label {
|
||||||
|
text: "⚙"
|
||||||
|
color: "#89b4fa"
|
||||||
|
font.pixelSize: 17
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: if (bar.controlCenter) bar.controlCenter.togglePanel() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: trayModule
|
||||||
|
Item {
|
||||||
|
implicitWidth: trayRow.implicitWidth
|
||||||
|
implicitHeight: 22
|
||||||
|
Row {
|
||||||
|
id: trayRow
|
||||||
|
spacing: 4
|
||||||
|
Repeater {
|
||||||
|
model: SystemTray.items
|
||||||
|
delegate: Item {
|
||||||
|
required property var modelData
|
||||||
|
property bool hiddenTrayItem: !modelData
|
||||||
|
|| /wayland to x11 video bridge|screen sharing|screen share/i.test(String(modelData.title || ""))
|
||||||
|
|| /video-display|screen-share|screen-sharing/i.test(String(modelData.icon || ""))
|
||||||
|
visible: !hiddenTrayItem
|
||||||
|
width: hiddenTrayItem ? 0 : 20
|
||||||
|
height: 22
|
||||||
|
Loader {
|
||||||
|
id: trayIcon
|
||||||
|
anchors.fill: parent
|
||||||
|
active: !hiddenTrayItem && modelData
|
||||||
|
sourceComponent: Component {
|
||||||
|
IconImage {
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitSize: 18
|
||||||
|
source: modelData && modelData.icon && modelData.icon.startsWith("image://")
|
||||||
|
? modelData.icon : (modelData ? Quickshell.iconPath(modelData.icon) : "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BarIcon {
|
||||||
|
visible: !hiddenTrayItem && !trayIcon.active
|
||||||
|
anchors.fill: parent
|
||||||
|
name: "video"
|
||||||
|
size: 15
|
||||||
|
anchors.margins: 2
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onClicked: function(mouse) {
|
||||||
|
if (!modelData) return;
|
||||||
|
if (mouse.button === Qt.RightButton && modelData.hasMenu)
|
||||||
|
modelData.display(barWindow, mouse.x, mouse.y + 20);
|
||||||
|
else modelData.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PwObjectTracker { objects: [bar.audioSink] }
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: workspaceReader
|
id: workspaceReader
|
||||||
command: ["niri", "msg", "-j", "workspaces"]
|
command: ["niri", "msg", "-j", "workspaces"]
|
||||||
@@ -373,18 +381,11 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: windowReader
|
id: windowReader
|
||||||
command: ["niri", "msg", "-j", "focused-window"]
|
command: ["niri", "msg", "-j", "focused-window"]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector { onStreamFinished: bar.windowTitle = bar.parseJson(this, {}).title || "Desktop" }
|
||||||
onStreamFinished: {
|
|
||||||
const value = bar.parseJson(this, {});
|
|
||||||
bar.windowTitle = value.title || "Desktop";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: languageReader
|
id: languageReader
|
||||||
command: ["niri", "msg", "-j", "keyboard-layouts"]
|
command: ["niri", "msg", "-j", "keyboard-layouts"]
|
||||||
@@ -400,31 +401,26 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: cpuReader
|
id: cpuReader
|
||||||
command: ["sh", "-c", "awk '/^cpu / { idle=$5; total=0; for (i=2; i<=NF; i++) total += $i; print int((total-idle)*100/total) }' /proc/stat"]
|
command: ["sh", "-c", "awk '/^cpu / { idle=$5; total=0; for (i=2; i<=NF; i++) total += $i; print int((total-idle)*100/total) }' /proc/stat"]
|
||||||
stdout: StdioCollector { onStreamFinished: bar.cpuUsage = parseInt(text.trim()) || 0 }
|
stdout: StdioCollector { onStreamFinished: bar.cpuUsage = parseInt(text.trim()) || 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: memoryReader
|
id: memoryReader
|
||||||
command: ["sh", "-c", "free | awk '/^Mem:/ { print int($3*100/$2) }'"]
|
command: ["sh", "-c", "free | awk '/^Mem:/ { print int($3*100/$2) }'"]
|
||||||
stdout: StdioCollector { onStreamFinished: bar.memoryUsage = parseInt(text.trim()) || 0 }
|
stdout: StdioCollector { onStreamFinished: bar.memoryUsage = parseInt(text.trim()) || 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: diskReader
|
id: diskReader
|
||||||
command: ["sh", "-c", "df -P / | awk 'NR==2 { print $5 }'"]
|
command: ["sh", "-c", "df -P / | awk 'NR==2 { print $5 }'"]
|
||||||
stdout: StdioCollector { onStreamFinished: bar.diskUsage = text.trim() || "--" }
|
stdout: StdioCollector { onStreamFinished: bar.diskUsage = text.trim() || "--" }
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: temperatureReader
|
id: temperatureReader
|
||||||
command: ["sh", "-c", "max=0; for file in /sys/class/thermal/thermal_zone*/temp; do value=$(cat $file 2>/dev/null); [ $value -gt $max ] 2>/dev/null && max=$value; done; if [ $max -gt 0 ]; then echo $((max / 1000))C; else echo --; fi"]
|
command: ["sh", "-c", "max=0; for file in /sys/class/thermal/thermal_zone*/temp; do value=$(cat $file 2>/dev/null); [ $value -gt $max ] 2>/dev/null && max=$value; done; if [ $max -gt 0 ]; then echo $((max / 1000))C; else echo --; fi"]
|
||||||
stdout: StdioCollector { onStreamFinished: bar.temperature = text.trim() || "--" }
|
stdout: StdioCollector { onStreamFinished: bar.temperature = text.trim() || "--" }
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: gpuReader
|
id: gpuReader
|
||||||
command: ["sh", "-c", "nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1"]
|
command: ["sh", "-c", "nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1"]
|
||||||
@@ -438,27 +434,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: weatherReader
|
id: weatherReader
|
||||||
command: ["bash", Quickshell.shellPath("../scripts/weather.sh")]
|
command: ["bash", Quickshell.shellPath("../scripts/weather.sh")]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector { onStreamFinished: bar.weather = bar.parseJson(this, {}).text || "N/A" }
|
||||||
onStreamFinished: {
|
|
||||||
const value = bar.parseJson(this, {});
|
|
||||||
bar.weather = value.text || "N/A";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: mailReader
|
id: mailReader
|
||||||
command: ["sh", "-c", "if [ -x $HOME/.local/bin/mail-counter ]; then $HOME/.local/bin/mail-counter; else printf '{}'; fi"]
|
command: ["sh", "-c", "if [ -x $HOME/.local/bin/mail-counter ]; then $HOME/.local/bin/mail-counter; else printf '{}'; fi"]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector { onStreamFinished: bar.mail = bar.parseJson(this, {}).text || "" }
|
||||||
onStreamFinished: {
|
|
||||||
const value = bar.parseJson(this, {});
|
|
||||||
bar.mail = value.text || "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -468,50 +452,17 @@ Item {
|
|||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
bar.currentTime = new Date();
|
bar.currentTime = new Date();
|
||||||
bar.start(windowReader);
|
bar.start(windowReader); bar.start(cpuReader); bar.start(memoryReader); bar.start(diskReader);
|
||||||
bar.start(cpuReader);
|
bar.start(temperatureReader); bar.start(gpuReader); bar.start(mailReader);
|
||||||
bar.start(memoryReader);
|
|
||||||
bar.start(diskReader);
|
|
||||||
bar.start(temperatureReader);
|
|
||||||
bar.start(gpuReader);
|
|
||||||
bar.start(mailReader);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Timer { id: weatherTimer; interval: 900000; running: true; repeat: true; onTriggered: bar.start(weatherReader) }
|
||||||
Timer {
|
Timer { id: languageTimer; interval: 200; running: true; repeat: true; onTriggered: bar.start(languageReader) }
|
||||||
id: weatherTimer
|
Timer { id: workspaceTimer; interval: 100; running: true; repeat: true; onTriggered: bar.start(workspaceReader) }
|
||||||
interval: 900000
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: bar.start(weatherReader)
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: languageTimer
|
|
||||||
interval: 200
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: bar.start(languageReader)
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: workspaceTimer
|
|
||||||
interval: 100
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: bar.start(workspaceReader)
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
bar.start(workspaceReader);
|
bar.start(workspaceReader); bar.start(windowReader); bar.start(languageReader); bar.start(cpuReader);
|
||||||
bar.start(windowReader);
|
bar.start(memoryReader); bar.start(diskReader); bar.start(temperatureReader); bar.start(gpuReader);
|
||||||
bar.start(languageReader);
|
bar.start(weatherReader); bar.start(mailReader);
|
||||||
bar.start(cpuReader);
|
|
||||||
bar.start(memoryReader);
|
|
||||||
bar.start(diskReader);
|
|
||||||
bar.start(temperatureReader);
|
|
||||||
bar.start(gpuReader);
|
|
||||||
bar.start(weatherReader);
|
|
||||||
bar.start(mailReader);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
fedora/quickshell/control-center/BarPage.qml
Normal file
97
fedora/quickshell/control-center/BarPage.qml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var settings
|
||||||
|
property var theme
|
||||||
|
property var availableModules: []
|
||||||
|
implicitHeight: pageColumn.implicitHeight
|
||||||
|
|
||||||
|
function moveModule(module, delta) {
|
||||||
|
let modules = settings.barModules.slice();
|
||||||
|
const index = modules.indexOf(module);
|
||||||
|
const next = index + delta;
|
||||||
|
if (index < 0 || next < 0 || next >= modules.length) return;
|
||||||
|
const swap = modules[next];
|
||||||
|
modules[next] = module;
|
||||||
|
modules[index] = swap;
|
||||||
|
settings.setBarModules(modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: pageColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Bar modules"
|
||||||
|
description: "Weather stays first; workspace buttons remain on the left."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.settings && Array.isArray(root.settings.barModules)
|
||||||
|
? root.settings.barModules : root.availableModules
|
||||||
|
delegate: SettingsRow {
|
||||||
|
required property string modelData
|
||||||
|
required property int index
|
||||||
|
theme: root.theme
|
||||||
|
label: modelData
|
||||||
|
description: index + 1 + " / " + (root.settings && Array.isArray(root.settings.barModules)
|
||||||
|
? root.settings.barModules.length : root.availableModules.length)
|
||||||
|
CCSwitch {
|
||||||
|
theme: root.theme
|
||||||
|
checked: root.settings.visibleBarModules().indexOf(modelData) >= 0
|
||||||
|
onToggled: root.settings.setModuleVisible(modelData, checked)
|
||||||
|
}
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: "↑"
|
||||||
|
enabled: index > 0
|
||||||
|
onClicked: root.moveModule(modelData, -1)
|
||||||
|
}
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: "↓"
|
||||||
|
enabled: index < (root.settings && Array.isArray(root.settings.barModules)
|
||||||
|
? root.settings.barModules.length : root.availableModules.length) - 1
|
||||||
|
onClicked: root.moveModule(modelData, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Indicator style"
|
||||||
|
description: "Keep the bar compact and consistent with the cards."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Card style"
|
||||||
|
ComboBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
palette.highlight: root.theme.primary
|
||||||
|
model: ["Cards", "Indicators"]
|
||||||
|
currentIndex: root.settings.panelStyle === "indicators" ? 1 : 0
|
||||||
|
onActivated: root.settings.panelStyle = currentIndex === 1 ? "indicators" : "cards"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Notification badge"
|
||||||
|
CCSwitch {
|
||||||
|
theme: root.theme
|
||||||
|
checked: root.settings.notificationBadge
|
||||||
|
onToggled: root.settings.notificationBadge = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
fedora/quickshell/control-center/CCButton.qml
Normal file
38
fedora/quickshell/control-center/CCButton.qml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
property bool accent: false
|
||||||
|
|
||||||
|
implicitHeight: 32
|
||||||
|
leftPadding: 11
|
||||||
|
rightPadding: 11
|
||||||
|
topPadding: 5
|
||||||
|
bottomPadding: 5
|
||||||
|
|
||||||
|
contentItem: Label {
|
||||||
|
text: root.text
|
||||||
|
color: root.enabled
|
||||||
|
? (root.accent && root.theme ? root.theme.background : (root.theme ? root.theme.text : "#cdd6f4"))
|
||||||
|
: (root.theme ? root.theme.dimText : "#6c7086")
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 12
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: root.theme ? root.theme.controlRadius : 9
|
||||||
|
color: !root.enabled
|
||||||
|
? (root.theme ? root.theme.surface : "#181825")
|
||||||
|
: (root.accent
|
||||||
|
? (root.theme ? root.theme.primary : "#89b4fa")
|
||||||
|
: (root.hovered ? (root.theme ? root.theme.surfaceRaised : "#24243a")
|
||||||
|
: (root.theme ? root.theme.surfaceVariant : "#1e1e2e")))
|
||||||
|
border.color: root.theme ? root.theme.border : "#313244"
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
36
fedora/quickshell/control-center/CCSlider.qml
Normal file
36
fedora/quickshell/control-center/CCSlider.qml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
implicitHeight: 24
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
x: root.leftPadding
|
||||||
|
y: root.topPadding + root.availableHeight / 2 - height / 2
|
||||||
|
width: root.availableWidth
|
||||||
|
height: 4
|
||||||
|
radius: 2
|
||||||
|
color: root.theme ? root.theme.surfaceRaised : "#24243a"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: root.visualPosition * parent.width
|
||||||
|
height: parent.height
|
||||||
|
radius: 2
|
||||||
|
color: root.theme ? root.theme.primary : "#89b4fa"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle: Rectangle {
|
||||||
|
x: root.leftPadding + root.visualPosition * (root.availableWidth - width)
|
||||||
|
y: root.topPadding + root.availableHeight / 2 - height / 2
|
||||||
|
width: 14
|
||||||
|
height: 14
|
||||||
|
radius: 7
|
||||||
|
color: root.theme ? root.theme.primary : "#89b4fa"
|
||||||
|
border.color: root.theme ? root.theme.background : "#11111b"
|
||||||
|
border.width: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
32
fedora/quickshell/control-center/CCSwitch.qml
Normal file
32
fedora/quickshell/control-center/CCSwitch.qml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
implicitWidth: 42
|
||||||
|
implicitHeight: 24
|
||||||
|
|
||||||
|
indicator: Rectangle {
|
||||||
|
x: root.leftPadding
|
||||||
|
y: (root.height - height) / 2
|
||||||
|
width: 40
|
||||||
|
height: 22
|
||||||
|
radius: 11
|
||||||
|
color: root.checked && root.enabled
|
||||||
|
? (root.theme ? root.theme.primary : "#89b4fa")
|
||||||
|
: (root.theme ? root.theme.surfaceRaised : "#24243a")
|
||||||
|
border.color: root.theme ? root.theme.border : "#313244"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: root.checked ? parent.width - width - 3 : 3
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: root.checked ? (root.theme ? root.theme.background : "#11111b") : (root.theme ? root.theme.mutedText : "#9399b2")
|
||||||
|
Behavior on x { NumberAnimation { duration: 120 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import Quickshell.Wayland
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
UiTokens { id: theme }
|
||||||
|
|
||||||
property bool open: false
|
property bool open: false
|
||||||
property var selectedPlayer: null
|
property var selectedPlayer: null
|
||||||
property var player: selectedPlayer
|
property var player: selectedPlayer
|
||||||
@@ -51,8 +53,8 @@ Item {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.topMargin: 52
|
anchors.topMargin: 52
|
||||||
radius: 20
|
radius: 20
|
||||||
color: "#f5f2ea"
|
color: "#11111b"
|
||||||
border.color: "#d8d0c2"
|
border.color: "#313244"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
@@ -70,30 +72,20 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Label {
|
Label {
|
||||||
text: "Media"
|
text: "Media"
|
||||||
color: "#252321"
|
color: "#cdd6f4"
|
||||||
font.pixelSize: 20
|
font.pixelSize: 20
|
||||||
font.bold: true
|
font.bold: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: root.player ? root.player.identity : "No player"
|
text: root.player ? root.player.identity : "No player"
|
||||||
color: "#7c756c"
|
color: "#9399b2"
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
Rectangle {
|
CCButton {
|
||||||
width: 26
|
theme: theme
|
||||||
height: 26
|
text: "Close"
|
||||||
radius: 13
|
onClicked: root.dismissed()
|
||||||
color: "#e5e0d7"
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "x"
|
|
||||||
color: "#6e675f"
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: root.dismissed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +99,7 @@ Item {
|
|||||||
Layout.preferredWidth: 132
|
Layout.preferredWidth: 132
|
||||||
Layout.preferredHeight: 132
|
Layout.preferredHeight: 132
|
||||||
radius: 14
|
radius: 14
|
||||||
color: "#e5e0d7"
|
color: "#181825"
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
@@ -132,7 +124,7 @@ Item {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: root.player ? (root.player.trackTitle || "Unknown track") : ""
|
text: root.player ? (root.player.trackTitle || "Unknown track") : ""
|
||||||
color: "#252321"
|
color: "#cdd6f4"
|
||||||
font.pixelSize: 17
|
font.pixelSize: 17
|
||||||
font.bold: true
|
font.bold: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -140,20 +132,21 @@ Item {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: root.player ? (root.player.trackArtist || root.player.trackAlbum || "Unknown artist") : ""
|
text: root.player ? (root.player.trackArtist || root.player.trackAlbum || "Unknown artist") : ""
|
||||||
color: "#7c756c"
|
color: "#9399b2"
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
Item { Layout.fillHeight: true }
|
Item { Layout.fillHeight: true }
|
||||||
Label {
|
Label {
|
||||||
text: root.player ? root.formatTime(root.player.position) + " / " + root.formatTime(root.player.length) : ""
|
text: root.player ? root.formatTime(root.player.position) + " / " + root.formatTime(root.player.length) : ""
|
||||||
color: "#7c756c"
|
color: "#9399b2"
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
CCSlider {
|
||||||
|
theme: theme
|
||||||
visible: root.player && root.player.length > 0
|
visible: root.player && root.player.length > 0
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
from: 0
|
from: 0
|
||||||
@@ -169,17 +162,20 @@ Item {
|
|||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
spacing: 14
|
spacing: 14
|
||||||
|
|
||||||
Button {
|
CCButton {
|
||||||
|
theme: theme
|
||||||
text: "Prev"
|
text: "Prev"
|
||||||
enabled: !!(root.player && root.player.canGoPrevious)
|
enabled: !!(root.player && root.player.canGoPrevious)
|
||||||
onClicked: root.player.previous()
|
onClicked: root.player.previous()
|
||||||
}
|
}
|
||||||
Button {
|
CCButton {
|
||||||
|
theme: theme
|
||||||
text: root.player && root.player.playbackState === MprisPlaybackState.Playing ? "Pause" : "Play"
|
text: root.player && root.player.playbackState === MprisPlaybackState.Playing ? "Pause" : "Play"
|
||||||
enabled: !!(root.player && root.player.canTogglePlaying)
|
enabled: !!(root.player && root.player.canTogglePlaying)
|
||||||
onClicked: root.player.togglePlaying()
|
onClicked: root.player.togglePlaying()
|
||||||
}
|
}
|
||||||
Button {
|
CCButton {
|
||||||
|
theme: theme
|
||||||
text: "Next"
|
text: "Next"
|
||||||
enabled: !!(root.player && root.player.canGoNext)
|
enabled: !!(root.player && root.player.canGoNext)
|
||||||
onClicked: root.player.next()
|
onClicked: root.player.next()
|
||||||
@@ -191,13 +187,14 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Label {
|
Label {
|
||||||
text: "Players"
|
text: "Players"
|
||||||
color: "#7c756c"
|
color: "#9399b2"
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
Repeater {
|
Repeater {
|
||||||
model: Mpris.players
|
model: Mpris.players
|
||||||
delegate: Button {
|
delegate: CCButton {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
theme: theme
|
||||||
text: modelData.identity
|
text: modelData.identity
|
||||||
onClicked: root.selectedPlayer = modelData
|
onClicked: root.selectedPlayer = modelData
|
||||||
}
|
}
|
||||||
|
|||||||
88
fedora/quickshell/control-center/NavigationRail.qml
Normal file
88
fedora/quickshell/control-center/NavigationRail.qml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
property int currentIndex: 0
|
||||||
|
signal selected(int index)
|
||||||
|
|
||||||
|
readonly property var names: ["Quick", "Bar", "Panel", "Notifications", "Status"]
|
||||||
|
implicitWidth: 78
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.theme ? root.theme.cardRadius : 14
|
||||||
|
color: root.theme ? root.theme.surface : "#181825"
|
||||||
|
border.color: root.theme ? root.theme.border : "#313244"
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 7
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "iN"
|
||||||
|
color: root.theme ? root.theme.primary : "#89b4fa"
|
||||||
|
font.pixelSize: 17
|
||||||
|
font.bold: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.names
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property int index
|
||||||
|
required property string modelData
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
radius: 11
|
||||||
|
color: index === root.currentIndex
|
||||||
|
? (root.theme ? root.theme.primary : "#89b4fa")
|
||||||
|
: (navMouse.containsMouse ? (root.theme ? root.theme.surfaceRaised : "#24243a") : "transparent")
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
Label {
|
||||||
|
text: ["⌂", "≡", "▣", "●", "⌁"][index]
|
||||||
|
color: index === root.currentIndex ? (root.theme ? root.theme.background : "#11111b") : (root.theme ? root.theme.mutedText : "#9399b2")
|
||||||
|
font.pixelSize: 17
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: 58
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: modelData
|
||||||
|
color: index === root.currentIndex ? (root.theme ? root.theme.background : "#11111b") : (root.theme ? root.theme.mutedText : "#9399b2")
|
||||||
|
font.pixelSize: 9
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: 58
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: navMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: root.selected(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Niri"
|
||||||
|
color: root.theme ? root.theme.dimText : "#6c7086"
|
||||||
|
font.pixelSize: 9
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,10 +9,13 @@ import Quickshell.Wayland
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
UiTokens { id: theme }
|
||||||
|
|
||||||
property bool open: false
|
property bool open: false
|
||||||
property bool dnd: false
|
property bool dnd: false
|
||||||
property bool toastOpen: false
|
property bool toastOpen: false
|
||||||
property var latest: null
|
property var latest: null
|
||||||
|
property var settings
|
||||||
readonly property int count: server.trackedNotifications ? server.trackedNotifications.values.length : 0
|
readonly property int count: server.trackedNotifications ? server.trackedNotifications.values.length : 0
|
||||||
|
|
||||||
signal dismissed()
|
signal dismissed()
|
||||||
@@ -32,7 +35,7 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: toastTimer
|
id: toastTimer
|
||||||
interval: 5000
|
interval: root.settings ? root.settings.notificationPopupDuration : 5000
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: root.toastOpen = false
|
onTriggered: root.toastOpen = false
|
||||||
}
|
}
|
||||||
@@ -48,13 +51,12 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Math.min(390, parent.width - 32)
|
width: Math.min(390, parent.width - 32)
|
||||||
height: 100
|
height: 112
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.right: parent.right
|
x: root.settings && root.settings.notificationPosition === "top-left" ? 16 : parent.width - width - 16
|
||||||
anchors.margins: 16
|
|
||||||
radius: 16
|
radius: 16
|
||||||
color: "#f5f2ea"
|
color: "#181825"
|
||||||
border.color: "#d8d0c2"
|
border.color: "#313244"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -69,16 +71,26 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 14
|
anchors.margins: 14
|
||||||
spacing: 3
|
spacing: 3
|
||||||
Label {
|
RowLayout {
|
||||||
text: root.latest ? root.latest.appName : ""
|
|
||||||
color: "#7c756c"
|
|
||||||
font.pixelSize: 11
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
spacing: 8
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.latest ? root.latest.appName : ""
|
||||||
|
color: "#9399b2"
|
||||||
|
font.pixelSize: 11
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
CCButton {
|
||||||
|
theme: theme
|
||||||
|
text: "Close"
|
||||||
|
onClicked: root.toastOpen = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: root.latest ? root.latest.summary : ""
|
text: root.latest ? root.latest.summary : ""
|
||||||
color: "#252321"
|
color: "#cdd6f4"
|
||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
font.bold: true
|
font.bold: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -86,7 +98,7 @@ Item {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: root.latest ? root.latest.body : ""
|
text: root.latest ? root.latest.body : ""
|
||||||
color: "#5f5952"
|
color: "#9399b2"
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@@ -115,8 +127,8 @@ Item {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.margins: 16
|
anchors.margins: 16
|
||||||
radius: 20
|
radius: 20
|
||||||
color: "#f5f2ea"
|
color: "#11111b"
|
||||||
border.color: "#d8d0c2"
|
border.color: "#313244"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
@@ -134,16 +146,18 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Label {
|
Label {
|
||||||
text: "Notifications"
|
text: "Notifications"
|
||||||
color: "#252321"
|
color: "#cdd6f4"
|
||||||
font.pixelSize: 22
|
font.pixelSize: 22
|
||||||
font.bold: true
|
font.bold: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
Button {
|
CCButton {
|
||||||
|
theme: theme
|
||||||
text: root.dnd ? "DND on" : "DND off"
|
text: root.dnd ? "DND on" : "DND off"
|
||||||
onClicked: root.dnd = !root.dnd
|
onClicked: root.dnd = !root.dnd
|
||||||
}
|
}
|
||||||
Button {
|
CCButton {
|
||||||
|
theme: theme
|
||||||
text: "Close"
|
text: "Close"
|
||||||
onClicked: root.dismissed()
|
onClicked: root.dismissed()
|
||||||
}
|
}
|
||||||
@@ -152,7 +166,7 @@ Item {
|
|||||||
Label {
|
Label {
|
||||||
visible: root.count === 0
|
visible: root.count === 0
|
||||||
text: "No notifications"
|
text: "No notifications"
|
||||||
color: "#7c756c"
|
color: "#9399b2"
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: 18
|
Layout.topMargin: 18
|
||||||
}
|
}
|
||||||
@@ -173,7 +187,7 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: notificationColumn.implicitHeight + 22
|
implicitHeight: notificationColumn.implicitHeight + 22
|
||||||
radius: 14
|
radius: 14
|
||||||
color: "#e8e3da"
|
color: "#181825"
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: notificationColumn
|
id: notificationColumn
|
||||||
@@ -185,19 +199,20 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Label {
|
Label {
|
||||||
text: modelData.appName
|
text: modelData.appName
|
||||||
color: "#7c756c"
|
color: "#9399b2"
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
Button {
|
CCButton {
|
||||||
|
theme: theme
|
||||||
text: "x"
|
text: "x"
|
||||||
onClicked: modelData.dismiss()
|
onClicked: modelData.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: modelData.summary
|
text: modelData.summary
|
||||||
color: "#252321"
|
color: "#cdd6f4"
|
||||||
font.pixelSize: 15
|
font.pixelSize: 15
|
||||||
font.bold: true
|
font.bold: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -205,7 +220,7 @@ Item {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: modelData.body
|
text: modelData.body
|
||||||
color: "#5f5952"
|
color: "#9399b2"
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|||||||
78
fedora/quickshell/control-center/NotificationsPage.qml
Normal file
78
fedora/quickshell/control-center/NotificationsPage.qml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var settings
|
||||||
|
property var theme
|
||||||
|
property var notificationCenter
|
||||||
|
implicitHeight: pageColumn.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: pageColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Notifications"
|
||||||
|
description: root.notificationCenter ? root.notificationCenter.count + " tracked notifications" : "Notification service"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Bar badge"
|
||||||
|
CCSwitch {
|
||||||
|
theme: root.theme
|
||||||
|
checked: root.settings.notificationBadge
|
||||||
|
onToggled: root.settings.notificationBadge = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Popup position"
|
||||||
|
ComboBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
palette.highlight: root.theme.primary
|
||||||
|
model: ["Top right", "Top left"]
|
||||||
|
currentIndex: root.settings.notificationPosition === "top-left" ? 1 : 0
|
||||||
|
onActivated: root.settings.notificationPosition = currentIndex === 1 ? "top-left" : "top-right"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Popup duration"
|
||||||
|
SpinBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
from: 1000
|
||||||
|
to: 15000
|
||||||
|
stepSize: 500
|
||||||
|
value: root.settings.notificationPopupDuration
|
||||||
|
editable: true
|
||||||
|
onValueModified: root.settings.notificationPopupDuration = value
|
||||||
|
}
|
||||||
|
Label { text: "ms"; color: root.theme.mutedText }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Current queue"
|
||||||
|
description: "Click the bar badge to open the existing notification center."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: "Open notifications"
|
||||||
|
accent: true
|
||||||
|
onClicked: if (root.notificationCenter) root.notificationCenter.open = !root.notificationCenter.open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
fedora/quickshell/control-center/PanelPage.qml
Normal file
93
fedora/quickshell/control-center/PanelPage.qml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var settings
|
||||||
|
property var theme
|
||||||
|
implicitHeight: pageColumn.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: pageColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Panel"
|
||||||
|
description: "The right-side full-height slide-out panel."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Width"
|
||||||
|
SpinBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
from: 360
|
||||||
|
to: 720
|
||||||
|
value: root.settings.panelWidth
|
||||||
|
editable: true
|
||||||
|
onValueModified: root.settings.panelWidth = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Corner radius"
|
||||||
|
SpinBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
from: 8
|
||||||
|
to: 40
|
||||||
|
value: root.settings.panelRadius
|
||||||
|
editable: true
|
||||||
|
onValueModified: root.settings.panelRadius = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Opacity"
|
||||||
|
CCSlider {
|
||||||
|
theme: root.theme
|
||||||
|
implicitWidth: 130
|
||||||
|
from: 0.75
|
||||||
|
to: 1.0
|
||||||
|
value: root.settings.panelOpacity
|
||||||
|
onMoved: root.settings.panelOpacity = value
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: Math.round(root.settings.panelOpacity * 100) + "%"
|
||||||
|
color: root.theme.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Open from corner"
|
||||||
|
description: "A small top-right hot corner."
|
||||||
|
CCSwitch {
|
||||||
|
theme: root.theme
|
||||||
|
checked: root.settings.panelOpenOnCorner
|
||||||
|
onToggled: root.settings.panelOpenOnCorner = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Motion"
|
||||||
|
description: "Content appears after the slide completes."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Label {
|
||||||
|
text: "The panel keeps its existing open/close animation and overlay dismissal."
|
||||||
|
color: root.theme.mutedText
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
219
fedora/quickshell/control-center/QuickPage.qml
Normal file
219
fedora/quickshell/control-center/QuickPage.qml
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import Quickshell.Networking
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var controlCenter
|
||||||
|
property var settings
|
||||||
|
property var theme
|
||||||
|
implicitHeight: pageColumn.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: pageColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Wi-Fi"
|
||||||
|
description: root.controlCenter && root.controlCenter.wifiDevice
|
||||||
|
? (root.controlCenter.wifiDevice.name || "Available networks")
|
||||||
|
: "Wireless networking unavailable"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Wireless"
|
||||||
|
CCSwitch {
|
||||||
|
theme: root.theme
|
||||||
|
checked: Networking.wifiEnabled
|
||||||
|
enabled: Networking.wifiHardwareEnabled
|
||||||
|
onToggled: Networking.wifiEnabled = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: root.controlCenter && root.controlCenter.wifiDevice
|
||||||
|
&& root.controlCenter.wifiDevice.networks.values.find(network => network.connected)
|
||||||
|
? root.controlCenter.wifiDevice.networks.values.find(network => network.connected).name
|
||||||
|
: "Not connected"
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: "Scan"
|
||||||
|
onClicked: {
|
||||||
|
if (root.controlCenter && root.controlCenter.wifiDevice)
|
||||||
|
root.controlCenter.wifiDevice.scannerEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
model: root.controlCenter && root.controlCenter.wifiDevice ? root.controlCenter.wifiDevice.networks : null
|
||||||
|
delegate: SettingsRow {
|
||||||
|
required property var modelData
|
||||||
|
theme: root.theme
|
||||||
|
label: modelData.name
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: modelData.connected ? "Disconnect" : "Connect"
|
||||||
|
enabled: !modelData.stateChanging
|
||||||
|
onClicked: modelData.connected ? modelData.disconnect() : root.controlCenter.connectWifi(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
visible: root.controlCenter && root.controlCenter.pendingNetwork !== null
|
||||||
|
theme: root.theme
|
||||||
|
label: "Password"
|
||||||
|
TextField {
|
||||||
|
implicitWidth: 150
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.placeholderText: root.theme.mutedText
|
||||||
|
palette.highlight: root.theme.primary
|
||||||
|
placeholderText: "Network password"
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
text: root.controlCenter ? root.controlCenter.wifiPassword : ""
|
||||||
|
onTextChanged: if (root.controlCenter) root.controlCenter.wifiPassword = text
|
||||||
|
onAccepted: if (root.controlCenter) root.controlCenter.submitWifiPassword()
|
||||||
|
}
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: "Join"
|
||||||
|
enabled: root.controlCenter && root.controlCenter.wifiPassword.length > 0
|
||||||
|
onClicked: root.controlCenter.submitWifiPassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Bluetooth"
|
||||||
|
description: root.controlCenter && root.controlCenter.bluetoothAdapter ? "Nearby devices" : "Bluetooth unavailable"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Bluetooth"
|
||||||
|
CCSwitch {
|
||||||
|
theme: root.theme
|
||||||
|
checked: root.controlCenter && root.controlCenter.bluetoothAdapter
|
||||||
|
? root.controlCenter.bluetoothAdapter.enabled : false
|
||||||
|
enabled: root.controlCenter && root.controlCenter.bluetoothAdapter !== null
|
||||||
|
onToggled: if (root.controlCenter.bluetoothAdapter) root.controlCenter.bluetoothAdapter.enabled = checked
|
||||||
|
}
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: root.controlCenter && root.controlCenter.bluetoothAdapter && root.controlCenter.bluetoothAdapter.discovering ? "Stop" : "Scan"
|
||||||
|
enabled: root.controlCenter && root.controlCenter.bluetoothAdapter && root.controlCenter.bluetoothAdapter.enabled
|
||||||
|
onClicked: if (root.controlCenter.bluetoothAdapter) root.controlCenter.bluetoothAdapter.discovering = !root.controlCenter.bluetoothAdapter.discovering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
model: root.controlCenter && root.controlCenter.bluetoothAdapter ? root.controlCenter.bluetoothAdapter.devices : null
|
||||||
|
delegate: SettingsRow {
|
||||||
|
required property var modelData
|
||||||
|
theme: root.theme
|
||||||
|
label: modelData.name || modelData.deviceName || "Unknown device"
|
||||||
|
description: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "Not paired")
|
||||||
|
Label {
|
||||||
|
visible: modelData.batteryAvailable
|
||||||
|
text: Math.round(modelData.battery * 100) + "%"
|
||||||
|
color: root.theme.mutedText
|
||||||
|
}
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: modelData.connected ? "Disconnect" : (modelData.paired ? "Connect" : "Pair")
|
||||||
|
onClicked: {
|
||||||
|
if (modelData.connected) modelData.disconnect();
|
||||||
|
else if (modelData.paired) modelData.connect();
|
||||||
|
else modelData.pair();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Audio"
|
||||||
|
description: root.controlCenter && root.controlCenter.audioSink
|
||||||
|
? (root.controlCenter.audioSink.description || "Default output") : "Audio unavailable"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Output volume"
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio && root.controlCenter.audioSink.audio.muted ? "Unmute" : "Mute"
|
||||||
|
enabled: !!(root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio)
|
||||||
|
onClicked: root.controlCenter.audioSink.audio.muted = !root.controlCenter.audioSink.audio.muted
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio ? Math.round(root.controlCenter.audioSink.audio.volume * 100) + "%" : "--"
|
||||||
|
color: root.theme.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CCSlider {
|
||||||
|
theme: root.theme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
value: root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio ? root.controlCenter.audioSink.audio.volume : 0
|
||||||
|
enabled: !!(root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio)
|
||||||
|
onMoved: if (root.controlCenter.audioSink && root.controlCenter.audioSink.audio) root.controlCenter.audioSink.audio.volume = value
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
model: Pipewire.nodes
|
||||||
|
delegate: SettingsRow {
|
||||||
|
required property var modelData
|
||||||
|
visible: modelData.isSink && !modelData.isStream
|
||||||
|
theme: root.theme
|
||||||
|
label: modelData.description || modelData.name
|
||||||
|
CCButton {
|
||||||
|
theme: root.theme
|
||||||
|
text: modelData === root.controlCenter.audioSink ? "Default" : "Use"
|
||||||
|
enabled: modelData !== root.controlCenter.audioSink
|
||||||
|
onClicked: Pipewire.preferredDefaultAudioSink = modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Display"
|
||||||
|
description: "Brightness and power profile"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Brightness"
|
||||||
|
description: root.controlCenter && root.controlCenter.brightness > 0 ? root.controlCenter.brightness + "%" : "Unavailable"
|
||||||
|
}
|
||||||
|
CCSlider {
|
||||||
|
theme: root.theme
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 1
|
||||||
|
to: 100
|
||||||
|
value: root.controlCenter ? root.controlCenter.brightness : 0
|
||||||
|
enabled: root.controlCenter && root.controlCenter.brightness > 0
|
||||||
|
onPressedChanged: if (!pressed && root.controlCenter) root.controlCenter.setBrightness(value)
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Power profile"
|
||||||
|
description: PowerProfile.toString(PowerProfiles.profile)
|
||||||
|
CCButton { theme: root.theme; text: "Saver"; onClicked: PowerProfiles.profile = PowerProfile.PowerSaver }
|
||||||
|
CCButton { theme: root.theme; text: "Balanced"; accent: true; onClicked: PowerProfiles.profile = PowerProfile.Balanced }
|
||||||
|
CCButton { theme: root.theme; text: "Performance"; enabled: PowerProfiles.hasPerformanceProfile; onClicked: PowerProfiles.profile = PowerProfile.Performance }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,39 @@ QtObject {
|
|||||||
property alias temperatureInterval: values.temperatureInterval
|
property alias temperatureInterval: values.temperatureInterval
|
||||||
property alias temperatureWarning: values.temperatureWarning
|
property alias temperatureWarning: values.temperatureWarning
|
||||||
property alias temperatureCritical: values.temperatureCritical
|
property alias temperatureCritical: values.temperatureCritical
|
||||||
|
property alias panelStyle: values.panelStyle
|
||||||
|
property alias panelWidth: values.panelWidth
|
||||||
|
property alias panelRadius: values.panelRadius
|
||||||
|
property alias panelOpacity: values.panelOpacity
|
||||||
|
property alias panelOpenOnCorner: values.panelOpenOnCorner
|
||||||
|
property alias notificationBadge: values.notificationBadge
|
||||||
|
property alias notificationPosition: values.notificationPosition
|
||||||
|
property alias notificationPopupDuration: values.notificationPopupDuration
|
||||||
|
property alias barModules: values.barModules
|
||||||
|
property alias hiddenBarModules: values.hiddenBarModules
|
||||||
|
|
||||||
|
readonly property var defaultBarModules: [
|
||||||
|
"weather", "mail", "media", "notifications", "gpu", "cpu", "memory",
|
||||||
|
"disk", "keyboard", "audio", "calendar", "settings", "tray"
|
||||||
|
]
|
||||||
|
|
||||||
|
function visibleBarModules() {
|
||||||
|
const configured = Array.isArray(root.barModules) ? root.barModules : root.defaultBarModules;
|
||||||
|
const hidden = Array.isArray(root.hiddenBarModules) ? root.hiddenBarModules : [];
|
||||||
|
return configured.filter(module => root.defaultBarModules.indexOf(module) >= 0 && hidden.indexOf(module) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBarModules(modules) {
|
||||||
|
root.barModules = modules.filter(module => root.defaultBarModules.indexOf(module) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setModuleVisible(module, visible) {
|
||||||
|
let hidden = Array.isArray(root.hiddenBarModules) ? root.hiddenBarModules.slice() : [];
|
||||||
|
const index = hidden.indexOf(module);
|
||||||
|
if (visible && index >= 0) hidden.splice(index, 1);
|
||||||
|
if (!visible && index < 0) hidden.push(module);
|
||||||
|
root.hiddenBarModules = hidden;
|
||||||
|
}
|
||||||
|
|
||||||
function setTemperatureWarning(value) {
|
function setTemperatureWarning(value) {
|
||||||
root.temperatureWarning = Math.min(value, root.temperatureCritical - 1);
|
root.temperatureWarning = Math.min(value, root.temperatureCritical - 1);
|
||||||
@@ -31,6 +64,19 @@ QtObject {
|
|||||||
property int temperatureInterval: 2000
|
property int temperatureInterval: 2000
|
||||||
property int temperatureWarning: 75
|
property int temperatureWarning: 75
|
||||||
property int temperatureCritical: 90
|
property int temperatureCritical: 90
|
||||||
|
property string panelStyle: "cards"
|
||||||
|
property int panelWidth: 470
|
||||||
|
property int panelRadius: 24
|
||||||
|
property real panelOpacity: 1.0
|
||||||
|
property bool panelOpenOnCorner: false
|
||||||
|
property bool notificationBadge: true
|
||||||
|
property string notificationPosition: "top-right"
|
||||||
|
property int notificationPopupDuration: 5000
|
||||||
|
property var barModules: [
|
||||||
|
"weather", "mail", "media", "notifications", "gpu", "cpu", "memory",
|
||||||
|
"disk", "keyboard", "audio", "calendar", "settings", "tray"
|
||||||
|
]
|
||||||
|
property var hiddenBarModules: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
fedora/quickshell/control-center/SettingsCard.qml
Normal file
58
fedora/quickshell/control-center/SettingsCard.qml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
property string title: ""
|
||||||
|
property string description: ""
|
||||||
|
default property alias contentData: content.data
|
||||||
|
|
||||||
|
implicitHeight: cardColumn.implicitHeight + 24
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.theme ? root.theme.cardRadius : 14
|
||||||
|
color: root.theme ? root.theme.surface : "#181825"
|
||||||
|
border.color: root.theme ? root.theme.border : "#313244"
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: cardColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: root.title.length > 0 || root.description.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.title
|
||||||
|
color: root.theme ? root.theme.text : "#cdd6f4"
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.bold: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
visible: root.description.length > 0
|
||||||
|
text: root.description
|
||||||
|
color: root.theme ? root.theme.mutedText : "#9399b2"
|
||||||
|
font.pixelSize: 11
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
fedora/quickshell/control-center/SettingsRow.qml
Normal file
40
fedora/quickshell/control-center/SettingsRow.qml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
property string label: ""
|
||||||
|
property string description: ""
|
||||||
|
default property alias controlData: controls.data
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.label
|
||||||
|
color: root.theme ? root.theme.text : "#cdd6f4"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
visible: root.description.length > 0
|
||||||
|
text: root.description
|
||||||
|
color: root.theme ? root.theme.mutedText : "#9399b2"
|
||||||
|
font.pixelSize: 10
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: controls
|
||||||
|
spacing: 6
|
||||||
|
}
|
||||||
|
}
|
||||||
91
fedora/quickshell/control-center/StatusPage.qml
Normal file
91
fedora/quickshell/control-center/StatusPage.qml
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var settings
|
||||||
|
property var theme
|
||||||
|
property string cpuTemperature: "--"
|
||||||
|
property string gpuTemperature: "--"
|
||||||
|
property string temperatureColorCpu: "#9399b2"
|
||||||
|
property string temperatureColorGpu: "#9399b2"
|
||||||
|
implicitHeight: pageColumn.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: pageColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Temperatures"
|
||||||
|
description: "The existing CPU and GPU readers remain active in the bar service."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
SettingsRow { theme: root.theme; label: "CPU temperature"; Label { text: root.cpuTemperature; color: root.temperatureColorCpu } }
|
||||||
|
SettingsRow { theme: root.theme; label: "GPU temperature"; Label { text: root.gpuTemperature; color: root.temperatureColorGpu } }
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Polling"
|
||||||
|
description: "Used by the system metrics and temperature readers."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Refresh interval"
|
||||||
|
ComboBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
palette.highlight: root.theme.primary
|
||||||
|
model: ["1 second", "2 seconds", "5 seconds"]
|
||||||
|
currentIndex: [1000, 2000, 5000].indexOf(root.settings.temperatureInterval)
|
||||||
|
onActivated: root.settings.temperatureInterval = [1000, 2000, 5000][currentIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRow {
|
||||||
|
theme: root.theme
|
||||||
|
label: "Warning / critical"
|
||||||
|
SpinBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
from: 40
|
||||||
|
to: 99
|
||||||
|
value: root.settings.temperatureWarning
|
||||||
|
editable: true
|
||||||
|
onValueModified: root.settings.setTemperatureWarning(value)
|
||||||
|
}
|
||||||
|
Label { text: "/"; color: root.theme.mutedText }
|
||||||
|
SpinBox {
|
||||||
|
palette.base: root.theme.surfaceVariant
|
||||||
|
palette.text: root.theme.text
|
||||||
|
palette.button: root.theme.surfaceVariant
|
||||||
|
palette.buttonText: root.theme.text
|
||||||
|
from: 41
|
||||||
|
to: 110
|
||||||
|
value: root.settings.temperatureCritical
|
||||||
|
editable: true
|
||||||
|
onValueModified: root.settings.setTemperatureCritical(value)
|
||||||
|
}
|
||||||
|
Label { text: "°C"; color: root.theme.mutedText }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
theme: root.theme
|
||||||
|
title: "Live status"
|
||||||
|
description: "Weather, keyboard layouts, workspaces and tray filtering keep their current polling paths."
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Label {
|
||||||
|
text: "Screen-sharing tray items remain hidden."
|
||||||
|
color: root.theme.success
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
fedora/quickshell/control-center/UiTokens.qml
Normal file
19
fedora/quickshell/control-center/UiTokens.qml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property color background: "#11111b"
|
||||||
|
readonly property color surface: "#181825"
|
||||||
|
readonly property color surfaceVariant: "#1e1e2e"
|
||||||
|
readonly property color surfaceRaised: "#24243a"
|
||||||
|
readonly property color border: "#313244"
|
||||||
|
readonly property color primary: "#89b4fa"
|
||||||
|
readonly property color secondary: "#cba6f7"
|
||||||
|
readonly property color warning: "#f9e2af"
|
||||||
|
readonly property color critical: "#f38ba8"
|
||||||
|
readonly property color success: "#a6e3a1"
|
||||||
|
readonly property color text: "#cdd6f4"
|
||||||
|
readonly property color mutedText: "#9399b2"
|
||||||
|
readonly property color dimText: "#6c7086"
|
||||||
|
readonly property int cardRadius: 14
|
||||||
|
readonly property int controlRadius: 9
|
||||||
|
}
|
||||||
@@ -15,10 +15,12 @@ Scope {
|
|||||||
|
|
||||||
property bool open: false
|
property bool open: false
|
||||||
property bool panelVisible: false
|
property bool panelVisible: false
|
||||||
|
property bool contentReady: false
|
||||||
property bool mediaOpen: false
|
property bool mediaOpen: false
|
||||||
property bool notificationsOpen: false
|
property bool notificationsOpen: false
|
||||||
property bool osdOpen: false
|
property bool osdOpen: false
|
||||||
property string osdKind: "volume"
|
property string osdKind: "volume"
|
||||||
|
property int pageIndex: 0
|
||||||
property int brightness: 0
|
property int brightness: 0
|
||||||
property var pendingNetwork: null
|
property var pendingNetwork: null
|
||||||
property string wifiPassword: ""
|
property string wifiPassword: ""
|
||||||
@@ -29,9 +31,8 @@ Scope {
|
|||||||
property alias cpuTemperature: statusBar.temperature
|
property alias cpuTemperature: statusBar.temperature
|
||||||
property alias gpuTemperature: statusBar.gpuTemperature
|
property alias gpuTemperature: statusBar.gpuTemperature
|
||||||
|
|
||||||
Settings {
|
Settings { id: statusSettings }
|
||||||
id: statusSettings
|
UiTokens { id: uiTheme }
|
||||||
}
|
|
||||||
|
|
||||||
Bar {
|
Bar {
|
||||||
id: statusBar
|
id: statusBar
|
||||||
@@ -50,6 +51,7 @@ Scope {
|
|||||||
|
|
||||||
Notifications {
|
Notifications {
|
||||||
id: notificationCenter
|
id: notificationCenter
|
||||||
|
settings: root.settings
|
||||||
open: root.notificationsOpen
|
open: root.notificationsOpen
|
||||||
onDismissed: root.notificationsOpen = false
|
onDismissed: root.notificationsOpen = false
|
||||||
}
|
}
|
||||||
@@ -74,7 +76,6 @@ Scope {
|
|||||||
network.connect();
|
network.connect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.pendingNetwork = network;
|
root.pendingNetwork = network;
|
||||||
root.wifiPassword = "";
|
root.wifiPassword = "";
|
||||||
}
|
}
|
||||||
@@ -102,20 +103,23 @@ Scope {
|
|||||||
function temperatureColor(value) {
|
function temperatureColor(value) {
|
||||||
const match = String(value).match(/(\d+)C\s*$/);
|
const match = String(value).match(/(\d+)C\s*$/);
|
||||||
const temperature = match ? Number(match[1]) : -1;
|
const temperature = match ? Number(match[1]) : -1;
|
||||||
if (temperature < 0) return "#806d95";
|
if (temperature < 0) return uiTheme.mutedText;
|
||||||
if (temperature >= root.settings.temperatureCritical) return "#c94f6d";
|
if (temperature >= root.settings.temperatureCritical) return uiTheme.critical;
|
||||||
if (temperature >= root.settings.temperatureWarning) return "#996315";
|
if (temperature >= root.settings.temperatureWarning) return uiTheme.warning;
|
||||||
return "#4d7c38";
|
return uiTheme.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPanel() {
|
function openPanel() {
|
||||||
closePanelTimer.stop();
|
closePanelTimer.stop();
|
||||||
root.panelVisible = true;
|
root.panelVisible = true;
|
||||||
|
root.contentReady = false;
|
||||||
openPanelTimer.restart();
|
openPanelTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closePanel() {
|
function closePanel() {
|
||||||
openPanelTimer.stop();
|
openPanelTimer.stop();
|
||||||
|
contentReadyTimer.stop();
|
||||||
|
root.contentReady = false;
|
||||||
root.open = false;
|
root.open = false;
|
||||||
closePanelTimer.restart();
|
closePanelTimer.restart();
|
||||||
}
|
}
|
||||||
@@ -129,9 +133,17 @@ Scope {
|
|||||||
id: openPanelTimer
|
id: openPanelTimer
|
||||||
interval: 1
|
interval: 1
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: root.open = true
|
onTriggered: {
|
||||||
|
root.open = true;
|
||||||
|
contentReadyTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: contentReadyTimer
|
||||||
|
interval: 220
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.contentReady = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closePanelTimer
|
id: closePanelTimer
|
||||||
interval: 240
|
interval: 240
|
||||||
@@ -141,62 +153,22 @@ Scope {
|
|||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "controlcenter"
|
target: "controlcenter"
|
||||||
|
function toggle() { root.togglePanel(); }
|
||||||
function toggle() {
|
function open() { root.openPanel(); }
|
||||||
root.togglePanel();
|
function close() { root.closePanel(); }
|
||||||
}
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
root.openPanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
root.closePanel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "osd"
|
target: "osd"
|
||||||
|
function volume_up() { Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%+"]); root.showOsd("volume"); }
|
||||||
function volume_up() {
|
function volume_down() { Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"]); root.showOsd("volume"); }
|
||||||
Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%+"]);
|
function volume_mute() { Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]); root.showOsd("volume"); }
|
||||||
root.showOsd("volume");
|
function mic_mute() { Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", "toggle"]); root.showOsd("mic"); }
|
||||||
}
|
function brightness_up() { root.adjustBrightness(5); }
|
||||||
|
function brightness_down() { root.adjustBrightness(-5); }
|
||||||
function volume_down() {
|
|
||||||
Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"]);
|
|
||||||
root.showOsd("volume");
|
|
||||||
}
|
|
||||||
|
|
||||||
function volume_mute() {
|
|
||||||
Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]);
|
|
||||||
root.showOsd("volume");
|
|
||||||
}
|
|
||||||
|
|
||||||
function mic_mute() {
|
|
||||||
Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", "toggle"]);
|
|
||||||
root.showOsd("mic");
|
|
||||||
}
|
|
||||||
|
|
||||||
function brightness_up() {
|
|
||||||
root.adjustBrightness(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
function brightness_down() {
|
|
||||||
root.adjustBrightness(-5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Timer { id: osdTimer; interval: 1400; repeat: false; onTriggered: root.osdOpen = false }
|
||||||
|
|
||||||
Timer {
|
PwObjectTracker { objects: [root.audioSink] }
|
||||||
id: osdTimer
|
|
||||||
interval: 1400
|
|
||||||
repeat: false
|
|
||||||
onTriggered: root.osdOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
PwObjectTracker {
|
|
||||||
objects: [root.audioSink]
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: brightnessReader
|
id: brightnessReader
|
||||||
@@ -208,26 +180,18 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: brightnessPoll
|
id: brightnessPoll
|
||||||
interval: 3000
|
interval: 3000
|
||||||
repeat: true
|
repeat: true
|
||||||
running: root.open
|
running: root.open
|
||||||
onTriggered: {
|
onTriggered: if (!brightnessReader.running) brightnessReader.running = true
|
||||||
if (!brightnessReader.running) brightnessReader.running = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
visible: root.panelVisible
|
visible: root.panelVisible
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
anchors {
|
anchors { top: true; bottom: true; left: true; right: true }
|
||||||
top: true
|
|
||||||
bottom: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
exclusionMode: ExclusionMode.Ignore
|
exclusionMode: ExclusionMode.Ignore
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.keyboardFocus: root.panelVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: root.panelVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||||
@@ -240,509 +204,133 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: card
|
id: card
|
||||||
width: Math.min(470, parent.width - 32)
|
width: Math.min(root.settings.panelWidth, parent.width - 24)
|
||||||
height: parent.height
|
height: parent.height
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 0
|
|
||||||
x: root.open ? parent.width - width - 16 : parent.width + 24
|
x: root.open ? parent.width - width - 16 : parent.width + 24
|
||||||
radius: 20
|
radius: root.settings.panelRadius
|
||||||
color: "#f5f2ea"
|
color: Qt.rgba(uiTheme.background.r, uiTheme.background.g, uiTheme.background.b, root.settings.panelOpacity)
|
||||||
border.color: "#d8d0c2"
|
border.color: uiTheme.border
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
NumberAnimation {
|
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
|
||||||
duration: 220
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea { anchors.fill: parent; onClicked: mouse.accepted = true }
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: mouse.accepted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
RowLayout {
|
||||||
id: content
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 18
|
anchors.margins: 12
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
RowLayout {
|
NavigationRail {
|
||||||
Layout.fillWidth: true
|
id: navigation
|
||||||
|
theme: uiTheme
|
||||||
ColumnLayout {
|
currentIndex: root.pageIndex
|
||||||
Layout.fillWidth: true
|
Layout.fillHeight: true
|
||||||
spacing: 0
|
onSelected: root.pageIndex = index
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Control center"
|
|
||||||
color: "#252321"
|
|
||||||
font.pixelSize: 22
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "Niri / QuickShell"
|
|
||||||
color: "#7c756c"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "✕"
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: "Close"
|
|
||||||
onClicked: root.closePanel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
Rectangle {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.preferredWidth: 1
|
||||||
|
color: uiTheme.border
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
clip: true
|
spacing: 10
|
||||||
|
|
||||||
ColumnLayout {
|
RowLayout {
|
||||||
width: parent.width
|
Layout.fillWidth: true
|
||||||
spacing: 10
|
ColumnLayout {
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: wifiColumn.implicitHeight + 24
|
spacing: 0
|
||||||
radius: 14
|
Label {
|
||||||
color: "#e5f0ef"
|
text: navigation.names[root.pageIndex]
|
||||||
|
color: uiTheme.text
|
||||||
ColumnLayout {
|
font.pixelSize: 22
|
||||||
id: wifiColumn
|
font.bold: true
|
||||||
anchors.fill: parent
|
}
|
||||||
anchors.margins: 12
|
Label {
|
||||||
spacing: 8
|
text: "Control center / Niri"
|
||||||
|
color: uiTheme.mutedText
|
||||||
RowLayout {
|
font.pixelSize: 11
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Wi-Fi"
|
|
||||||
color: "#1c666d"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: root.wifiDevice ? (root.wifiDevice.name || "Available") : "Unavailable"
|
|
||||||
color: "#52777a"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
Switch {
|
|
||||||
checked: Networking.wifiEnabled
|
|
||||||
enabled: Networking.wifiHardwareEnabled
|
|
||||||
onToggled: Networking.wifiEnabled = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
visible: root.wifiDevice !== null
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: root.wifiDevice && root.wifiDevice.networks.values.find(network => network.connected)
|
|
||||||
? root.wifiDevice.networks.values.find(network => network.connected).name
|
|
||||||
: "Not connected"
|
|
||||||
color: "#252321"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: "Scan"
|
|
||||||
onClicked: {
|
|
||||||
if (root.wifiDevice) root.wifiDevice.scannerEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.wifiDevice ? root.wifiDevice.networks : null
|
|
||||||
delegate: RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: modelData.name
|
|
||||||
color: "#252321"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: modelData.connected ? "Connected" : Math.round(modelData.signalStrength) + "%"
|
|
||||||
color: modelData.connected ? "#1c666d" : "#7c756c"
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: modelData.connected ? "Disconnect" : "Connect"
|
|
||||||
enabled: !modelData.stateChanging
|
|
||||||
onClicked: modelData.connected ? modelData.disconnect() : root.connectWifi(modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
visible: root.pendingNetwork !== null
|
|
||||||
Layout.fillWidth: true
|
|
||||||
TextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
placeholderText: "Password for " + (root.pendingNetwork ? root.pendingNetwork.name : "network")
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
text: root.wifiPassword
|
|
||||||
onTextChanged: root.wifiPassword = text
|
|
||||||
onAccepted: root.submitWifiPassword()
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: "Join"
|
|
||||||
enabled: root.wifiPassword.length > 0
|
|
||||||
onClicked: root.submitWifiPassword()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CCButton { theme: uiTheme; text: "Close"; onClicked: root.closePanel() }
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
ScrollView {
|
||||||
Layout.fillWidth: true
|
id: pageScroll
|
||||||
implicitHeight: bluetoothColumn.implicitHeight + 24
|
Layout.fillWidth: true
|
||||||
radius: 14
|
Layout.fillHeight: true
|
||||||
color: "#eceafd"
|
clip: true
|
||||||
|
contentWidth: availableWidth
|
||||||
|
|
||||||
ColumnLayout {
|
Loader {
|
||||||
id: bluetoothColumn
|
id: pageLoader
|
||||||
anchors.fill: parent
|
width: pageScroll.availableWidth
|
||||||
anchors.margins: 12
|
active: root.contentReady
|
||||||
spacing: 8
|
sourceComponent: [quickPage, barPage, panelPage, notificationsPage, statusPage][root.pageIndex]
|
||||||
|
opacity: root.contentReady ? 1 : 0
|
||||||
RowLayout {
|
Behavior on opacity { NumberAnimation { duration: 140 } }
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Bluetooth"
|
|
||||||
color: "#55499c"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Switch {
|
|
||||||
checked: root.bluetoothAdapter ? root.bluetoothAdapter.enabled : false
|
|
||||||
enabled: root.bluetoothAdapter !== null
|
|
||||||
onToggled: {
|
|
||||||
if (root.bluetoothAdapter) root.bluetoothAdapter.enabled = checked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: root.bluetoothAdapter && root.bluetoothAdapter.discovering ? "Stop scan" : "Scan"
|
|
||||||
enabled: root.bluetoothAdapter && root.bluetoothAdapter.enabled
|
|
||||||
onClicked: {
|
|
||||||
if (root.bluetoothAdapter) root.bluetoothAdapter.discovering = !root.bluetoothAdapter.discovering;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.bluetoothAdapter ? root.bluetoothAdapter.devices : null
|
|
||||||
delegate: RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 0
|
|
||||||
Label {
|
|
||||||
text: modelData.name || modelData.deviceName || "Unknown device"
|
|
||||||
color: "#252321"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "Not paired")
|
|
||||||
color: "#7c756c"
|
|
||||||
font.pixelSize: 11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
visible: modelData.batteryAvailable
|
|
||||||
text: Math.round(modelData.battery * 100) + "%"
|
|
||||||
color: "#7c756c"
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: modelData.connected ? "Disconnect" : (modelData.paired ? "Connect" : "Pair")
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.connected) modelData.disconnect();
|
|
||||||
else if (modelData.paired) modelData.connect();
|
|
||||||
else modelData.pair();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: audioColumn.implicitHeight + 24
|
|
||||||
radius: 14
|
|
||||||
color: "#fff1d7"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: audioColumn
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Audio"
|
|
||||||
color: "#996315"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: root.audioSink ? (root.audioSink.description || "Default output") : "Unavailable"
|
|
||||||
color: "#806a48"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.maximumWidth: 230
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Button {
|
|
||||||
text: root.audioSink && root.audioSink.audio && root.audioSink.audio.muted ? "Unmute" : "Mute"
|
|
||||||
enabled: root.audioSink && root.audioSink.audio
|
|
||||||
onClicked: {
|
|
||||||
if (root.audioSink && root.audioSink.audio) root.audioSink.audio.muted = !root.audioSink.audio.muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Slider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
value: root.audioSink && root.audioSink.audio ? root.audioSink.audio.volume : 0
|
|
||||||
enabled: root.audioSink && root.audioSink.audio
|
|
||||||
onMoved: {
|
|
||||||
if (root.audioSink && root.audioSink.audio) root.audioSink.audio.volume = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: root.audioSink && root.audioSink.audio ? Math.round(root.audioSink.audio.volume * 100) + "%" : "--"
|
|
||||||
color: "#806a48"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Pipewire.nodes
|
|
||||||
delegate: RowLayout {
|
|
||||||
visible: modelData.isSink && !modelData.isStream
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: modelData.description || modelData.name
|
|
||||||
color: "#252321"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: modelData === root.audioSink ? "Default" : "Use"
|
|
||||||
enabled: modelData !== root.audioSink
|
|
||||||
onClicked: Pipewire.preferredDefaultAudioSink = modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: displayColumn.implicitHeight + 24
|
|
||||||
radius: 14
|
|
||||||
color: "#f8e4dc"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: displayColumn
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Display brightness"
|
|
||||||
color: "#9e4d39"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: root.brightness > 0 ? root.brightness + "%" : "Unavailable"
|
|
||||||
color: "#8b665b"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Slider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
from: 1
|
|
||||||
to: 100
|
|
||||||
value: root.brightness
|
|
||||||
enabled: root.brightness > 0
|
|
||||||
onPressedChanged: {
|
|
||||||
if (!pressed) root.setBrightness(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: statusColumn.implicitHeight + 24
|
|
||||||
radius: 14
|
|
||||||
color: "#eee8f7"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: statusColumn
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Status bar"
|
|
||||||
color: "#684b8f"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "Temperature"
|
|
||||||
color: "#806d95"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "CPU temperature"
|
|
||||||
color: "#252321"
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: root.cpuTemperature
|
|
||||||
color: root.temperatureColor(root.cpuTemperature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "GPU temperature"
|
|
||||||
color: "#252321"
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: root.gpuTemperature
|
|
||||||
color: root.temperatureColor(root.gpuTemperature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Refresh interval"
|
|
||||||
color: "#252321"
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
ComboBox {
|
|
||||||
model: ["1 second", "2 seconds", "5 seconds"]
|
|
||||||
currentIndex: [1000, 2000, 5000].indexOf(root.settings.temperatureInterval)
|
|
||||||
onActivated: root.settings.temperatureInterval = [1000, 2000, 5000][currentIndex]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Warning / critical"
|
|
||||||
color: "#252321"
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
SpinBox {
|
|
||||||
from: 40
|
|
||||||
to: 99
|
|
||||||
value: root.settings.temperatureWarning
|
|
||||||
editable: true
|
|
||||||
onValueModified: root.settings.setTemperatureWarning(value)
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "/"
|
|
||||||
color: "#7c756c"
|
|
||||||
}
|
|
||||||
SpinBox {
|
|
||||||
from: 41
|
|
||||||
to: 110
|
|
||||||
value: root.settings.temperatureCritical
|
|
||||||
editable: true
|
|
||||||
onValueModified: root.settings.setTemperatureCritical(value)
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "°C"
|
|
||||||
color: "#7c756c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: powerColumn.implicitHeight + 24
|
|
||||||
radius: 14
|
|
||||||
color: "#e8f1df"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: powerColumn
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Label {
|
|
||||||
text: "Power profile"
|
|
||||||
color: "#4d7c38"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: PowerProfile.toString(PowerProfiles.profile)
|
|
||||||
color: "#66825a"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Button {
|
|
||||||
text: "Saver"
|
|
||||||
Layout.fillWidth: true
|
|
||||||
onClicked: PowerProfiles.profile = PowerProfile.PowerSaver
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: "Balanced"
|
|
||||||
Layout.fillWidth: true
|
|
||||||
onClicked: PowerProfiles.profile = PowerProfile.Balanced
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: "Performance"
|
|
||||||
enabled: PowerProfiles.hasPerformanceProfile
|
|
||||||
Layout.fillWidth: true
|
|
||||||
onClicked: PowerProfiles.profile = PowerProfile.Performance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
visible: root.settings.panelOpenOnCorner && !root.panelVisible
|
||||||
|
implicitWidth: 30
|
||||||
|
implicitHeight: 30
|
||||||
|
anchors { top: true; right: true }
|
||||||
|
color: "transparent"
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
WlrLayershell.namespace: "quickshell-control-center-hot-corner"
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
radius: 8
|
||||||
|
color: uiTheme.primary
|
||||||
|
opacity: 0.35
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: root.openPanel() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: quickPage
|
||||||
|
QuickPage { width: pageLoader.width; controlCenter: root; settings: root.settings; theme: uiTheme }
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: barPage
|
||||||
|
BarPage { width: pageLoader.width; settings: root.settings; theme: uiTheme; availableModules: root.settings.defaultBarModules }
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: panelPage
|
||||||
|
PanelPage { width: pageLoader.width; settings: root.settings; theme: uiTheme }
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: notificationsPage
|
||||||
|
NotificationsPage { width: pageLoader.width; settings: root.settings; theme: uiTheme; notificationCenter: notificationCenter }
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: statusPage
|
||||||
|
StatusPage {
|
||||||
|
width: pageLoader.width
|
||||||
|
settings: root.settings
|
||||||
|
theme: uiTheme
|
||||||
|
cpuTemperature: root.cpuTemperature
|
||||||
|
gpuTemperature: root.gpuTemperature
|
||||||
|
temperatureColorCpu: root.temperatureColor(root.cpuTemperature)
|
||||||
|
temperatureColorGpu: root.temperatureColor(root.gpuTemperature)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user