add noctalia and fuzzel

This commit is contained in:
2026-05-23 21:20:58 +02:00
parent 364801f1a3
commit 9a3eceb3ab
599 changed files with 204318 additions and 0 deletions
@@ -0,0 +1,249 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Compositor
import qs.Services.Hardware
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
Layout.fillWidth: true
ColumnLayout {
spacing: Style.marginL
Repeater {
model: Quickshell.screens || []
delegate: NBox {
Layout.fillWidth: true
implicitHeight: Math.round(contentCol.implicitHeight + Style.margin2L)
color: Color.mSurface
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
property real localBrightness: 0.5
property bool localBrightnessChanging: false
readonly property string automaticOptionLabel: {
var baseLabel = I18n.tr("panels.display.monitors-backlight-device-auto-option");
var autoDevicePath = (BrightnessService.availableBacklightDevices && BrightnessService.availableBacklightDevices.length > 0) ? BrightnessService.availableBacklightDevices[0] : "";
if (autoDevicePath === "")
return baseLabel;
var autoDeviceName = BrightnessService.getBacklightDeviceName(autoDevicePath) || autoDevicePath;
return baseLabel + "(" + autoDeviceName + ")";
}
readonly property var backlightDeviceOptions: {
var options = [
{
"key": "",
"name": automaticOptionLabel
}
];
var devices = BrightnessService.availableBacklightDevices || [];
for (var i = 0; i < devices.length; i++) {
var devicePath = devices[i];
var deviceName = BrightnessService.getBacklightDeviceName(devicePath) || devicePath;
options.push({
"key": devicePath,
"name": deviceName
});
}
return options;
}
onBrightnessMonitorChanged: {
if (brightnessMonitor && !localBrightnessChanging)
localBrightness = brightnessMonitor.brightness || 0.5;
}
Connections {
target: BrightnessService
function onMonitorBrightnessChanged(monitor, newBrightness) {
if (monitor === brightnessMonitor && !localBrightnessChanging) {
localBrightness = newBrightness;
}
}
}
Connections {
target: brightnessMonitor
ignoreUnknownSignals: true
function onBrightnessUpdated() {
if (brightnessMonitor && !localBrightnessChanging) {
localBrightness = brightnessMonitor.brightness || 0;
}
}
}
Timer {
id: debounceTimer
interval: 120
repeat: false
onTriggered: {
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable && Math.abs(localBrightness - brightnessMonitor.brightness) >= 0.005) {
brightnessMonitor.setBrightness(localBrightness);
}
}
}
ColumnLayout {
id: contentCol
width: parent.width - 2 * Style.marginL
x: Style.marginL
y: Style.marginL
spacing: Style.marginXXS
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
NText {
text: modelData.name || "Unknown"
pointSize: Style.fontSizeL
font.weight: Style.fontWeightSemiBold
Layout.alignment: Qt.AlignBottom
}
NText {
Layout.fillWidth: true
readonly property real compositorScale: {
const info = CompositorService.displayScales[modelData.name];
return (info && info.scale) ? info.scale : 1.0;
}
text: {
I18n.tr("system.monitor-description", {
"model": modelData.model,
"width": modelData.width * compositorScale,
"height": modelData.height * compositorScale,
"scale": compositorScale
});
}
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignBottom
}
}
ColumnLayout {
spacing: Style.marginS
Layout.fillWidth: true
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
RowLayout {
Layout.fillWidth: true
spacing: Style.marginL
NText {
text: I18n.tr("common.brightness")
Layout.preferredWidth: 90
Layout.alignment: Qt.AlignVCenter
}
NValueSlider {
id: brightnessSlider
from: 0
to: 1
value: localBrightness
stepSize: 0.01
enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false
onMoved: value => {
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
localBrightness = value;
debounceTimer.restart();
}
}
onPressedChanged: (pressed, value) => {
localBrightnessChanging = pressed;
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
if (pressed) {
localBrightness = value;
debounceTimer.restart();
} else {
localBrightness = value;
debounceTimer.restart();
}
}
}
Layout.fillWidth: true
}
NText {
text: brightnessMonitor ? Math.round(localBrightness * 100) + "%" : "N/A"
Layout.preferredWidth: 55
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignVCenter
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
}
Item {
Layout.preferredWidth: 30
Layout.fillHeight: true
NIcon {
icon: brightnessMonitor && brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop"
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
}
}
}
NText {
visible: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable && !(brightnessMonitor.method === "internal" && brightnessMonitor.initInProgress)
text: !Settings.data.brightness.enableDdcSupport ? I18n.tr("panels.display.monitors-brightness-unavailable-ddc-disabled") : I18n.tr("panels.display.monitors-brightness-unavailable-generic")
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
NComboBox {
Layout.fillWidth: true
visible: brightnessMonitor && brightnessMonitor.method === "internal"
label: I18n.tr("panels.display.monitors-backlight-device-label")
description: I18n.tr("panels.display.monitors-backlight-device-description")
model: backlightDeviceOptions
currentKey: BrightnessService.getMappedBacklightDevice(modelData.name) || ""
onSelected: key => BrightnessService.setMappedBacklightDevice(modelData.name, key)
}
}
}
}
}
NSpinBox {
Layout.fillWidth: true
label: I18n.tr("panels.display.monitors-brightness-step-label")
description: I18n.tr("panels.display.monitors-brightness-step-description")
minimum: 1
maximum: 50
value: Settings.data.brightness.brightnessStep
stepSize: 1
suffix: "%"
onValueChanged: Settings.data.brightness.brightnessStep = value
defaultValue: Settings.getDefaultValue("brightness.brightnessStep")
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("panels.display.monitors-enforce-minimum-label")
description: I18n.tr("panels.display.monitors-enforce-minimum-description")
checked: Settings.data.brightness.enforceMinimum
onToggled: checked => Settings.data.brightness.enforceMinimum = checked
defaultValue: Settings.getDefaultValue("brightness.enforceMinimum")
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("panels.display.monitors-external-brightness-label")
description: I18n.tr("panels.display.monitors-external-brightness-description")
checked: Settings.data.brightness.enableDdcSupport
onToggled: checked => {
Settings.data.brightness.enableDdcSupport = checked;
}
defaultValue: Settings.getDefaultValue("brightness.enableDdcSupport")
}
}
}
@@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import qs.Commons
import qs.Services.Location
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: 0
// Time dropdown options (00:00 .. 23:30)
ListModel {
id: timeOptions
}
function populateTimeOptions() {
for (var h = 0; h < 24; h++) {
for (var m = 0; m < 60; m += 30) {
var hh = ("0" + h).slice(-2);
var mm = ("0" + m).slice(-2);
var key = hh + ":" + mm;
timeOptions.append({
"key": key,
"name": key
});
}
}
}
Component.onCompleted: {
Qt.callLater(populateTimeOptions);
}
// Check for wlsunset availability when enabling Night Light
Process {
id: wlsunsetCheck
command: ["sh", "-c", "command -v wlsunset"]
running: false
onExited: function (exitCode) {
if (exitCode === 0) {
Settings.data.nightLight.enabled = true;
NightLightService.apply();
ToastService.showNotice(I18n.tr("common.night-light"), I18n.tr("common.enabled"), "nightlight-on");
} else {
Settings.data.nightLight.enabled = false;
ToastService.showWarning(I18n.tr("common.night-light"), I18n.tr("toast.night-light.not-installed"));
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
NTabBar {
id: subTabBar
Layout.fillWidth: true
Layout.bottomMargin: Style.marginM
distributeEvenly: true
currentIndex: tabView.currentIndex
NTabButton {
text: I18n.tr("common.brightness")
tabIndex: 0
checked: subTabBar.currentIndex === 0
}
NTabButton {
text: I18n.tr("common.night-light")
tabIndex: 1
checked: subTabBar.currentIndex === 1
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.marginL
}
NTabView {
id: tabView
currentIndex: subTabBar.currentIndex
BrightnessSubTab {}
NightLightSubTab {
timeOptions: timeOptions
onCheckWlsunset: wlsunsetCheck.running = true
}
}
}
@@ -0,0 +1,219 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.Location
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
Layout.fillWidth: true
property var timeOptions
signal checkWlsunset
NToggle {
label: I18n.tr("panels.display.night-light-enable-label")
description: I18n.tr("panels.display.night-light-enable-description")
checked: Settings.data.nightLight.enabled
onToggled: checked => {
if (checked) {
root.checkWlsunset();
} else {
Settings.data.nightLight.enabled = false;
Settings.data.nightLight.forced = false;
NightLightService.apply();
ToastService.showNotice(I18n.tr("common.night-light"), I18n.tr("common.disabled"), "nightlight-off");
}
}
}
ColumnLayout {
enabled: Settings.data.nightLight.enabled
spacing: Style.marginL
Layout.fillWidth: true
NLabel {
label: I18n.tr("panels.display.night-light-temperature-night")
description: I18n.tr("panels.display.night-light-temperature-night-description")
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
NSlider {
id: nightSlider
Layout.fillWidth: true
from: 1000
to: 6500
value: Settings.data.nightLight.nightTemp
onValueChanged: {
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
var v = Math.round(value);
if (!isNaN(dayTemp)) {
var maxNight = dayTemp - 500;
v = Math.min(maxNight, Math.max(1000, v));
} else {
v = Math.max(1000, v);
}
if (v !== value)
value = v;
}
onPressedChanged: {
if (!pressed) {
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
var v = Math.round(value);
if (!isNaN(dayTemp)) {
var maxNight = dayTemp - 500;
v = Math.min(maxNight, Math.max(1000, v));
} else {
v = Math.max(1000, v);
}
Settings.data.nightLight.nightTemp = v;
}
}
}
NText {
text: nightSlider.value + "K"
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
}
NLabel {
label: I18n.tr("panels.display.night-light-temperature-day")
description: I18n.tr("panels.display.night-light-temperature-day-description")
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
NSlider {
id: daySlider
Layout.fillWidth: true
from: 1000
to: 6500
value: Settings.data.nightLight.dayTemp
onValueChanged: {
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
var v = Math.round(value);
if (!isNaN(nightTemp)) {
var minDay = nightTemp + 500;
v = Math.max(minDay, Math.min(6500, v));
} else {
v = Math.min(6500, v);
}
if (v !== value)
value = v;
}
onPressedChanged: {
if (!pressed) {
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
var v = Math.round(value);
if (!isNaN(nightTemp)) {
var minDay = nightTemp + 500;
v = Math.max(minDay, Math.min(6500, v));
} else {
v = Math.min(6500, v);
}
Settings.data.nightLight.dayTemp = v;
}
}
}
NText {
text: daySlider.value + "K"
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
}
NToggle {
label: I18n.tr("panels.display.night-light-auto-schedule-label")
description: I18n.tr("panels.display.night-light-auto-schedule-description", {
"location": LocationService.stableName
})
checked: Settings.data.nightLight.autoSchedule
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
}
ColumnLayout {
spacing: Style.marginS
Layout.fillWidth: true
visible: !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
NLabel {
label: I18n.tr("panels.display.night-light-manual-schedule-label")
description: I18n.tr("panels.display.night-light-manual-schedule-description")
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NText {
text: I18n.tr("panels.display.night-light-manual-schedule-sunrise")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NComboBox {
model: root.timeOptions
currentKey: Settings.data.nightLight.manualSunrise
placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-start")
onSelected: key => Settings.data.nightLight.manualSunrise = key
Layout.fillWidth: true
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NText {
text: I18n.tr("panels.display.night-light-manual-schedule-sunset")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NComboBox {
model: root.timeOptions
currentKey: Settings.data.nightLight.manualSunset
placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-stop")
onSelected: key => Settings.data.nightLight.manualSunset = key
Layout.fillWidth: true
}
}
}
NToggle {
label: I18n.tr("panels.display.night-light-force-activation-label")
description: I18n.tr("panels.display.night-light-force-activation-description")
checked: Settings.data.nightLight.forced
onToggled: checked => {
Settings.data.nightLight.forced = checked;
if (checked && !Settings.data.nightLight.enabled) {
root.checkWlsunset();
} else {
NightLightService.apply();
}
}
}
}
}