diff options
| author | Thomas Voss <mail@thomasvoss.com> | 2026-03-23 13:06:10 +0100 |
|---|---|---|
| committer | Thomas Voss <mail@thomasvoss.com> | 2026-03-23 13:06:10 +0100 |
| commit | 25d3f382b1218b9112b2c4c7219abf2e6ced3c74 (patch) | |
| tree | dccbfdd2b9f21f691276c909d0ccad4ef2a07c1b /.config | |
| parent | 7d6bc7e062af943c70332404c925a9e24fdc6127 (diff) | |
noctalia: Add the Noctalia config
Diffstat (limited to '.config')
28 files changed, 2237 insertions, 0 deletions
diff --git a/.config/noctalia/colors.json b/.config/noctalia/colors.json new file mode 100644 index 0000000..2c13fcf --- /dev/null +++ b/.config/noctalia/colors.json @@ -0,0 +1,18 @@ +{ + "mError": "#bf616a", + "mHover": "#6fa9a8", + "mOnError": "#eceff4", + "mOnHover": "#eceff4", + "mOnPrimary": "#eceff4", + "mOnSecondary": "#eceff4", + "mOnSurface": "#2e3440", + "mOnSurfaceVariant": "#4c566a", + "mOnTertiary": "#eceff4", + "mOutline": "#c5cedd", + "mPrimary": "#5e81ac", + "mSecondary": "#64adc2", + "mShadow": "#d8dee9", + "mSurface": "#eceff4", + "mSurfaceVariant": "#e5e9f0", + "mTertiary": "#6fa9a8" +} diff --git a/.config/noctalia/plugins.json b/.config/noctalia/plugins.json new file mode 100644 index 0000000..735522c --- /dev/null +++ b/.config/noctalia/plugins.json @@ -0,0 +1,16 @@ +{ + "sources": [ + { + "enabled": true, + "name": "Noctalia Plugins", + "url": "https://github.com/noctalia-dev/noctalia-plugins" + } + ], + "states": { + "timer": { + "enabled": true, + "sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins" + } + }, + "version": 2 +} diff --git a/.config/noctalia/plugins/timer/BarWidget.qml b/.config/noctalia/plugins/timer/BarWidget.qml new file mode 100644 index 0000000..81d2cb9 --- /dev/null +++ b/.config/noctalia/plugins/timer/BarWidget.qml @@ -0,0 +1,179 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets +import qs.Services.UI +import qs.Services.System + +Item { + id: root + + property var pluginApi: null + property ShellScreen screen + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + readonly property bool pillDirection: BarService.getPillDirection(root) + + readonly property var mainInstance: pluginApi?.mainInstance + readonly property bool isActive: mainInstance && (mainInstance.cdRunning || mainInstance.swRunning || mainInstance.swElapsedSeconds > 0 || mainInstance.cdRemainingSeconds > 0 || mainInstance.cdSoundPlaying) + + property var cfg: pluginApi?.pluginSettings || ({}) + property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({}) + + readonly property string iconColorKey: cfg.iconColor ?? defaults.iconColor ?? "none" + readonly property color iconColor: Color.resolveColorKey(iconColorKey) + + readonly property string textColorKey: cfg.textColor ?? defaults.textColor ?? "none" + readonly property color textColor: Color.resolveColorKey(textColorKey) + + // Bar positioning properties + readonly property string screenName: screen ? screen.name : "" + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + readonly property real contentWidth: { + if (isVertical) return root.capsuleHeight + if (isActive) return contentRow.implicitWidth + Style.marginM * 2 + return root.capsuleHeight + } + readonly property real contentHeight: root.capsuleHeight + + implicitWidth: contentWidth + implicitHeight: contentHeight + + function formatTime(seconds) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + + if (hours > 0) { + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + Rectangle { + id: visualCapsule + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + width: root.contentWidth + height: root.contentHeight + color: mouseArea.containsMouse ? Color.mHover : Style.capsuleColor + radius: Style.radiusL + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + RowLayout { + id: contentRow + anchors.centerIn: parent + spacing: Style.marginS + layoutDirection: pillDirection ? Qt.LeftToRight : Qt.RightToLeft + + NIcon { + icon: { + if (mainInstance && mainInstance.timerSoundPlaying) return "bell-ringing" + if (mainInstance && mainInstance.timerStopwatchMode) return "stopwatch" + return "hourglass" + } + applyUiScale: false + color: mouseArea.containsMouse ? Color.mOnHover : root.iconColor + } + + NText { + visible: !isVertical && mainInstance && (mainInstance.cdRunning || mainInstance.swRunning || mainInstance.swElapsedSeconds > 0 || mainInstance.cdRemainingSeconds > 0 || mainInstance.cdSoundPlaying) + family: Settings.data.ui.fontFixed + pointSize: root.barFontSize + font.weight: Style.fontWeightBold + color: mouseArea.containsMouse ? Color.mOnHover : root.textColor + text: { + if (!mainInstance) return "" + if (mainInstance.timerStopwatchMode) { + return formatTime(mainInstance.timerElapsedSeconds) + } + return formatTime(mainInstance.timerRemainingSeconds) + } + } + } + } + + NPopupContextMenu { + id: contextMenu + + model: { + var items = []; + + if (mainInstance) { + // Pause / Resume & Reset + const modeActive = mainInstance.timerStopwatchMode + ? (mainInstance.swRunning || mainInstance.swElapsedSeconds > 0) + : (mainInstance.cdRunning || mainInstance.cdRemainingSeconds > 0 || mainInstance.cdSoundPlaying); + if (modeActive) { + items.push({ + "label": mainInstance.timerRunning ? pluginApi.tr("panel.pause") : pluginApi.tr("panel.resume"), + "action": "toggle", + "icon": mainInstance.timerRunning ? "media-pause" : "media-play" + }); + + items.push({ + "label": pluginApi.tr("panel.reset"), + "action": "reset", + "icon": "refresh" + }); + } + } + + // Settings + items.push({ + "label": pluginApi.tr("panel.settings"), + "action": "widget-settings", + "icon": "settings" + }); + + return items; + } + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + + if (action === "widget-settings") { + BarService.openPluginSettings(screen, pluginApi.manifest); + } else if (mainInstance) { + if (action === "toggle") { + if (mainInstance.timerRunning) { + mainInstance.timerPause(); + } else { + mainInstance.timerStart(); + } + } else if (action === "reset") { + mainInstance.timerReset(); + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: (mouse) => { + if (mouse.button === Qt.LeftButton) { + if (pluginApi) { + pluginApi.openPanel(root.screen, root) + } + } else if (mouse.button === Qt.RightButton) { + PanelService.showContextMenu(contextMenu, root, screen); + } + } + } +} diff --git a/.config/noctalia/plugins/timer/ControlCenterWidget.qml b/.config/noctalia/plugins/timer/ControlCenterWidget.qml new file mode 100644 index 0000000..528ee04 --- /dev/null +++ b/.config/noctalia/plugins/timer/ControlCenterWidget.qml @@ -0,0 +1,45 @@ +import QtQuick +import Quickshell +import qs.Widgets +import qs.Commons + +NIconButton { + property ShellScreen screen + property var pluginApi: null + readonly property var mainInstance: pluginApi?.mainInstance + + icon: { + if (mainInstance && mainInstance.timerSoundPlaying) return "bell-ringing" + if (mainInstance && mainInstance.timerStopwatchMode) return "stopwatch" + return "hourglass" + } + + tooltipText: { + if (!mainInstance) return "Timer" + if (mainInstance.timerSoundPlaying) return "Timer Finished!" + if (mainInstance.timerStopwatchMode) { + return mainInstance.timerRunning ? "Stopwatch Running" : "Stopwatch" + } + return mainInstance.timerRunning ? "Timer Running" : "Timer" + } + + colorFg: { + if (mainInstance && (mainInstance.cdRunning || mainInstance.swRunning || mainInstance.cdSoundPlaying)) { + return Color.mOnPrimary + } + return Color.mPrimary + } + + colorBg: { + if (mainInstance && (mainInstance.cdRunning || mainInstance.swRunning || mainInstance.cdSoundPlaying)) { + return Color.mPrimary + } + return Style.capsuleColor + } + + onClicked: { + if (pluginApi) { + pluginApi.togglePanel(screen); + } + } +} diff --git a/.config/noctalia/plugins/timer/Main.qml b/.config/noctalia/plugins/timer/Main.qml new file mode 100644 index 0000000..db2193d --- /dev/null +++ b/.config/noctalia/plugins/timer/Main.qml @@ -0,0 +1,239 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Services.System +import qs.Services.UI +import Quickshell.Io + +Item { + id: root + + property var pluginApi: null + + IpcHandler { + target: "plugin:timer" + + function toggle() { + if (pluginApi) { + pluginApi.withCurrentScreen(screen => { + pluginApi.togglePanel(screen); + }); + } + } + + function start(duration_str: string) { + if (duration_str && duration_str === "stopwatch") { + root.stopwatchReset(); + root.timerStopwatchMode = true; + root.stopwatchStart(); + } else if (duration_str && duration_str !== "") { + const seconds = root.parseDuration(duration_str); + if (seconds > 0) { + root.countdownReset(); + root.cdRemainingSeconds = seconds; + root.timerStopwatchMode = false; + root.countdownStart(); + } + } else { + root.timerStart(); + } + } + + function pause() { + root.timerPause(); + } + + function reset() { + root.timerReset(); + } + } + + // View mode (which tab is active in the panel) + property bool timerStopwatchMode: false + + // Countdown state + property bool cdRunning: false + property int cdRemainingSeconds: 0 + property int cdTotalSeconds: 0 + property int cdStartTimestamp: 0 + property int cdPausedAt: 0 + property bool cdSoundPlaying: false + + // Stopwatch state + property bool swRunning: false + property int swElapsedSeconds: 0 + property int swStartTimestamp: 0 + property int swPausedAt: 0 + + // Backward-compatible computed properties (used by bar/CC widgets) + readonly property bool timerRunning: timerStopwatchMode ? swRunning : cdRunning + readonly property int timerRemainingSeconds: cdRemainingSeconds + readonly property int timerTotalSeconds: cdTotalSeconds + readonly property int timerElapsedSeconds: swElapsedSeconds + readonly property bool timerSoundPlaying: cdSoundPlaying + + // Current timestamp + property int timestamp: Math.floor(Date.now() / 1000) + + // Main timer loop + Timer { + id: updateTimer + interval: 1000 + repeat: true + running: true + triggeredOnStart: false + onTriggered: { + var now = new Date(); + root.timestamp = Math.floor(now.getTime() / 1000); + + // Update countdown if running + if (root.cdRunning && root.cdStartTimestamp > 0) { + const elapsed = root.timestamp - root.cdStartTimestamp; + root.cdRemainingSeconds = root.cdTotalSeconds - elapsed; + if (root.cdRemainingSeconds <= 0) { + root.countdownOnFinished(); + } + } + + // Update stopwatch if running + if (root.swRunning && root.swStartTimestamp > 0) { + const elapsed = root.timestamp - root.swStartTimestamp; + root.swElapsedSeconds = root.swPausedAt + elapsed; + } + + // Sync to next second + var msIntoSecond = now.getMilliseconds(); + if (msIntoSecond > 100) { + updateTimer.interval = 1000 - msIntoSecond + 10; + updateTimer.restart(); + } else { + updateTimer.interval = 1000; + } + } + } + + Component.onCompleted: { + // Sync start + var now = new Date(); + var msUntilNextSecond = 1000 - now.getMilliseconds(); + updateTimer.interval = msUntilNextSecond + 10; + updateTimer.restart(); + } + + // Countdown logic + function countdownStart() { + if (root.cdRemainingSeconds <= 0) return; + root.cdTotalSeconds = root.cdRemainingSeconds; + root.cdStartTimestamp = root.timestamp; + root.cdPausedAt = 0; + root.cdRunning = true; + } + + function countdownPause() { + if (root.cdRunning) { + const currentTimestamp = Math.floor(Date.now() / 1000); + const elapsed = currentTimestamp - root.cdStartTimestamp; + const remaining = root.cdTotalSeconds - elapsed; + root.cdPausedAt = Math.max(0, remaining); + root.cdRemainingSeconds = root.cdPausedAt; + } + root.cdRunning = false; + root.cdStartTimestamp = 0; + SoundService.stopSound("alarm-beep.wav"); + root.cdSoundPlaying = false; + } + + function countdownReset() { + root.cdRunning = false; + root.cdStartTimestamp = 0; + root.cdRemainingSeconds = 0; + root.cdTotalSeconds = 0; + root.cdPausedAt = 0; + SoundService.stopSound("alarm-beep.wav"); + root.cdSoundPlaying = false; + } + + // Stopwatch logic + function stopwatchStart() { + root.swStartTimestamp = root.timestamp; + root.swPausedAt = root.swElapsedSeconds; + root.swRunning = true; + } + + function stopwatchPause() { + if (root.swRunning) { + root.swPausedAt = root.swElapsedSeconds; + } + root.swRunning = false; + root.swStartTimestamp = 0; + } + + function stopwatchReset() { + root.swRunning = false; + root.swStartTimestamp = 0; + root.swElapsedSeconds = 0; + root.swPausedAt = 0; + } + + // Convenience: operate on current mode + function timerStart() { + if (root.timerStopwatchMode) stopwatchStart(); + else countdownStart(); + } + + function timerPause() { + if (root.timerStopwatchMode) stopwatchPause(); + else countdownPause(); + } + + function timerReset() { + if (root.timerStopwatchMode) stopwatchReset(); + else countdownReset(); + } + + function parseDuration(duration_str) { + if (!duration_str) return 0; + + // Default to minutes if just a number + if (/^\d+$/.test(duration_str)) { + return parseInt(duration_str) * 60; + } + + var totalSeconds = 0; + var regex = /(\d+)([hms])/g; + var match; + + while ((match = regex.exec(duration_str)) !== null) { + var value = parseInt(match[1]); + var unit = match[2]; + + if (unit === 'h') totalSeconds += value * 3600; + else if (unit === 'm') totalSeconds += value * 60; + else if (unit === 's') totalSeconds += value; + } + + return totalSeconds; + } + + function countdownOnFinished() { + root.cdRunning = false; + root.cdRemainingSeconds = 0; + root.cdSoundPlaying = true; + SoundService.playSound("alarm-beep.wav", { + repeat: true, + volume: 0.3 + }); + ToastService.showNotice( + pluginApi?.tr("toast.title") || "Timer", + pluginApi?.tr("toast.finished") || "Timer finished!", + "hourglass", + { + onDismissed: () => { + if (root.cdSoundPlaying) { + root.countdownPause(); + } + } + } + ); + } +} diff --git a/.config/noctalia/plugins/timer/Panel.qml b/.config/noctalia/plugins/timer/Panel.qml new file mode 100644 index 0000000..b79389e --- /dev/null +++ b/.config/noctalia/plugins/timer/Panel.qml @@ -0,0 +1,593 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import qs.Commons +import qs.Services.System +import qs.Widgets + +Item { + id: root + + property var pluginApi: null + readonly property var geometryPlaceholder: panelContainer + property real contentPreferredWidth: (compactMode ? 340 : 380) * Style.uiScaleRatio + property real contentPreferredHeight: (compactMode ? 230 : 350) * Style.uiScaleRatio + readonly property bool allowAttach: true + + + + readonly property bool compactMode: + pluginApi?.pluginSettings?.compactMode ?? + pluginApi?.manifest?.metadata?.defaultSettings?.compactMode ?? + false + + + + anchors.fill: parent + + readonly property var mainInstance: pluginApi?.mainInstance + + readonly property bool isRunning: mainInstance ? mainInstance.timerRunning : false + property bool isStopwatchMode: mainInstance ? mainInstance.timerStopwatchMode : false + readonly property int remainingSeconds: mainInstance ? mainInstance.timerRemainingSeconds : 0 + readonly property int totalSeconds: mainInstance ? mainInstance.timerTotalSeconds : 0 + readonly property int elapsedSeconds: mainInstance ? mainInstance.timerElapsedSeconds : 0 + readonly property bool soundPlaying: mainInstance ? mainInstance.timerSoundPlaying : false + + function startTimer() { if (mainInstance) mainInstance.timerStart(); } + function pauseTimer() { if (mainInstance) mainInstance.timerPause(); } + function resetTimer() { + if (mainInstance) { + mainInstance.timerReset(); + // Do not apply default duration here. User wants 00:00:00 on reset. + } + } + + function setTimerStopwatchMode(mode) { + if (mainInstance) { + mainInstance.timerStopwatchMode = mode; + } + } + + function setTimerRemainingSeconds(seconds) { + if (mainInstance) mainInstance.cdRemainingSeconds = seconds; + } + + function formatTime(seconds, totalTimeSeconds) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + + if (seconds === 0 && totalTimeSeconds > 0) { + return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + if (!totalTimeSeconds || totalTimeSeconds === 0) { + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + if (totalTimeSeconds < 3600) { + return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + function formatTimeFromDigits(digits) { + const len = digits.length; + let seconds = 0; + let minutes = 0; + let hours = 0; + + if (len > 0) { + seconds = parseInt(digits.substring(Math.max(0, len - 2))) || 0; + } + if (len > 2) { + minutes = parseInt(digits.substring(Math.max(0, len - 4), len - 2)) || 0; + } + if (len > 4) { + hours = parseInt(digits.substring(0, len - 4)) || 0; + } + + seconds = Math.min(59, seconds); + minutes = Math.min(59, minutes); + hours = Math.min(99, hours); + + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } + + function parseDigitsToTime(digits) { + const len = digits.length; + let seconds = 0; + let minutes = 0; + let hours = 0; + + if (len > 0) { + seconds = parseInt(digits.substring(Math.max(0, len - 2))) || 0; + } + if (len > 2) { + minutes = parseInt(digits.substring(Math.max(0, len - 4), len - 2)) || 0; + } + if (len > 4) { + hours = parseInt(digits.substring(0, len - 4)) || 0; + } + + seconds = Math.min(59, seconds); + minutes = Math.min(59, minutes); + hours = Math.min(99, hours); + + setTimerRemainingSeconds((hours * 3600) + (minutes * 60) + seconds); + } + + Component.onCompleted: { + // Do not auto-set default duration on load + } + + function applyTimeFromBuffer() { + if (timerDisplayItem.inputBuffer !== "") { + parseDigitsToTime(timerDisplayItem.inputBuffer); + timerDisplayItem.inputBuffer = ""; + } + } + + onVisibleChanged: { + if (visible) { + if (!isRunning && !isStopwatchMode && totalSeconds === 0) { + timerInput.forceActiveFocus(); + } + } + } + + Rectangle { + id: panelContainer + anchors.fill: parent + color: "transparent" + + ColumnLayout { + anchors { + fill: parent + margins: Style.marginM + } + spacing: Style.marginL + + NBox { + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + id: content + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + clip: true + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: isStopwatchMode ? "clock" : "hourglass" + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + NText { + text: pluginApi?.tr("panel.title") || "Timer" + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + } + + Item { + id: timerDisplayItem + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + + property string inputBuffer: "" + property bool isEditing: false + + WheelHandler { + target: timerDisplayItem + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + enabled: !isRunning && !isStopwatchMode && totalSeconds === 0 + onWheel: function (event) { + if (!enabled || !mainInstance) { + return; + } + const step = 5; + if (event.angleDelta.y > 0) { + mainInstance.cdRemainingSeconds = Math.max(0, mainInstance.cdRemainingSeconds + step); + event.accepted = true; + } else if (event.angleDelta.y < 0) { + mainInstance.cdRemainingSeconds = Math.max(0, mainInstance.cdRemainingSeconds - step); + event.accepted = true; + } + } + } + + Rectangle { + id: textboxBorder + anchors.centerIn: parent + width: Math.max(timerInput.implicitWidth + Style.marginM * 2, parent.width * 0.8) + height: timerInput.implicitHeight + Style.marginM * 2 + radius: Style.iRadiusM + color: Color.mSurfaceVariant + border.color: (timerInput.activeFocus || timerDisplayItem.isEditing) ? Color.mPrimary : Color.mOutline + border.width: Style.borderS + visible: !isRunning && !isStopwatchMode && totalSeconds === 0 + z: 0 + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + Canvas { + id: progressRing + anchors.centerIn: parent + width: Math.min(parent.width, parent.height) * 1.0 + height: width + visible: !isStopwatchMode && totalSeconds > 0 && !compactMode && (isRunning || elapsedSeconds > 0) + z: -1 + + property real progressRatio: { + if (totalSeconds <= 0) + return 0; + const ratio = remainingSeconds / totalSeconds; + return Math.max(0, Math.min(1, ratio)); + } + + onProgressRatioChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d"); + if (width <= 0 || height <= 0) { + return; + } + + var centerX = width / 2; + var centerY = height / 2; + var radius = Math.min(width, height) / 2 - 5; + + if (radius <= 0) { + return; + } + + ctx.reset(); + + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); + ctx.lineWidth = 10; + ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.1); + ctx.stroke(); + + if (progressRatio > 0) { + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI); + ctx.lineWidth = 10; + ctx.strokeStyle = Color.mPrimary; + ctx.lineCap = "round"; + ctx.stroke(); + } + } + } + + Item { + id: timerContainer + anchors.centerIn: parent + width: timerInput.implicitWidth + height: timerInput.implicitHeight + 8 + + TextInput { + id: timerInput + anchors.centerIn: parent + width: Math.max(implicitWidth, timerDisplayItem.width * 0.8) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + selectByMouse: false + cursorVisible: false + cursorDelegate: Item {} + readOnly: isStopwatchMode || isRunning || totalSeconds > 0 + enabled: !isRunning && !isStopwatchMode && totalSeconds === 0 + font.family: Settings.data.ui.fontFixed + + readonly property bool showingHours: { + if (isStopwatchMode) { + return elapsedSeconds >= 3600; + } + if (timerDisplayItem.isEditing) { + return true; + } + return totalSeconds >= 3600; + } + + font.pointSize: { + const scale = compactMode ? 0.8 : 1.0; + if (totalSeconds === 0) { + return Style.fontSizeXXXL * 1.5 * scale; + } + return (showingHours ? Style.fontSizeXXL * 1.3 : (Style.fontSizeXXL * 1.8)) * scale; + } + + font.weight: Style.fontWeightBold + color: { + if (totalSeconds > 0) { + return Color.mPrimary; + } + if (timerDisplayItem.isEditing) { + return Color.mPrimary; + } + return Color.mOnSurface; + } + + property string _cachedText: "" + property int _textUpdateCounter: 0 + + function updateText() { + if (isStopwatchMode) { + _cachedText = formatTime(elapsedSeconds, elapsedSeconds); + } else if (timerDisplayItem.isEditing && totalSeconds === 0 && timerDisplayItem.inputBuffer !== "") { + _cachedText = formatTimeFromDigits(timerDisplayItem.inputBuffer); + } else if (timerDisplayItem.isEditing && totalSeconds === 0) { + _cachedText = formatTime(0, 0); + } else { + _cachedText = formatTime(remainingSeconds, totalSeconds); + } + _textUpdateCounter = _textUpdateCounter + 1; + } + + text: { + const counter = _textUpdateCounter; + return _cachedText; + } + + Connections { + target: root + function onRemainingSecondsChanged() { timerInput.updateText(); } + function onTotalSecondsChanged() { timerInput.updateText(); } + function onIsRunningChanged() { timerInput.updateText(); Qt.callLater(() => { timerInput.updateText(); }); } + function onElapsedSecondsChanged() { timerInput.updateText(); } + function onIsStopwatchModeChanged() { timerInput.updateText(); } + } + + Connections { + target: timerDisplayItem + function onIsEditingChanged() { + timerInput.updateText(); + } + } + + Component.onCompleted: updateText() + + Keys.onPressed: event => { + if (isRunning || isStopwatchMode || totalSeconds > 0) { + if (event.key === Qt.Key_Space) { + if (isRunning) mainInstance.timerPause(); + else mainInstance.timerStart(); + event.accepted = true; + return; + } + event.accepted = true; + return; + } + + const keyText = event.text.toLowerCase(); + + if (event.key === Qt.Key_Backspace) { + if (timerDisplayItem.isEditing && timerDisplayItem.inputBuffer.length > 0) { + timerDisplayItem.inputBuffer = timerDisplayItem.inputBuffer.slice(0, -1); + if (timerDisplayItem.inputBuffer !== "") { + parseDigitsToTime(timerDisplayItem.inputBuffer); + } else { + setTimerRemainingSeconds(0); + } + } + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Delete) { + if (timerDisplayItem.isEditing) { + timerDisplayItem.inputBuffer = ""; + setTimerRemainingSeconds(0); + } + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) { + applyTimeFromBuffer(); + timerDisplayItem.isEditing = false; + timerInput.focus = false; + if (remainingSeconds > 0) { + mainInstance.timerStart(); + } + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Escape) { + timerDisplayItem.inputBuffer = ""; + setTimerRemainingSeconds(0); + timerDisplayItem.isEditing = false; + timerInput.focus = false; + event.accepted = true; + return; + } + + if (keyText === 'h' || keyText === 'm' || keyText === 's') { + if (timerDisplayItem.inputBuffer.length > 0) { + let val = parseInt(timerDisplayItem.inputBuffer) || 0; + let secs = 0; + if (keyText === 'h') secs = val * 3600; + else if (keyText === 'm') secs = val * 60; + else if (keyText === 's') secs = val; + + setTimerRemainingSeconds(Math.min(99 * 3600 + 59 * 60 + 59, secs)); + timerDisplayItem.inputBuffer = ""; + timerDisplayItem.isEditing = false; + timerInput.focus = false; + } + event.accepted = true; + return; + } + + const isDigitKey = event.key >= Qt.Key_0 && event.key <= Qt.Key_9; + const isDigitText = keyText.length === 1 && keyText >= '0' && keyText <= '9'; + + if (isDigitKey && isDigitText) { + if (timerDisplayItem.inputBuffer.length >= 6) { + event.accepted = true; + return; + } + timerDisplayItem.inputBuffer += keyText; + parseDigitsToTime(timerDisplayItem.inputBuffer); + event.accepted = true; + } else { + event.accepted = true; + } + } + + onActiveFocusChanged: { + if (activeFocus) { + timerDisplayItem.isEditing = true; + timerDisplayItem.inputBuffer = ""; + } else { + applyTimeFromBuffer(); + timerDisplayItem.isEditing = false; + timerDisplayItem.inputBuffer = ""; + } + } + + MouseArea { + anchors.fill: parent + enabled: !isRunning && !isStopwatchMode && totalSeconds === 0 + cursorShape: enabled ? Qt.IBeamCursor : Qt.ArrowCursor + preventStealing: true + onPressed: (mouse) => { + if (!isRunning && !isStopwatchMode && totalSeconds === 0) { + timerInput.forceActiveFocus(); + mouse.accepted = true; + } + } + } + } + } + } + + RowLayout { + id: buttonRow + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + Layout.fillWidth: true + Layout.preferredWidth: 0 + implicitHeight: startButton.implicitHeight + color: "transparent" + + NButton { + id: startButton + anchors.fill: parent + text: { + if (isRunning) return pluginApi?.tr("panel.pause") || "Pause"; + if (isStopwatchMode && elapsedSeconds > 0) return pluginApi?.tr("panel.resume") || "Resume"; + if (!isStopwatchMode && totalSeconds > 0) return pluginApi?.tr("panel.resume") || "Resume"; + return pluginApi?.tr("panel.start") || "Start"; + } + icon: isRunning ? "player-pause" : "player-play" + enabled: isStopwatchMode || remainingSeconds > 0 + onClicked: { + if (isRunning) { + pauseTimer(); + } else { + startTimer(); + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredWidth: 0 + implicitHeight: resetButton.implicitHeight + color: "transparent" + + NButton { + id: resetButton + anchors.fill: parent + text: pluginApi?.tr("panel.reset") || "Reset" + icon: "refresh" + enabled: (isStopwatchMode && (elapsedSeconds > 0 || isRunning)) || (!isStopwatchMode && (remainingSeconds > 0 || isRunning || soundPlaying)) + onClicked: { + resetTimer(); + } + } + } + } + + NTabBar { + id: modeTabBar + Layout.fillWidth: true + Layout.preferredHeight: Style.baseWidgetSize + currentIndex: isStopwatchMode ? 1 : 0 + margins: 0 + distributeEvenly: true + onCurrentIndexChanged: { + const newMode = currentIndex === 1; + if (newMode !== isStopwatchMode) { + timerInput.focus = false; + setTimerStopwatchMode(newMode); + } + } + spacing: Style.marginS + + + Component.onCompleted: { + Qt.callLater(() => { + if (modeTabBar.children && modeTabBar.children.length > 0) { + for (var i = 0; i < modeTabBar.children.length; i++) { + var child = modeTabBar.children[i]; + if (child && typeof child.spacing !== 'undefined' && child.anchors) { + child.anchors.margins = 0; + break; + } + } + } + }); + } + + NTabButton { + text: pluginApi?.tr("panel.countdown") || "Timer" + tabIndex: 0 + checked: !isStopwatchMode + radius: Style.iRadiusS + } + + NTabButton { + text: pluginApi?.tr("panel.stopwatch") || "Stopwatch" + tabIndex: 1 + checked: isStopwatchMode + radius: Style.iRadiusS + } + } + } + } + } + } + + Shortcut { + sequence: "Space" + onActivated: { + if (!timerInput.activeFocus) { + if (isRunning) mainInstance.timerPause(); + else if (isStopwatchMode || remainingSeconds > 0) mainInstance.timerStart(); + } + } + } +} + diff --git a/.config/noctalia/plugins/timer/README.md b/.config/noctalia/plugins/timer/README.md new file mode 100644 index 0000000..ae04689 --- /dev/null +++ b/.config/noctalia/plugins/timer/README.md @@ -0,0 +1,52 @@ +# Timer Plugin + +A simple and elegant timer and stopwatch plugin for Noctalia. + +## Features + +- **Countdown Timer**: Set a duration and get notified when it finishes. +- **Stopwatch**: Measure elapsed time. +- **Bar Widget**: Shows status and remaining/elapsed time. +- **Control Center Widget**: Quick access from the control center. +- **Notifications**: Sound and toast notification when timer finishes. +- **Multi-language**: Support for 14 languages. + +## IPC Commands + +You can control the timer plugin via the command line using the Noctalia IPC interface. + +### General Usage +```bash +qs -c noctalia-shell ipc call plugin:timer <command> +``` + +### Available Commands + +| Command | Arguments | Description | Example | +|---|---|---|---| +| `toggle` | | Opens or closes the timer panel on the current screen | `qs -c noctalia-shell ipc call plugin:timer toggle` | +| `start` | `[duration]` or `stopwatch` (optional) | Starts/resumes timer or switches to stopwatch mode. | `qs -c noctalia-shell ipc call plugin:timer start 10m` | +| `pause` | | Pauses the running timer/stopwatch | `qs -c noctalia-shell ipc call plugin:timer pause` | +| `reset` | | Resets the timer/stopwatch to initial state | `qs -c noctalia-shell ipc call plugin:timer reset` | + +### Duration Format + +The `start` command accepts duration strings in the following formats: +- `30` (defaults to minutes) +- `10s` (seconds) +- `5m` (minutes) +- `2h` (hours) +- `1h30m` (combined) +- `stopwatch` (keyword to start stopwatch) + +### Examples + +**Start a 25-minute timer (Pomodoro):** +```bash +qs -c noctalia-shell ipc call plugin:timer start 25m +``` + +**Start the stopwatch:** +```bash +qs -c noctalia-shell ipc call plugin:timer start stopwatch +``` diff --git a/.config/noctalia/plugins/timer/Settings.qml b/.config/noctalia/plugins/timer/Settings.qml new file mode 100644 index 0000000..fc963d8 --- /dev/null +++ b/.config/noctalia/plugins/timer/Settings.qml @@ -0,0 +1,65 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Widgets +import qs.Services.UI + +ColumnLayout { + id: root + spacing: Style.marginL + + property var pluginApi: null + + property bool editCompactMode: + pluginApi?.pluginSettings?.compactMode ?? + pluginApi?.manifest?.metadata?.defaultSettings?.compactMode ?? + false + + property string editIconColor: + pluginApi?.pluginSettings?.iconColor ?? + pluginApi?.manifest?.metadata?.defaultSettings?.iconColor ?? + "none" + + property string editTextColor: + pluginApi?.pluginSettings?.textColor ?? + pluginApi?.manifest?.metadata?.defaultSettings?.textColor ?? + "none" + + function saveSettings() { + if (!pluginApi) { + Logger.e("Timer", "Cannot save: pluginApi is null") + return + } + + pluginApi.pluginSettings.compactMode = root.editCompactMode + pluginApi.pluginSettings.iconColor = root.editIconColor + pluginApi.pluginSettings.textColor = root.editTextColor + + pluginApi.saveSettings() + Logger.i("Timer", "Settings saved successfully") + } + + // Icon Color + NColorChoice { + label: I18n.tr("common.select-icon-color") + description: I18n.tr("common.select-color-description") + currentKey: root.editIconColor + onSelected: key => root.editIconColor = key + } + + // Text Color + NColorChoice { + currentKey: root.editTextColor + onSelected: key => root.editTextColor = key + } + + // Compact Mode + NToggle { + label: pluginApi?.tr("settings.compact-mode") || "Compact Mode" + description: pluginApi?.tr("settings.compact-mode-desc") || "Hide the circular progress bar for a cleaner look" + checked: root.editCompactMode + onToggled: checked => root.editCompactMode = checked + defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.compactMode ?? false + } +} diff --git a/.config/noctalia/plugins/timer/i18n/de.json b/.config/noctalia/plugins/timer/i18n/de.json new file mode 100644 index 0000000..41f35c8 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/de.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Countdown", + "pause": "Pause", + "reset": "Zurücksetzen", + "resume": "Fortsetzen", + "settings": "Widget-Einstellungen", + "start": "Start", + "stopwatch": "Stoppuhr", + "title": "Timer" + }, + "settings": { + "compact-mode": "Kompaktmodus", + "compact-mode-desc": "Kreisförmigen Fortschrittsbalken für ein saubereres Aussehen ausblenden" + }, + "toast": { + "finished": "Timer abgelaufen!", + "title": "Timer" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/en.json b/.config/noctalia/plugins/timer/i18n/en.json new file mode 100644 index 0000000..bdfa722 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/en.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Countdown", + "pause": "Pause", + "reset": "Reset", + "resume": "Resume", + "settings": "Widget Settings", + "start": "Start", + "stopwatch": "Stopwatch", + "title": "Timer" + }, + "settings": { + "compact-mode": "Compact Mode", + "compact-mode-desc": "Hide the circular progress bar for a cleaner look" + }, + "toast": { + "finished": "Timer finished!", + "title": "Timer" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/es.json b/.config/noctalia/plugins/timer/i18n/es.json new file mode 100644 index 0000000..4a0be12 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/es.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Cuenta atrás", + "pause": "Pausa", + "reset": "Reiniciar", + "resume": "Reanudar", + "settings": "Configuración del Widget", + "start": "Iniciar", + "stopwatch": "Cronómetro", + "title": "Temporizador" + }, + "settings": { + "compact-mode": "Modo compacto", + "compact-mode-desc": "Ocultar la barra de progreso circular para una apariencia más limpia" + }, + "toast": { + "finished": "¡Temporizador terminado!", + "title": "Temporizador" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/fr.json b/.config/noctalia/plugins/timer/i18n/fr.json new file mode 100644 index 0000000..d11c630 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/fr.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Compte à rebours", + "pause": "Pause", + "reset": "Réinitialiser", + "resume": "Reprendre", + "settings": "Paramètres du Widget", + "start": "Démarrer", + "stopwatch": "Chronomètre", + "title": "Minuteur" + }, + "settings": { + "compact-mode": "Mode compact", + "compact-mode-desc": "Masquer la barre de progression circulaire pour un aspect plus épuré" + }, + "toast": { + "finished": "Minuteur terminé !", + "title": "Minuteur" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/hu.json b/.config/noctalia/plugins/timer/i18n/hu.json new file mode 100644 index 0000000..be49bb5 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/hu.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Visszaszámlálás", + "pause": "Szünet", + "reset": "Visszaállítás", + "resume": "Folytatás", + "settings": "Widget beállítások", + "start": "Indítás", + "stopwatch": "Stopper", + "title": "Időzítő" + }, + "settings": { + "compact-mode": "Kompakt mód", + "compact-mode-desc": "A körkörös folyamatjelző elrejtése a tisztább megjelenés érdekében" + }, + "toast": { + "finished": "Időzítő lejárt!", + "title": "Időzítő" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/it.json b/.config/noctalia/plugins/timer/i18n/it.json new file mode 100644 index 0000000..40517c8 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/it.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Countdown", + "pause": "Pausa", + "reset": "Reimposta", + "resume": "Riprendi", + "settings": "Impostazioni", + "start": "Avvia", + "stopwatch": "Cronometro", + "title": "Timer" + }, + "settings": { + "compact-mode": "Modalità compatta", + "compact-mode-desc": "Nascondi la barra di avanzamento circolare per un aspetto più pulito" + }, + "toast": { + "finished": "Timer terminato", + "title": "Timer" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/ja.json b/.config/noctalia/plugins/timer/i18n/ja.json new file mode 100644 index 0000000..948109b --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/ja.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "カウントダウン", + "pause": "一時停止", + "reset": "リセット", + "resume": "再開", + "settings": "ウィジェット設定", + "start": "開始", + "stopwatch": "ストップウォッチ", + "title": "タイマー" + }, + "settings": { + "compact-mode": "コンパクトモード", + "compact-mode-desc": "円形の進行状況バーを非表示にして、すっきりとした見た目にする" + }, + "toast": { + "finished": "タイマー終了!", + "title": "タイマー" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/ku.json b/.config/noctalia/plugins/timer/i18n/ku.json new file mode 100644 index 0000000..abff80f --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/ku.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Jimartina Paşve", + "pause": "Rawestandin", + "reset": "Nûkirin", + "resume": "Berdewamkirin", + "settings": "Mîhengên Widget", + "start": "Destpêkirin", + "stopwatch": "Demgir", + "title": "Demjimêr" + }, + "settings": { + "compact-mode": "Moda Kompakt", + "compact-mode-desc": "Barê pêşveçûnê yê dorûber veşêre ji bo dîmenek paqijtir" + }, + "toast": { + "finished": "Demjimêr qediya!", + "title": "Demjimêr" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/nl.json b/.config/noctalia/plugins/timer/i18n/nl.json new file mode 100644 index 0000000..8098f3c --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/nl.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Aftellen", + "pause": "Pauze", + "reset": "Resetten", + "resume": "Hervatten", + "settings": "Widget-instellingen", + "start": "Start", + "stopwatch": "Stopwatch", + "title": "Timer" + }, + "settings": { + "compact-mode": "Compacte modus", + "compact-mode-desc": "Verberg de circulaire voortgangsbalk voor een schoner uiterlijk" + }, + "toast": { + "finished": "Timer afgelopen!", + "title": "Timer" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/pl.json b/.config/noctalia/plugins/timer/i18n/pl.json new file mode 100644 index 0000000..8aa6cf2 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/pl.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Odliczanie", + "pause": "Pauza", + "reset": "Resetuj", + "resume": "Wznów", + "settings": "Ustawienia widżetu", + "start": "Start", + "stopwatch": "Stoper", + "title": "Czasomierz" + }, + "settings": { + "compact-mode": "Tryb kompaktowy", + "compact-mode-desc": "Ukryj okrągły pasek postępu dla czystszego wyglądu" + }, + "toast": { + "finished": "Czasomierz zakończony!", + "title": "Czasomierz" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/pt.json b/.config/noctalia/plugins/timer/i18n/pt.json new file mode 100644 index 0000000..50c490d --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/pt.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Contagem regressiva", + "pause": "Pausar", + "reset": "Reiniciar", + "resume": "Retomar", + "settings": "Configurações do Widget", + "start": "Iniciar", + "stopwatch": "Cronômetro", + "title": "Temporizador" + }, + "settings": { + "compact-mode": "Modo compacto", + "compact-mode-desc": "Ocultar a barra de progresso circular para um visual mais limpo" + }, + "toast": { + "finished": "Temporizador finalizado!", + "title": "Temporizador" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/ru.json b/.config/noctalia/plugins/timer/i18n/ru.json new file mode 100644 index 0000000..0269aa7 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/ru.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Обратный отсчет", + "pause": "Пауза", + "reset": "Сброс", + "resume": "Продолжить", + "settings": "Настройки виджета", + "start": "Старт", + "stopwatch": "Секундомер", + "title": "Таймер" + }, + "settings": { + "compact-mode": "Компактный режим", + "compact-mode-desc": "Скрыть круговой индикатор прогресса для более чистого вида" + }, + "toast": { + "finished": "Таймер завершён!", + "title": "Таймер" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/tr.json b/.config/noctalia/plugins/timer/i18n/tr.json new file mode 100644 index 0000000..c3d8f8f --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/tr.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Geri Sayım", + "pause": "Duraklat", + "reset": "Sıfırla", + "resume": "Devam Et", + "settings": "Widget Ayarları", + "start": "Başlat", + "stopwatch": "Kronometre", + "title": "Zamanlayıcı" + }, + "settings": { + "compact-mode": "Kompakt Mod", + "compact-mode-desc": "Daha temiz bir görünüm için dairesel ilerleme çubuğunu gizle" + }, + "toast": { + "finished": "Zamanlayıcı bitti!", + "title": "Zamanlayıcı" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/uk-UA.json b/.config/noctalia/plugins/timer/i18n/uk-UA.json new file mode 100644 index 0000000..a4326cc --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/uk-UA.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "Зворотний відлік", + "pause": "Пауза", + "reset": "Скинути", + "resume": "Продовжити", + "settings": "Налаштування віджета", + "start": "Старт", + "stopwatch": "Секундомір", + "title": "Таймер" + }, + "settings": { + "compact-mode": "Компактний режим", + "compact-mode-desc": "Приховати круговий індикатор прогресу для чистішого вигляду" + }, + "toast": { + "finished": "Таймер завершено!", + "title": "Таймер" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/zh-CN.json b/.config/noctalia/plugins/timer/i18n/zh-CN.json new file mode 100644 index 0000000..72bb0e1 --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/zh-CN.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "倒计时", + "pause": "暂停", + "reset": "重置", + "resume": "继续", + "settings": "小部件设置", + "start": "开始", + "stopwatch": "秒表", + "title": "计时器" + }, + "settings": { + "compact-mode": "紧凑模式", + "compact-mode-desc": "隐藏圆形进度条以获得更整洁的外观" + }, + "toast": { + "finished": "计时器结束!", + "title": "计时器" + } +} diff --git a/.config/noctalia/plugins/timer/i18n/zh-TW.json b/.config/noctalia/plugins/timer/i18n/zh-TW.json new file mode 100644 index 0000000..e9b89bd --- /dev/null +++ b/.config/noctalia/plugins/timer/i18n/zh-TW.json @@ -0,0 +1,20 @@ +{ + "panel": { + "countdown": "倒數", + "pause": "暫停", + "reset": "重置", + "resume": "繼續", + "settings": "小工具設定", + "start": "開始", + "stopwatch": "碼表", + "title": "計時器" + }, + "settings": { + "compact-mode": "緊湊模式", + "compact-mode-desc": "隱藏進度圓環以得到更精簡的外觀" + }, + "toast": { + "finished": "時間到了!", + "title": "計時器" + } +} diff --git a/.config/noctalia/plugins/timer/manifest.json b/.config/noctalia/plugins/timer/manifest.json new file mode 100644 index 0000000..766392a --- /dev/null +++ b/.config/noctalia/plugins/timer/manifest.json @@ -0,0 +1,33 @@ +{ + "id": "timer", + "name": "Timer", + "version": "1.1.2", + "minNoctaliaVersion": "4.4.4", + "author": "Noctalia Team", + "official": true, + "license": "MIT", + "repository": "https://github.com/noctalia-dev/noctalia-plugins", + "description": "A timer and stopwatch plugin for the bar & control center.", + "tags": [ + "Bar", + "Utility" + ], + "entryPoints": { + "main": "Main.qml", + "barWidget": "BarWidget.qml", + "panel": "Panel.qml", + "controlCenterWidget": "ControlCenterWidget.qml", + "settings": "Settings.qml" + }, + "dependencies": { + "plugins": [] + }, + "metadata": { + "defaultSettings": { + "defaultDuration": 0, + "compactMode": false, + "iconColor": "none", + "textColor": "none" + } + } +}
\ No newline at end of file diff --git a/.config/noctalia/plugins/timer/preview.png b/.config/noctalia/plugins/timer/preview.png Binary files differnew file mode 100644 index 0000000..bead8e2 --- /dev/null +++ b/.config/noctalia/plugins/timer/preview.png diff --git a/.config/noctalia/plugins/timer/settings.json b/.config/noctalia/plugins/timer/settings.json new file mode 100644 index 0000000..3979fb2 --- /dev/null +++ b/.config/noctalia/plugins/timer/settings.json @@ -0,0 +1,5 @@ +{ + "compactMode": true, + "iconColor": "none", + "textColor": "none" +} diff --git a/.config/noctalia/settings.json b/.config/noctalia/settings.json new file mode 100644 index 0000000..a8139a1 --- /dev/null +++ b/.config/noctalia/settings.json @@ -0,0 +1,672 @@ +{ + "appLauncher": { + "autoPasteClipboard": false, + "clipboardWatchImageCommand": "wl-paste --type image --watch cliphist store", + "clipboardWatchTextCommand": "wl-paste --type text --watch cliphist store", + "clipboardWrapText": true, + "customLaunchPrefix": "", + "customLaunchPrefixEnabled": false, + "density": "default", + "enableClipPreview": true, + "enableClipboardHistory": false, + "enableSessionSearch": true, + "enableSettingsSearch": true, + "enableWindowsSearch": true, + "iconMode": "tabler", + "ignoreMouseInput": false, + "overviewLayer": false, + "pinnedApps": [ + ], + "position": "center", + "screenshotAnnotationTool": "", + "showCategories": true, + "showIconBackground": false, + "sortByMostUsed": true, + "terminalCommand": "foot -e", + "useApp2Unit": false, + "viewMode": "list" + }, + "audio": { + "mprisBlacklist": [ + ], + "preferredPlayer": "mpv", + "spectrumFrameRate": 30, + "visualizerType": "linear", + "volumeFeedback": false, + "volumeFeedbackSoundFile": "", + "volumeOverdrive": false, + "volumeStep": 5 + }, + "bar": { + "autoHideDelay": 500, + "autoShowDelay": 150, + "backgroundOpacity": 0.8, + "barType": "simple", + "capsuleColorKey": "none", + "capsuleOpacity": 1, + "contentPadding": 3, + "density": "comfortable", + "displayMode": "always_visible", + "floating": false, + "fontScale": 1, + "frameRadius": 8, + "frameThickness": 4, + "hideOnOverview": true, + "marginHorizontal": 4, + "marginVertical": 4, + "middleClickAction": "none", + "middleClickCommand": "", + "middleClickFollowMouse": false, + "monitors": [ + ], + "mouseWheelAction": "none", + "mouseWheelWrap": true, + "outerCorners": false, + "position": "top", + "reverseScroll": false, + "rightClickAction": "controlCenter", + "rightClickCommand": "", + "rightClickFollowMouse": true, + "screenOverrides": [ + ], + "showCapsule": true, + "showOnWorkspaceSwitch": true, + "showOutline": true, + "useSeparateOpacity": true, + "widgetSpacing": 6, + "widgets": { + "center": [ + { + "clockColor": "none", + "customFont": "", + "formatHorizontal": "HH:mm:ss t – dddd, d MMMM yyyy", + "formatVertical": "HH mm - dd MM", + "id": "Clock", + "tooltipFormat": "HH:mm ddd, MMM dd", + "useCustomFont": false + } + ], + "left": [ + { + "characterCount": 2, + "colorizeIcons": false, + "emptyColor": "secondary", + "enableScrollWheel": true, + "focusedColor": "primary", + "followFocusedScreen": false, + "fontWeight": "bold", + "groupedBorderOpacity": 1, + "hideUnoccupied": false, + "iconScale": 0.8, + "id": "Workspace", + "labelMode": "index", + "occupiedColor": "secondary", + "pillSize": 0.6, + "showApplications": false, + "showBadge": true, + "showLabelsOnlyWhenOccupied": true, + "unfocusedIconsOpacity": 1 + }, + { + "colorizeIcons": false, + "hideMode": "hidden", + "id": "ActiveWindow", + "maxWidth": 1000, + "scrollingMode": "always", + "showIcon": false, + "textColor": "none", + "useFixedWidth": false + } + ], + "right": [ + { + "compactMode": false, + "hideMode": "hidden", + "hideWhenIdle": false, + "id": "MediaMini", + "maxWidth": 1000, + "panelShowAlbumArt": true, + "scrollingMode": "hover", + "showAlbumArt": true, + "showArtistFirst": true, + "showProgressRing": true, + "showVisualizer": true, + "textColor": "none", + "useFixedWidth": false, + "visualizerType": "linear" + }, + { + "id": "Spacer", + "width": 20 + }, + { + "blacklist": [ + ], + "chevronColor": "none", + "colorizeIcons": false, + "drawerEnabled": true, + "hidePassive": false, + "id": "Tray", + "pinned": [ + ] + }, + { + "compactMode": true, + "diskPath": "/", + "iconColor": "none", + "id": "SystemMonitor", + "showCpuCores": false, + "showCpuFreq": false, + "showCpuTemp": true, + "showCpuUsage": true, + "showDiskAvailable": false, + "showDiskUsage": false, + "showDiskUsageAsPercent": false, + "showGpuTemp": false, + "showLoadAverage": false, + "showMemoryAsPercent": false, + "showMemoryUsage": true, + "showNetworkStats": false, + "showSwapUsage": false, + "textColor": "none", + "useMonospaceFont": true, + "usePadding": false + }, + { + "hideWhenZero": false, + "hideWhenZeroUnread": false, + "iconColor": "none", + "id": "NotificationHistory", + "showUnreadBadge": true, + "unreadBadgeColor": "primary" + }, + { + "displayMode": "onhover", + "iconColor": "none", + "id": "Volume", + "middleClickCommand": "pwvucontrol || pavucontrol", + "textColor": "none" + }, + { + "displayMode": "onhover", + "iconColor": "none", + "id": "Network", + "textColor": "none" + }, + { + "displayMode": "onhover", + "iconColor": "none", + "id": "Bluetooth", + "textColor": "none" + }, + { + "displayMode": "forceOpen", + "iconColor": "none", + "id": "KeyboardLayout", + "showIcon": false, + "textColor": "none" + }, + { + "iconColor": "none", + "id": "SessionMenu" + }, + { + "capsLockIcon": "letter-c", + "hideWhenOff": true, + "id": "LockKeys", + "numLockIcon": "letter-n", + "scrollLockIcon": "letter-s", + "showCapsLock": true, + "showNumLock": false, + "showScrollLock": true + } + ] + } + }, + "brightness": { + "backlightDeviceMappings": [ + ], + "brightnessStep": 5, + "enableDdcSupport": true, + "enforceMinimum": false + }, + "calendar": { + "cards": [ + { + "enabled": true, + "id": "calendar-header-card" + }, + { + "enabled": true, + "id": "calendar-month-card" + }, + { + "enabled": true, + "id": "weather-card" + } + ] + }, + "colorSchemes": { + "darkMode": false, + "generationMethod": "tonal-spot", + "manualSunrise": "06:30", + "manualSunset": "18:30", + "monitorForColors": "", + "predefinedScheme": "Nord", + "schedulingMode": "location", + "useWallpaperColors": false + }, + "controlCenter": { + "cards": [ + { + "enabled": true, + "id": "profile-card" + }, + { + "enabled": true, + "id": "shortcuts-card" + }, + { + "enabled": true, + "id": "audio-card" + }, + { + "enabled": false, + "id": "brightness-card" + }, + { + "enabled": true, + "id": "weather-card" + }, + { + "enabled": true, + "id": "media-sysmon-card" + } + ], + "diskPath": "/", + "position": "close_to_bar_button", + "shortcuts": { + "left": [ + { + "id": "WallpaperSelector" + } + ], + "right": [ + { + "id": "NightLight" + }, + { + "id": "DarkMode" + } + ] + } + }, + "desktopWidgets": { + "enabled": false, + "gridSnap": false, + "monitorWidgets": [ + { + "name": "DP-1", + "widgets": [ + ] + } + ], + "overviewEnabled": false + }, + "dock": { + "animationSpeed": 1, + "backgroundOpacity": 1, + "colorizeIcons": true, + "deadOpacity": 0.6, + "displayMode": "always_visible", + "dockType": "floating", + "enabled": false, + "floatingRatio": 1, + "groupApps": true, + "groupClickAction": "cycle", + "groupContextMenuMode": "extended", + "groupIndicatorStyle": "dots", + "inactiveIndicators": true, + "indicatorColor": "primary", + "indicatorOpacity": 0.6, + "indicatorThickness": 3, + "launcherIconColor": "none", + "launcherPosition": "end", + "monitors": [ + ], + "onlySameOutput": true, + "pinnedApps": [ + ], + "pinnedStatic": true, + "position": "bottom", + "showDockIndicator": false, + "showLauncherIcon": true, + "sitOnFrame": false, + "size": 1 + }, + "general": { + "allowPanelsOnScreenWithoutBar": true, + "allowPasswordWithFprintd": false, + "animationDisabled": false, + "animationSpeed": 1, + "autoStartAuth": false, + "avatarImage": "/home/thomas/.face", + "boxRadiusRatio": 1, + "clockFormat": "hh\\nmm", + "clockStyle": "custom", + "compactLockScreen": false, + "dimmerOpacity": 0, + "enableBlurBehind": true, + "enableLockScreenCountdown": true, + "enableLockScreenMediaControls": false, + "enableShadows": true, + "forceBlackScreenCorners": false, + "iRadiusRatio": 1, + "keybinds": { + "keyDown": [ + "Down" + ], + "keyEnter": [ + "Return", + "Enter" + ], + "keyEscape": [ + "Esc" + ], + "keyLeft": [ + "Left" + ], + "keyRemove": [ + "Del" + ], + "keyRight": [ + "Right" + ], + "keyUp": [ + "Up" + ] + }, + "language": "", + "lockOnSuspend": true, + "lockScreenAnimations": false, + "lockScreenBlur": 0, + "lockScreenCountdownDuration": 10000, + "lockScreenMonitors": [ + ], + "lockScreenTint": 0, + "passwordChars": false, + "radiusRatio": 1, + "reverseScroll": false, + "scaleRatio": 1, + "screenRadiusRatio": 1, + "shadowDirection": "bottom_right", + "shadowOffsetX": 2, + "shadowOffsetY": 3, + "showChangelogOnStartup": true, + "showHibernateOnLockScreen": false, + "showScreenCorners": false, + "showSessionButtonsOnLockScreen": true, + "telemetryEnabled": false + }, + "hooks": { + "darkModeChange": "", + "enabled": false, + "performanceModeDisabled": "", + "performanceModeEnabled": "", + "screenLock": "", + "screenUnlock": "", + "session": "", + "startup": "", + "wallpaperChange": "" + }, + "idle": { + "customCommands": "[]", + "enabled": false, + "fadeDuration": 5, + "lockCommand": "", + "lockTimeout": 660, + "resumeLockCommand": "", + "resumeScreenOffCommand": "", + "resumeSuspendCommand": "", + "screenOffCommand": "", + "screenOffTimeout": 600, + "suspendCommand": "", + "suspendTimeout": 1800 + }, + "location": { + "analogClockInCalendar": false, + "firstDayOfWeek": -1, + "hideWeatherCityName": false, + "hideWeatherTimezone": false, + "name": "Växjö", + "showCalendarEvents": true, + "showCalendarWeather": true, + "showWeekNumberInCalendar": true, + "use12hourFormat": false, + "useFahrenheit": false, + "weatherEnabled": true, + "weatherShowEffects": true + }, + "network": { + "airplaneModeEnabled": false, + "bluetoothAutoConnect": true, + "bluetoothDetailsViewMode": "grid", + "bluetoothHideUnnamedDevices": false, + "bluetoothRssiPollIntervalMs": 60000, + "bluetoothRssiPollingEnabled": false, + "disableDiscoverability": false, + "networkPanelView": "wifi", + "wifiDetailsViewMode": "grid", + "wifiEnabled": false + }, + "nightLight": { + "autoSchedule": true, + "dayTemp": "6500", + "enabled": true, + "forced": false, + "manualSunrise": "06:30", + "manualSunset": "18:30", + "nightTemp": "4002" + }, + "noctaliaPerformance": { + "disableDesktopWidgets": true, + "disableWallpaper": true + }, + "notifications": { + "backgroundOpacity": 0.7000000000000001, + "clearDismissed": true, + "criticalUrgencyDuration": 15, + "density": "default", + "enableBatteryToast": true, + "enableKeyboardLayoutToast": true, + "enableMarkdown": false, + "enableMediaToast": true, + "enabled": true, + "location": "top_right", + "lowUrgencyDuration": 3, + "monitors": [ + ], + "normalUrgencyDuration": 8, + "overlayLayer": true, + "respectExpireTimeout": false, + "saveToHistory": { + "critical": true, + "low": true, + "normal": true + }, + "sounds": { + "criticalSoundFile": "", + "enabled": false, + "excludedApps": "discord,firefox,chrome,chromium,edge", + "lowSoundFile": "", + "normalSoundFile": "", + "separateSounds": false, + "volume": 0.5 + } + }, + "osd": { + "autoHideMs": 2000, + "backgroundOpacity": 1, + "enabled": true, + "enabledTypes": [ + 0, + 1, + 2 + ], + "location": "top_right", + "monitors": [ + ], + "overlayLayer": true + }, + "plugins": { + "autoUpdate": true + }, + "sessionMenu": { + "countdownDuration": 10000, + "enableCountdown": true, + "largeButtonsLayout": "single-row", + "largeButtonsStyle": true, + "position": "center", + "powerOptions": [ + { + "action": "lock", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "1" + }, + { + "action": "suspend", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "2" + }, + { + "action": "hibernate", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "3" + }, + { + "action": "reboot", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "4" + }, + { + "action": "logout", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "5" + }, + { + "action": "shutdown", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "6" + }, + { + "action": "rebootToUefi", + "command": "", + "countdownEnabled": true, + "enabled": true, + "keybind": "7" + }, + { + "action": "userspaceReboot", + "command": "", + "countdownEnabled": true, + "enabled": false, + "keybind": "" + } + ], + "showHeader": true, + "showKeybinds": true + }, + "settingsVersion": 57, + "systemMonitor": { + "batteryCriticalThreshold": 5, + "batteryWarningThreshold": 20, + "cpuCriticalThreshold": 90, + "cpuWarningThreshold": 80, + "criticalColor": "#bf616a", + "diskAvailCriticalThreshold": 10, + "diskAvailWarningThreshold": 20, + "diskCriticalThreshold": 90, + "diskWarningThreshold": 80, + "enableDgpuMonitoring": false, + "externalMonitor": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", + "gpuCriticalThreshold": 90, + "gpuWarningThreshold": 80, + "memCriticalThreshold": 90, + "memWarningThreshold": 80, + "swapCriticalThreshold": 90, + "swapWarningThreshold": 80, + "tempCriticalThreshold": 90, + "tempWarningThreshold": 80, + "useCustomColors": false, + "warningColor": "#6fa9a8" + }, + "templates": { + "activeTemplates": [ + ], + "enableUserTheming": false + }, + "ui": { + "boxBorderEnabled": false, + "fontDefault": "Sans Serif", + "fontDefaultScale": 1, + "fontFixed": "monospace", + "fontFixedScale": 1, + "panelBackgroundOpacity": 1, + "panelsAttachedToBar": true, + "settingsPanelMode": "attached", + "settingsPanelSideBarCardStyle": false, + "tooltipsEnabled": true + }, + "wallpaper": { + "automationEnabled": false, + "directory": "/home/thomas/media/img/wall", + "enableMultiMonitorDirectories": false, + "enabled": true, + "favorites": [ + ], + "fillColor": "#000000", + "fillMode": "crop", + "hideWallpaperFilenames": true, + "monitorDirectories": [ + ], + "overviewBlur": 0.4, + "overviewEnabled": true, + "overviewTint": 0.6, + "panelPosition": "follow_bar", + "randomIntervalSec": 3600, + "setWallpaperOnAllMonitors": true, + "showHiddenFiles": false, + "skipStartupTransition": false, + "solidColor": "#1a1a2e", + "sortOrder": "name", + "transitionDuration": 1500, + "transitionEdgeSmoothness": 0.05, + "transitionType": "random", + "useSolidColor": false, + "useWallhaven": false, + "viewMode": "single", + "wallhavenApiKey": "", + "wallhavenCategories": "111", + "wallhavenOrder": "desc", + "wallhavenPurity": "100", + "wallhavenQuery": "", + "wallhavenRatios": "", + "wallhavenResolutionHeight": "", + "wallhavenResolutionMode": "atleast", + "wallhavenResolutionWidth": "", + "wallhavenSorting": "relevance", + "wallpaperChangeMode": "random" + } +} |