Merge branch 'main' into idle

This commit is contained in:
Lemmy
2026-02-23 08:26:41 -05:00
36 changed files with 424 additions and 69 deletions
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Zeige den Hintergrundcontainer für das Wetter-Widget an."
},
"display": {
"monitors-backlight-device-auto-option": "Standard",
"monitors-backlight-device-description": "Wähle ein Hintergrundbeleuchtungsgerät für diesen Ausgang.",
"monitors-backlight-device-label": "Hintergrundbeleuchtungsgerät",
"monitors-brightness-step-description": "Schrittgröße für Helligkeitsänderungen anpassen (Mausrad und Tastenkürzel).",
"monitors-brightness-step-label": "Helligkeits-Schrittgröße",
"monitors-brightness-unavailable-ddc-disabled": "Helligkeitssteuerung nicht verfügbar. Aktivieren Sie \"Externe Helligkeitsunterstützung\", um die Helligkeit dieses Displays zu steuern.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Show the background container for the weather widget."
},
"display": {
"monitors-backlight-device-auto-option": "Default",
"monitors-backlight-device-description": "Select a backlight device for this output.",
"monitors-backlight-device-label": "Backlight device",
"monitors-brightness-step-description": "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts).",
"monitors-brightness-step-label": "Brightness step size",
"monitors-brightness-unavailable-ddc-disabled": "Brightness control unavailable. Enable \"External brightness support\" to control this display's brightness.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Mostrar el contenedor de fondo para el widget del clima."
},
"display": {
"monitors-backlight-device-auto-option": "Predeterminado",
"monitors-backlight-device-description": "Selecciona un dispositivo de retroiluminación para esta salida.",
"monitors-backlight-device-label": "Dispositivo de Retroiluminación",
"monitors-brightness-step-description": "Ajusta el tamaño del paso para los cambios de brillo (rueda de desplazamiento y atajos de teclado).",
"monitors-brightness-step-label": "Tamaño del paso de brillo",
"monitors-brightness-unavailable-ddc-disabled": "Control de brillo no disponible. Habilita \"Soporte de brillo externo\" para controlar el brillo de esta pantalla.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Afficher le conteneur d'arrière-plan pour le widget météo."
},
"display": {
"monitors-backlight-device-auto-option": "Par défaut",
"monitors-backlight-device-description": "Sélectionnez un appareil de rétroéclairage pour cette sortie.",
"monitors-backlight-device-label": "Appareil de Rétroéclairage",
"monitors-brightness-step-description": "Ajustez l'incrément pour les changements de luminosité (molette de la souris et raccourcis clavier).",
"monitors-brightness-step-label": "Incrément de luminosité",
"monitors-brightness-unavailable-ddc-disabled": "Contrôle de la luminosité indisponible. Activez \"Prise en charge de la luminosité externe\" pour contrôler la luminosité de cet écran.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Jelenítse meg az időjárás widget háttérkonténerét."
},
"display": {
"monitors-backlight-device-auto-option": "Alapértelmezett",
"monitors-backlight-device-description": "Válasszon háttérvilágítási eszközt ehhez a kimenethez.",
"monitors-backlight-device-label": "Háttérvilágítási Eszköz",
"monitors-brightness-step-description": "Állítsa be a lépésméretet a fényerő változásokhoz (görgetőkerék és billentyűparancsok).",
"monitors-brightness-step-label": "Fényerő lépésméret",
"monitors-brightness-unavailable-ddc-disabled": "Fényerő vezérlés nem érhető el. Engedélyezze a \"Külső fényerő támogatás\" opciót a kijelző fényerejének szabályozásához.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "天気ウィジェットの背景コンテナを表示します。"
},
"display": {
"monitors-backlight-device-auto-option": "デフォルト",
"monitors-backlight-device-description": "この出力のバックライトデバイスを選択してください。",
"monitors-backlight-device-label": "バックライトデバイス",
"monitors-brightness-step-description": "明るさの変化量(スクロールホイールやショートカットキー)を調整します。",
"monitors-brightness-step-label": "明るさの調整ステップ",
"monitors-brightness-unavailable-ddc-disabled": "明るさ調整を利用できません。このディスプレイを操作するには「外部ディスプレイの明るさ制御」を有効にしてください。",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "날씨 위젯의 배경 컨테이너를 표시합니다."
},
"display": {
"monitors-backlight-device-auto-option": "기본값",
"monitors-backlight-device-description": "이 출력에 대한 백라이트 장치를 선택하세요.",
"monitors-backlight-device-label": "백라이트 장치",
"monitors-brightness-step-description": "밝기 변경(스크롤 휠 및 키보드 단축키)의 단계 크기를 조정합니다.",
"monitors-brightness-step-label": "밝기 단계 크기",
"monitors-brightness-unavailable-ddc-disabled": "밝기 제어를 사용할 수 없습니다. 이 디스플레이의 밝기를 제어하려면 \"외부 밝기 지원\"을 활성화하세요.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Toon de achtergrondcontainer voor de weerwidget."
},
"display": {
"monitors-backlight-device-auto-option": "Standaard",
"monitors-backlight-device-description": "Selecteer een achtergrondverlichtingsapparaat voor deze uitvoer.",
"monitors-backlight-device-label": "Achtergrondverlichting Apparaat",
"monitors-brightness-step-description": "Pas de stapgrootte voor helderheidswijzigingen aan (scrollwiel en sneltoetsen).",
"monitors-brightness-step-label": "Stapgrootte helderheid",
"monitors-brightness-unavailable-ddc-disabled": "Helderheidsregeling niet beschikbaar. Schakel \"Ondersteuning voor externe helderheid\" in om de helderheid van dit scherm te regelen.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Pokaż tło dla widżetu pogody."
},
"display": {
"monitors-backlight-device-auto-option": "Domyślny",
"monitors-backlight-device-description": "Wybierz urządzenie podświetlenia dla tego wyjścia.",
"monitors-backlight-device-label": "Urządzenie Podświetlenia",
"monitors-brightness-step-description": "Dostosuj skok zmiany jasności (kółko myszy i skróty klawiszowe).",
"monitors-brightness-step-label": "Skok jasności",
"monitors-brightness-unavailable-ddc-disabled": "Sterowanie jasnością niedostępne. Włącz \"Obsługa zewnętrznej jasności\", aby sterować jasnością tego ekranu.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Mostrar o contêiner de fundo para o widget de clima."
},
"display": {
"monitors-backlight-device-auto-option": "Padrão",
"monitors-backlight-device-description": "Selecione um dispositivo de retroiluminação para esta saída.",
"monitors-backlight-device-label": "Dispositivo de Retroiluminação",
"monitors-brightness-step-description": "Ajuste o tamanho do passo para alterações de brilho (roda do mouse e atalhos de teclado).",
"monitors-brightness-step-label": "Tamanho do passo do brilho",
"monitors-brightness-unavailable-ddc-disabled": "Controle de brilho indisponível. Ative \"Suporte de brilho externo\" para controlar o brilho desta tela.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Показать фоновый контейнер для погодного виджета."
},
"display": {
"monitors-backlight-device-auto-option": "По умолчанию",
"monitors-backlight-device-description": "Выберите устройство подсветки для этого вывода.",
"monitors-backlight-device-label": "Устройство Подсветки",
"monitors-brightness-step-description": "Настройка шага изменения яркости (колесо прокрутки и сочетания клавиш).",
"monitors-brightness-step-label": "Шаг изменения яркости",
"monitors-brightness-unavailable-ddc-disabled": "Управление яркостью недоступно. Включите \"Поддержка внешней яркости\", чтобы управлять яркостью этого дисплея.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Visa bakgrundsbehållaren för väderwidgeten."
},
"display": {
"monitors-backlight-device-auto-option": "Standard",
"monitors-backlight-device-description": "Välj en bakgrundsbelysningsenhet för denna utgång.",
"monitors-backlight-device-label": "Bakgrundsbelysningsenhet",
"monitors-brightness-step-description": "Justera stegstorleken för ljusstyrkeändringar (rullhjul och kortkommandon).",
"monitors-brightness-step-label": "Ljusstyrkestegstorlek",
"monitors-brightness-unavailable-ddc-disabled": "Ljusstyrkekontroll otillgänglig. Aktivera \"Extern ljusstyrkestöd\"för att kontrollera ljusstyrkan på denna skärm.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Hava durumu widget'ı için arka plan konteynerini göster."
},
"display": {
"monitors-backlight-device-auto-option": "Varsayılan",
"monitors-backlight-device-description": "Bu çıkış için bir arka ışık cihazı seçin.",
"monitors-backlight-device-label": "Arka Işık Cihazı",
"monitors-brightness-step-description": "Parlaklık değişimleri için adım boyutunu ayarlayın (tekerlek ve klavye kısayolları).",
"monitors-brightness-step-label": "Parlaklık adım boyutu",
"monitors-brightness-unavailable-ddc-disabled": "Parlaklık kontrolü kullanılamıyor. Bu ekranın parlaklığını kontrol etmek için \"Harici parlaklık desteği\"ni etkinleştirin.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "Показати фоновий контейнер для погодного віджета."
},
"display": {
"monitors-backlight-device-auto-option": "За замовчуванням",
"monitors-backlight-device-description": "Виберіть пристрій підсвічування для цього виходу.",
"monitors-backlight-device-label": "Пристрій Підсвічування",
"monitors-brightness-step-description": "Налаштуйте крок зміни яскравості (колесо миші та гарячі клавіші).",
"monitors-brightness-step-label": "Крок зміни яскравості",
"monitors-brightness-unavailable-ddc-disabled": "Регулювання яскравості недоступне. Увімкніть \"Підтримку зовнішньої яскравості\", щоб керувати яскравістю цього дисплея.",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "显示天气小部件的背景容器。"
},
"display": {
"monitors-backlight-device-auto-option": "默认",
"monitors-backlight-device-description": "为此输出选择一个背光设备。",
"monitors-backlight-device-label": "背光设备",
"monitors-brightness-step-description": "调整亮度变化的步长(滚轮和键盘快捷键)。",
"monitors-brightness-step-label": "亮度步长",
"monitors-brightness-unavailable-ddc-disabled": "亮度控制不可用。启用“外部亮度支持”以控制此显示器的亮度。",
+3
View File
@@ -1006,6 +1006,9 @@
"weather-show-background-description": "顯示天氣小工具的填充背景"
},
"display": {
"monitors-backlight-device-auto-option": "預設",
"monitors-backlight-device-description": "為此輸出選擇一個背光裝置。",
"monitors-backlight-device-label": "背光裝置",
"monitors-brightness-step-description": "微調亮度調整一格的大小 (滾輪及鍵盤快捷鍵)",
"monitors-brightness-step-label": "亮度步進大小",
"monitors-brightness-unavailable-ddc-disabled": "無法使用亮度控制, 啟用 \"外部亮度調整支援\" 來控制這個顯示器的亮度",
+3 -2
View File
@@ -460,7 +460,8 @@
"brightness": {
"brightnessStep": 5,
"enforceMinimum": true,
"enableDdcSupport": false
"enableDdcSupport": false,
"backlightDeviceMappings": []
},
"colorSchemes": {
"useWallpaperColors": false,
@@ -512,4 +513,4 @@
"gridSnap": false,
"monitorWidgets": []
}
}
}
+9
View File
@@ -519,6 +519,15 @@
"tabLabel": "panels.desktop-widgets.title",
"subTab": null
},
{
"labelKey": "panels.display.monitors-backlight-device-label",
"descriptionKey": "panels.display.monitors-backlight-device-description",
"widget": "NComboBox",
"tab": 14,
"tabLabel": "panels.display.title",
"subTab": 0,
"subTabLabel": "common.brightness"
},
{
"labelKey": "panels.display.monitors-brightness-step-label",
"descriptionKey": "panels.display.monitors-brightness-step-description",
+2
View File
@@ -670,6 +670,8 @@ Singleton {
property int brightnessStep: 5
property bool enforceMinimum: true
property bool enableDdcSupport: false
property list<var> backlightDeviceMappings: []
// Format: [{ "output": "eDP-1", "device": "/sys/class/backlight/intel_backlight" }]
}
property JsonObject colorSchemes: JsonObject {
+1
View File
@@ -205,6 +205,7 @@ Item {
rotateText: isVerticalBar && currentMaxTextLength > 0
autoHide: false
forceOpen: _pillForceOpen
forceClose: !_pillForceOpen
customTextIconColor: iconColor
// Helper function to build tooltip content
+7 -3
View File
@@ -102,7 +102,7 @@ Loader {
readonly property int barHeight: Style.getBarHeightForScreen(modelData?.name)
readonly property int peekEdgeLength: {
const edgeSize = isVertical ? Math.round(modelData?.height || maxHeight) : Math.round(modelData?.width || maxWidth);
const minLength = Math.max(1, Math.round(edgeSize * 0.1));
const minLength = Math.max(1, Math.round(edgeSize * ((isStaticMode && Settings.data.dock.showFrameIndicator && Settings.data.bar.barType === "framed" && hasBar) ? 0.1 : 0.25)));
return Math.max(minLength, frameIndicatorLength);
}
readonly property int peekCenterOffsetX: {
@@ -791,8 +791,9 @@ Loader {
readonly property int extraLeft: (!isVertical && !exclusive && barOnLeft) ? barHeight : 0
readonly property int extraRight: (!isVertical && !exclusive && barOnRight) ? barHeight : 0
width: dockContent.dockContainer.width + extraLeft + extraRight
height: dockContent.dockContainer.height + extraTop + extraBottom
// Add +2 buffer for fractional scaling issues
width: dockContent.dockContainer.width + extraLeft + extraRight + (root.isVertical ? 2 : Style.margin2XL * 6)
height: dockContent.dockContainer.height + extraTop + extraBottom + 2
anchors.horizontalCenter: isVertical ? undefined : parent.horizontalCenter
anchors.verticalCenter: isVertical ? parent.verticalCenter : undefined
@@ -802,6 +803,9 @@ Loader {
anchors.left: dockPosition === "left" ? parent.left : undefined
anchors.right: dockPosition === "right" ? parent.right : undefined
// Enable layer caching to reduce GPU usage from continuous animations
layer.enabled: true
opacity: hidden ? 0 : 1
scale: hidden ? 0.85 : 1
-3
View File
@@ -41,9 +41,6 @@ Item {
border.width: Style.borderS
border.color: Qt.alpha(Color.mOutline, (isStaticMode ? 0 : Settings.data.dock.backgroundOpacity))
// Enable layer caching to reduce GPU usage from continuous animations
layer.enabled: true
MouseArea {
id: dockMouseArea
anchors.fill: parent
+9 -6
View File
@@ -37,7 +37,7 @@ PopupWindow {
property real menuMinWidth: 120
property real menuMaxWidth: 360
property real menuMaxHeight: Math.max(180, Math.min(420, Math.round((targetScreen ? targetScreen.height : 600) * 0.3)))
property int separatorCompactHeight: 8
property int separatorCompactHeight: Style.borderS + Style.margin2S
property string forcedGroupMenuMode: ""
readonly property int separatorIndex: {
for (let i = 0; i < root.items.length; i++) {
@@ -49,7 +49,7 @@ PopupWindow {
readonly property bool splitExtendedLayout: separatorIndex >= 0
readonly property var scrollItems: splitExtendedLayout ? root.items.slice(0, separatorIndex) : root.items
readonly property var fixedItems: splitExtendedLayout ? root.items.slice(separatorIndex + 1) : []
readonly property real menuInnerHeight: Math.max(0, implicitHeight - Style.marginXL)
readonly property real menuInnerHeight: Math.max(0, implicitHeight - Style.margin2M)
readonly property real fixedActionsHeight: listHeight(fixedItems)
readonly property real separatorBlockHeight: splitExtendedLayout ? separatorCompactHeight : 0
readonly property real scrollAreaHeight: splitExtendedLayout ? Math.max(0, menuInnerHeight - fixedActionsHeight - separatorBlockHeight) : menuInnerHeight
@@ -709,12 +709,14 @@ PopupWindow {
}
Rectangle {
id: separator
visible: root.splitExtendedLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: menuFlick.bottom
anchors.leftMargin: Style.marginS
anchors.rightMargin: Style.marginS
anchors.topMargin: Style.marginS
height: Style.borderS
color: Qt.alpha(Color.mOutline, 0.7)
radius: Style.radiusXS
@@ -728,15 +730,16 @@ PopupWindow {
anchors.bottom: parent.bottom
anchors.leftMargin: Style.marginM
anchors.rightMargin: Style.marginM
anchors.topMargin: Style.marginS
anchors.bottomMargin: Style.marginM
anchors.top: menuFlick.bottom
anchors.topMargin: root.separatorBlockHeight
anchors.top: separator.bottom
spacing: 0
Repeater {
model: root.fixedItems
Rectangle {
id: fixedItemRect
readonly property int globalIndex: root.fixedItemGlobalIndex(index)
width: fixedColumn.width
height: root.rowHeightForItem(modelData)
@@ -755,7 +758,7 @@ PopupWindow {
NIcon {
icon: modelData.icon
pointSize: Style.fontSizeL
color: root.hoveredItem === parent.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant
color: root.hoveredItem === fixedItemRect.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant
visible: icon !== ""
anchors.verticalCenter: parent.verticalCenter
}
@@ -763,7 +766,7 @@ PopupWindow {
NText {
text: modelData.text
pointSize: Style.fontSizeS
color: root.hoveredItem === parent.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant
color: root.hoveredItem === fixedItemRect.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
width: fixedRowLayout.width - ((modelData.icon && modelData.icon !== "") ? (Style.fontSizeL + Style.marginS) : 0)
elide: Text.ElideRight
+1 -1
View File
@@ -201,7 +201,7 @@ NBox {
color: Color.mError
radius: height * 0.5
width: Math.round(forgettingText.implicitWidth + Style.margin2S)
height: math.round(forgettingText.implicitHeight + Style.margin2XXS)
height: Math.round(forgettingText.implicitHeight + Style.margin2XXS)
NText {
id: forgettingText
+19 -3
View File
@@ -326,13 +326,29 @@ Item {
highlightOverlay.opacity = 0;
}
// Find and highlight a widget by its label key
function isEffectivelyVisible(item) {
var current = item;
while (current) {
if (current.visible === false)
return false;
if (current.opacity !== undefined && current.opacity <= 0)
return false;
current = current.parent;
}
return true;
}
// Find and highlight a widget by its label key.
function findAndHighlightWidget(item, labelKey) {
if (!item)
return null;
// Check if this item has a matching label
if (item.hasOwnProperty("label") && item.label === I18n.tr(labelKey)) {
// Skip hidden branches to avoid highlighting controls that are not on screen.
if (!isEffectivelyVisible(item))
return null;
// Check if this item has a matching label.
if (item.hasOwnProperty("label") && item.label === I18n.tr(labelKey) && item.width > 0 && item.height > 0) {
return item;
}
@@ -196,7 +196,7 @@ Item {
id: connectedDevicesBox
visible: root.connectedDevices.length > 0 && BluetoothService.enabled
Layout.fillWidth: true
Layout.preferredHeight: connectedDevicesCol.implicitHeight + Style.marginXL
Layout.preferredHeight: connectedDevicesCol.implicitHeight + Style.margin2M
border.color: showOnlyLists ? Style.boxBorderColor : "transparent"
ColumnLayout {
@@ -226,7 +226,7 @@ Item {
id: pairedDevicesBox
visible: root.pairedDevices.length > 0 && BluetoothService.enabled
Layout.fillWidth: true
Layout.preferredHeight: pairedDevicesCol.implicitHeight + Style.marginXL
Layout.preferredHeight: pairedDevicesCol.implicitHeight + Style.margin2M
border.color: showOnlyLists ? Style.boxBorderColor : "transparent"
ColumnLayout {
@@ -256,7 +256,7 @@ Item {
id: availableDevicesBox
visible: !root.showOnlyLists && root.unnamedAvailableDevices.length > 0 && BluetoothService.enabled
Layout.fillWidth: true
Layout.preferredHeight: availableDevicesCol.implicitHeight + Style.marginXL
Layout.preferredHeight: availableDevicesCol.implicitHeight + Style.margin2M
border.color: "transparent"
ColumnLayout {
@@ -25,6 +25,32 @@ ColumnLayout {
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)
@@ -160,13 +186,23 @@ ColumnLayout {
}
NText {
visible: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable
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)
}
}
}
}
+11 -15
View File
@@ -260,19 +260,17 @@ PopupWindow {
const tipWidth = Math.ceil(Math.min(contentWidth + (padding * 2), maxWidth));
root.implicitWidth = tipWidth;
// Add +2 buffer for fractional scaling issues (especially with "top" direction)
const tipHeight = Math.ceil(contentHeight + (padding * 2)) + 2;
const tipHeight = Math.ceil(contentHeight + (padding * 2));
root.implicitHeight = tipHeight;
// Get target's global position and convert to screen-relative
// Round all values to avoid sub-pixel positioning issues with fractional scaling
var targetGlobalAbs = targetItem.mapToGlobal(0, 0);
var targetGlobal = {
"x": Math.round(targetGlobalAbs.x - screenX),
"y": Math.round(targetGlobalAbs.y - screenY)
"x": targetGlobalAbs.x - screenX,
"y": targetGlobalAbs.y - screenY
};
const targetWidth = Math.round(targetItem.width);
const targetHeight = Math.round(targetItem.height);
const targetWidth = targetItem.width;
const targetHeight = targetItem.height;
var newAnchorX = 0;
var newAnchorY = 0;
@@ -427,7 +425,6 @@ PopupWindow {
}
// Apply position first (before making visible)
// Round to avoid sub-pixel positioning issues with fractional scaling
// Use floor for negative values to push tooltip away from target
anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX);
anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY);
@@ -519,19 +516,18 @@ PopupWindow {
const tipWidth = Math.ceil(Math.min(contentWidth + (padding * 2), maxWidth));
root.implicitWidth = tipWidth;
// Add +2 buffer for fractional scaling issues (especially with "top" direction)
const tipHeight = Math.ceil(contentHeight + (padding * 2)) + 2;
const tipHeight = Math.ceil(contentHeight + (padding * 2));
root.implicitHeight = tipHeight;
// Reposition based on current direction (screen-relative)
// Round all values to avoid sub-pixel positioning issues with fractional scaling
var targetGlobalAbs = targetItem.mapToGlobal(0, 0);
var targetGlobal = {
"x": Math.round(targetGlobalAbs.x - screenX),
"y": Math.round(targetGlobalAbs.y - screenY)
"x": targetGlobalAbs.x - screenX,
"y": targetGlobalAbs.y - screenY
};
const targetWidth = Math.round(targetItem.width);
const targetHeight = Math.round(targetItem.height);
const targetWidth = targetItem.width;
const targetHeight = targetItem.height;
// Recalculate base anchor position (center on target for top/bottom, etc.)
var newAnchorX = anchorX;
@@ -608,7 +604,6 @@ PopupWindow {
}
// Apply the new anchor positions
// Round to avoid sub-pixel positioning issues with fractional scaling
// Use floor for negative values to push tooltip away from target
anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX);
anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY);
@@ -663,6 +658,7 @@ PopupWindow {
Rectangle {
anchors.fill: parent
anchors.margins: border.width / 2
color: Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
+4
View File
@@ -499,6 +499,10 @@ Singleton {
});
}
function lock() {
CompositorService.lock();
}
function lockAndSuspend() {
CompositorService.lockAndSuspend();
}
+166 -6
View File
@@ -11,6 +11,7 @@ Singleton {
property list<var> ddcMonitors: []
readonly property list<Monitor> monitors: variants.instances
property bool appleDisplayPresent: false
property list<var> availableBacklightDevices: []
function getMonitorForScreen(screen: ShellScreen): var {
return monitors.find(m => m.modelData === screen);
@@ -47,10 +48,109 @@ Singleton {
return detectedDisplays;
}
function normalizeBacklightDevicePath(devicePath): string {
if (devicePath === undefined || devicePath === null)
return "";
var normalized = String(devicePath).trim();
if (normalized === "")
return "";
if (normalized.startsWith("/sys/class/backlight/"))
return normalized;
if (normalized.indexOf("/") === -1)
return "/sys/class/backlight/" + normalized;
return normalized;
}
function getBacklightDeviceName(devicePath): string {
var normalized = normalizeBacklightDevicePath(devicePath);
if (normalized === "")
return "";
var parts = normalized.split("/");
while (parts.length > 0 && parts[parts.length - 1] === "") {
parts.pop();
}
return parts.length > 0 ? parts[parts.length - 1] : "";
}
function getMappedBacklightDevice(outputName): string {
var normalizedOutput = String(outputName || "").trim();
if (normalizedOutput === "")
return "";
var mappings = Settings.data.brightness.backlightDeviceMappings || [];
for (var i = 0; i < mappings.length; i++) {
var mapping = mappings[i];
if (!mapping || typeof mapping !== "object")
continue;
if (String(mapping.output || "").trim() === normalizedOutput)
return normalizeBacklightDevicePath(mapping.device || "");
}
return "";
}
function setMappedBacklightDevice(outputName, devicePath): void {
var normalizedOutput = String(outputName || "").trim();
if (normalizedOutput === "")
return;
var normalizedDevicePath = normalizeBacklightDevicePath(devicePath);
var mappings = Settings.data.brightness.backlightDeviceMappings || [];
var nextMappings = [];
var replaced = false;
for (var i = 0; i < mappings.length; i++) {
var mapping = mappings[i];
if (!mapping || typeof mapping !== "object")
continue;
var mappingOutput = String(mapping.output || "").trim();
var mappingDevice = normalizeBacklightDevicePath(mapping.device || "");
if (mappingOutput === "" || mappingDevice === "")
continue;
if (mappingOutput === normalizedOutput) {
if (!replaced && normalizedDevicePath !== "") {
nextMappings.push({
"output": normalizedOutput,
"device": normalizedDevicePath
});
}
replaced = true;
} else {
nextMappings.push({
"output": mappingOutput,
"device": mappingDevice
});
}
}
if (!replaced && normalizedDevicePath !== "") {
nextMappings.push({
"output": normalizedOutput,
"device": normalizedDevicePath
});
}
Settings.data.brightness.backlightDeviceMappings = nextMappings;
}
function scanBacklightDevices(): void {
if (!scanBacklightProc.running)
scanBacklightProc.running = true;
}
reloadableId: "brightness"
Component.onCompleted: {
Logger.i("Brightness", "Service started");
scanBacklightDevices();
if (Settings.data.brightness.enableDdcSupport) {
ddcProc.running = true;
}
@@ -58,6 +158,7 @@ Singleton {
onMonitorsChanged: {
ddcMonitors = [];
scanBacklightDevices();
if (Settings.data.brightness.enableDdcSupport) {
ddcProc.running = true;
}
@@ -75,6 +176,14 @@ Singleton {
ddcMonitors = [];
}
}
function onBacklightDeviceMappingsChanged() {
scanBacklightDevices();
for (var i = 0; i < monitors.length; i++) {
var m = monitors[i];
if (m && !m.isDdc && !m.isAppleDisplay)
m.initBrightness();
}
}
}
Variants {
@@ -92,6 +201,34 @@ Singleton {
}
}
// Detect available internal backlight devices
Process {
id: scanBacklightProc
command: ["sh", "-c", "for dev in /sys/class/backlight/*; do if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then echo \"$dev\"; fi; done"]
stdout: StdioCollector {
onStreamFinished: {
var data = text.trim();
if (data === "") {
root.availableBacklightDevices = [];
return;
}
var lines = data.split("\n");
var found = [];
var seen = ({});
for (var i = 0; i < lines.length; i++) {
var path = root.normalizeBacklightDevicePath(lines[i]);
if (path === "" || seen[path])
continue;
seen[path] = true;
found.push(path);
}
root.availableBacklightDevices = found;
}
}
}
// Detect DDC monitors
Process {
id: ddcProc
@@ -152,6 +289,7 @@ Singleton {
property string maxBrightnessPath: ""
property int maxBrightness: 100
property bool ignoreNextChange: false
property bool initInProgress: false
// Signal for brightness changes
signal brightnessUpdated(real newBrightness)
@@ -296,14 +434,22 @@ Singleton {
Logger.d("Brightness", "Internal brightness:", current + "/" + max + " =", monitor.brightness);
Logger.d("Brightness", "Using backlight device:", monitor.backlightDevice);
}
} else {
monitor.backlightDevice = "";
monitor.brightnessPath = "";
monitor.maxBrightnessPath = "";
}
}
// Always update
monitor.brightnessUpdated(monitor.brightness);
root.monitorBrightnessChanged(monitor, monitor.brightness);
monitor.initInProgress = false;
}
}
onExited: (exitCode, exitStatus) => {
monitor.initInProgress = false;
}
}
readonly property real stepSize: Settings.data.brightness.brightnessStep / 100.0
@@ -379,12 +525,18 @@ Singleton {
} else if (!isDdc) {
monitor.commandRunning = true;
monitor.ignoreNextChange = true;
setBrightnessProc.command = ["brightnessctl", "s", rounded + "%"];
var backlightDeviceName = root.getBacklightDeviceName(monitor.backlightDevice);
if (backlightDeviceName !== "") {
setBrightnessProc.command = ["brightnessctl", "-d", backlightDeviceName, "s", rounded + "%"];
} else {
setBrightnessProc.command = ["brightnessctl", "s", rounded + "%"];
}
setBrightnessProc.running = true;
}
}
function initBrightness(): void {
monitor.initInProgress = true;
if (isAppleDisplay) {
initProc.command = ["asdbctl", "get"];
initProc.running = true;
@@ -392,16 +544,24 @@ Singleton {
initProc.command = ["ddcutil", "-b", busNum, "--sleep-multiplier=0.05", "getvcp", "10", "--brief"];
initProc.running = true;
} else if (!isDdc) {
// Internal backlight - find the first available backlight device and get its info
// This now returns: device_path, current_brightness, max_brightness (on separate lines)
initProc.command = ["sh", "-c", "for dev in /sys/class/backlight/*; do " + " if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then " + " echo \"$dev\"; " + " cat \"$dev/brightness\"; " + " cat \"$dev/max_brightness\"; " + " break; " + " fi; " + "done"];
// Internal backlight: first try explicit output mapping, then fall back to first available.
var preferredDevicePath = root.getMappedBacklightDevice(modelData.name);
var probeScript = [
"preferred=\"$1\"",
"if [ -n \"$preferred\" ] && [ ! -d \"$preferred\" ]; then preferred=\"/sys/class/backlight/$preferred\"; fi",
"selected=\"\"",
"if [ -n \"$preferred\" ] && [ -f \"$preferred/brightness\" ] && [ -f \"$preferred/max_brightness\" ]; then selected=\"$preferred\"; else for dev in /sys/class/backlight/*; do if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then selected=\"$dev\"; break; fi; done; fi",
"if [ -n \"$selected\" ]; then echo \"$selected\"; cat \"$selected/brightness\"; cat \"$selected/max_brightness\"; fi"
].join("; ");
initProc.command = ["sh", "-c", probeScript, "sh", preferredDevicePath];
initProc.running = true;
} else {
monitor.initInProgress = false;
}
}
onBusNumChanged: initBrightness()
onIsDdcChanged: if (isDdc)
initBrightness()
onIsDdcChanged: initBrightness()
Component.onCompleted: initBrightness()
}
}
+16 -9
View File
@@ -192,16 +192,23 @@ Singleton {
stderr: StdioCollector {}
}
// Read /etc/hostname
FileView {
id: hostName
path: "/etc/hostname"
onLoaded: {
const name = text().trim();
if (name) {
root.hostName = name;
Logger.i("HostService", "resolved hostname", name);
// Resolve hostname from distro-specific locations.
// Prefer /etc/hostname, fallback to Gentoo's /etc/conf.d/hostname.
Process {
id: hostNameProcess
command: ["sh", "-c",
"if [ -r /etc/hostname ]; then sed -n '1p' /etc/hostname; exit 0; fi; if [ -r /etc/conf.d/hostname ]; then v=$(sed -n -E 's/^[[:space:]]*[Hh][Oo][Ss][Tt][Nn][Aa][Mm][Ee][[:space:]]*=[[:space:]]*//p' /etc/conf.d/hostname | sed -n '1p'); v=$(printf '%s' \"$v\" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; s/^\"//; s/\"$//; s/^\x27//; s/\x27$//'); printf '%s\n' \"$v\"; exit 0; fi; exit 0"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const name = String(text || "").trim();
if (name.length > 0) {
root.hostName = name;
Logger.i("HostService", "resolved hostname", name);
}
}
}
stderr: StdioCollector {}
}
}
+9 -1
View File
@@ -547,7 +547,15 @@ Singleton {
// Image handling
function queueImage(path, appName, summary, notificationId) {
if (!path || !path.startsWith("image://") || !notificationId)
if (!path || !notificationId)
return;
// Cache image:// URIs and temporary file paths (e.g. /tmp/ from Chromium)
const filePath = path.startsWith("file://") ? path.substring(7) : path;
const isImageUri = path.startsWith("image://");
const isTempFile = (path.startsWith("/") || path.startsWith("file://")) && filePath.startsWith("/tmp/");
if (!isImageUri && !isTempFile)
return;
ImageCacheService.getNotificationIcon(path, appName, summary, function (cachedPath, success) {
+13 -5
View File
@@ -13,16 +13,19 @@ Singleton {
// When the wallpaper changes, regenerate theme if necessary
function onWallpaperChanged(screenName, path) {
if (!Settings.data.colorSchemes.useWallpaperColors)
return;
var effectiveMonitor = Settings.data.colorSchemes.monitorForColors;
if (effectiveMonitor === "" || effectiveMonitor === undefined) {
effectiveMonitor = Screen.name;
}
if (screenName === effectiveMonitor) {
if (screenName !== effectiveMonitor)
return;
if (Settings.data.colorSchemes.useWallpaperColors) {
generateFromWallpaper();
} else {
// Re-run predefined scheme templates so {{image}} reflects the new wallpaper path
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
}
}
}
@@ -77,6 +80,11 @@ Singleton {
function generateFromPredefinedScheme(schemeData) {
Logger.i("AppThemeService", "Generating templates from predefined color scheme");
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
TemplateProcessor.processPredefinedScheme(schemeData, mode);
var effectiveMonitor = Settings.data.colorSchemes.monitorForColors;
if (effectiveMonitor === "" || effectiveMonitor === undefined) {
effectiveMonitor = Screen.name;
}
const wallpaperPath = WallpaperService.getWallpaper(effectiveMonitor) || "";
TemplateProcessor.processPredefinedScheme(schemeData, mode, wallpaperPath);
}
}
+13 -8
View File
@@ -118,16 +118,17 @@ Singleton {
* Uses --scheme flag to expand 14-color scheme to full 48-color palette
* Uses debouncing to prevent spawning multiple processes when spamming scheme changes
*/
function processPredefinedScheme(schemeData, mode) {
function processPredefinedScheme(schemeData, mode, wallpaperPath) {
pendingPredefinedRequest = {
schemeData: schemeData,
mode: mode
mode: mode,
wallpaperPath: wallpaperPath || ""
};
pendingWallpaperRequest = null;
debounceTimer.restart();
}
function executePredefinedScheme(schemeData, mode) {
function executePredefinedScheme(schemeData, mode, wallpaperPath) {
// 1. Handle terminal themes (runtime generation or pre-rendered file copy)
handleTerminalThemes(schemeData, mode);
@@ -161,10 +162,12 @@ Singleton {
// Run Python template processor with --scheme flag
// Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both)
// Pass --default-mode so "default" in templates resolves to the current theme mode
script += `python3 "${templateProcessorScript}" --scheme '${schemeJsonPathEsc}' --config '${configPathEsc}' --default-mode ${mode}\n`;
// Pass wallpaper as positional arg so image_path is available in templates (no extraction occurs when --scheme is used)
const wpArg = wallpaperPath ? `'${wallpaperPath.replace(/'/g, "'\\''")}'` : "";
script += `python3 "${templateProcessorScript}" ${wpArg} --scheme '${schemeJsonPathEsc}' --config '${configPathEsc}' --default-mode ${mode}\n`;
// Add user templates if enabled
script += buildUserTemplateCommandForPredefined(schemeData, mode);
script += buildUserTemplateCommandForPredefined(schemeData, mode, wallpaperPath);
generateProcess.command = ["sh", "-c", script];
generateProcess.running = true;
@@ -515,7 +518,7 @@ Singleton {
return script;
}
function buildUserTemplateCommandForPredefined(schemeData, mode) {
function buildUserTemplateCommandForPredefined(schemeData, mode, wallpaperPath) {
if (!Settings.data.templates.enableUserTheming)
return "";
@@ -523,13 +526,15 @@ Singleton {
// Reuse the scheme JSON already written by processPredefinedScheme()
const schemeJsonPathEsc = schemeJsonPath.replace(/'/g, "'\\''");
const wpArg = wallpaperPath ? `'${wallpaperPath.replace(/'/g, "'\\''")}'` : "";
let script = "\n# Execute user templates with predefined scheme colors\n";
script += `if [ -f '${userConfigPath}' ]; then\n`;
// Use --scheme flag with the already-written scheme JSON
// Don't pass --mode so user templates get both dark and light colors
// Pass --default-mode so "default" in templates resolves to the current theme mode
script += ` python3 "${templateProcessorScript}" --scheme '${schemeJsonPathEsc}' --config '${userConfigPath}' --default-mode ${mode}\n`;
// Pass wallpaper as positional arg so image_path is available in templates
script += ` python3 "${templateProcessorScript}" ${wpArg} --scheme '${schemeJsonPathEsc}' --config '${userConfigPath}' --default-mode ${mode}\n`;
script += "fi";
return script;
@@ -551,7 +556,7 @@ Singleton {
} else if (pendingPredefinedRequest) {
const req = pendingPredefinedRequest;
pendingPredefinedRequest = null;
executePredefinedScheme(req.schemeData, req.mode);
executePredefinedScheme(req.schemeData, req.mode, req.wallpaperPath);
} else {
Logger.d("TemplateProcessor", "executePendingRequest: no pending request");
}
+52 -2
View File
@@ -162,8 +162,11 @@ Singleton {
return;
}
// File paths are used directly, not cached
if (imageUri.startsWith("/") || imageUri.startsWith("file://")) {
// Resolve bare file path for temp check
const filePath = imageUri.startsWith("file://") ? imageUri.substring(7) : imageUri;
// File paths in persistent locations are used directly, not cached
if ((imageUri.startsWith("/") || imageUri.startsWith("file://")) && !isTemporaryPath(filePath)) {
callback(imageUri, false);
return;
}
@@ -171,12 +174,59 @@ Singleton {
const cacheKey = generateNotificationKey(imageUri, appName, summary);
const cachedPath = notificationsDir + cacheKey + ".png";
// Temporary file paths are copied to cache before the source is cleaned up
if (imageUri.startsWith("/") || imageUri.startsWith("file://")) {
processRequest(cacheKey, cachedPath, imageUri, callback, function () {
copyTempFileToCache(filePath, cachedPath, cacheKey);
});
return;
}
processRequest(cacheKey, cachedPath, imageUri, callback, function () {
// Notifications always use Qt fallback (image:// URIs can't be read by ImageMagick)
queueFallbackProcessing(imageUri, cachedPath, cacheKey, 64);
});
}
// Check if a path is in a temporary directory that may be cleaned up
function isTemporaryPath(path) {
return path.startsWith("/tmp/");
}
// Copy a temporary file to the cache directory
function copyTempFileToCache(sourcePath, destPath, cacheKey) {
const srcEsc = sourcePath.replace(/'/g, "'\\''");
const dstEsc = destPath.replace(/'/g, "'\\''");
const processString = `
import QtQuick
import Quickshell.Io
Process {
command: ["cp", "--", "${srcEsc}", "${dstEsc}"]
stdout: StdioCollector {}
stderr: StdioCollector {}
}
`;
queueUtilityProcess({
name: "CopyTempFile_" + cacheKey,
processString: processString,
onComplete: function (exitCode) {
if (exitCode === 0) {
Logger.d("ImageCache", "Temp file cached:", destPath);
notifyCallbacks(cacheKey, destPath, true);
} else {
Logger.w("ImageCache", "Failed to cache temp file:", sourcePath);
notifyCallbacks(cacheKey, "", false);
}
},
onError: function () {
Logger.e("ImageCache", "Error caching temp file:", sourcePath);
notifyCallbacks(cacheKey, "", false);
}
});
}
// -------------------------------------------------
// Public API: Get Circular Avatar (256x256)
// -------------------------------------------------
+1 -1
View File
@@ -15,7 +15,7 @@ Item {
property color borderColor: "transparent"
property int imageFillMode: Image.PreserveAspectCrop
readonly property bool showFallback: (fallbackIcon !== undefined && fallbackIcon !== "") && (imagePath === undefined || imagePath === "")
readonly property bool showFallback: (fallbackIcon !== undefined && fallbackIcon !== "") && (imagePath === undefined || imagePath === "" || imageSource.status === Image.Error)
readonly property int status: imageSource.status
Rectangle {