ScreenRecorder Service + Reconnected the utility button

This commit is contained in:
quadbyte 2025-08-12 16:18:49 -04:00
parent c1f22d5e87
commit e7588b29d9
10 changed files with 142 additions and 51 deletions

View file

@ -18,14 +18,20 @@ NBox {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
// Record // Screen Recorder
NIconButton { NIconButton {
icon: "videocam" icon: "videocam"
showFilled: ScreenRecorder.isRecording
onClicked: {
ScreenRecorder.toggleRecording()
} }
}
// Wallpaper // Wallpaper
NIconButton { NIconButton {
icon: "image" icon: "image"
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }

View file

@ -10,8 +10,8 @@ NBox {
readonly property real scaling: Scaling.scale(screen) readonly property real scaling: Scaling.scale(screen)
readonly property bool weatherReady: (Location.data.weather !== null) 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 Layout.fillWidth: true
// Height driven by content // Height driven by content
implicitHeight: content.implicitHeight + Style.marginLarge * 2 * scaling implicitHeight: content.implicitHeight + Style.marginLarge * 2 * scaling

View file

@ -49,7 +49,6 @@ Singleton {
vol = 0 vol = 0
} }
root._volume = vol root._volume = vol
} }
function onMutedChanged() { function onMutedChanged() {

View file

@ -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"])
}
}
}

View file

@ -105,9 +105,8 @@ Singleton {
property string videoCodec: "h264" property string videoCodec: "h264"
property string quality: "very_high" property string quality: "very_high"
property string colorRange: "limited" property string colorRange: "limited"
property bool showCursor: true
// New: optional audio source selection (default: system output)
property string audioSource: "default_output" property string audioSource: "default_output"
property bool showCursor: true
} }
// wallpaper // wallpaper

View file

@ -43,6 +43,26 @@ Singleton {
return Math.floor(Date.now() / 1000) return Math.floor(Date.now() / 1000)
} }
/**
* 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()
// 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')
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 // Format an easy to read approximate duration ex: 4h32m
// Used to display the time remaining on the Battery widget // Used to display the time remaining on the Battery widget
function formatVagueHumanReadableDuration(totalSeconds) { function formatVagueHumanReadableDuration(totalSeconds) {

View file

@ -53,6 +53,7 @@ Singleton {
changeWallpaperProcess.running = true changeWallpaperProcess.running = true
} else { } else {
// Fallback: update the settings directly for non-SWWW mode // Fallback: update the settings directly for non-SWWW mode
//console.log("[WP] Not using Swww, setting wallpaper directly") //console.log("[WP] Not using Swww, setting wallpaper directly")
} }
@ -159,6 +160,7 @@ Singleton {
running: false running: false
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
// console.log(this.text) // console.log(this.text)
} }
} }

View file

@ -13,6 +13,7 @@ Rectangle {
property string icon property string icon
property string tooltipText property string tooltipText
property bool showBorder: true property bool showBorder: true
property bool showFilled: false
property bool enabled: true property bool enabled: true
property bool hovering: false property bool hovering: false
property real fontPointSize: Style.fontSizeMedium property real fontPointSize: Style.fontSizeMedium
@ -25,7 +26,7 @@ Rectangle {
implicitWidth: size implicitWidth: size
implicitHeight: size implicitHeight: size
color: root.hovering ? Colors.accentPrimary : "transparent" color: (root.hovering || showFilled) ? Colors.accentPrimary : "transparent"
radius: width * 0.5 radius: width * 0.5
border.color: showBorder ? Colors.accentPrimary : "transparent" border.color: showBorder ? Colors.accentPrimary : "transparent"
border.width: Math.max(1, Style.borderThin * scaling) border.width: Math.max(1, Style.borderThin * scaling)
@ -41,7 +42,7 @@ Rectangle {
font.variableAxes: { font.variableAxes: {
"wght": (Font.Normal + Font.Bold) / 2.0 "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 horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium opacity: root.enabled ? Style.opacityFull : Style.opacityMedium