From e7588b29d945c53779adf2146e05b33bc572aaa2 Mon Sep 17 00:00:00 2001 From: quadbyte Date: Tue, 12 Aug 2025 16:18:49 -0400 Subject: [PATCH] ScreenRecorder Service + Reconnected the utility button --- Modules/SidePanel/Cards/UtilitiesCard.qml | 8 ++- Modules/SidePanel/Cards/WeatherCard.qml | 2 +- Services/Audio.qml | 1 - Services/Colors.qml | 32 +++++------ Services/GitHub.qml | 4 +- Services/ScreenRecorder.qml | 64 +++++++++++++++++++++ Services/Settings.qml | 3 +- Services/Time.qml | 68 +++++++++++++++-------- Services/Wallpapers.qml | 6 +- Widgets/NIconButton.qml | 5 +- 10 files changed, 142 insertions(+), 51 deletions(-) create mode 100644 Services/ScreenRecorder.qml diff --git a/Modules/SidePanel/Cards/UtilitiesCard.qml b/Modules/SidePanel/Cards/UtilitiesCard.qml index ec1126f..8040e06 100644 --- a/Modules/SidePanel/Cards/UtilitiesCard.qml +++ b/Modules/SidePanel/Cards/UtilitiesCard.qml @@ -18,14 +18,20 @@ NBox { Item { Layout.fillWidth: true } - // Record + // Screen Recorder NIconButton { icon: "videocam" + showFilled: ScreenRecorder.isRecording + onClicked: { + ScreenRecorder.toggleRecording() + } } + // Wallpaper NIconButton { icon: "image" } + Item { Layout.fillWidth: true } diff --git a/Modules/SidePanel/Cards/WeatherCard.qml b/Modules/SidePanel/Cards/WeatherCard.qml index a7e749a..4bbe940 100644 --- a/Modules/SidePanel/Cards/WeatherCard.qml +++ b/Modules/SidePanel/Cards/WeatherCard.qml @@ -10,8 +10,8 @@ NBox { readonly property real scaling: Scaling.scale(screen) readonly property bool weatherReady: (Location.data.weather !== null) - // TBC weatherReady is not turning to false when we reset weather... + // TBC weatherReady is not turning to false when we reset weather... Layout.fillWidth: true // Height driven by content implicitHeight: content.implicitHeight + Style.marginLarge * 2 * scaling diff --git a/Services/Audio.qml b/Services/Audio.qml index 4b25e09..ea88d63 100644 --- a/Services/Audio.qml +++ b/Services/Audio.qml @@ -49,7 +49,6 @@ Singleton { vol = 0 } root._volume = vol - } function onMutedChanged() { diff --git a/Services/Colors.qml b/Services/Colors.qml index c5376a1..be5ce2c 100644 --- a/Services/Colors.qml +++ b/Services/Colors.qml @@ -53,30 +53,30 @@ Singleton { // Default theme colors QtObject { id: defaultTheme - + property color backgroundPrimary: "#191724" property color backgroundSecondary: "#1f1d2e" property color backgroundTertiary: "#26233a" - + property color surface: "#1f1d2e" property color surfaceVariant: "#37354c" - + property color textPrimary: "#e0def4" property color textSecondary: "#908caa" property color textDisabled: "#6e6a86" - + property color accentPrimary: "#ebbcba" property color accentSecondary: "#31748f" property color accentTertiary: "#9ccfd8" - + property color error: "#eb6f92" property color warning: "#f6c177" - + property color hover: "#c4a7e7" - + property color onAccent: "#191724" property color outline: "#44415a" - + property color shadow: "#191724" property color overlay: "#191724" } @@ -84,30 +84,30 @@ Singleton { // Wallust theme colors (loaded from Theme.json) QtObject { id: wallustTheme - + property color backgroundPrimary: wallustData.backgroundPrimary property color backgroundSecondary: wallustData.backgroundSecondary property color backgroundTertiary: wallustData.backgroundTertiary - + property color surface: wallustData.surface property color surfaceVariant: wallustData.surfaceVariant - + property color textPrimary: wallustData.textPrimary property color textSecondary: wallustData.textSecondary property color textDisabled: wallustData.textDisabled - + property color accentPrimary: wallustData.accentPrimary property color accentSecondary: wallustData.accentSecondary property color accentTertiary: wallustData.accentTertiary - + property color error: wallustData.error property color warning: wallustData.warning - + property color hover: wallustData.hover - + property color onAccent: wallustData.onAccent property color outline: wallustData.outline - + property color shadow: wallustData.shadow property color overlay: wallustData.overlay } diff --git a/Services/GitHub.qml b/Services/GitHub.qml index 8932d03..620a616 100644 --- a/Services/GitHub.qml +++ b/Services/GitHub.qml @@ -49,7 +49,7 @@ Singleton { // -------------------------------- function loadFromCache() { - const now = Time.timestamp + const now = Time.timestamp if (!data.timestamp || (now >= data.timestamp + githubUpdateFrequency)) { console.log("[GitHub] Cache expired or missing, fetching new data from GitHub...") fetchFromGitHub() @@ -79,7 +79,7 @@ Singleton { // -------------------------------- function saveData() { - data.timestamp = Time.timestamp + data.timestamp = Time.timestamp Qt.callLater(() => { // Access the FileView's writeAdapter method var fileView = root.children.find(child => child.objectName === "githubDataFileView") diff --git a/Services/ScreenRecorder.qml b/Services/ScreenRecorder.qml new file mode 100644 index 0000000..4f48d15 --- /dev/null +++ b/Services/ScreenRecorder.qml @@ -0,0 +1,64 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Services + +Singleton { + id: root + + readonly property var settings: Settings.data.screenRecorder + property bool isRecording: false + property string outputPath: "" + + // Start or Stop recording + function toggleRecording() { + isRecording ? stopRecording() : startRecording() + } + + // Start screen recording using Quickshell.execDetached + function startRecording() { + if (isRecording) { + return + } + isRecording = true + + var filename = Time.getFormattedTimestamp() + ".mp4" + var videoDir = settings.directory + if (videoDir && !videoDir.endsWith("/")) { + videoDir += "/" + } + outputPath = videoDir + filename + var command = "gpu-screen-recorder -w portal" + " -f " + settings.frameRate + " -ac " + settings.audioCodec + + " -k " + settings.videoCodec + " -a " + settings.audioSource + " -q " + settings.quality + + " -cursor " + (settings.showCursor ? "yes" : "no") + " -cr " + settings.colorRange + " -o " + outputPath + + //console.log("[ScreenRecorder]", command) + Quickshell.execDetached(["sh", "-c", command]) + console.log("[ScreenRecorder] Started recording") + } + + // Stop recording using Quickshell.execDetached + function stopRecording() { + if (!isRecording) { + return + } + + Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder.*portal'"]) + console.log("[ScreenRecorder] Finished recording:", outputPath) + + // Just in case, force kill after 3 seconds + killTimer.running = true + isRecording = false + } + + Timer { + id: killTimer + interval: 3000 + running: false + repeat: false + onTriggered: { + Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder.*portal' 2>/dev/null || true"]) + } + } +} diff --git a/Services/Settings.qml b/Services/Settings.qml index 422baa9..a732496 100644 --- a/Services/Settings.qml +++ b/Services/Settings.qml @@ -105,9 +105,8 @@ Singleton { property string videoCodec: "h264" property string quality: "very_high" property string colorRange: "limited" - property bool showCursor: true - // New: optional audio source selection (default: system output) property string audioSource: "default_output" + property bool showCursor: true } // wallpaper diff --git a/Services/Time.qml b/Services/Time.qml index 0ce3593..7a2463c 100644 --- a/Services/Time.qml +++ b/Services/Time.qml @@ -43,31 +43,51 @@ Singleton { return Math.floor(Date.now() / 1000) } - // Format an easy to read approximate duration ex: 4h32m - // Used to display the time remaining on the Battery widget - function formatVagueHumanReadableDuration(totalSeconds) { - const hours = Math.floor(totalSeconds / 3600) - const minutes = Math.floor((totalSeconds - (hours * 3600)) / 60) - const seconds = totalSeconds - (hours * 3600) - (minutes * 60) - var str = "" - if (hours) { - str += hours.toString() + "h" - } - if (minutes) { - str += minutes.toString() + "m" - } - if (!hours && !minutes) { - str += seconds.toString() + "s" - } - return str - } + /** + * Formats a Date object into a YYYYMMDD-HHMMSS string. + * @param {Date} [date=new Date()] - The date to format. Defaults to the current date and time. + * @returns {string} The formatted date string. + */ + function getFormattedTimestamp(date = new Date()) { + const year = date.getFullYear() - Timer { - interval: 1000 - repeat: true - running: true + // getMonth() is zero-based, so we add 1 + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') - onTriggered: root.date = new Date() - } + const hours = String(date.getHours()).padStart(2, '0') + const minutes = String(date.getMinutes()).padStart(2, '0') + const seconds = String(date.getSeconds()).padStart(2, '0') + + return `${year}${month}${day}-${hours}${minutes}${seconds}` +} + +// Format an easy to read approximate duration ex: 4h32m +// Used to display the time remaining on the Battery widget +function formatVagueHumanReadableDuration(totalSeconds) { + const hours = Math.floor(totalSeconds / 3600) + const minutes = Math.floor((totalSeconds - (hours * 3600)) / 60) + const seconds = totalSeconds - (hours * 3600) - (minutes * 60) + + var str = "" + if (hours) { + str += hours.toString() + "h" + } + if (minutes) { + str += minutes.toString() + "m" + } + if (!hours && !minutes) { + str += seconds.toString() + "s" + } + return str +} + +Timer { + interval: 1000 + repeat: true + running: true + + onTriggered: root.date = new Date() +} } diff --git a/Services/Wallpapers.qml b/Services/Wallpapers.qml index 1b5792e..6a0c183 100644 --- a/Services/Wallpapers.qml +++ b/Services/Wallpapers.qml @@ -50,9 +50,10 @@ Singleton { } else { transitionType = Settings.data.wallpaper.swww.transitionType } - + changeWallpaperProcess.running = true } else { + // Fallback: update the settings directly for non-SWWW mode //console.log("[WP] Not using Swww, setting wallpaper directly") } @@ -140,7 +141,7 @@ Singleton { running: false onStarted: { - + } onExited: function (exitCode, exitStatus) { @@ -159,6 +160,7 @@ Singleton { running: false stdout: StdioCollector { onStreamFinished: { + // console.log(this.text) } } diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index b42f2ec..28e0b6f 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -13,6 +13,7 @@ Rectangle { property string icon property string tooltipText property bool showBorder: true + property bool showFilled: false property bool enabled: true property bool hovering: false property real fontPointSize: Style.fontSizeMedium @@ -25,7 +26,7 @@ Rectangle { implicitWidth: size implicitHeight: size - color: root.hovering ? Colors.accentPrimary : "transparent" + color: (root.hovering || showFilled) ? Colors.accentPrimary : "transparent" radius: width * 0.5 border.color: showBorder ? Colors.accentPrimary : "transparent" border.width: Math.max(1, Style.borderThin * scaling) @@ -41,7 +42,7 @@ Rectangle { font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 } - color: root.hovering ? Colors.onAccent : showBorder ? Colors.accentPrimary : Colors.textPrimary + color: (root.hovering || showFilled) ? Colors.onAccent : showBorder ? Colors.accentPrimary : Colors.textPrimary horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter opacity: root.enabled ? Style.opacityFull : Style.opacityMedium