Merge pull request #1059 from lonerOrz/ipc/osd

OSD notification with custom text
This commit is contained in:
Lysec
2025-12-15 08:11:21 +01:00
committed by GitHub
16 changed files with 197 additions and 44 deletions
+4
View File
@@ -1974,6 +1974,10 @@
"description": "OSD anzeigen, wenn Feststelltaste, Num-Taste oder Rollen-Taste umgeschaltet werden.",
"label": "Feststelltasten"
},
"custom-text": {
"description": "OSD für benutzerdefinierte Textnachrichten aus IPC anzeigen.",
"label": "Benutzerdefinierter Text"
},
"section": {
"description": "Wählen Sie die Ereignisse aus, die das OSD auslösen.",
"label": "OSD-Auslöseereignisse"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Show OSD when Caps Lock, Num Lock, or Scroll Lock are toggled.",
"label": "Lock keys"
},
"custom-text": {
"description": "Show OSD for custom text messages from IPC.",
"label": "Custom Text"
},
"section": {
"description": "Select the events that trigger the OSD.",
"label": "OSD trigger events"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Mostrar el OSD cuando se activen o desactiven Bloq Mayús, Bloq Num o Bloq Despl.",
"label": "Teclas de bloqueo"
},
"custom-text": {
"description": "Mostrar el OSD para mensajes de texto personalizados desde IPC.",
"label": "Texto personalizado"
},
"section": {
"description": "Seleccione los eventos que activan el OSD.",
"label": "Eventos de activación OSD"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Afficher l'OSD lorsque Verr Maj, Verr Num ou Arrêt défil est activée ou désactivée.",
"label": "Touches de verrouillage"
},
"custom-text": {
"description": "Afficher lOSD pour les messages texte personnalisés provenant de lIPC.",
"label": "Texte personnalisé"
},
"section": {
"description": "Sélectionnez les événements qui déclenchent l'OSD.",
"label": "Événements de déclenchement OSD"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Caps Lock、Num Lock、Scroll Lock の切り替え時にOSDを表示します。",
"label": "ロックキー"
},
"custom-text": {
"description": "IPC からのカスタムテキストメッセージの OSD を表示します。",
"label": "カスタムテキスト"
},
"section": {
"description": "OSD を表示するトリガー(イベント)を選択します。",
"label": "OSD のトリガーイベント"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Toon het OSD wanneer Caps Lock, Num Lock of Scroll Lock wordt omgeschakeld.",
"label": "Vergrendeltoetsen"
},
"custom-text": {
"description": "OSD weergeven voor aangepaste tekstberichten via IPC.",
"label": "Aangepaste tekst"
},
"section": {
"description": "Selecteer de gebeurtenissen die de OSD activeren.",
"label": "OSD triggergebeurtenissen"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Mostrar o OSD quando Caps Lock, Num Lock ou Scroll Lock forem alternadas.",
"label": "Teclas de bloqueio"
},
"custom-text": {
"description": "Mostrar OSD para mensagens de texto personalizadas via IPC.",
"label": "Texto personalizado"
},
"section": {
"description": "Selecione os eventos que acionam o OSD.",
"label": "Eventos de disparo OSD"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Показывать OSD при переключении клавиш Caps Lock, Num Lock или Scroll Lock.",
"label": "Клавиши блокировки"
},
"custom-text": {
"description": "Показывать OSD для пользовательских текстовых сообщений через IPC.",
"label": "Пользовательский текст"
},
"section": {
"description": "Выберите события, которые должны запускать экранное меню (OSD).",
"label": "События, запускающие экранное меню"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Büyük Harf Kilidi, Sayı Kilidi veya Kaydırma Kilidi değiştirildiğinde OSD'yi göster.",
"label": "Kilit tuşları"
},
"custom-text": {
"description": "IPC üzerinden özel metin iletileri için OSD göster.",
"label": "Özel Metin"
},
"section": {
"description": "OSD'yi tetikleyecek olayları seçin.",
"label": "OSD tetikleme olayları"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "Показувати OSD, коли перемикаються Caps Lock, Num Lock або Scroll Lock.",
"label": "Клавіші блокування"
},
"custom-text": {
"description": "Показувати OSD для користувацьких текстових повідомлень через IPC.",
"label": "Користувацький текст"
},
"section": {
"description": "Виберіть події, які запускають екранне меню.",
"label": "Події, що запускають OSD"
+4
View File
@@ -1974,6 +1974,10 @@
"description": "当切换大写锁定、数字锁定或滚动锁定时显示 OSD。",
"label": "锁定键"
},
"custom-text": {
"description": "显示来自 IPC 的自定义文本消息的 OSD。",
"label": "自定义文本"
},
"section": {
"description": "选择触发OSD的事件。",
"label": "OSD触发事件"
+2 -6
View File
@@ -332,11 +332,7 @@
"autoHideMs": 2000,
"overlayLayer": true,
"backgroundOpacity": 1,
"enabledTypes": [
0,
1,
2
],
"enabledTypes": [0, 1, 2, 4],
"monitors": []
},
"audio": {
@@ -407,4 +403,4 @@
"editMode": false,
"monitorWidgets": []
}
}
}
+125 -38
View File
@@ -7,6 +7,7 @@ import qs.Commons
import qs.Services.Hardware
import qs.Services.Keyboard
import qs.Services.Media
import qs.Services.UI
import qs.Widgets
// Unified OSD component that displays volume, input volume, and brightness changes
@@ -18,7 +19,8 @@ Variants {
Volume,
InputVolume,
Brightness,
LockKey
LockKey,
CustomText
}
model: Quickshell.screens.filter(screen => (Settings.data.osd.monitors.includes(screen.name) || Settings.data.osd.monitors.length === 0) && Settings.data.osd.enabled)
@@ -34,6 +36,8 @@ Variants {
property int currentOSDType: -1 // OSD.Type enum value, -1 means none
property bool startupComplete: false
property real currentBrightness: 0
property string customText: ""
property string customIcon: ""
// Lock Key States
property string lastLockKeyChanged: "" // "caps", "num", "scroll", or ""
@@ -48,13 +52,17 @@ Variants {
// LockKey OSD enabled state (reactive to settings)
readonly property bool lockKeyOSDEnabled: {
const enabledTypes = Settings.data.osd.enabledTypes || [];
// If enabledTypes is empty, no types are enabled (no OSD will be shown)
if (enabledTypes.length === 0)
return false;
return enabledTypes.includes(OSD.Type.LockKey);
}
// Helper Functions
readonly property var validIcons: {
const iconKeys = Object.keys(Icons.icons);
const aliasKeys = Object.keys(Icons.aliases);
return new Set([...iconKeys, ...aliasKeys]);
}
function getIcon() {
switch (currentOSDType) {
case OSD.Type.Volume:
@@ -73,6 +81,8 @@ Variants {
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
case OSD.Type.LockKey:
return "keyboard";
case OSD.Type.CustomText:
return root.customIcon || "info-circle";
default:
return "";
}
@@ -88,6 +98,8 @@ Variants {
return currentBrightness;
case OSD.Type.LockKey:
return 1.0; // Always show 100% when showing lock key status
case OSD.Type.CustomText:
return 1.0;
default:
return 0;
}
@@ -105,6 +117,9 @@ Variants {
// For lock keys, return the pre-determined status text
return lastLockKeyChanged;
}
if (currentOSDType === OSD.Type.CustomText) {
return customText;
}
const value = getCurrentValue();
const max = getMaxValue();
@@ -139,6 +154,9 @@ Variants {
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
}
}
if (currentOSDType === OSD.Type.CustomText) {
return Color.mPrimary;
}
return Color.mPrimary;
}
@@ -219,6 +237,23 @@ Variants {
}
// Signal Connections
Connections {
target: OSDService
function onShowCustomText(text, icon) {
root.customText = text;
if (icon && root.validIcons.has(icon)) {
root.customIcon = icon;
} else {
if (icon) {
Logger.w("OSD", "Invalid custom icon name received: '" + icon + "'. Falling back to default.");
}
root.customIcon = "info-circle";
}
showOSD(OSD.Type.CustomText);
}
}
// AudioService monitoring
Connections {
@@ -332,8 +367,19 @@ Variants {
wrapMode: Text.NoWrap
}
NText {
id: customTextMetrics
visible: false
text: root.customText
pointSize: Style.fontSizeM
family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
elide: Text.ElideNone
wrapMode: Text.NoWrap
}
// Dimensions
readonly property bool isShortMode: root.currentOSDType === OSD.Type.LockKey
readonly property bool isShortMode: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText
readonly property int longHWidth: Math.round(320 * Style.uiScaleRatio)
readonly property int longHHeight: Math.round(72 * Style.uiScaleRatio)
readonly property int shortHWidth: Math.round(180 * Style.uiScaleRatio)
@@ -341,6 +387,25 @@ Variants {
readonly property int longVHeight: Math.round(280 * Style.uiScaleRatio)
readonly property int shortVHeight: Math.round(180 * Style.uiScaleRatio)
// Dynamic width for horizontal custom text based on text length
readonly property int customTextHWidth: {
if (root.currentOSDType !== OSD.Type.CustomText || verticalMode) {
return shortHWidth;
}
const textWidth = Math.ceil(customTextMetrics.contentWidth || 0);
if (textWidth === 0) {
// Fallback if measurement not ready
return longHWidth;
}
const iconWidth = Style.fontSizeXL * Style.uiScaleRatio;
const margins = Style.marginL * 2; // Left and right content margins
const spacing = Style.marginM; // Spacing between icon and text
const bgMargins = Style.marginM * 1.5 * 2; // Background margins
const totalWidth = textWidth + iconWidth + margins + spacing + bgMargins;
// Ensure minimum width and add some buffer
return Math.max(shortHWidth, Math.round(totalWidth * 1.1));
}
// Dynamic width for horizontal lock keys based on text length
// Explicitly bind to contentWidth to ensure reactivity
readonly property int lockKeyHWidth: {
@@ -402,6 +467,27 @@ Variants {
return Math.max(shortVHeight, Math.round(totalHeight * 1.1));
}
readonly property int customTextVHeight: {
if (root.currentOSDType !== OSD.Type.CustomText || !verticalMode) {
return shortVHeight;
}
const text = root.customText;
const charCount = text ? text.length : 0;
if (charCount === 0) {
return shortVHeight;
}
const fontSize = Style.fontSizeM * Settings.data.ui.fontFixedScale * Style.uiScaleRatio;
const charHeight = fontSize * 1.3;
const textHeight = charCount * charHeight;
const bgMargins = Style.marginM * 1.5 * 2;
const contentMargins = Style.marginL * 2;
const iconSize = Style.fontSizeXL * Style.uiScaleRatio * 1.8;
const textIconSpacing = Style.marginM;
const buffer = Style.marginL;
const totalHeight = textHeight + bgMargins + contentMargins + iconSize + textIconSpacing + buffer;
return Math.max(shortVHeight, Math.round(totalHeight * 1.1));
}
readonly property int barThickness: {
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
return base % 2 === 0 ? base : base + 1;
@@ -430,8 +516,8 @@ Variants {
margins.left: calculateMargin(anchors.left, "left")
margins.right: calculateMargin(anchors.right, "right")
implicitWidth: verticalMode ? longVWidth : (isShortMode ? lockKeyHWidth : longHWidth)
implicitHeight: verticalMode ? (isShortMode ? lockKeyVHeight : longVHeight) : longHHeight
implicitWidth: verticalMode ? longVWidth : (root.currentOSDType === OSD.Type.CustomText ? customTextHWidth : (isShortMode ? lockKeyHWidth : longHWidth))
implicitHeight: verticalMode ? (root.currentOSDType === OSD.Type.CustomText ? customTextVHeight : (isShortMode ? lockKeyVHeight : longVHeight)) : longHHeight
color: Color.transparent
WlrLayershell.namespace: "noctalia-osd-" + (screen?.name || "unknown")
@@ -472,7 +558,9 @@ Variants {
onTriggered: {
osdItem.visible = false;
root.currentOSDType = -1;
root.lastLockKeyChanged = ""; // Reset the lock key change indicator
root.lastLockKeyChanged = "";
root.customText = "";
root.customIcon = "";
root.active = false;
}
}
@@ -511,15 +599,13 @@ Variants {
anchors.leftMargin: Style.marginL
anchors.rightMargin: Style.marginL
spacing: Style.marginM
clip: true
// TextMetrics to measure the maximum possible percentage width
TextMetrics {
id: percentageMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: Style.fontSizeS * (Settings.data.ui.fontFixedScale * Style.uiScaleRatio)
text: "150%" // Maximum possible value with volumeOverdrive
text: "150%"
}
// Common Icon for all types
@@ -537,9 +623,9 @@ Variants {
}
}
// Lock Key Status Text (replaces progress bar)
// Lock Key or Custom Status Text (replaces progress bar)
NText {
visible: root.currentOSDType === OSD.Type.LockKey
visible: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText
text: root.getDisplayPercentage()
color: root.getProgressColor()
pointSize: Style.fontSizeM
@@ -553,7 +639,7 @@ Variants {
// Progress Bar for Volume/Brightness
Rectangle {
visible: root.currentOSDType !== OSD.Type.LockKey
visible: root.currentOSDType !== OSD.Type.LockKey && root.currentOSDType !== OSD.Type.CustomText
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
height: panel.barThickness
@@ -585,7 +671,7 @@ Variants {
// Percentage Text for Volume/Brightness
NText {
visible: root.currentOSDType !== OSD.Type.LockKey
visible: root.currentOSDType !== OSD.Type.LockKey && root.currentOSDType !== OSD.Type.CustomText
text: root.getDisplayPercentage()
color: Color.mOnSurface
pointSize: Style.fontSizeS
@@ -607,47 +693,51 @@ Variants {
anchors.fill: parent
anchors.topMargin: Style.marginL
anchors.bottomMargin: Style.marginL
spacing: root.currentOSDType === OSD.Type.LockKey ? Style.marginM : Style.marginS
clip: root.currentOSDType !== OSD.Type.LockKey
spacing: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText ? Style.marginM : Style.marginS
clip: root.currentOSDType !== OSD.Type.LockKey && root.currentOSDType !== OSD.Type.CustomText
// Vertical text display for Lock Keys
ColumnLayout {
id: lockKeyVerticalLayout
visible: root.currentOSDType === OSD.Type.LockKey
id: textVerticalLayout
visible: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText
Layout.fillWidth: true
Layout.fillHeight: false
Layout.alignment: Qt.AlignHCenter
spacing: 0
property var lockKeyChars: []
property var verticalTextChars: []
function updateLockKeyChars() {
function updateVerticalTextChars() {
const text = root.getDisplayPercentage();
const chars = [];
for (let i = 0; i < text.length; i++) {
chars.push(text[i]);
}
lockKeyChars = chars;
verticalTextChars = chars;
}
Component.onCompleted: updateLockKeyChars()
Component.onCompleted: updateVerticalTextChars()
Connections {
target: root
function onLastLockKeyChangedChanged() {
if (root.currentOSDType === OSD.Type.LockKey) {
lockKeyVerticalLayout.updateLockKeyChars();
textVerticalLayout.updateVerticalTextChars();
}
}
function onCustomTextChanged() {
if (root.currentOSDType === OSD.Type.CustomText) {
textVerticalLayout.updateVerticalTextChars();
}
}
function onCurrentOSDTypeChanged() {
if (root.currentOSDType === OSD.Type.LockKey) {
lockKeyVerticalLayout.updateLockKeyChars();
if (root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText) {
textVerticalLayout.updateVerticalTextChars();
}
}
}
Repeater {
model: lockKeyVerticalLayout.lockKeyChars
model: textVerticalLayout.verticalTextChars
NText {
text: modelData || ""
@@ -658,7 +748,7 @@ Variants {
Layout.fillWidth: true
Layout.preferredHeight: {
const fontSize = Style.fontSizeM * Settings.data.ui.fontFixedScale * Style.uiScaleRatio;
return Math.round(fontSize * 1.3); // Add 30% for line height
return Math.round(fontSize * 1.3);
}
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
@@ -667,9 +757,8 @@ Variants {
}
}
// Unified Text display for Percentage (non-lock key)
NText {
visible: root.currentOSDType !== OSD.Type.LockKey
visible: root.currentOSDType !== OSD.Type.LockKey && root.currentOSDType !== OSD.Type.CustomText
text: root.getDisplayPercentage()
color: Color.mOnSurface
pointSize: Style.fontSizeS
@@ -682,11 +771,10 @@ Variants {
Layout.preferredHeight: Math.round(20 * Style.uiScaleRatio)
}
// Progress Bar for Volume/Brightness
Item {
visible: root.currentOSDType !== OSD.Type.LockKey
visible: root.currentOSDType !== OSD.Type.LockKey && root.currentOSDType !== OSD.Type.CustomText
Layout.fillWidth: true
Layout.fillHeight: root.currentOSDType !== OSD.Type.LockKey
Layout.fillHeight: root.currentOSDType !== OSD.Type.LockKey && root.currentOSDType !== OSD.Type.CustomText
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
@@ -720,14 +808,13 @@ Variants {
}
}
// Unified Icon display
NIcon {
icon: root.getIcon()
color: root.getIconColor()
pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeXL : Style.fontSizeL
Layout.alignment: root.currentOSDType === OSD.Type.LockKey ? Qt.AlignHCenter : (Qt.AlignHCenter | Qt.AlignBottom)
Layout.preferredHeight: root.currentOSDType === OSD.Type.LockKey ? (Style.fontSizeXL * Style.uiScaleRatio * 1.5) : -1
Layout.minimumHeight: root.currentOSDType === OSD.Type.LockKey ? (Style.fontSizeXL * Style.uiScaleRatio) : 0
pointSize: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText ? Style.fontSizeXL : Style.fontSizeL
Layout.alignment: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText ? Qt.AlignHCenter : (Qt.AlignHCenter | Qt.AlignBottom)
Layout.preferredHeight: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText ? (Style.fontSizeXL * Style.uiScaleRatio * 1.5) : -1
Layout.minimumHeight: root.currentOSDType === OSD.Type.LockKey || root.currentOSDType === OSD.Type.CustomText ? (Style.fontSizeXL * Style.uiScaleRatio) : 0
Behavior on color {
ColorAnimation {
+4
View File
@@ -175,6 +175,10 @@ ColumnLayout {
{
type: OSD.Type.LockKey,
key: "lockkey"
},
{
type: OSD.Type.CustomText,
key: "custom-text"
}
]
delegate: NCheckbox {
+12
View File
@@ -383,6 +383,18 @@ Item {
}
}
IpcHandler {
target: "osd"
function showText(text: string) {
OSDService.showCustomText(text, "");
}
function showTextWithIcon(text: string, icon: string) {
OSDService.showCustomText(text, icon);
}
}
IpcHandler {
target: "media"
function playPause() {
+10
View File
@@ -0,0 +1,10 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
signal showCustomText(string text, string icon)
}