mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Switched to qmlformat.
This commit is contained in:
@@ -12,12 +12,12 @@ Singleton {
|
||||
readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => {
|
||||
if (!node.isStream) {
|
||||
if (node.isSink) {
|
||||
acc.sinks.push(node)
|
||||
acc.sinks.push(node);
|
||||
} else if (node.audio) {
|
||||
acc.sources.push(node)
|
||||
acc.sources.push(node);
|
||||
}
|
||||
}
|
||||
return acc
|
||||
return acc;
|
||||
}, {
|
||||
"sources": [],
|
||||
"sinks": []
|
||||
@@ -50,21 +50,21 @@ Singleton {
|
||||
|
||||
function updateInputVolume() {
|
||||
if (source && source.audio) {
|
||||
var vol = source.audio.volume
|
||||
var vol = source.audio.volume;
|
||||
if (vol !== undefined && !isNaN(vol)) {
|
||||
root._inputVolume = vol
|
||||
root._inputVolume = vol;
|
||||
}
|
||||
// Don't reset to 0 if volume is undefined/NaN - preserve last known value
|
||||
root._inputMuted = !!source.audio.muted
|
||||
root._inputMuted = !!source.audio.muted;
|
||||
} else {
|
||||
// Only reset muted state
|
||||
root._inputMuted = true
|
||||
root._inputMuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update input volume when source property changes
|
||||
onSourceChanged: {
|
||||
updateInputVolume()
|
||||
updateInputVolume();
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
@@ -75,22 +75,22 @@ Singleton {
|
||||
target: sink?.audio ? sink?.audio : null
|
||||
|
||||
function onVolumeChanged() {
|
||||
var vol = (sink?.audio.volume ?? 0)
|
||||
var vol = (sink?.audio.volume ?? 0);
|
||||
if (isNaN(vol)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// Only update if the value actually changed to prevent spurious signals
|
||||
if (Math.abs(root._volume - vol) > 0.001) {
|
||||
root._volume = vol
|
||||
root._volume = vol;
|
||||
}
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
var newMuted = (sink?.audio.muted ?? true)
|
||||
var newMuted = (sink?.audio.muted ?? true);
|
||||
// Only update if the value actually changed
|
||||
if (root._muted !== newMuted) {
|
||||
root._muted = newMuted
|
||||
Logger.i("AudioService", "OnMuteChanged:", root._muted)
|
||||
root._muted = newMuted;
|
||||
Logger.i("AudioService", "OnMuteChanged:", root._muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,112 +99,113 @@ Singleton {
|
||||
target: source?.audio ? source?.audio : null
|
||||
|
||||
function onVolumeChanged() {
|
||||
var vol = source?.audio?.volume
|
||||
var vol = source?.audio?.volume;
|
||||
if (vol === undefined || isNaN(vol)) {
|
||||
// Don't reset to 0 if volume is undefined/NaN - preserve last known value
|
||||
return
|
||||
return;
|
||||
}
|
||||
// Only update if the value actually changed to prevent spurious signals
|
||||
if (Math.abs(root._inputVolume - vol) > 0.001) {
|
||||
root._inputVolume = vol
|
||||
root._inputVolume = vol;
|
||||
}
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
var newMuted = (source?.audio.muted ?? true)
|
||||
var newMuted = (source?.audio.muted ?? true);
|
||||
// Only update if the value actually changed
|
||||
if (root._inputMuted !== newMuted) {
|
||||
root._inputMuted = newMuted
|
||||
root._inputMuted = newMuted;
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: Pipewire
|
||||
|
||||
function onDefaultAudioSinkChanged() {}
|
||||
function onDefaultAudioSinkChanged() {
|
||||
}
|
||||
|
||||
function onDefaultAudioSourceChanged() {
|
||||
updateInputVolume()
|
||||
updateInputVolume();
|
||||
}
|
||||
}
|
||||
|
||||
function increaseVolume() {
|
||||
setVolume(volume + stepVolume)
|
||||
setVolume(volume + stepVolume);
|
||||
}
|
||||
|
||||
function decreaseVolume() {
|
||||
setVolume(volume - stepVolume)
|
||||
setVolume(volume - stepVolume);
|
||||
}
|
||||
|
||||
function setVolume(newVolume: real) {
|
||||
if (sink?.ready && sink?.audio) {
|
||||
// Clamp it accordingly
|
||||
sink.audio.muted = false
|
||||
sink.audio.volume = Math.max(0, Math.min(Settings.data.audio.volumeOverdrive ? 1.5 : 1.0, newVolume))
|
||||
sink.audio.muted = false;
|
||||
sink.audio.volume = Math.max(0, Math.min(Settings.data.audio.volumeOverdrive ? 1.5 : 1.0, newVolume));
|
||||
//Logger.i("AudioService", "SetVolume", sink.audio.volume);
|
||||
} else {
|
||||
Logger.w("AudioService", "No sink available")
|
||||
Logger.w("AudioService", "No sink available");
|
||||
}
|
||||
}
|
||||
|
||||
function setOutputMuted(muted: bool) {
|
||||
if (sink?.ready && sink?.audio) {
|
||||
sink.audio.muted = muted
|
||||
sink.audio.muted = muted;
|
||||
} else {
|
||||
Logger.w("AudioService", "No sink available")
|
||||
Logger.w("AudioService", "No sink available");
|
||||
}
|
||||
}
|
||||
|
||||
function increaseInputVolume() {
|
||||
setInputVolume(inputVolume + stepVolume)
|
||||
setInputVolume(inputVolume + stepVolume);
|
||||
}
|
||||
|
||||
function decreaseInputVolume() {
|
||||
setInputVolume(inputVolume - stepVolume)
|
||||
setInputVolume(inputVolume - stepVolume);
|
||||
}
|
||||
|
||||
function setInputVolume(newVolume: real) {
|
||||
if (source?.ready && source?.audio) {
|
||||
// Clamp it accordingly
|
||||
source.audio.muted = false
|
||||
source.audio.volume = Math.max(0, Math.min(Settings.data.audio.volumeOverdrive ? 1.5 : 1.0, newVolume))
|
||||
source.audio.muted = false;
|
||||
source.audio.volume = Math.max(0, Math.min(Settings.data.audio.volumeOverdrive ? 1.5 : 1.0, newVolume));
|
||||
} else {
|
||||
Logger.w("AudioService", "No source available")
|
||||
Logger.w("AudioService", "No source available");
|
||||
}
|
||||
}
|
||||
|
||||
function setInputMuted(muted: bool) {
|
||||
if (source?.ready && source?.audio) {
|
||||
source.audio.muted = muted
|
||||
source.audio.muted = muted;
|
||||
} else {
|
||||
Logger.w("AudioService", "No source available")
|
||||
Logger.w("AudioService", "No source available");
|
||||
}
|
||||
}
|
||||
|
||||
function setAudioSink(newSink: PwNode): void {
|
||||
Pipewire.preferredDefaultAudioSink = newSink
|
||||
Pipewire.preferredDefaultAudioSink = newSink;
|
||||
// Volume is changed by the sink change
|
||||
root._volume = newSink?.audio?.volume ?? 0
|
||||
root._muted = !!newSink?.audio?.muted
|
||||
root._volume = newSink?.audio?.volume ?? 0;
|
||||
root._muted = !!newSink?.audio?.muted;
|
||||
}
|
||||
|
||||
function setAudioSource(newSource: PwNode): void {
|
||||
Pipewire.preferredDefaultAudioSource = newSource
|
||||
Pipewire.preferredDefaultAudioSource = newSource;
|
||||
// The source property will update automatically, which triggers onSourceChanged
|
||||
// which calls updateInputVolume()
|
||||
}
|
||||
|
||||
function getOutputIcon() {
|
||||
if (muted) {
|
||||
return "volume-mute"
|
||||
return "volume-mute";
|
||||
}
|
||||
return (volume <= Number.EPSILON) ? "volume-zero" : (volume <= 0.5) ? "volume-low" : "volume-high"
|
||||
return (volume <= Number.EPSILON) ? "volume-zero" : (volume <= 0.5) ? "volume-low" : "volume-high";
|
||||
}
|
||||
|
||||
function getInputIcon() {
|
||||
if (inputMuted) {
|
||||
return "microphone-mute"
|
||||
return "microphone-mute";
|
||||
}
|
||||
return (inputVolume <= Number.EPSILON) ? "microphone-mute" : "microphone"
|
||||
return (inputVolume <= Number.EPSILON) ? "microphone-mute" : "microphone";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,12 @@ import qs.Services.UI
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
/**
|
||||
* Cava runs if:
|
||||
* - Bar has an audio visualizer
|
||||
* - LockScreen is opened
|
||||
* - A control center is open
|
||||
*/
|
||||
* Cava runs if:
|
||||
* - Bar has an audio visualizer
|
||||
* - LockScreen is opened
|
||||
* - A control center is open
|
||||
*/
|
||||
property bool shouldRun: BarService.hasAudioVisualizer || PanelService.lockScreen?.active || (PanelService.openedPanel && PanelService.openedPanel.panelWrapper.objectName.startsWith("controlCenterPanel"))
|
||||
|
||||
property var values: Array(barsCount).fill(0)
|
||||
@@ -57,68 +56,68 @@ Singleton {
|
||||
running: root.shouldRun
|
||||
command: ["cava", "-p", "/dev/stdin"]
|
||||
onRunningChanged: {
|
||||
Logger.d("Cava", "Process running:", running)
|
||||
Logger.d("Cava", "Process running:", running);
|
||||
}
|
||||
onExited: {
|
||||
Logger.d("Cava", "Process exited")
|
||||
stdinEnabled = true
|
||||
values = Array(barsCount).fill(0)
|
||||
Logger.d("Cava", "Process exited");
|
||||
stdinEnabled = true;
|
||||
values = Array(barsCount).fill(0);
|
||||
}
|
||||
onStarted: {
|
||||
Logger.d("Cava", "Process started")
|
||||
Logger.d("Cava", "Process started");
|
||||
for (const k in config) {
|
||||
if (typeof config[k] !== "object") {
|
||||
write(k + "=" + config[k] + "\n")
|
||||
continue
|
||||
write(k + "=" + config[k] + "\n");
|
||||
continue;
|
||||
}
|
||||
write("[" + k + "]\n")
|
||||
const obj = config[k]
|
||||
write("[" + k + "]\n");
|
||||
const obj = config[k];
|
||||
for (const k2 in obj) {
|
||||
write(k2 + "=" + obj[k2] + "\n")
|
||||
write(k2 + "=" + obj[k2] + "\n");
|
||||
}
|
||||
}
|
||||
stdinEnabled = false
|
||||
values = Array(barsCount).fill(0)
|
||||
stdinEnabled = false;
|
||||
values = Array(barsCount).fill(0);
|
||||
}
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const newValues = data.slice(0, -1).split(";").map(v => parseInt(v, 10) / 100)
|
||||
const newValues = data.slice(0, -1).split(";").map(v => parseInt(v, 10) / 100);
|
||||
|
||||
// Check if all values are effectively zero (< 0.01)
|
||||
const allZero = newValues.every(v => v < 0.01)
|
||||
const allZero = newValues.every(v => v < 0.01);
|
||||
|
||||
if (allZero) {
|
||||
root.idleFrameCount++
|
||||
root.idleFrameCount++;
|
||||
if (root.idleFrameCount >= root.idleThreshold) {
|
||||
// We're idle - stop updating values to save GPU
|
||||
if (!root.isIdle) {
|
||||
root.isIdle = true
|
||||
root.isIdle = true;
|
||||
// Set all values to 0 one final time
|
||||
root.values = Array(root.barsCount).fill(0)
|
||||
Logger.d("Cava", "Idle detected - stopped rendering")
|
||||
root.values = Array(root.barsCount).fill(0);
|
||||
Logger.d("Cava", "Idle detected - stopped rendering");
|
||||
}
|
||||
// Don't update values while idle
|
||||
return
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Audio detected - resume updates
|
||||
root.idleFrameCount = 0
|
||||
root.idleFrameCount = 0;
|
||||
if (root.isIdle) {
|
||||
root.isIdle = false
|
||||
Logger.d("Cava", "Audio detected - resumed rendering")
|
||||
root.isIdle = false;
|
||||
Logger.d("Cava", "Audio detected - resumed rendering");
|
||||
}
|
||||
}
|
||||
|
||||
// Update values only if there's a significant change
|
||||
if (!isIdle) {
|
||||
root.values = newValues
|
||||
root.values = newValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
Logger.w("Cava", "Error", text)
|
||||
Logger.w("Cava", "Error", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,60 +26,60 @@ Singleton {
|
||||
property real infiniteTrackLength: 922337203685
|
||||
|
||||
Component.onCompleted: {
|
||||
updateCurrentPlayer()
|
||||
updateCurrentPlayer();
|
||||
}
|
||||
|
||||
function getAvailablePlayers() {
|
||||
if (!Mpris.players || !Mpris.players.values) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
let allPlayers = Mpris.players.values
|
||||
let finalPlayers = []
|
||||
const genericBrowsers = ["firefox", "chromium", "chrome"]
|
||||
const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : []
|
||||
let allPlayers = Mpris.players.values;
|
||||
let finalPlayers = [];
|
||||
const genericBrowsers = ["firefox", "chromium", "chrome"];
|
||||
const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : [];
|
||||
|
||||
// Separate players into specific and generic lists
|
||||
let specificPlayers = []
|
||||
let genericPlayers = []
|
||||
let specificPlayers = [];
|
||||
let genericPlayers = [];
|
||||
for (var i = 0; i < allPlayers.length; i++) {
|
||||
const identity = String(allPlayers[i].identity || "").toLowerCase()
|
||||
const identity = String(allPlayers[i].identity || "").toLowerCase();
|
||||
const match = blacklist.find(b => {
|
||||
const s = String(b || "").toLowerCase()
|
||||
return s && (identity.includes(s))
|
||||
})
|
||||
const s = String(b || "").toLowerCase();
|
||||
return s && (identity.includes(s));
|
||||
});
|
||||
if (match)
|
||||
continue
|
||||
continue;
|
||||
if (genericBrowsers.some(b => identity.includes(b))) {
|
||||
genericPlayers.push(allPlayers[i])
|
||||
genericPlayers.push(allPlayers[i]);
|
||||
} else {
|
||||
specificPlayers.push(allPlayers[i])
|
||||
specificPlayers.push(allPlayers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let matchedGenericIndices = {}
|
||||
let matchedGenericIndices = {};
|
||||
|
||||
// For each specific player, try to find and pair it with a generic partner
|
||||
for (var i = 0; i < specificPlayers.length; i++) {
|
||||
let specificPlayer = specificPlayers[i]
|
||||
let title1 = String(specificPlayer.trackTitle || "").trim()
|
||||
let wasMatched = false
|
||||
let specificPlayer = specificPlayers[i];
|
||||
let title1 = String(specificPlayer.trackTitle || "").trim();
|
||||
let wasMatched = false;
|
||||
|
||||
if (title1) {
|
||||
for (var j = 0; j < genericPlayers.length; j++) {
|
||||
if (matchedGenericIndices[j])
|
||||
continue
|
||||
let genericPlayer = genericPlayers[j]
|
||||
let title2 = String(genericPlayer.trackTitle || "").trim()
|
||||
continue;
|
||||
let genericPlayer = genericPlayers[j];
|
||||
let title2 = String(genericPlayer.trackTitle || "").trim();
|
||||
|
||||
if (title2 && (title1.includes(title2) || title2.includes(title1))) {
|
||||
let dataPlayer = genericPlayer
|
||||
let identityPlayer = specificPlayer
|
||||
let dataPlayer = genericPlayer;
|
||||
let identityPlayer = specificPlayer;
|
||||
|
||||
let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0)
|
||||
let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0)
|
||||
let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0);
|
||||
let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0);
|
||||
if (scoreSpecific > scoreGeneric) {
|
||||
dataPlayer = specificPlayer
|
||||
dataPlayer = specificPlayer;
|
||||
}
|
||||
|
||||
let virtualPlayer = {
|
||||
@@ -101,171 +101,171 @@ Singleton {
|
||||
"canControl": dataPlayer.canControl || false,
|
||||
"_stateSource": dataPlayer,
|
||||
"_controlTarget": identityPlayer
|
||||
}
|
||||
finalPlayers.push(virtualPlayer)
|
||||
matchedGenericIndices[j] = true
|
||||
wasMatched = true
|
||||
break
|
||||
};
|
||||
finalPlayers.push(virtualPlayer);
|
||||
matchedGenericIndices[j] = true;
|
||||
wasMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!wasMatched) {
|
||||
finalPlayers.push(specificPlayer)
|
||||
finalPlayers.push(specificPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any generic players that were not matched
|
||||
for (var i = 0; i < genericPlayers.length; i++) {
|
||||
if (!matchedGenericIndices[i]) {
|
||||
finalPlayers.push(genericPlayers[i])
|
||||
finalPlayers.push(genericPlayers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter for controllable players
|
||||
let controllablePlayers = []
|
||||
let controllablePlayers = [];
|
||||
for (var i = 0; i < finalPlayers.length; i++) {
|
||||
let player = finalPlayers[i]
|
||||
let player = finalPlayers[i];
|
||||
if (player && player.canControl) {
|
||||
controllablePlayers.push(player)
|
||||
controllablePlayers.push(player);
|
||||
}
|
||||
}
|
||||
return controllablePlayers
|
||||
return controllablePlayers;
|
||||
}
|
||||
|
||||
function findActivePlayer() {
|
||||
let availablePlayers = getAvailablePlayers()
|
||||
let availablePlayers = getAvailablePlayers();
|
||||
if (availablePlayers.length === 0) {
|
||||
//Logger.i("Media", "No active player found")
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prioritize the actively playing player ---
|
||||
for (var i = 0; i < availablePlayers.length; i++) {
|
||||
if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) {
|
||||
Logger.d("Media", "Found actively playing player: " + availablePlayers[i].identity)
|
||||
selectedPlayerIndex = i
|
||||
return availablePlayers[i]
|
||||
Logger.d("Media", "Found actively playing player: " + availablePlayers[i].identity);
|
||||
selectedPlayerIndex = i;
|
||||
return availablePlayers[i];
|
||||
}
|
||||
}
|
||||
|
||||
// fallback if nothing is playing)
|
||||
const preferred = (Settings.data.audio.preferredPlayer || "")
|
||||
const preferred = (Settings.data.audio.preferredPlayer || "");
|
||||
if (preferred !== "") {
|
||||
for (var i = 0; i < availablePlayers.length; i++) {
|
||||
const p = availablePlayers[i]
|
||||
const identity = String(p.identity || "").toLowerCase()
|
||||
const pref = preferred.toLowerCase()
|
||||
const p = availablePlayers[i];
|
||||
const identity = String(p.identity || "").toLowerCase();
|
||||
const pref = preferred.toLowerCase();
|
||||
if (identity.includes(pref)) {
|
||||
selectedPlayerIndex = i
|
||||
return p
|
||||
selectedPlayerIndex = i;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedPlayerIndex < availablePlayers.length) {
|
||||
return availablePlayers[selectedPlayerIndex]
|
||||
return availablePlayers[selectedPlayerIndex];
|
||||
} else {
|
||||
selectedPlayerIndex = 0
|
||||
return availablePlayers[0]
|
||||
selectedPlayerIndex = 0;
|
||||
return availablePlayers[0];
|
||||
}
|
||||
}
|
||||
|
||||
property bool autoSwitchingPaused: false
|
||||
|
||||
function switchToPlayer(index) {
|
||||
let availablePlayers = getAvailablePlayers()
|
||||
let availablePlayers = getAvailablePlayers();
|
||||
if (index >= 0 && index < availablePlayers.length) {
|
||||
let newPlayer = availablePlayers[index]
|
||||
let newPlayer = availablePlayers[index];
|
||||
if (newPlayer !== currentPlayer) {
|
||||
currentPlayer = newPlayer
|
||||
selectedPlayerIndex = index
|
||||
currentPosition = currentPlayer ? currentPlayer.position : 0
|
||||
Logger.d("Media", "Manually switched to player " + currentPlayer.identity)
|
||||
currentPlayer = newPlayer;
|
||||
selectedPlayerIndex = index;
|
||||
currentPosition = currentPlayer ? currentPlayer.position : 0;
|
||||
Logger.d("Media", "Manually switched to player " + currentPlayer.identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to the most recently active player
|
||||
function updateCurrentPlayer() {
|
||||
let newPlayer = findActivePlayer()
|
||||
let newPlayer = findActivePlayer();
|
||||
if (newPlayer !== currentPlayer) {
|
||||
currentPlayer = newPlayer
|
||||
currentPosition = currentPlayer ? currentPlayer.position : 0
|
||||
Logger.d("Media", "Switching player")
|
||||
currentPlayer = newPlayer;
|
||||
currentPosition = currentPlayer ? currentPlayer.position : 0;
|
||||
Logger.d("Media", "Switching player");
|
||||
}
|
||||
}
|
||||
|
||||
function playPause() {
|
||||
if (currentPlayer) {
|
||||
let stateSource = currentPlayer._stateSource || currentPlayer
|
||||
let controlTarget = currentPlayer._controlTarget || currentPlayer
|
||||
let stateSource = currentPlayer._stateSource || currentPlayer;
|
||||
let controlTarget = currentPlayer._controlTarget || currentPlayer;
|
||||
if (stateSource.playbackState === MprisPlaybackState.Playing) {
|
||||
controlTarget.pause()
|
||||
controlTarget.pause();
|
||||
} else {
|
||||
controlTarget.play()
|
||||
controlTarget.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function play() {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canPlay) {
|
||||
target.play()
|
||||
target.play();
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target) {
|
||||
target.stop()
|
||||
target.stop();
|
||||
}
|
||||
}
|
||||
|
||||
function pause() {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canPause) {
|
||||
target.pause()
|
||||
target.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function next() {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canGoNext) {
|
||||
target.next()
|
||||
target.next();
|
||||
}
|
||||
}
|
||||
|
||||
function previous() {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canGoPrevious) {
|
||||
target.previous()
|
||||
target.previous();
|
||||
}
|
||||
}
|
||||
|
||||
function seek(position) {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canSeek) {
|
||||
target.position = position
|
||||
currentPosition = position
|
||||
target.position = position;
|
||||
currentPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
function seekRelative(offset) {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canSeek && target.length > 0) {
|
||||
let seekPosition = target.position + offset
|
||||
target.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
let seekPosition = target.position + offset;
|
||||
target.position = seekPosition;
|
||||
currentPosition = seekPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// Seek to position based on ratio (0.0 to 1.0)
|
||||
function seekByRatio(ratio) {
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
|
||||
let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null;
|
||||
if (target && target.canSeek && target.length > 0) {
|
||||
let seekPosition = ratio * target.length
|
||||
target.position = seekPosition
|
||||
currentPosition = seekPosition
|
||||
let seekPosition = ratio * target.length;
|
||||
target.position = seekPosition;
|
||||
currentPosition = seekPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,9 +277,9 @@ Singleton {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.playbackState === MprisPlaybackState.Playing) {
|
||||
currentPosition = currentPlayer.position
|
||||
currentPosition = currentPlayer.position;
|
||||
} else {
|
||||
running = false
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,12 +289,12 @@ Singleton {
|
||||
target: currentPlayer
|
||||
function onPositionChanged() {
|
||||
if (!root.isSeeking && currentPlayer) {
|
||||
currentPosition = currentPlayer.position
|
||||
currentPosition = currentPlayer.position;
|
||||
}
|
||||
}
|
||||
function onPlaybackStateChanged() {
|
||||
if (!root.isSeeking && currentPlayer) {
|
||||
currentPosition = currentPlayer.position
|
||||
currentPosition = currentPlayer.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +302,7 @@ Singleton {
|
||||
// Reset position when switching to inactive player
|
||||
onCurrentPlayerChanged: {
|
||||
if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) {
|
||||
currentPosition = 0
|
||||
currentPosition = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,10 +314,10 @@ Singleton {
|
||||
onTriggered: {
|
||||
//Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused)
|
||||
if (autoSwitchingPaused)
|
||||
return
|
||||
return;
|
||||
// Only update if we don't have a playing player or if current player is paused
|
||||
if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) {
|
||||
updateCurrentPlayer()
|
||||
updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,8 +326,8 @@ Singleton {
|
||||
Connections {
|
||||
target: Mpris.players
|
||||
function onValuesChanged() {
|
||||
Logger.d("Media", "Players changed")
|
||||
updateCurrentPlayer()
|
||||
Logger.d("Media", "Players changed");
|
||||
updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,43 +27,43 @@ Singleton {
|
||||
|
||||
// Start or Stop recording
|
||||
function toggleRecording() {
|
||||
(isRecording || isPending) ? stopRecording() : startRecording()
|
||||
(isRecording || isPending) ? stopRecording() : startRecording();
|
||||
}
|
||||
|
||||
// Start screen recording using Quickshell.execDetached
|
||||
function startRecording() {
|
||||
if (!isAvailable) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (isRecording || isPending) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
isPending = true
|
||||
hasActiveRecording = false
|
||||
isPending = true;
|
||||
hasActiveRecording = false;
|
||||
|
||||
// Close any opened panel
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close()
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
|
||||
// First, ensure xdg-desktop-portal and a compositor portal are running
|
||||
portalCheckProcess.exec({
|
||||
"command": ["sh", "-c", // require core portal AND one of the backends
|
||||
"pidof xdg-desktop-portal >/dev/null 2>&1 && (pidof xdg-desktop-portal-wlr >/dev/null 2>&1 || pidof xdg-desktop-portal-hyprland >/dev/null 2>&1 || pidof xdg-desktop-portal-gnome >/dev/null 2>&1 || pidof xdg-desktop-portal-kde >/dev/null 2>&1)"]
|
||||
})
|
||||
"command": ["sh", "-c" // require core portal AND one of the backends
|
||||
, "pidof xdg-desktop-portal >/dev/null 2>&1 && (pidof xdg-desktop-portal-wlr >/dev/null 2>&1 || pidof xdg-desktop-portal-hyprland >/dev/null 2>&1 || pidof xdg-desktop-portal-gnome >/dev/null 2>&1 || pidof xdg-desktop-portal-kde >/dev/null 2>&1)"]
|
||||
});
|
||||
}
|
||||
|
||||
function launchRecorder() {
|
||||
var filename = Time.getFormattedTimestamp() + ".mp4"
|
||||
var videoDir = Settings.preprocessPath(settings.directory)
|
||||
var filename = Time.getFormattedTimestamp() + ".mp4";
|
||||
var videoDir = Settings.preprocessPath(settings.directory);
|
||||
if (videoDir && !videoDir.endsWith("/")) {
|
||||
videoDir += "/"
|
||||
videoDir += "/";
|
||||
}
|
||||
outputPath = videoDir + filename
|
||||
outputPath = videoDir + filename;
|
||||
|
||||
var audioArg = (settings.audioSource === "both") ? `-a "default_output|default_input"` : `-a ${settings.audioSource}`
|
||||
var audioArg = (settings.audioSource === "both") ? `-a "default_output|default_input"` : `-a ${settings.audioSource}`;
|
||||
|
||||
var flags = `-w ${settings.videoSource} -f ${settings.frameRate} -ac ${settings.audioCodec} -k ${settings.videoCodec} ${audioArg} -q ${settings.quality} -cursor ${settings.showCursor ? "yes" : "no"} -cr ${settings.colorRange} -o "${outputPath}"`
|
||||
var flags = `-w ${settings.videoSource} -f ${settings.frameRate} -ac ${settings.audioCodec} -k ${settings.videoCodec} ${audioArg} -q ${settings.quality} -cursor ${settings.showCursor ? "yes" : "no"} -cr ${settings.colorRange} -o "${outputPath}"`;
|
||||
var command = `
|
||||
_gpuscreenrecorder_flatpak_installed() {
|
||||
flatpak list --app | grep -q "com.dec05eba.gpu_screen_recorder"
|
||||
@@ -74,35 +74,35 @@ Singleton {
|
||||
flatpak run --command=gpu-screen-recorder --file-forwarding com.dec05eba.gpu_screen_recorder ${flags}
|
||||
else
|
||||
echo "GPU_SCREEN_RECORDER_NOT_INSTALLED"
|
||||
fi`
|
||||
fi`;
|
||||
|
||||
// Use Process instead of execDetached so we can monitor it and read stderr
|
||||
recorderProcess.exec({
|
||||
"command": ["sh", "-c", command]
|
||||
})
|
||||
});
|
||||
|
||||
// Start monitoring - if process ends quickly, it was likely cancelled
|
||||
pendingTimer.running = true
|
||||
pendingTimer.running = true;
|
||||
}
|
||||
|
||||
// Stop recording using Quickshell.execDetached
|
||||
function stopRecording() {
|
||||
if (!isRecording && !isPending) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
ToastService.showNotice(I18n.tr("toast.recording.stopping"), outputPath, "settings-screen-recorder")
|
||||
ToastService.showNotice(I18n.tr("toast.recording.stopping"), outputPath, "settings-screen-recorder");
|
||||
|
||||
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder' || pkill -SIGINT -f 'com.dec05eba.gpu_screen_recorder'"])
|
||||
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder' || pkill -SIGINT -f 'com.dec05eba.gpu_screen_recorder'"]);
|
||||
|
||||
isRecording = false
|
||||
isPending = false
|
||||
pendingTimer.running = false
|
||||
monitorTimer.running = false
|
||||
hasActiveRecording = false
|
||||
isRecording = false;
|
||||
isPending = false;
|
||||
pendingTimer.running = false;
|
||||
monitorTimer.running = false;
|
||||
hasActiveRecording = false;
|
||||
|
||||
// Just in case, force kill after 3 seconds
|
||||
killTimer.running = true
|
||||
killTimer.running = true;
|
||||
}
|
||||
|
||||
// Process to run and monitor gpu-screen-recorder
|
||||
@@ -113,37 +113,37 @@ Singleton {
|
||||
onExited: function (exitCode, exitStatus) {
|
||||
if (isPending) {
|
||||
// Process ended while we were pending - likely cancelled or error
|
||||
isPending = false
|
||||
pendingTimer.running = false
|
||||
isPending = false;
|
||||
pendingTimer.running = false;
|
||||
|
||||
// Check if gpu-screen-recorder is not installed
|
||||
const stdout = String(recorderProcess.stdout.text || "").trim()
|
||||
const stdout = String(recorderProcess.stdout.text || "").trim();
|
||||
if (stdout === "GPU_SCREEN_RECORDER_NOT_INSTALLED") {
|
||||
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"))
|
||||
return
|
||||
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"));
|
||||
return;
|
||||
}
|
||||
|
||||
// If it failed to start, show a clear error toast with stderr
|
||||
if (exitCode !== 0) {
|
||||
const err = String(recorderProcess.stderr.text || "").trim()
|
||||
const err = String(recorderProcess.stderr.text || "").trim();
|
||||
if (err.length > 0)
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), err)
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), err);
|
||||
else
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-gpu"))
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-gpu"));
|
||||
}
|
||||
} else if (isRecording) {
|
||||
// Process ended normally while recording
|
||||
isRecording = false
|
||||
monitorTimer.running = false
|
||||
isRecording = false;
|
||||
monitorTimer.running = false;
|
||||
// Consider successful save if exitCode == 0
|
||||
if (exitCode === 0) {
|
||||
ToastService.showNotice(I18n.tr("toast.recording.saved"), outputPath, "settings-screen-recorder")
|
||||
ToastService.showNotice(I18n.tr("toast.recording.saved"), outputPath, "settings-screen-recorder");
|
||||
} else {
|
||||
const err2 = String(recorderProcess.stderr.text || "").trim()
|
||||
const err2 = String(recorderProcess.stderr.text || "").trim();
|
||||
if (err2.length > 0)
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), err2)
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), err2);
|
||||
else
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-general"))
|
||||
ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-general"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,11 +155,11 @@ Singleton {
|
||||
onExited: function (exitCode, exitStatus) {
|
||||
if (exitCode === 0) {
|
||||
// Portals available, proceed to launch
|
||||
launchRecorder()
|
||||
launchRecorder();
|
||||
} else {
|
||||
isPending = false
|
||||
hasActiveRecording = false
|
||||
ToastService.showError(I18n.tr("toast.recording.no-portals"), I18n.tr("toast.recording.no-portals-desc"))
|
||||
isPending = false;
|
||||
hasActiveRecording = false;
|
||||
ToastService.showError(I18n.tr("toast.recording.no-portals"), I18n.tr("toast.recording.no-portals-desc"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,14 +172,14 @@ Singleton {
|
||||
onTriggered: {
|
||||
if (isPending && recorderProcess.running) {
|
||||
// Process is still running after 2 seconds - assume recording started successfully
|
||||
isPending = false
|
||||
isRecording = true
|
||||
hasActiveRecording = true
|
||||
monitorTimer.running = true
|
||||
isPending = false;
|
||||
isRecording = true;
|
||||
hasActiveRecording = true;
|
||||
monitorTimer.running = true;
|
||||
// Don't show a toast when recording starts to avoid having the toast in every video.
|
||||
} else if (isPending) {
|
||||
// Process not running anymore - was cancelled or failed
|
||||
isPending = false
|
||||
isPending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,8 +192,8 @@ Singleton {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (!recorderProcess.running && isRecording) {
|
||||
isRecording = false
|
||||
running = false
|
||||
isRecording = false;
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ Singleton {
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder' 2>/dev/null || pkill -9 -f 'com.dec05eba.gpu_screen_recorder' 2>/dev/null || true"])
|
||||
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder' 2>/dev/null || pkill -9 -f 'com.dec05eba.gpu_screen_recorder' 2>/dev/null || true"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user