rebrand!: Antergos NeXT -> Euri Linux

This commit is contained in:
2026-08-21 21:43:47 +02:00
parent 7256fd331a
commit 0cfc149d37
1452 changed files with 599 additions and 597 deletions
@@ -0,0 +1,20 @@
import QtQuick
import org.kde.plasma.configuration
ConfigModel {
ConfigCategory {
name: i18n("Visualizer")
icon: "waveform-symbolic"
source: "configVisualizer.qml"
}
ConfigCategory {
name: "CAVA"
icon: "view-process-system-symbolic"
source: "configCava.qml"
}
ConfigCategory {
name: i18n("General")
icon: "configure-symbolic"
source: "configGeneral.qml"
}
}
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<!-- Cava config -->
<entry name="framerate" type="Int">
<default>60</default>
</entry>
<entry name="barCount" type="Int">
<default>25</default>
</entry>
<entry name="noiseReduction" type="Int">
<default>77</default>
</entry>
<entry name="monstercat" type="Bool">
<default>false</default>
</entry>
<entry name="waves" type="Bool">
<default>false</default>
</entry>
<entry name="autoSensitivity" type="Bool">
<default>true</default>
</entry>
<entry name="sensitivityEnabled" type="Bool">
<default>false</default>
</entry>
<entry name="sensitivity" type="Int">
<default>100</default>
</entry>
<entry name="lowerCutoffFreq" type="Int">
<default>50</default>
</entry>
<entry name="higherCutoffFreq" type="Int">
<default>10000</default>
</entry>
<entry name="inputMethod" type="String">
<default></default>
</entry>
<entry name="inputSource" type="String">
<default></default>
</entry>
<entry name="sampleRate" type="Int">
<default>44100</default>
</entry>
<entry name="sampleBits" type="Int">
<default>16</default>
</entry>
<entry name="inputChannels" type="Int">
<default>2</default>
</entry>
<entry name="autoconnect" type="Int">
<default>2</default>
</entry>
<entry name="active" type="Bool">
<default>false</default>
</entry>
<entry name="remix" type="Bool">
<default>true</default>
</entry>
<entry name="virtual" type="Bool">
<default>true</default>
</entry>
<entry name="outputChannels" type="String">
<default>mono</default>
</entry>
<entry name="monoOption" type="String">
<default>average</default>
</entry>
<entry name="reverse" type="Bool">
<default>false</default>
</entry>
<entry name="eqEnabled" type="Bool">
<default>false</default>
</entry>
<entry name="eq" type="StringList">
<default>1.0,1.0,1.0,1.0,1.0</default>
</entry>
<!-- Widget config -->
<entry name="debugMode" type="Bool">
<default>false</default>
</entry>
<entry name="hideWhenIdle" type="Bool">
<default>false</default>
</entry>
<entry name="idleTimer" type="Int">
<default>5</default>
</entry>
<entry name="cavaSleepTimer" type="Int">
<default>5</default>
</entry>
<entry name="visualizerStyle" type="Int">
<default>1</default>
</entry>
<entry name="circleMode" type="Bool">
<default>false</default>
</entry>
<entry name="circleModeSize" type="Double">
<default>0.5</default>
</entry>
<entry name="fillWave" type="Bool">
<default>true</default>
</entry>
<entry name="barWidth" type="Int">
<default>2</default>
</entry>
<entry name="blockHeight" type="Int">
<default>2</default>
</entry>
<entry name="blockSpacing" type="Int">
<default>2</default>
</entry>
<entry name="barGap" type="Int">
<default>5</default>
</entry>
<!-- ./package/contents/ui/code/globals.js[baseBarColors] -->
<entry name="barColors" type="String">
<default>{}</default>
</entry>
<!-- ./package/contents/ui/code/globals.js[baseWaveFillColors] -->
<entry name="waveFillColors" type="String">
<default>{}</default>
</entry>
<!-- ./package/contents/ui/code/globals.js[inactiveBlockColor] -->
<entry name="inactiveBlockColors" type="String">
<default>{}</default>
</entry>
<entry name="drawInactiveBlocks" type="Bool">
<default>false</default>
</entry>
<entry name="desktopWidgetBg" type="Int">
<default>1</default>
</entry>
<entry name="centeredBars" type="Bool">
<default>false</default>
</entry>
<entry name="roundedBars" type="Bool">
<default>false</default>
</entry>
<!-- fill panel thickness -->
<entry name="fillPanel" type="Bool">
<default>false</default>
</entry>
<!-- take all the available space in the panel -->
<entry name="expanding" type="Bool">
<default>false</default>
</entry>
<entry name="length" type="Int">
<default>200</default>
</entry>
<entry name="orientation" type="Int">
<!-- ./package/contents/ui/code/enum.js[Orientation] -->
<default>1</default>
</entry>
<entry name="_stopCava" type="Bool">
<default>false</default>
</entry>
<entry name="hideToolTip" type="Bool">
<default>false</default>
</entry>
<entry name="disableLeftClick" type="Bool">
<default>false</default>
</entry>
<entry name="pauseOnFullScreenWindow" type="Bool">
<default>true</default>
</entry>
<entry name="pauseOnMaximizedWindow" type="Bool">
<default>false</default>
</entry>
</group>
</kcfg>
@@ -0,0 +1,139 @@
import QtQuick
import "./components"
Item {
id: root
property int framerate
property int barCount
property int noiseReduction
property int monstercat // boolean
property int waves // boolean
property int autoSensitivity // boolean
property bool sensitivityEnabled
property int sensitivity
property int lowerCutoffFreq
property int higherCutoffFreq
property string inputMethod
property string inputSource
property int sampleRate
property int sampleBits
property int inputChannels
property int autoconnect
property int active // boolean
property int remix // boolean
property int virtual // boolean
property string outputChannels
property string monoOption
property int reverse // boolean
property bool eqEnabled
property list<real> eq
property list<int> values
property bool idle
property bool idleCheck
property int idleTimer
property int cavaSleepTimer
property int asciiMaxRange: 100
readonly property bool hasError: error !== "" || loadingFailed
readonly property string error: process.stderr
readonly property list<string> loadingErrors: process.loadingErrors
readonly property bool loadingFailed: process.loadingFailed
readonly property bool usingFallback: process.usingFallback
readonly property bool running: process.running
readonly property string cavaCommand: process.command
readonly property string cavaConfig: {
let config = `[general]
framerate=${root.framerate}
bars=${root.barCount}
autosens=${root.autoSensitivity}
lower_cutoff_freq=${root.lowerCutoffFreq}
higher_cutoff_freq=${root.higherCutoffFreq}
sleep_timer=${root.cavaSleepTimer}
`;
if (root.sensitivityEnabled) {
config += `sensitivity=${root.sensitivity}\n`;
}
config += "[input]\n";
if (root.inputMethod !== "") {
config += `method=${root.inputMethod}\n`;
}
if (root.inputSource !== "") {
config += `source=${root.inputSource}\n`;
}
config += `sample_rate=${root.sampleRate}
sample_bits=${root.sampleBits}
channels=${root.inputChannels}
autoconnect=${root.autoconnect}
active=${root.active}
remix=${root.remix}
virtual=${root.virtual}
[output]
channels=${root.outputChannels}
mono_option=${root.monoOption}
reverse=${root.reverse}
method=raw
raw_target=/dev/stdout
data_format=ascii
ascii_max_range=${asciiMaxRange}
[smoothing]
noise_reduction=${root.noiseReduction}
monstercat=${root.monstercat}
waves=${root.waves}
`;
if (root.eqEnabled) {
config += "[eq]\n";
for (let i = 0; i < eq.length; i++) {
config += `${i + 1}=${eq[i]}`;
if (i < eq.length - 1) {
config += '\n';
}
}
}
return config;
}
function restart() {
process.restart();
}
function start() {
process.start();
}
function stop() {
process.stop();
}
onCavaConfigChanged: {
if (barCount > 0 && cavaConfig != "") {
process.command = `exec cava -p /dev/stdin <<-EOF
${cavaConfig}
EOF
`;
}
}
ProcessMonitor {
id: process
onStdoutChanged: {
let output = process.stdout.trim();
if (output.endsWith(';')) {
output = output.slice(0, -1);
}
root.values = output.split(";").map(v => parseInt(v, 10));
if (!root.idleCheck) {
return;
}
if (root.values.find(v => v > 0)) {
if (idleTimer.running) {
idleTimer.stop();
}
root.idle = false;
} else {
idleTimer.restart();
}
}
}
Timer {
id: idleTimer
interval: root.idleTimer * 1000
onTriggered: root.idle = true
}
}
@@ -0,0 +1,198 @@
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasmoid
import "./components"
import "code/enum.js" as Enum
import "code/globals.js" as Globals
import "code/utils.js" as Utils
Item {
id: root
Layout.preferredWidth: Plasmoid.configuration.expanding || main.onDesktop ? -1 : content.implicitWidth
Layout.preferredHeight: Plasmoid.configuration.expanding || main.onDesktop ? -1 : content.implicitHeight
Layout.minimumWidth: Layout.preferredWidth
Layout.minimumHeight: Layout.preferredHeight
Layout.fillHeight: main.horizontal || Plasmoid.configuration.expanding || main.onDesktop
Layout.fillWidth: !main.horizontal || Plasmoid.configuration.expanding || main.onDesktop
property int framerate: Plasmoid.configuration.framerate
property int barGap: Plasmoid.configuration.barGap
property int barWidth: Plasmoid.configuration.barWidth
property int blockHeight: Plasmoid.configuration.blockHeight
property int blockSpacing: Plasmoid.configuration.blockSpacing
property int noiseReduction: Plasmoid.configuration.noiseReduction
property int monstercat: Plasmoid.configuration.monstercat
property int waves: Plasmoid.configuration.waves
property bool centeredBars: Plasmoid.configuration.centeredBars
property bool roundedBars: Plasmoid.configuration.roundedBars
property int visualizerStyle: Plasmoid.configuration.visualizerStyle
property bool circleMode: Plasmoid.configuration.circleMode
property real circleModeSize: Plasmoid.configuration.circleModeSize
property bool fillWave: Plasmoid.configuration.fillWave
property int orientation: Plasmoid.configuration.orientation
property bool disableLeftClick: Plasmoid.configuration.disableLeftClick
clip: !Plasmoid.configuration.debugMode
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
property var barColorsCfg: {
let barColors;
try {
barColors = JSON.parse(Plasmoid.configuration.barColors);
} catch (e) {
logger.error(e, e.stack);
globalSettings = Globals.baseBarColors;
}
const config = Utils.mergeConfigs(Globals.baseBarColors, barColors);
const configStr = JSON.stringify(config);
if (Plasmoid.configuration.barColors !== configStr) {
Plasmoid.configuration.barColors = configStr;
Plasmoid.configuration.writeConfig();
}
return config;
}
property var waveFillColorsCfg: {
let waveFillColors;
try {
waveFillColors = JSON.parse(Plasmoid.configuration.waveFillColors);
} catch (e) {
logger.error(e, e.stack);
globalSettings = Globals.baseWaveFillColors;
}
const config = Utils.mergeConfigs(Globals.baseWaveFillColors, waveFillColors);
const configStr = JSON.stringify(config);
if (Plasmoid.configuration.waveFillColors !== configStr) {
Plasmoid.configuration.waveFillColors = configStr;
Plasmoid.configuration.writeConfig();
}
return config;
}
property var inactiveBlockColorsCfg: {
let inactiveBlockColors;
try {
inactiveBlockColors = JSON.parse(Plasmoid.configuration.inactiveBlockColors);
} catch (e) {
logger.error(e, e.stack);
globalSettings = Globals.baseInactiveBlockColors;
}
const config = Utils.mergeConfigs(Globals.baseInactiveBlockColors, inactiveBlockColors);
const configStr = JSON.stringify(config);
if (Plasmoid.configuration.inactiveBlockColors !== configStr) {
Plasmoid.configuration.inactiveBlockColors = configStr;
Plasmoid.configuration.writeConfig();
}
return config;
}
property bool drawInactiveBlocks: Plasmoid.configuration.drawInactiveBlocks
RowLayout {
id: content
height: [Enum.Orientation.Left, Enum.Orientation.Right].includes(root.orientation) ? parent.width : parent.height
width: [Enum.Orientation.Left, Enum.Orientation.Right].includes(root.orientation) ? parent.height : parent.width
anchors.centerIn: parent
spacing: 0
Visualizer {
id: visualizer
visualizerStyle: root.visualizerStyle
circleMode: root.circleMode
circleModeSize: root.circleModeSize
barWidth: root.barWidth
blockHeight: root.blockHeight
blockSpacing: root.blockSpacing
barGap: root.barGap
centeredBars: root.centeredBars
roundedBars: root.roundedBars
fillWave: root.fillWave
barColorsCfg: root.barColorsCfg
waveFillColorsCfg: root.waveFillColorsCfg
inactiveBlockColorsCfg: root.inactiveBlockColorsCfg
drawInactiveBlocks: root.drawInactiveBlocks
values: cava.values
debugMode: Plasmoid.configuration.debugMode
visible: !cava.hasError && !cava.idle
fixVertical: !main.horizontal
Layout.preferredWidth: (main.horizontal && !Plasmoid.configuration.expanding) ? Plasmoid.configuration.length : -1
Layout.preferredHeight: !(main.horizontal && !Plasmoid.configuration.expanding) ? Plasmoid.configuration.length : -1
Layout.fillHeight: main.horizontal || Plasmoid.configuration.expanding || [Enum.Orientation.Left, Enum.Orientation.Right].includes(root.orientation) || main.onDesktop
Layout.fillWidth: !main.horizontal || Plasmoid.configuration.expanding || [Enum.Orientation.Left, Enum.Orientation.Right].includes(root.orientation) || main.onDesktop
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
property var orientationBottom: Rotation {
origin.x: Math.floor(visualizer.width / 2)
origin.y: Math.floor(visualizer.height / 2)
axis {
x: 1
y: 0
z: 0
}
angle: 0
}
property var orientationTop: Rotation {
origin.x: Math.floor(visualizer.width / 2)
origin.y: Math.floor(visualizer.height / 2)
axis {
x: 1
y: 0
z: 0
}
angle: 180
}
property var orientationLeft: Rotation {
origin.x: Math.floor(visualizer.width / 2)
origin.y: Math.floor(visualizer.height / 2)
angle: 90
}
property var orientationRight: Rotation {
origin.x: Math.floor(visualizer.width / 2)
origin.y: Math.floor(visualizer.height / 2)
angle: -90
}
transform: {
let t = [];
if (main.orientation === Enum.Orientation.Top) {
t.push(orientationTop);
}
if (main.orientation === Enum.Orientation.Left) {
t.push(orientationLeft);
}
if (main.orientation === Enum.Orientation.Right) {
t.push(orientationRight);
t.push(orientationTop);
}
return t;
}
}
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.preferredWidth: Kirigami.Units.iconSizes.roundedIconSize(Math.min(main.height, main.width))
Layout.fillHeight: Layout.preferredWidth
source: Qt.resolvedUrl("./icons/error.svg").toString().replace("file://", "")
active: mouseArea.containsMouse
isMask: true
color: Kirigami.Theme.negativeTextColor
visible: cava.hasError
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
main.expanded = !main.expanded;
}
enabled: !root.disableLeftClick || cava.hasError || main.expanded
}
PlasmaCore.ToolTipArea {
anchors.fill: parent
mainItem: Tooltip {}
active: !Plasmoid.configuration.hideToolTip
}
}
@@ -0,0 +1,126 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.extras as PlasmaExtras
import org.kde.plasma.plasmoid
import "./components"
ColumnLayout {
id: root
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
Layout.maximumWidth: Kirigami.Units.gridUnit * 25
Layout.minimumHeight: Kirigami.Units.gridUnit * 25
Layout.maximumHeight: Kirigami.Units.gridUnit * 25
property string cavaVersion: ""
ColumnLayout {
id: content
PlasmaExtras.Heading {
Layout.fillWidth: true
text: Plasmoid.metaData.name
wrapMode: Text.Wrap
horizontalAlignment: TextEdit.AlignHCenter
}
PlasmaComponents.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
Layout.rightMargin: Kirigami.Units.gridUnit
Layout.fillWidth: true
text: i18n("Oh no! Something went wrong")
wrapMode: Text.Wrap
horizontalAlignment: TextEdit.AlignHCenter
visible: cava.hasError
font.bold: true
color: Kirigami.Theme.negativeTextColor
}
PlasmaComponents.Button {
text: cava.running ? i18n("Stop CAVA") : i18n("Start CAVA")
onClicked: {
if (cava.running) {
cava.stop();
} else {
cava.start();
}
}
Layout.alignment: Qt.AlignHCenter
}
PlasmaComponents.ToolButton {
id: copyButton
display: PlasmaComponents.AbstractButton.TextBesideIcon
text: i18n("Copy to clipboard")
icon.name: "edit-copy"
onClicked: {
textArea.selectAll();
textArea.copy();
textArea.deselect();
}
Layout.alignment: Qt.AlignHCenter
}
PlasmaComponents.ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
TextArea {
id: textArea
text: {
let msg = "";
if (cava.error) {
msg += `Error: ${cava.error}\n`;
}
if (!cava.running && cava.loadingFailed) {
msg += `Error: ${cava.loadingErrors.join('\n')}\n`;
}
if (cava.running) {
msg += `CAVA is running\n`;
} else {
msg += ` CAVA is not running\n`;
}
msg += `Widget version: ${Plasmoid.metaData.version}\n`;
if (root.cavaVersion) {
msg += `CAVA version: ${root.cavaVersion}\n`;
} else {
msg += ` CAVA not found\n`;
}
msg += `Using ProcessMonitorFallback: ${cava.usingFallback}\n`;
msg += `Widget install location: ${Qt.resolvedUrl("../../").toString().substring(7)}\n`;
msg += `\nCava command:\n${cava.cavaCommand}\n`;
return msg;
}
// HACK: silence binding loop warnings.
// contentWidth seems to be causing the binding loop,
// but contentWidth is read-only and we have no control
// over how it is calculated.
implicitWidth: 0
Layout.fillWidth: true
Layout.fillHeight: true
wrapMode: Text.Wrap
readOnly: true
selectByMouse: true
}
}
}
RunCommand {
id: cavaVersion
onExited: (cmd, exitCode, exitStatus, stdout, stderr) => {
if (exitCode !== 0) {
root.cavaVersion = "";
return;
}
root.cavaVersion = stdout.trim().split(" ").pop();
}
}
Component.onCompleted: cavaVersion.run("cava -v")
Timer {
interval: 1000
onTriggered: {
if (root.visible) {
cavaVersion.run("cava -v");
}
}
running: root.visible
repeat: true
triggeredOnStart: true
}
}
@@ -0,0 +1,84 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property int logLevel: LoggingCategory.Info
readonly property string name: "luisbocanegra.audio.visualizer"
readonly property LoggingCategory loggingCategory: {
let cat;
switch (logLevel) {
case LoggingCategory.Info:
cat = infoCategory;
break;
case LoggingCategory.Warning:
cat = warningCategory;
break;
case LoggingCategory.Critical:
cat = errorCategory;
break;
default:
cat = debugCategory;
}
return cat;
}
readonly property LoggingCategory debugCategory: LoggingCategory {
name: root.name
defaultLogLevel: LoggingCategory.Debug
}
readonly property LoggingCategory infoCategory: LoggingCategory {
name: root.name
defaultLogLevel: LoggingCategory.Info
}
readonly property LoggingCategory warningCategory: LoggingCategory {
name: root.name
defaultLogLevel: LoggingCategory.Warning
}
readonly property LoggingCategory errorCategory: LoggingCategory {
name: root.name
defaultLogLevel: LoggingCategory.Critical
}
function _fmt(arg) {
if (arg === null)
return "null";
if (arg === undefined)
return "undefined";
if (typeof arg === "object") {
try {
return JSON.stringify(arg);
} catch (e) {
return String(arg);
}
}
return String(arg);
}
function _joinMessage(arr) {
return Array.prototype.map.call(arr, _fmt).join(" ");
}
function debug(...message) {
console.debug(loggingCategory, `[DEBUG]: ${_joinMessage(message)}`);
}
function info(...message) {
console.info(loggingCategory, `[INFO]: ${_joinMessage(message)}`);
}
function warn(...message) {
console.warn(loggingCategory, `[WARNING]: ${_joinMessage(message)}`);
}
function error(...message) {
console.error(loggingCategory, `[ERROR]: ${_joinMessage(message)}`);
}
function create(logLevel) {
if (logLevel != undefined && logLevel !== null) {
root.logLevel = logLevel;
}
return this;
}
}
@@ -0,0 +1,139 @@
import QtQuick
import org.kde.taskmanager as TaskManager
Item {
id: root
property var screenGeometry
property bool activeExists: false
property bool maximizedExists: false
property bool fullScreenExists: false
property bool visibleExists: false
property var abstractTasksModel: TaskManager.AbstractTasksModel
property var isMaximized: abstractTasksModel.IsMaximized
property var isActive: abstractTasksModel.IsActive
property var isWindow: abstractTasksModel.IsWindow
property var isFullScreen: abstractTasksModel.IsFullScreen
property var isMinimized: abstractTasksModel.IsMinimized
property bool filterByActive: false
property bool filterByScreen: true
property bool trackLastActive: false
function getTopTask() {
let highestTask = null;
let maxStackingOrder = 0;
for (var i = 0; i < tasksModel.count; i++) {
const currentTask = tasksModel.index(i, 0);
if (currentTask === undefined || !tasksModel.data(currentTask, isWindow))
continue;
const stakingOder = tasksModel.data(currentTask, abstractTasksModel.StackingOrder);
if (stakingOder > maxStackingOrder) {
maxStackingOrder = stakingOder;
highestTask = currentTask;
}
}
return highestTask;
}
function updateWindowsInfo() {
let activeCount = 0;
let visibleCount = 0;
let maximizedCount = 0;
let fullScreenCount = 0;
for (var i = 0; i < tasksModel.count; i++) {
const currentTask = tasksModel.index(i, 0);
if (currentTask === undefined || !tasksModel.data(currentTask, isWindow))
continue;
if (filterByActive && !tasksModel.data(currentTask, isActive))
continue;
visibleCount += 1;
if (tasksModel.data(currentTask, isMaximized))
maximizedCount += 1;
if (tasksModel.data(currentTask, isFullScreen))
fullScreenCount += 1;
}
let _activeExists = tasksModel.data(tasksModel.activeTask, isActive) || false;
let _activeTask = null;
let _maximizedExists = maximizedCount > 0;
let _fullScreenExists = fullScreenCount > 0;
let _visibleExists = visibleCount > 0;
if (filterByActive && _activeExists) {
_activeTask = tasksModel.activeTask;
}
if (filterByActive && !_activeTask && trackLastActive) {
_activeTask = getTopTask();
_activeExists = Boolean(_activeTask);
}
if (_activeTask) {
_maximizedExists = tasksModel.data(_activeTask, isMaximized) || false;
_fullScreenExists = tasksModel.data(_activeTask, isFullScreen) || false;
_visibleExists = _activeExists;
}
activeExists = _activeExists;
fullScreenExists = _fullScreenExists;
maximizedExists = _maximizedExists;
visibleExists = _visibleExists;
}
Connections {
function onValueChanged() {
if (!updateTimer.running)
updateTimer.start();
}
target: plasmoid.configuration
}
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
TaskManager.ActivityInfo {
id: activityInfo
readonly property string nullUuid: "00000000-0000-0000-0000-000000000000"
}
TaskManager.TasksModel {
id: tasksModel
sortMode: TaskManager.TasksModel.SortVirtualDesktop
groupMode: TaskManager.TasksModel.GroupDisabled
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
screenGeometry: root.screenGeometry
filterByVirtualDesktop: true
filterByScreen: root.filterByScreen
filterByActivity: true
filterMinimized: true
onDataChanged: {
Qt.callLater(() => {
if (!updateTimer.running)
updateTimer.start();
});
}
onCountChanged: {
Qt.callLater(() => {
if (!updateTimer.running)
updateTimer.start();
});
}
}
Timer {
id: updateTimer
interval: 5
onTriggered: {
root.updateWindowsInfo();
}
}
}
@@ -0,0 +1,356 @@
function bars(ctx, canvas, circleMode = false) {
if (circleMode) {
barsCircle(ctx, canvas);
} else {
barsRect(ctx, canvas);
}
}
function wave(ctx, canvas, circleMode = false) {
if (circleMode) {
waveCircle(ctx, canvas);
} else {
waveRect(ctx, canvas);
}
}
function blocks(ctx, canvas, circleMode = false) {
if (circleMode) {
blocksCircle(ctx, canvas);
} else {
blocksRect(ctx, canvas);
}
}
/**
* Bars
* @param {Context2D} ctx QML Type (canvas.getContext('2d'))
* @param {Canvas} canvas QML Type
*/
function barsRect(ctx, canvas) {
const canvasHeight = canvas.height;
const maxValue = canvasHeight;
const barCount = canvas.barCount;
const roundedBars = canvas.roundedBars;
const barWidth = canvas.barWidth;
const centeredBars = canvas.centeredBars;
const values = canvas.values;
const radiusOffset = canvas.radiusOffset;
const spacing = canvas.spacing;
ctx.lineCap = roundedBars ? "round" : "butt";
ctx.lineWidth = barWidth;
let x = barWidth / 2;
const centerY = canvasHeight / 2;
for (let i = 0; i < barCount; i++) {
const value = Math.max(1, Math.min(maxValue, values[i]));
let barHeight;
let yBottom;
let yTop;
if (centeredBars) {
if (roundedBars) {
barHeight = (value / maxValue) * ((canvasHeight - barWidth) / 2);
} else {
barHeight = (value / maxValue) * (canvasHeight / 2);
}
yBottom = centerY - barHeight;
yTop = yBottom + (barHeight * 2);
} else {
if (roundedBars) {
barHeight = (value / maxValue) * (canvasHeight - barWidth);
yBottom = canvasHeight - radiusOffset;
} else {
barHeight = (value / maxValue) * canvasHeight;
yBottom = canvasHeight;
}
yTop = yBottom - barHeight;
}
ctx.beginPath();
ctx.moveTo(x, yBottom);
ctx.lineTo(x, yTop);
ctx.stroke();
x += barWidth + spacing;
}
}
/**
* Wave
* @param {Context2D} ctx QML Type (canvas.getContext('2d'))
* @param {Canvas} canvas QML Type
*/
function waveRect(ctx, canvas) {
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const maxValue = canvasHeight;
const barCount = canvas.barCount;
const roundedBars = canvas.roundedBars;
const barWidth = canvas.barWidth;
const centeredBars = canvas.centeredBars;
const fillWave = canvas.fillWave;
const waveFillGradient = canvas.waveFillGradient;
const values = canvas.values;
if (barCount < 2)
return;
ctx.lineCap = roundedBars ? "round" : "butt";
ctx.lineWidth = barWidth;
const step = canvasWidth / (barCount - 1);
const yBottom = centeredBars ? (canvasHeight / 2) : (canvasHeight - barWidth / 2);
canvas.gradientHeight = yBottom;
ctx.beginPath();
let prevX = 0;
let prevY = yBottom - Math.max(0, Math.min(maxValue, values[0])) / maxValue * yBottom;
ctx.lineTo(prevX - 0.5, prevY);
for (let i = 1; i < barCount; i++) {
const norm = Math.max(0, Math.min(maxValue, values[i])) / maxValue;
const x = i * step;
const y = yBottom - norm * yBottom;
const midX = (prevX + x) / 2;
const midY = (prevY + y) / 2;
ctx.quadraticCurveTo(prevX, prevY, midX, midY);
prevX = x;
prevY = y;
}
ctx.lineTo(canvasWidth + 0.5, prevY);
ctx.stroke();
if (fillWave && waveFillGradient) {
const yBottom = centeredBars ? (canvasHeight / 2 + barWidth / 2) : canvasHeight;
ctx.beginPath();
ctx.moveTo(0, yBottom);
prevX = 0;
prevY = yBottom - Math.max(0, Math.min(maxValue, values[0])) / maxValue * yBottom;
ctx.lineTo(prevX, prevY);
for (let i = 1; i < barCount; i++) {
const norm = Math.max(0, Math.min(maxValue, values[i])) / maxValue;
const x = i * step;
const y = yBottom - norm * yBottom;
const midX = (prevX + x) / 2;
const midY = (prevY + y) / 2;
ctx.quadraticCurveTo(prevX, prevY, midX, midY);
prevX = x;
prevY = y;
}
ctx.lineTo(canvasWidth, prevY);
ctx.lineTo(canvasWidth, yBottom);
ctx.closePath();
ctx.fillStyle = waveFillGradient;
ctx.fill();
}
}
/**
* Bars in circle mode
* @param {Context2D} ctx QML Type (canvas.getContext('2d'))
* @param {Canvas} canvas QML Type
*/
function barsCircle(ctx, canvas) {
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const maxValue = Math.min(canvasWidth, canvasHeight) / 2;
const barCount = canvas.barCount;
const roundedBars = canvas.roundedBars;
const barWidth = canvas.barWidth;
const values = canvas.values;
const barRadiusOffset = canvas.radiusOffset * 2;
const circleSize = canvas.circleModeSize;
ctx.lineCap = roundedBars ? "round" : "butt";
ctx.lineWidth = barWidth;
const centerX = canvasWidth / 2;
const centerY = canvasHeight / 2;
const angleStep = (2 * Math.PI) / barCount;
const innerRadius = (Math.min(canvasWidth, canvasHeight) / 2) * circleSize - barRadiusOffset;
for (let i = 0; i < barCount; i++) {
const value = Math.max(1, Math.min(maxValue, values[i]));
const norm = value / maxValue;
const barLength = norm * (maxValue - barWidth / 2) * (1 - circleSize);
const angle = i * angleStep - Math.PI / 2;
const xStart = centerX + Math.cos(angle) * innerRadius;
const yStart = centerY + Math.sin(angle) * innerRadius;
const xEnd = centerX + Math.cos(angle) * (innerRadius + barLength);
const yEnd = centerY + Math.sin(angle) * (innerRadius + barLength);
ctx.beginPath();
ctx.moveTo(xStart, yStart);
ctx.lineTo(xEnd, yEnd);
ctx.stroke();
}
}
/**
* Wave in circle mode
* @param {Context2D} ctx QML Type (canvas.getContext('2d'))
* @param {Canvas} canvas QML Type
*/
function waveCircle(ctx, canvas) {
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const maxValue = Math.min(canvasWidth, canvasHeight) / 2;
const barCount = canvas.barCount;
const circleSize = canvas.circleModeSize;
const innerRadius = (Math.min(canvasWidth, canvasHeight) / 2) * circleSize;
const barWidth = canvas.barWidth;
const fillWave = canvas.fillWave;
const waveFillGradient = canvas.waveFillGradient;
const values = canvas.values;
if (barCount < 2) {
return;
}
ctx.lineWidth = barWidth;
const centerX = canvasWidth / 2;
const centerY = canvasHeight / 2;
const angleStep = (2 * Math.PI) / barCount;
canvas.gradientHeight = maxValue - innerRadius;
const outerPoints = new Array(barCount + 1);
for (let i = 0; i <= barCount; i++) {
const idx = i % barCount;
const val = Math.max(0, Math.min(maxValue, values[idx]));
const radial = val * (1 - circleSize);
const angle = idx * angleStep - Math.PI / 2;
outerPoints[i] = {
x: centerX + Math.cos(angle) * (innerRadius + radial),
y: centerY + Math.sin(angle) * (innerRadius + radial)
};
}
// wave line
ctx.beginPath();
ctx.moveTo(outerPoints[0].x, outerPoints[0].y);
let prev = outerPoints[0];
for (let i = 1; i < outerPoints.length; i++) {
const cur = outerPoints[i];
const midX = (prev.x + cur.x) / 2;
const midY = (prev.y + cur.y) / 2;
ctx.quadraticCurveTo(prev.x, prev.y, midX, midY);
prev = cur;
}
// back to start
ctx.quadraticCurveTo(prev.x, prev.y, (prev.x + outerPoints[0].x) / 2, (prev.y + outerPoints[0].y) / 2);
ctx.stroke();
if (fillWave && waveFillGradient) {
ctx.beginPath();
ctx.moveTo(outerPoints[0].x, outerPoints[0].y);
prev = outerPoints[0];
for (let i = 1; i < outerPoints.length; i++) {
const cur = outerPoints[i];
const midX = (prev.x + cur.x) / 2;
const midY = (prev.y + cur.y) / 2;
ctx.quadraticCurveTo(prev.x, prev.y, midX, midY);
prev = cur;
}
ctx.quadraticCurveTo(prev.x, prev.y, (prev.x + outerPoints[0].x) / 2, (prev.y + outerPoints[0].y) / 2);
const startAngle = -Math.PI / 2;
const innerStartX = centerX + Math.cos(startAngle) * innerRadius;
const innerStartY = centerY + Math.sin(startAngle) * innerRadius;
ctx.lineTo(innerStartX, innerStartY);
ctx.arc(centerX, centerY, innerRadius, startAngle, startAngle + 2 * Math.PI, false);
ctx.closePath();
ctx.fillStyle = waveFillGradient;
ctx.fill();
}
}
/**
* Blocks
* @param {Context2D} ctx QML Type (canvas.getContext('2d'))
* @param {Canvas} canvas QML Type
*/
function blocksRect(ctx, canvas) {
const canvasHeight = canvas.height;
const barCount = canvas.barCount;
const blockWidth = canvas.barWidth;
const blockHeight = canvas.blockHeight;
const blockSpacing = canvas.blockSpacing;
const totalRows = Math.floor((canvasHeight + blockSpacing) / (blockHeight + blockSpacing));
const columnSpacing = canvas.spacing;
const values = canvas.values || [];
for (let col = 0; col < barCount; col++) {
let value = values[col];
const activeRows = Math.floor((value / canvasHeight) * totalRows);
for (let row = 0; row < totalRows; row++) {
const x = col * (blockWidth + columnSpacing);
const y = canvasHeight - (row + 1) * blockHeight - row * blockSpacing;
if (row < activeRows) {
ctx.fillStyle = canvas.gradient;
ctx.fillRect(x, y, blockWidth, blockHeight);
}
else if (canvas.drawInactiveBlocks) {
ctx.fillStyle = canvas.inactiveBlockGradient;
ctx.fillRect(x, y, blockWidth, blockHeight);
}
}
}
}
function blocksCircle(ctx, canvas) {
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const barCount = canvas.barCount;
const blockWidth = canvas.barWidth;
const circleSize = canvas.circleModeSize;
const innerRadius = (Math.min(canvasWidth, canvasHeight) / 2) * circleSize;
const blockHeight = canvas.blockHeight;
const blockSpacing = canvas.blockSpacing;
const totalRows = Math.floor(((Math.min(canvasWidth, canvasHeight) / 2) - innerRadius + blockSpacing) / (blockHeight + blockSpacing));
const angleStep = (2 * Math.PI) / barCount;
const values = canvas.values || [];
const centerX = canvasWidth / 2;
const centerY = canvasHeight / 2;
for (let col = 0; col < barCount; col++) {
let value = values[col];
const activeRows = Math.floor((value / (Math.min(canvasWidth, canvasHeight) / 2)) * totalRows);
for (let row = 0; row < totalRows; row++) {
const radius = innerRadius + row * (blockHeight + blockSpacing) + blockHeight / 2;
const angle = col * angleStep - Math.PI / 2;
const x = centerX + Math.cos(angle) * radius - (blockHeight / 2);
const y = centerY + Math.sin(angle) * radius - (blockHeight / 2);
ctx.save();
ctx.translate(x + blockHeight / 2, y + blockHeight / 2);
ctx.rotate(angle + Math.PI / 2);
ctx.translate(-(x + blockHeight / 2), -(y + blockHeight / 2));
if (row < activeRows) {
ctx.fillStyle = canvas.gradient;
ctx.fillRect(x, y, blockWidth, blockHeight);
}
else if (canvas.drawInactiveBlocks) {
ctx.fillStyle = canvas.inactiveBlockGradient;
ctx.fillRect(x, y, blockWidth, blockHeight);
}
ctx.restore();
}
}
}
@@ -0,0 +1,21 @@
const VisualizerStyles = {
Bars: 0,
Wave: 1,
Blocks: 2,
};
const Orientation = {
Top: 0,
Bottom: 1,
Left: 2,
Right: 3,
};
const ColorSourceType = {
Custom: 0,
SystemTheme: 1,
List: 2,
Random: 3,
// 4-6 unused
Hue: 7,
};
@@ -0,0 +1,81 @@
const baseColorList = [
"#ED8796",
"#A6DA95",
"#EED49F",
"#8AADF4",
"#F5BDE6",
"#8BD5CA",
"#f5a97f",
];
const baseBarColors = {
enabled: false,
lightness: 0.5,
saturation: 0.5,
alpha: 1,
systemColor: "highlightColor",
systemColorSet: "Window",
custom: "#013eff",
list: baseColorList,
reverseList: false,
followColor: 0,
saturationEnabled: false,
lightnessEnabled: false,
sourceType: 7,
smoothGradient: true,
colorsOrientation: 0,
image: {
source: "",
fillMode: 2
},
hueStart: 0,
hueEnd: 360,
};
const baseWaveFillColors = {
enabled: false,
lightness: 0.5,
saturation: 0.5,
alpha: 0.3,
systemColor: "highlightColor",
systemColorSet: "Window",
custom: "#013eff",
list: baseColorList,
reverseList: false,
followColor: 0,
saturationEnabled: false,
lightnessEnabled: false,
sourceType: 7,
smoothGradient: true,
colorsOrientation: 0,
image: {
source: "",
fillMode: 2
},
hueStart: 0,
hueEnd: 360,
};
const baseInactiveBlockColors = {
enabled: false,
lightness: 0.5,
saturation: 0.5,
alpha: 0.3,
systemColor: "textColor",
systemColorSet: "Window",
custom: "#013eff",
list: baseColorList,
reverseList: false,
followColor: 0,
saturationEnabled: false,
lightnessEnabled: false,
sourceType: 1,
smoothGradient: true,
colorsOrientation: 0,
image: {
source: "",
fillMode: 2
},
hueStart: 0,
hueEnd: 360,
};
@@ -0,0 +1,161 @@
function mergeConfigs(sourceConfig, newConfig) {
for (var key in sourceConfig) {
if (Array.isArray(sourceConfig[key])) {
if (!newConfig.hasOwnProperty(key)) {
newConfig[key] = sourceConfig[key].slice();
}
} else if (typeof sourceConfig[key] === "object" && sourceConfig[key] !== null) {
if (!newConfig.hasOwnProperty(key)) {
newConfig[key] = {};
}
mergeConfigs(sourceConfig[key], newConfig[key]);
} else {
if (!newConfig.hasOwnProperty(key)) {
newConfig[key] = sourceConfig[key];
}
}
}
return newConfig;
}
function getRandomColor(h, s, l, a) {
h = h ?? Math.random();
s = s ?? Math.random();
l = l ?? Math.random();
a = a ?? 1.0;
return Qt.hsla(h, s, l, a);
}
function scaleSaturation(color, saturation) {
return Qt.hsla(color.hslHue, saturation, color.hslLightness, color.a);
}
function scaleLightness(color, lightness) {
return Qt.hsla(color.hslHue, color.hslSaturation, lightness, color.a);
}
function adjustColor(color, saturationEnabled, saturation, lightnessEnabled, lightness, alpha) {
if (saturationEnabled) {
color = scaleSaturation(color, saturation);
}
if (lightnessEnabled) {
color = scaleLightness(color, lightness);
}
if (alpha !== 1.0) {
color = Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha);
}
return color;
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
function rgbToQtColor(rgb) {
return Qt.rgba(rgb.r / 255, rgb.g / 255, rgb.b / 255, 1);
}
function hexToQtColor(hex) {
let rgb = hexToRgb(hex);
return rgbToQtColor(rgb);
}
function buildCanvasGradient(ctx, smooth, gradientStops, orientation, height, width, circleMode = false) {
let gradient;
if (circleMode) {
const centerX = width / 2;
const centerY = height / 2;
const radius = Math.min(width, height) / 2;
gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
} else if (orientation === 0) {
gradient = ctx.createLinearGradient(0, 0, width, 0);
} else {
gradient = ctx.createLinearGradient(0, height, 0, 0);
}
for (let i = 0; i < gradientStops.length; i++) {
const stop = gradientStops[i];
let color = stop.color ?? stop;
let position = stop.position ?? (1 / gradientStops.length) * i;
gradient.addColorStop(position, color);
if (!smooth && i > 0 && i < gradientStops.length) {
let prevStop = gradientStops[i - 1];
let prevColor = prevStop.color ?? prevStop;
gradient.addColorStop(Math.max(position - 0.0001, 0), prevColor);
}
}
return gradient;
}
function getColors(barColorsCfg, barCount, themeColor) {
const saturationEnabled = barColorsCfg.saturationEnabled;
const saturation = barColorsCfg.saturation;
const lightnessEnabled = barColorsCfg.lightnessEnabled;
const lightness = barColorsCfg.lightness;
const alpha = barColorsCfg.alpha;
const colorSourceType = barColorsCfg.sourceType;
const reverseList = barColorsCfg.reverseList;
const hueStart = barColorsCfg.hueStart;
const hueEnd = barColorsCfg.hueEnd;
let colors = [];
let color = null;
if (colorSourceType === Enum.ColorSourceType.Custom) {
color = hexToQtColor(barColorsCfg.custom);
} else if (colorSourceType === Enum.ColorSourceType.SystemTheme) {
color = themeColor;
}
if (color) {
colors.push(adjustColor(color, saturationEnabled, saturation, lightnessEnabled, lightness, alpha));
}
if (colorSourceType === Enum.ColorSourceType.List) {
colors = barColorsCfg.list.map(c => adjustColor(hexToQtColor(c), saturationEnabled, saturation, lightnessEnabled, lightness, alpha));
}
if (colorSourceType === Enum.ColorSourceType.Random) {
for (let i = 0; i < barCount; i++) {
colors.push(adjustColor(getRandomColor(null, 0.8, 0.7, null), saturationEnabled, saturation, lightnessEnabled, lightness, alpha));
}
}
if (colorSourceType === Enum.ColorSourceType.Hue) {
const start = hueStart / 360;
const end = hueEnd / 360;
for (let i = 0; i < barCount; i++) {
let c = Qt.hsla(start + ((i / barCount) * (end - start)), 0.8, 0.7, 1.0);
colors.push(adjustColor(c, saturationEnabled, saturation, lightnessEnabled, lightness, alpha));
}
}
if (reverseList) {
let reversed = [];
for (let i = colors.length - 1; i >= 0; i--) {
reversed.push(colors[i]);
}
colors = reversed;
}
return colors;
}
// https://stackoverflow.com/questions/28507619/how-to-create-delay-function-in-qml
function delay(interval, callback, parentItem) {
let timer = Qt.createQmlObject("import QtQuick; Timer {}", parentItem);
timer.interval = interval;
timer.repeat = false;
timer.triggered.connect(callback);
timer.triggered.connect(function release() {
timer.triggered.disconnect(callback);
timer.triggered.disconnect(release);
timer.destroy();
});
timer.start();
}
function makeEven(n) {
return n - (n % 2);
}
@@ -0,0 +1,74 @@
// This is a modified version of https://invent.kde.org/frameworks/kdeclarative/-/blob/master/src/qmlcontrols/kquickcontrols/ColorButton.qml to make it look more like a button
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
Button {
id: root
/**
* The user selected color
*/
property alias color: colorDialog.selectedColor
/**
* Title to show in the dialog
*/
property alias dialogTitle: colorDialog.title
/**
* Allow the user to configure an alpha value
*/
property bool showAlphaChannel: false
property bool showCurentColor: true
/**
* This signal is emitted when the color dialog has been accepted
*
* @since 5.61
*/
signal accepted(string color)
onClicked: {
colorDialog.open();
}
ColorDialog {
id: colorDialog
onAccepted: root.accepted(color)
parentWindow: root.Window.window
options: root.showAlphaChannel ? ColorDialog.ShowAlphaChannel : undefined
}
contentItem: Item {
implicitWidth: btnL.implicitWidth + 8
implicitHeight: btnL.implicitHeight
RowLayout {
id: btnL
anchors.centerIn: parent
Rectangle {
color: root.color
height: currentColorText.implicitHeight
width: height
radius: 2
border {
width: 1
color: Kirigami.Theme.textColor
}
}
Label {
id: currentColorText
text: root.color.toString().toUpperCase()
visible: root.showCurentColor
textFormat: Text.PlainText
font.family: "Monospace"
}
}
}
}
@@ -0,0 +1,266 @@
// pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
import "../"
// A component to create a list of color pickers
// color (hex) | ColorButton | randomize | position | delete | add new
// ...
// text field with colors | update list above
ColumnLayout {
id: root
property var stopsList: []
signal colorsChanged(stopsList: var)
property bool ready: false
signal removeColor(index: int)
onRemoveColor: index => {
stopsListModel.remove(index);
updateColorsList();
}
ListModel {
id: stopsListModel
}
ListModel {
id: stopsListModelTmp
ListElement {
color: "#ff0000"
position: 0.0
}
}
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
Connections {
target: stopsListModel
function onCountChanged(count, ready) {
if (!ready)
return;
logger.debug("model count changed:", count);
updateColorsList();
}
}
onStopsListChanged: {
initColorsListModel();
}
function initColorsListModel() {
if (stopsListModel.count !== 0) {
return;
}
ready = false;
const stops = stopsList;
for (let stop of stops) {
stopsListModel.append({
"color": stop.color,
"position": stop.position
});
}
ready = true;
}
function getRandomColor() {
const h = Math.random();
const s = Math.random();
const l = Math.random();
const a = 1.0;
logger.debug(h, s, l);
return Qt.hsla(h, s, l, a);
}
function updateColorsList() {
logger.debug("updateColorsList()");
let colors_list = [];
for (let i = 0; i < stopsListModel.count; i++) {
colors_list.push({
"color": stopsListModel.get(i).color,
"position": stopsListModel.get(i).position
});
}
stopsList = colors_list;
colorsChanged(stopsList);
}
Component.onCompleted: {
initColorsListModel();
}
GroupBox {
ColumnLayout {
Layout.alignment: Qt.AlignTop
Repeater {
id: customColorsRepeater
model: stopsListModel
delegate: RowLayout {
TextMetrics {
id: metrics
text: (model.length + 1).toString()
}
Label {
text: (index + 1).toString() + "."
Layout.preferredWidth: metrics.width
}
TextMetrics {
id: colorMetrics
text: "#FFFFFF"
}
TextArea {
text: model.color
font.capitalization: Font.AllUppercase
Kirigami.SpellCheck.enabled: false
Layout.preferredWidth: colorMetrics.width * 1.4
onTextChanged: {
if (text !== model.color) {
stopsListModel.set(index, {
"color": text
});
updateColorsList();
}
}
}
ColorButton {
showAlphaChannel: true
dialogTitle: i18n("Widget background") + "(" + index + ")"
color: model.color
showCurentColor: false
onAccepted: color => {
stopsListModel.set(index, {
"color": color.toString()
});
updateColorsList();
}
}
Button {
icon.name: "randomize-symbolic"
onClicked: {
stopsListModel.set(index, {
"color": getRandomColor().toString()
});
updateColorsList();
}
}
Button {
icon.name: "arrow-up"
enabled: index > 0
onClicked: {
let prevIndex = index - 1;
let prev = stopsListModel.get(prevIndex).color;
stopsListModel.set(prevIndex, stopsListModel.get(index));
stopsListModel.set(index, {
"color": prev
});
updateColorsList();
}
}
Button {
icon.name: "arrow-down"
enabled: index < stopsListModel.count - 1
onClicked: {
let nextIndex = index + 1;
let next = stopsListModel.get(nextIndex).color;
stopsListModel.set(nextIndex, stopsListModel.get(index));
stopsListModel.set(index, {
"color": next
});
updateColorsList();
}
}
DoubleSpinBox {
id: stepPositionSpinbox
value: model.position * multiplier
// Component.onCompleted: value = parseFloat(model.position)
from: 0 * multiplier
to: 1 * multiplier
onValueModified: {
if (value !== model.position / stepPositionSpinbox.multiplier) {
stopsListModel.set(index, {
"position": value / stepPositionSpinbox.multiplier
});
updateColorsList();
}
}
}
Button {
icon.name: "edit-delete-remove"
onClicked: {
root.removeColor(index);
}
}
Button {
icon.name: "list-add-symbolic"
onClicked: {
stopsListModel.insert(index + 1, {
"color": getRandomColor().toString(),
"position": 0.0
});
updateColorsList();
}
}
}
}
RowLayout {
visible: stopsListModel.count === 0
Item {
Layout.fillWidth: true
}
Button {
icon.name: "list-add-symbolic"
onClicked: {
stopsListModel.insert(0, {
"color": getRandomColor().toString(),
"position": 0.0
});
updateColorsList();
}
}
}
RowLayout {
TextArea {
id: customColors
text: stopsList?.map(s => s.color + ":" + s.position).join(" ") || []
Layout.preferredWidth: 300
Layout.fillWidth: true
wrapMode: TextEdit.WordWrap
font.capitalization: Font.AllUppercase
Kirigami.SpellCheck.enabled: false
}
Button {
id: btn
icon.name: "view-refresh-symbolic"
onClicked: {
stopsList = customColors.text.split(" ").map(s => {
logger.debug(s.split(":"));
return {
"color": s.split(":")[0],
"position": parseFloat(s.split(":")[1])
};
});
stopsListModel.clear();
initColorsListModel();
updateColorsList();
}
}
}
}
}
}
@@ -0,0 +1,237 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
import "../"
// A component to create a list of color pickers
// color (hex) | ColorButton | randomize | move up | move down | delete | add new
// ...
// text field with colors | update list above
ColumnLayout {
id: root
property var colorsList: []
signal colorsChanged(newColors: var)
property bool ready: false
signal removeColor(index: int)
onRemoveColor: index => {
colorsListModel.remove(index);
updateColorsList();
}
ListModel {
id: colorsListModel
}
ListModel {
id: colorsListModelTmp
ListElement {
color: "#ff0000"
}
}
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
Connections {
target: colorsListModel
function onCountChanged(count, ready) {
if (!ready)
return;
logger.debug("model count changed:", count);
updateColorsList();
}
}
onColorsListChanged: {
initColorsListModel();
}
function initColorsListModel() {
if (colorsListModel.count !== 0) {
return;
}
ready = false;
const colors = colorsList;
for (let i in colors) {
colorsListModel.append({
"color": colors[i]
});
}
ready = true;
}
function getRandomColor() {
const h = Math.random();
const s = Math.random();
const l = Math.random();
const a = 1.0;
logger.debug(h, s, l);
return Qt.hsla(h, s, l, a);
}
function updateColorsList() {
logger.debug("updateColorsList()");
let colors_list = [];
for (let i = 0; i < colorsListModel.count; i++) {
let c = colorsListModel.get(i).color;
colors_list.push(c);
}
colorsList = colors_list;
colorsChanged(colorsList);
}
Component.onCompleted: {
initColorsListModel();
}
GroupBox {
ColumnLayout {
Layout.alignment: Qt.AlignTop
Repeater {
id: customColorsRepeater
model: colorsListModel
delegate: RowLayout {
TextMetrics {
id: metrics
text: (model.length + 1).toString()
}
Label {
text: (index + 1).toString() + "."
Layout.preferredWidth: metrics.width
}
TextMetrics {
id: colorMetrics
text: "#FFFFFF"
}
TextArea {
text: modelData
font.capitalization: Font.AllUppercase
Kirigami.SpellCheck.enabled: false
Layout.preferredWidth: colorMetrics.width * 1.4
onTextChanged: {
if (text !== modelData) {
colorsListModel.set(index, {
"color": text
});
updateColorsList();
}
}
}
ColorButton {
showAlphaChannel: false
dialogTitle: i18n("Widget background") + "(" + index + ")"
color: modelData
showCurentColor: false
onAccepted: color => {
colorsListModel.set(index, {
"color": color.toString()
});
updateColorsList();
}
}
Button {
icon.name: "randomize-symbolic"
onClicked: {
colorsListModel.set(index, {
"color": getRandomColor().toString()
});
updateColorsList();
}
}
Button {
icon.name: "arrow-up"
enabled: index > 0
onClicked: {
let prevIndex = index - 1;
let prev = colorsListModel.get(prevIndex).color;
colorsListModel.set(prevIndex, colorsListModel.get(index));
colorsListModel.set(index, {
"color": prev
});
updateColorsList();
}
}
Button {
icon.name: "arrow-down"
enabled: index < colorsListModel.count - 1
onClicked: {
let nextIndex = index + 1;
let next = colorsListModel.get(nextIndex).color;
colorsListModel.set(nextIndex, colorsListModel.get(index));
colorsListModel.set(index, {
"color": next
});
updateColorsList();
}
}
Button {
icon.name: "edit-delete-remove"
onClicked: {
root.removeColor(index);
}
}
Button {
icon.name: "list-add-symbolic"
onClicked: {
colorsListModel.insert(index + 1, {
"color": getRandomColor().toString()
});
updateColorsList();
}
}
}
}
RowLayout {
visible: colorsListModel.count === 0
Item {
Layout.fillWidth: true
}
Button {
icon.name: "list-add-symbolic"
onClicked: {
colorsListModel.insert(0, {
"color": getRandomColor().toString()
});
updateColorsList();
}
}
}
RowLayout {
TextArea {
id: customColors
text: colorsList?.join(" ") || []
Layout.preferredWidth: 300
Layout.fillWidth: true
wrapMode: TextEdit.WordWrap
font.capitalization: Font.AllUppercase
Kirigami.SpellCheck.enabled: false
}
Button {
id: btn
icon.name: "view-refresh-symbolic"
onClicked: {
colorsList = customColors.text.split(" ");
colorsListModel.clear();
initColorsListModel();
updateColorsList();
}
}
}
}
}
}
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2024 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls
// Simple double spinbox based on Qt example implementation
SpinBox {
editable: true
wheelEnabled: true
property int decimals: 2
property int multiplier: Math.pow(10, decimals)
validator: DoubleValidator {
bottom: Math.min(from, to)
top: Math.max(from, to)
}
textFromValue: function (value, locale) {
return Number(value / multiplier).toLocaleString(locale, 'f', decimals);
}
valueFromText: function (text, locale) {
return Number.fromLocaleString(locale, text) * multiplier;
}
}
@@ -0,0 +1,133 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
ColumnLayout {
id: root
property list<real> values
property int fromFreq
property int toFreq
readonly property real freqStep: Math.abs(toFreq - fromFreq) / values.length
signal valueChanged(index: int, newValue: real)
signal bandAdded
signal bandRemoved
signal flat
function formatFreq(freq) {
let formatted = "";
if (freq > 1000) {
freq = freq / 1000;
formatted = `${Math.round((freq * 10)) / 10} kHz`;
} else {
formatted = `${freq} Hz`;
}
return formatted;
}
TextMetrics {
id: labelWidth
text: "99.9 kHz"
font.features: {
"tnum": 1
}
font.pointSize: Kirigami.Theme.smallFont.pointSize
}
ScrollView {
id: sv
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width
RowLayout {
// HACK: silence binding loop warnings.
// contentWidth seems to be causing the binding loop,
// but contentWidth is read-only and we have no control
// over how it is calculated.
implicitWidth: 0
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Kirigami.Units.mediumSpacing
Repeater {
model: root.values
ColumnLayout {
id: col
required property int index
required property var modelData
spacing: Kirigami.Units.smallSpacing
Layout.preferredWidth: labelWidth.width
Label {
text: col.index + 1
font.features: {
"tnum": 1
}
font.pointSize: Kirigami.Theme.smallFont.pointSize
opacity: 0.75
Layout.alignment: Qt.AlignHCenter
}
Label {
text: root.formatFreq(Math.round((col.index + 1) * root.freqStep))
font.features: {
"tnum": 1
}
font.pointSize: Kirigami.Theme.smallFont.pointSize
opacity: 0.75
Layout.alignment: Qt.AlignHCenter
}
Slider {
id: slider
orientation: Qt.Vertical
from: 0
to: 5
stepSize: 0.1
onValueChanged: {
root.valueChanged(col.index, value);
}
value: col.modelData
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 250
}
Label {
text: parseFloat(slider.value).toFixed(1)
font.features: {
"tnum": 1
}
font.pointSize: Kirigami.Theme.smallFont.pointSize
opacity: 0.75
Layout.alignment: Qt.AlignHCenter
}
}
}
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Button {
enabled: root.values.length >= 3
icon.name: "edit-clear"
autoRepeat: true
onClicked: {
root.bandRemoved();
}
text: i18n("Remove band")
}
Button {
icon.name: "list-add-symbolic"
text: i18n("Add band")
autoRepeat: true
onClicked: {
root.bandAdded();
}
}
Button {
icon.name: "gnumeric-object-line-symbolic"
onClicked: {
root.flat();
}
text: i18n("Flat response")
}
}
}
@@ -0,0 +1,765 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
import "../"
import "../code/enum.js" as Enum
import "../code/utils.js" as Utils
Kirigami.FormLayout {
id: root
// required to align with parent form
property alias formLayout: root
property bool isSection: true
property string sectionName
// wether read from the string or existing config object
property bool handleString
// internal config objects to be sent, both string and json
property string configString: "{}"
property var config: handleString ? JSON.parse(configString) : undefined
// to hide options that make no sense
property var followOptions: {
"panel": false,
"widget": false,
"tray": false
}
property bool showFollowPanel: followOptions.panel
property bool showFollowWidget: followOptions.widget
property bool showFollowTray: followOptions.tray
property bool showFollowRadio: showFollowPanel || showFollowWidget || showFollowTray
// wether or not show these options
property bool multiColor: true
property bool supportsGradient: false
property bool supportsImage: false
signal updateConfigString(string configString, var config)
function updateConfig() {
updateConfigString(configString, config);
}
twinFormLayouts: parentLayout
Layout.fillWidth: true
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
ListModel {
id: themeColorSetModel
ListElement {
value: "View"
displayName: "View"
}
ListElement {
value: "Window"
displayName: "Window"
}
ListElement {
value: "Button"
displayName: "Button"
}
ListElement {
value: "Selection"
displayName: "Selection"
}
ListElement {
value: "Tooltip"
displayName: "Tooltip"
}
ListElement {
value: "Complementary"
displayName: "Complementary"
}
ListElement {
value: "Header"
displayName: "Header"
}
}
ListModel {
id: themeColorModel
ListElement {
value: "textColor"
displayName: "Text Color"
}
ListElement {
value: "disabledTextColor"
displayName: "Disabled Text Color"
}
ListElement {
value: "highlightedTextColor"
displayName: "Highlighted Text Color"
}
ListElement {
value: "activeTextColor"
displayName: "Active Text Color"
}
ListElement {
value: "linkColor"
displayName: "Link Color"
}
ListElement {
value: "visitedLinkColor"
displayName: "Visited LinkColor"
}
ListElement {
value: "negativeTextColor"
displayName: "Negative Text Color"
}
ListElement {
value: "neutralTextColor"
displayName: "Neutral Text Color"
}
ListElement {
value: "positiveTextColor"
displayName: "Positive Text Color"
}
ListElement {
value: "backgroundColor"
displayName: "Background Color"
}
ListElement {
value: "highlightColor"
displayName: "Highlight Color"
}
ListElement {
value: "activeBackgroundColor"
displayName: "Active Background Color"
}
ListElement {
value: "linkBackgroundColor"
displayName: "Link Background Color"
}
ListElement {
value: "visitedLinkBackgroundColor"
displayName: "Visited Link Background Color"
}
ListElement {
value: "negativeBackgroundColor"
displayName: "Negative Background Color"
}
ListElement {
value: "neutralBackgroundColor"
displayName: "Neutral Background Color"
}
ListElement {
value: "positiveBackgroundColor"
displayName: "Positive Background Color"
}
ListElement {
value: "alternateBackgroundColor"
displayName: "Alternate Background Color"
}
ListElement {
value: "focusColor"
displayName: "Focus Color"
}
ListElement {
value: "hoverColor"
displayName: "Hover Color"
}
}
Item {
Kirigami.FormData.isSection: root.isSection
Kirigami.FormData.label: root.sectionName || i18n("Color")
Layout.fillWidth: true
}
CheckBox {
id: animationCheckbox
Kirigami.FormData.label: i18n("Animation:")
checked: root.config.animation.enabled
onCheckedChanged: {
root.config.animation.enabled = checked;
root.updateConfig();
// ensure valid option is checked as single and accent are
// disabled in animated mode
if (checked && (root.config.sourceType <= 1 || root.config.sourceType >= 4))
listColorRadio.checked = true;
}
visible: false
}
SpinBox {
id: animationInterval
Kirigami.FormData.label: i18n("Interval (ms):")
from: 0
to: 30000
stepSize: 100
value: root.config.animation.interval
onValueModified: {
root.config.animation.interval = value;
root.updateConfig();
}
enabled: animationCheckbox.checked
visible: false
}
SpinBox {
id: animationTransition
Kirigami.FormData.label: i18n("Smoothing (ms):")
from: 0
to: animationInterval.value
stepSize: 100
value: root.config.animation.smoothing
onValueModified: {
root.config.animation.smoothing = value;
root.updateConfig();
}
enabled: animationCheckbox.checked
visible: false
}
RowLayout {
Kirigami.FormData.label: i18n("Color source:")
RadioButton {
id: singleColorRadio
property int index: 0
text: i18n("Custom")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
enabled: !animationCheckbox.checked
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("A fixed custom color")
}
}
RowLayout {
RadioButton {
id: accentColorRadio
property int index: 1
text: i18n("System")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
enabled: !animationCheckbox.checked
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Color that updates with your System theme")
}
}
RowLayout {
visible: root.multiColor
RadioButton {
id: listColorRadio
property int index: 2
text: i18n("Custom list")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Define a list of colors that will be applied to all the elements, wraps around if there are more elements than colors")
}
}
RowLayout {
RadioButton {
id: randomColorRadio
property int index: 3
text: i18n("Random")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Random color for each element")
}
}
RowLayout {
visible: root.showFollowRadio
RadioButton {
id: followColorRadio
property int index: 4
text: i18n("Follow")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
enabled: !animationCheckbox.checked
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Follow the color of a parent element")
}
}
RowLayout {
visible: root.supportsGradient
RadioButton {
id: gradientRadio
property int index: 5
text: i18n("Gradient")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Horizontal or vertical customizable color gradient")
}
}
RowLayout {
visible: root.supportsImage
RadioButton {
id: imageRadio
property int index: 6
text: i18n("Image")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("High resolution images can slow down the desktop when the panel or widget size changes!")
}
}
RowLayout {
RadioButton {
id: hueRadio
property int index: 7
text: i18n("Hue")
ButtonGroup.group: colorModeGroup
checked: root.config.sourceType === index
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Automatically shifted color for each bar")
}
}
ButtonGroup {
id: colorModeGroup
onCheckedButtonChanged: {
if (checkedButton) {
root.config.sourceType = checkedButton.index;
root.updateConfig();
}
}
}
// >
RadioButton {
id: followPanelBgRadio
property int index: 0
Kirigami.FormData.label: i18n("Element:")
text: i18n("Panel background")
ButtonGroup.group: followColorGroup
checked: root.config.followColor === index
visible: followColorRadio.checked && root.showFollowPanel
}
RadioButton {
id: followWidgetBgRadio
property int index: 1
text: i18n("Widget background")
ButtonGroup.group: followColorGroup
checked: root.config.followColor === index
visible: followColorRadio.checked && root.showFollowWidget
}
RadioButton {
id: followTrayWidgetBgRadio
property int index: 2
text: i18n("Tray widget background")
ButtonGroup.group: followColorGroup
checked: root.config.followColor === index
visible: followColorRadio.checked && root.showFollowTray
}
ButtonGroup {
id: followColorGroup
onCheckedButtonChanged: {
if (checkedButton) {
root.config.followColor = checkedButton.index;
root.updateConfig();
}
}
}
ComboBox {
id: imageFillMode
Kirigami.FormData.label: i18n("Positioning:")
visible: root.supportsImage && imageRadio.checked
model: [
{
'label': i18n("Stretch"),
'fillMode': AnimatedImage.Stretch
},
{
'label': i18n("Tile"),
'fillMode': AnimatedImage.Tile
},
{
'label': i18n("Scaled and Cropped"),
'fillMode': AnimatedImage.PreserveAspectCrop
}
]
textRole: "label"
valueRole: "fillMode"
onActivated: {
root.config.image.fillMode = currentValue;
root.updateConfig();
}
currentIndex: indexOfValue(root.config.image.fillMode)
}
RowLayout {
visible: root.supportsImage && imageRadio.checked
Kirigami.FormData.label: i18n("Image:")
TextField {
id: imgTextArea
Layout.preferredWidth: 300
text: root.config.image.source
onTextChanged: {
if (root.config.image.source !== text) {
root.config.image.source = text;
root.updateConfig();
}
}
}
Button {
icon.name: "insert-image-symbolic"
onClicked: fileDialog.open()
}
}
FileDialog {
id: fileDialog
fileMode: FileDialog.OpenFile
title: i18n("Pick a image file")
nameFilters: [i18n("Image files") + " (*.png *.jpg *.jpeg *.gif *.webp *.bmp *.svg)", i18n("All files") + " (*)"]
onAccepted: {
if (fileDialog.selectedFile) {
logger.debug(fileDialog.selectedFile);
imgTextArea.text = fileDialog.selectedFile;
}
}
}
ColorButton {
id: customColorBtn
Kirigami.FormData.label: i18n("Color:")
color: root.config.custom
visible: singleColorRadio.checked
onAccepted: color => {
root.config.custom = color.toString();
root.updateConfig();
}
}
ComboBox {
id: colorSetCombobx
Kirigami.FormData.label: i18n("Color set:")
model: themeColorSetModel
textRole: "displayName"
visible: accentColorRadio.checked
onCurrentIndexChanged: {
root.config.systemColorSet = themeColorSetModel.get(currentIndex).value;
root.updateConfig();
}
Binding {
target: colorSetCombobx
property: "currentIndex"
value: {
for (var i = 0; i < themeColorSetModel.count; i++) {
if (themeColorSetModel.get(i).value === root.config.systemColorSet)
return i;
}
return 0; // Default to the first item if no match is found
}
}
}
ComboBox {
id: colorThemeCombobx
Kirigami.FormData.label: i18n("Color:")
model: themeColorModel
textRole: "displayName"
visible: accentColorRadio.checked
onCurrentIndexChanged: {
root.config.systemColor = themeColorModel.get(currentIndex).value;
root.updateConfig();
}
Binding {
target: colorThemeCombobx
property: "currentIndex"
value: {
for (var i = 0; i < themeColorModel.count; i++) {
if (themeColorModel.get(i).value === root.config.systemColor)
return i;
}
return 0; // Default to the first item if no match is found
}
}
}
ColumnLayout {
visible: root.multiColor && listColorRadio.checked
Kirigami.FormData.label: i18n("Colors:")
Loader {
asynchronous: true
sourceComponent: listColorRadio.checked ? pickerList : null
onLoaded: {
item.colorsList = root.config.list;
item.onColorsChanged.connect(colorsList => {
root.config.list = colorsList;
root.updateConfig();
});
}
}
Component {
id: pickerList
ColorPickerList {}
}
}
ColumnLayout {
visible: root.config.sourceType === Enum.ColorSourceType.Hue
Kirigami.FormData.label: i18n("Range:")
Kirigami.FormData.buddyFor: hue
RowLayout {
id: hue
SpinBox {
id: hueStart
from: 0
to: 360
stepSize: 1
onValueModified: {
if (value >= hueEnd.value - 1) {
value = hueEnd.value - 1;
}
root.config.hueStart = value;
root.updateConfig();
}
Component.onCompleted: value = root.config.hueStart
}
Label {
text: "-"
}
SpinBox {
id: hueEnd
from: 0
to: 360
stepSize: 1
onValueModified: {
if (value <= hueStart.value + 1) {
value = hueStart.value + 1;
}
root.config.hueEnd = value;
root.updateConfig();
}
Component.onCompleted: value = root.config.hueEnd
}
}
Canvas {
id: canvas
property list<color> colors: {
let colors = [];
const start = hueStart.value / 360;
const end = hueEnd.value / 360;
for (let i = 0; i < canvas.width; i++) {
let c = Qt.hsla(start + ((i / canvas.width) * (end - start)), 0.8, 0.7, 1.0);
colors.push(c);
}
return colors;
}
property var gradient: {
if (canvas.available) {
return Utils.buildCanvasGradient(getContext("2d"), true, colors, 0, height, width);
}
return null;
}
Layout.fillWidth: true
Layout.preferredHeight: 8
onColorsChanged: canvas.requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
}
}
RadioButton {
id: gradientHorizontalRadio
Kirigami.FormData.label: i18n("Orientation:")
property int index: 0
text: i18n("Horizontal")
ButtonGroup.group: gradientOrientationBtnGroup
checked: root.config.colorsOrientation === index
visible: root.config.sourceType > 1
}
RadioButton {
id: gradientVerticalRadio
property int index: 1
text: i18n("Vertical")
ButtonGroup.group: gradientOrientationBtnGroup
checked: root.config.colorsOrientation === index
visible: root.config.sourceType > 1
}
ButtonGroup {
id: gradientOrientationBtnGroup
onCheckedButtonChanged: {
if (checkedButton) {
root.config.colorsOrientation = checkedButton.index;
root.updateConfig();
}
}
}
CheckBox {
Kirigami.FormData.label: i18n("Gradient:")
checked: root.config.smoothGradient
onCheckedChanged: {
root.config.smoothGradient = checked;
root.updateConfig();
}
visible: root.config.sourceType > 1
}
RowLayout {
Kirigami.FormData.label: i18n("Reverse:")
CheckBox {
checked: root.config.reverseList
onCheckedChanged: {
root.config.reverseList = checked;
root.updateConfig();
}
visible: root.config.sourceType > 1
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Display colors the other way around")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Alpha:")
visible: colorModeGroup.checkedButton.index !== 6
DoubleSpinBox {
id: alphaSpinbox
from: 0 * multiplier
to: 1 * multiplier
value: (root.config.alpha ?? 0) * multiplier
onValueModified: {
root.config.alpha = value / alphaSpinbox.multiplier;
root.updateConfig();
}
}
}
Kirigami.Separator {
Kirigami.FormData.isSection: false
Kirigami.FormData.label: i18n("Contrast Correction")
Layout.fillWidth: true
visible: colorModeGroup.checkedButton.index !== 6
}
RowLayout {
Kirigami.FormData.label: i18n("Saturation:")
visible: colorModeGroup.checkedButton.index !== 6
CheckBox {
id: saturationEnabled
checked: root.config.saturationEnabled
onCheckedChanged: {
root.config.saturationEnabled = checked;
root.updateConfig();
}
}
DoubleSpinBox {
id: saturationSpinbox
from: 0 * multiplier
to: 1 * multiplier
value: (root.config.saturation ?? 0) * multiplier
onValueModified: {
root.config.saturation = value / saturationSpinbox.multiplier;
root.updateConfig();
}
}
}
RowLayout {
Kirigami.FormData.label: i18n("Lightness:")
visible: colorModeGroup.checkedButton.index !== 6
CheckBox {
id: lightnessEnabled
checked: root.config.lightnessEnabled
onCheckedChanged: {
root.config.lightnessEnabled = checked;
root.updateConfig();
}
}
DoubleSpinBox {
id: lightnessSpinbox
from: 0 * multiplier
to: 1 * multiplier
value: (root.config.lightness ?? 0) * multiplier
onValueModified: {
root.config.lightness = value / lightnessSpinbox.multiplier;
root.updateConfig();
}
}
}
}
@@ -0,0 +1,21 @@
import QtQuick
Item {
id: root
property list<string> names
RunCommand {
id: process
onExited: (cmd, exitCode, exitStatus, stdout, stderr) => {
if (exitCode !== 0) {
return;
}
if (stdout) {
let output = stdout.trim().split('\n');
output = output.map(l => l.split(/\t+/)[1]);
output.unshift("auto");
root.names = output;
}
}
}
Component.onCompleted: process.run("pactl list sources short && pactl list sinks short")
}
@@ -0,0 +1,68 @@
import QtQuick
import org.kde.plasma.plasmoid
import "../"
Item {
id: root
property string command: ""
readonly property string stdout: process.stdout ?? ""
readonly property string stderr: process.stderr ?? ""
readonly property bool forceFallback: false
readonly property bool running: process.running ?? false
property bool usingFallback: false
property var process: null
property bool loadingFailed: false
property list<string> loadingErrors
function restart() {
if (process !== null) {
process.restart();
}
}
function start() {
if (process !== null) {
process.start();
}
}
function stop() {
if (process !== null) {
process.stop();
}
}
onCommandChanged: {
if (process !== null) {
process.command = root.command;
}
}
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
Component.onCompleted: {
let component = null;
const sources = ["ProcessMonitorFallback.qml"];
if (!root.forceFallback) {
sources.unshift("ProcessMonitorPrimary.qml");
}
for (let source of sources) {
logger.debug("Trying to load", source);
component = Qt.createComponent(source);
if (component.status === Component.Ready) {
process = component.createObject(root);
process.command = root.command;
break;
} else {
logger.warn(component.errorString());
root.loadingErrors.push(component.errorString());
}
}
if (process === null) {
root.loadingFailed = true;
}
root.usingFallback = component.url.toString().includes("ProcessMonitorFallback.qml");
}
}
@@ -0,0 +1,98 @@
import QtQuick
import QtWebSockets
import "../code/utils.js" as Utils
import org.kde.plasma.plasmoid
import "../"
Item {
id: root
property string command: ""
property string stdout: ""
property string stderr: ""
property bool running: stdout !== ""
property string pid: ""
property bool isReady: false
property bool pendingRestart: false
readonly property string toolsDir: Qt.resolvedUrl("../tools").toString().substring(7) + "/"
readonly property string commandMonitorTool: "'" + toolsDir + "commandMonitor'"
readonly property string monitorCommand: `${commandMonitorTool} ${server.url} "${command}"`
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
// run command
RunCommand {
id: runCommand
onExited: (cmd, exitCode, exitStatus, stdout, stderr) => {
if (exitCode !== 0) {
root.logger.error(`ProcessMonitorFallback cmd: ${cmd}, exitCode: ${exitCode}, exitStatus: ${exitStatus}, stdout: ${stdout}, stderr: ${stderr}`);
root.stderr = stderr;
} else {
root.logger.debug(`cmd: ${cmd}, exitCode: ${exitCode}, exitStatus: ${exitStatus}, stdout: ${stdout}, stderr: ${stderr}`);
}
root.stdout = "";
root.pid = "";
}
}
// get live output lines
WebSocketServer {
id: server
listen: true
onUrlChanged: url => {
root.logger.debug("started websocket server:", url);
}
onClientConnected: webSocket => {
webSocket.onTextMessageReceived.connect(function (message) {
if (message) {
if (message.includes("ERROR:")) {
logger.error(message);
root.stderr = message;
root.stdout = "";
root.pid = "";
return;
}
if (message.includes("PID:")) {
root.pid = message.trim().split(" ")[1];
logger.debug(`started ${root.monitorCommand} with pid:`, root.pid);
root.stderr = "";
root.pendingRestart = false;
return;
}
root.stdout = message.trim().replace(/"/g, "");
}
});
}
}
function start() {
logger.debug("ProcessMonitorFallback.start()");
pendingRestart = true;
Utils.delay(100, () => {
isReady = true;
runCommand.run(root.monitorCommand);
}, root);
}
function stop() {
logger.debug("ProcessMonitorFallback.stop()");
if (pid) {
runCommand.run(`kill -TERM ${pid}`);
}
}
function restart() {
if (pendingRestart)
return;
logger.debug("ProcessMonitorFallback.restart()");
stop();
start();
}
onCommandChanged: {
if (command == "")
return;
logger.debug("ProcessMonitorFallback.onCommandChanged:", command);
restart();
}
}
@@ -0,0 +1,38 @@
import QtQuick
import com.github.luisbocanegra.audiovisualizer.process
import org.kde.plasma.plasmoid
import "../"
Item {
id: root
property string command: ""
readonly property string stdout: process.stdout
readonly property string stderr: process.stderr
readonly property bool running: process.running
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
function restart() {
logger.debug("ProcessMonitorPrimary.restart()");
process.restart();
}
// the plugin keeps a single process so no special handling is needed here
// this function is here just to match the signature of the fallback
function start() {
logger.debug("ProcessMonitorPrimary.start()");
restart();
}
function stop() {
logger.debug("ProcessMonitorPrimary.stop()");
process.stop();
}
Process {
id: process
}
onCommandChanged: {
if (command == "")
return;
process.command = ["sh", "-c", `${command}`];
logger.debug("ProcessMonitorPrimary.onCommandChanged:", command);
}
}
@@ -0,0 +1,50 @@
import QtQuick
import org.kde.plasma.plasma5support as P5Support
Item {
id: root
property string output: ""
signal exited(string cmd, int exitCode, int exitStatus, string stdout, string stderr)
function onExited(cmd, exitCode, exitStatus, stdout, stderr) {
if (exitCode !== 0)
return;
if (stdout.length > 0) {
output = stdout.trim()
}
}
function run(cmd) {
runCommand.exec(cmd);
}
function terminate(cmd) {
runCommand.exit(cmd);
}
P5Support.DataSource {
id: runCommand
function exec(cmd) {
runCommand.connectSource(cmd);
}
function exit(cmd) {
runCommand.disconnectSource(cmd);
}
engine: "executable"
connectedSources: []
onNewData: function (source, data) {
var exitCode = data["exit code"];
var exitStatus = data["exit status"];
var stdout = data["stdout"];
var stderr = data["stderr"];
root.exited(source, exitCode, exitStatus, stdout, stderr);
disconnectSource(source);
}
}
}
@@ -0,0 +1,38 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.extras as PlasmaExtras
import org.kde.plasma.plasmoid
import org.kde.kirigami as Kirigami
Item {
property int preferredTextWidth: Kirigami.Units.gridUnit * 20
implicitWidth: mainLayout.implicitWidth + Kirigami.Units.gridUnit
implicitHeight: mainLayout.implicitHeight + Kirigami.Units.gridUnit
ColumnLayout {
id: mainLayout
anchors {
centerIn: parent
}
spacing: 0
PlasmaExtras.Heading {
id: label
Layout.fillWidth: true
level: 3
text: Plasmoid.metaData.name
}
Label {
text: Plasmoid.metaData.description
Layout.fillWidth: true
wrapMode: Label.Wrap
}
}
}
@@ -0,0 +1,226 @@
import QtQuick
import QtQuick.Shapes
import QtQuick.Controls
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasmoid
import "../code/utils.js" as Utils
import "../code/enum.js" as Enum
import "../code/drawCanvas.js" as DrawCanvas
Item {
id: root
required property int visualizerStyle
required property bool circleMode
required property real circleModeSize
required property int barWidth
required property int blockHeight
required property int blockSpacing
required property int barGap
required property bool centeredBars
required property bool roundedBars
required property bool fillWave
required property var barColorsCfg
required property var waveFillColorsCfg
required property var inactiveBlockColorsCfg
required property bool drawInactiveBlocks
required property bool fixVertical
property list<int> values
property bool debugMode: false
Rectangle {
id: kirigamiColorItem
opacity: 0
height: 1
width: height
Kirigami.Theme.colorSet: Kirigami.Theme[root.barColorsCfg.systemColorSet]
}
Rectangle {
id: kirigamiColorItem2
opacity: 0
height: 1
width: height
Kirigami.Theme.colorSet: Kirigami.Theme[root.waveFillColorsCfg.systemColorSet]
}
Rectangle {
color: Kirigami.Theme.highlightColor
opacity: 0.2
visible: root.debugMode
width: canvas.width
height: canvas.height
anchors.centerIn: parent
}
Canvas {
id: canvas
anchors.centerIn: parent
// renderStrategy: Canvas.Threaded
property int visualizerStyle: root.visualizerStyle
property bool circleMode: root.circleMode
property real circleModeSize: root.circleModeSize
property int barWidth: root.barWidth
property int blockHeight: root.blockHeight
property int blockSpacing: root.blockSpacing
property int spacing: {
if (visualizerStyle === Enum.VisualizerStyles.Wave) {
return Math.max(1, root.barGap);
}
return root.barGap;
}
property int barCount: root.values.length
property bool centeredBars: root.centeredBars
property bool roundedBars: root.roundedBars
property var values: {
if (root.debugMode) {
let copy = root.values.slice();
copy[0] = height;
return copy;
}
return root.values;
}
property bool fillWave: root.fillWave
property real radiusOffset: barWidth / 2
property int gradientHeight: canvas.height
property int gradientWidth: canvas.width
function updateGradients() {
if (!canvas.available) {
return;
}
canvas.requestPaint();
}
onHeightChanged: updateGradients()
onWidthChanged: updateGradients()
property var barColorsCfg: root.barColorsCfg
property list<color> colors: Utils.getColors(barColorsCfg, barCount, kirigamiColorItem.Kirigami.Theme[barColorsCfg.systemColor])
property var gradient: {
if (canvas.available) {
return Utils.buildCanvasGradient(getContext("2d"), barColorsCfg.smoothGradient, colors, barColorsCfg.colorsOrientation, canvas.height, canvas.width, circleMode);
}
return null;
}
property var waveFillColorsCfg: root.waveFillColorsCfg
property list<color> waveFillColors: Utils.getColors(waveFillColorsCfg, barCount, kirigamiColorItem2.Kirigami.Theme[waveFillColorsCfg.systemColor])
property var waveFillGradient: {
if (canvas.available) {
return Utils.buildCanvasGradient(getContext("2d"), waveFillColorsCfg.smoothGradient, waveFillColors, waveFillColorsCfg.colorsOrientation, canvas.height, canvas.width, circleMode);
}
return null;
}
property var inactiveBlockColorsCfg: root.inactiveBlockColorsCfg
property list<color> inactiveBlockColors: Utils.getColors(inactiveBlockColorsCfg, barCount, kirigamiColorItem.Kirigami.Theme[inactiveBlockColorsCfg.systemColor])
property var inactiveBlockGradient: {
if (canvas.available) {
return Utils.buildCanvasGradient(getContext("2d"), inactiveBlockColorsCfg.smoothGradient, inactiveBlockColors, inactiveBlockColorsCfg.colorsOrientation, canvas.height, canvas.width, circleMode);
}
return null;
}
property bool drawInactiveBlocks: root.drawInactiveBlocks
width: {
if (visualizerStyle === Enum.VisualizerStyles.Wave) {
return barWidth + ((barCount - 1) * spacing);
}
return barCount * barWidth + ((barCount - 1) * spacing);
}
height: parent.height
property bool fixAlign: barWidth % 2 === 0 && centeredBars && visualizerStyle === Enum.VisualizerStyles.Wave && !root.fixVertical
onValuesChanged: canvas.requestPaint()
onPaint: {
const ctx = getContext("2d");
ctx.reset();
if (fixAlign) {
ctx.translate(0.0, 0.5);
}
if (root.fixVertical) {
ctx.translate(0.0, -0.5);
}
if (gradient) {
ctx.strokeStyle = gradient;
ctx.fillStyle = gradient;
}
switch (visualizerStyle) {
case Enum.VisualizerStyles.Bars:
DrawCanvas.bars(ctx, canvas, circleMode);
break;
case Enum.VisualizerStyles.Wave:
DrawCanvas.wave(ctx, canvas, circleMode);
break;
case Enum.VisualizerStyles.Blocks:
DrawCanvas.blocks(ctx, canvas, circleMode);
break;
default:
DrawCanvas.bars(ctx, canvas, circleMode);
break;
}
if (fixAlign) {
ctx.translate(0.0, -0.5);
}
}
}
Shape {
id: shape
visible: root.debugMode
width: canvas.width
height: canvas.height
anchors.centerIn: parent
ShapePath {
fillColor: "transparent"
strokeWidth: 1
strokeColor: "red"
strokeStyle: ShapePath.DashLine
dashPattern: [1, 8]
startX: 0
startY: shape.height
PathLine {
x: shape.width
y: shape.height
}
PathLine {
x: shape.width
y: 0
}
PathLine {
x: 0
y: 0
}
PathLine {
x: 0
y: shape.height
}
}
ShapePath {
fillColor: "transparent"
strokeWidth: 1
strokeColor: "red"
strokeStyle: ShapePath.DashLine
dashPattern: [1, 8]
startX: 0
startY: shape.height / 2
PathLine {
x: shape.width
y: shape.height / 2
}
}
ShapePath {
fillColor: "transparent"
strokeWidth: 1
strokeColor: "red"
strokeStyle: ShapePath.DashLine
dashPattern: [1, 8]
startX: shape.width / 2
startY: 0
PathLine {
x: shape.width / 2
y: shape.height
}
}
}
}
@@ -0,0 +1,540 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
import org.kde.plasma.plasmoid
import "./components"
KCM.SimpleKCM {
id: root
// general
property alias cfg_barCount: barCountSpinbox.value
property alias cfg_framerate: framerateSpinbox.value
property alias cfg_autoSensitivity: autoSensitivityCheckbox.checked
property alias cfg_sensitivityEnabled: sensitivityEnabled.checked
property alias cfg_sensitivity: sensitivitySpinbox.value
property alias cfg_lowerCutoffFreq: lowerCutoffFreqSpinbox.value
property alias cfg_higherCutoffFreq: higherCutoffFreqSpinbox.value
property alias cfg_cavaSleepTimer: cavaSleepTimerSpinbox.value
// smoothing
property alias cfg_noiseReduction: noiseReductionSpinbox.value
property alias cfg_monstercat: monstercatCheckbox.checked
property alias cfg_waves: wavesCheckbox.checked
// input
property string cfg_inputMethod
property alias cfg_inputSource: inputSourceField.text
property alias cfg_sampleRate: sampleRateSpinbox.value
property alias cfg_sampleBits: sampleBitsSpinbox.value
property alias cfg_inputChannels: inputChannelsSpinbox.value
property alias cfg_autoconnect: autoconnectSpinbox.value
property alias cfg_active: activeCheckbox.checked
property alias cfg_remix: remixCheckbox.checked
property alias cfg_virtual: virtualCheckbox.checked
// output
property string cfg_outputChannels
property string cfg_monoOption
property alias cfg_reverse: reverseCheckbox.checked
// eq
property alias cfg_eqEnabled: eqEnabled.checked
property var cfg_eq
property alias cfg_pauseOnFullScreenWindow: pauseOnFullScreenWindow.checked
property alias cfg_pauseOnMaximizedWindow: pauseOnMaximizedWindow.checked
PactlList {
id: pactl
}
ColumnLayout {
Kirigami.FormLayout {
id: parentLayout
Layout.fillWidth: true
Kirigami.Separator {
Kirigami.FormData.isSection: true
Kirigami.FormData.label: i18n("General")
}
Button {
id: stopCavaButton
text: Plasmoid.configuration._stopCava ? i18n("Start CAVA") : i18n("Stop CAVA")
onClicked: {
Plasmoid.configuration._stopCava = !Plasmoid.configuration._stopCava;
Plasmoid.configuration.writeConfig();
}
}
SpinBox {
id: framerateSpinbox
Kirigami.FormData.label: i18n("Framerate:")
from: 1
to: 144
}
RowLayout {
id: barsRow
Kirigami.FormData.buddyFor: barCountSpinbox
Kirigami.FormData.label: i18n("Number of bars:")
SpinBox {
id: barCountSpinbox
enabled: false //TODO enable when we have a visualization style that can use it
from: 1
to: 512
Layout.alignment: Qt.AlignTop
}
Label {
visible: !barCountSpinbox.enabled
text: i18n("Automatically calculated for the current visualizer style.")
font: Kirigami.Theme.smallFont
Layout.maximumWidth: 200
wrapMode: Label.Wrap
enabled: false
Layout.alignment: Qt.AlignTop
}
}
RowLayout {
Kirigami.FormData.label: i18n("Automatic sensitivity:")
CheckBox {
id: autoSensitivityCheckbox
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Attempt to decrease sensitivity if the bars peak.")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Sensitivity:")
CheckBox {
id: sensitivityEnabled
}
SpinBox {
id: sensitivitySpinbox
from: 1
to: 999
enabled: sensitivityEnabled.checked
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Manual sensitivity in %.\nIf autosens is enabled, this will only be the initial value")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Frequency range (Hz):")
Label {
text: i18n("Min")
}
SpinBox {
id: lowerCutoffFreqSpinbox
from: 20
to: 22000
stepSize: 100
}
Label {
text: i18n("Max")
}
SpinBox {
id: higherCutoffFreqSpinbox
from: 1
to: 22000
stepSize: 100
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Lower and higher cutoff frequencies for lowest and highest bars, the bandwidth of the visualizer.\nNote: Cava will automatically increase the higher cutoff if a too low band is specified.")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Sleep timer (seconds):")
SpinBox {
id: cavaSleepTimerSpinbox
from: 1
to: 60
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Seconds with no input before cava goes to sleep mode.\nCava will not perform FFT and only check for input once per second.")
}
}
CheckBox {
id: pauseOnFullScreenWindow
Kirigami.FormData.label: i18n("Pause on:")
text: i18n("Full screen window")
}
CheckBox {
id: pauseOnMaximizedWindow
text: i18n("Maximized window")
}
Kirigami.Separator {
Kirigami.FormData.isSection: true
Kirigami.FormData.label: i18n("Input")
}
RowLayout {
Kirigami.FormData.label: i18n("Method:")
Layout.preferredWidth: 300
ComboBox {
id: inputMethodCombobox
textRole: "label"
valueRole: "value"
Layout.fillWidth: true
model: [
{
label: i18n("Default"),
value: ""
},
{
label: "OSS",
value: "oss"
},
{
label: "PipeWire",
value: "pipewire"
},
{
label: "Sndio",
value: "sndio"
},
{
label: "JACK",
value: "jack"
},
{
label: "PulseAudio",
value: "pulse"
},
{
label: "ALSA",
value: "alsa"
},
{
label: "PortAudio",
value: "portaudio"
},
{
label: "FIFO",
value: "fifo"
},
{
label: "shmem",
value: "shmem"
},
]
onActivated: {
root.cfg_inputMethod = currentValue;
if (!["pipewire", "pulse"].includes(root.cfg_inputMethod)) {
sourcesCard.visible = false;
root.cfg_inputSource = "";
}
}
Component.onCompleted: currentIndex = indexOfValue(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Audio capturing method.\nDefaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.\n")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Source:")
Layout.preferredWidth: 300
TextField {
id: inputSourceField
Layout.fillWidth: true
placeholderText: "auto"
}
Button {
visible: ["pipewire", "pulse"].includes(root.cfg_inputMethod)
icon.name: sourcesCard.visible ? "arrow-up" : "arrow-down"
onClicked: {
sourcesCard.visible = !sourcesCard.visible;
}
checkable: true
checked: sourcesCard.visible
}
ToolButton {
onCheckedChanged: inputSourceHelpCard.visible = !inputSourceHelpCard.visible
checkable: true
icon.name: "help"
}
}
Kirigami.AbstractCard {
id: sourcesCard
implicitHeight: 200
visible: false
Layout.fillWidth: true
contentItem: ScrollView {
clip: true
ListView {
model: pactl.names
delegate: ItemDelegate {
id: delegate
required property string modelData
// required property Item root
width: ListView.view.width
text: modelData
contentItem: Label {
text: delegate.text
wrapMode: Label.WrapAnywhere
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
}
ToolTip.visible: false
onClicked: root.cfg_inputSource = modelData
}
}
}
}
}
Kirigami.AbstractCard {
id: inputSourceHelpCard
spacing: 0
visible: false
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.SelectableLabel {
text: i18n("For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For pipewire 'source' will be the object name or object.serial of the device to capture from. Both input and output devices are supported.")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For alsa 'source' will be the capture device.")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For fifo 'source' will be the path to fifo-file.")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address.")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.")
Layout.fillWidth: true
}
Kirigami.SelectableLabel {
text: i18n("For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.")
Layout.fillWidth: true
}
}
}
Kirigami.FormLayout {
twinFormLayouts: [parentLayout]
RowLayout {
Kirigami.FormData.label: i18n("Sample rate:")
SpinBox {
id: sampleRateSpinbox
from: 1
to: 192000
stepSize: 100
enabled: ["", "fifo", "pipewire", "sndio", "oss"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for fifo, pipewire, sndio, oss")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Sample bits:")
SpinBox {
id: sampleBitsSpinbox
from: 8
to: 32
stepSize: 8
enabled: ["", "fifo", "pipewire", "sndio", "oss"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for fifo, pipewire, sndio, oss")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Channels:")
SpinBox {
id: inputChannelsSpinbox
from: 2
to: 4
enabled: ["", "pipewire", "sndio", "oss", "jack"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for pipewire & cava>=0.10.6, sndio, oss, jack")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Auto connect:")
SpinBox {
id: autoconnectSpinbox
from: 2
to: 4
enabled: ["", "jack"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for jack")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Active:")
CheckBox {
id: activeCheckbox
enabled: ["", "pipewire"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for pipewire & cava>=0.10.6. Force the node to always process. Useful for monitoring sources when no other application is active.")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Remix:")
CheckBox {
id: remixCheckbox
enabled: ["", "pipewire"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for pipewire & cava>=0.10.6. Allow to remix audio channels to match cava's channel count. Useful for surround sound.")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Virtual:")
CheckBox {
id: virtualCheckbox
enabled: ["", "pipewire"].includes(root.cfg_inputMethod)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Only for pipewire & cava>=0.10.6. Set the node to virtual, to avoid recording notifications from the DE.")
}
}
}
Kirigami.FormLayout {
twinFormLayouts: [parentLayout]
Kirigami.Separator {
Kirigami.FormData.isSection: true
Kirigami.FormData.label: i18n("Output")
}
RowLayout {
Kirigami.FormData.label: i18n("Visual channels:")
ComboBox {
id: outputChannelsCombobox
model: ["mono", "stereo"]
onActivated: root.cfg_outputChannels = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.cfg_outputChannels)
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("'mono' outputs left to right lowest to highest frequencies.\n'stereo' mirrors both channels with low frequencies in center.")
}
}
ComboBox {
id: monoOptionCombobox
Kirigami.FormData.label: i18n("Mono channel:")
model: ['left', 'right', 'average']
onActivated: root.cfg_monoOption = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.cfg_monoOption)
enabled: root.cfg_outputChannels === "mono"
}
RowLayout {
Kirigami.FormData.label: i18n("Reverse:")
CheckBox {
id: reverseCheckbox
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Display frequencies the other way around")
}
}
Kirigami.Separator {
Kirigami.FormData.isSection: true
Kirigami.FormData.label: i18n("Smoothing")
}
RowLayout {
Kirigami.FormData.label: i18n("Noise reduction:")
SpinBox {
id: noiseReductionSpinbox
from: 0
to: 100
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth.\n100 will be very slow and smooth, 0 will be fast but noisy.")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Monstercat:")
CheckBox {
id: monstercatCheckbox
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Enable the so-called \"Monstercat smoothing\" with or without \"waves\".")
}
}
CheckBox {
id: wavesCheckbox
text: i18n("Waves")
enabled: monstercatCheckbox.checked
}
Kirigami.Separator {
Kirigami.FormData.isSection: true
Kirigami.FormData.label: i18n("Equalizer")
}
RowLayout {
Kirigami.FormData.label: i18n("Enabled:")
CheckBox {
id: eqEnabled
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Adjust frequencies by a multiplication factor, more bands equals more precision.")
Layout.alignment: Qt.AlignHCenter
}
}
}
Eq {
enabled: eqEnabled.checked
Layout.preferredWidth: parent.width - Kirigami.Units.gridUnit * 2
Layout.alignment: Qt.AlignHCenter
fromFreq: root.cfg_lowerCutoffFreq
toFreq: root.cfg_higherCutoffFreq
onValueChanged: (index, value) => {
root.cfg_eq[index] = parseFloat(value.toString()).toFixed(1);
const tmp = root.cfg_eq;
root.cfg_eq = null;
root.cfg_eq = tmp;
}
Component.onCompleted: values = root.cfg_eq.map(v => parseFloat(v).toFixed(1))
onBandRemoved: {
root.cfg_eq.pop();
values = root.cfg_eq.map(v => parseFloat(v).toFixed(1));
}
onBandAdded: {
root.cfg_eq.push("1.0");
values = root.cfg_eq.map(v => parseFloat(v).toFixed(1));
}
onFlat: {
root.cfg_eq = root.cfg_eq.map(v => "1.0");
// HACK: setting values alone doesn't update the values in the controls
bandAdded();
bandRemoved();
}
}
}
}
@@ -0,0 +1,114 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
import org.kde.plasma.core as PlasmaCore
KCM.SimpleKCM {
id: root
property alias cfg_desktopWidgetBg: desktopWidgetBackgroundRadio.value
property alias cfg_hideWhenIdle: hideWhenIdleCheckbox.checked
property int cfg_visualizerStyle
property string cfg_barColors
property string cfg_waveFillColors
property alias cfg_debugMode: debugModeCheckbox.checked
property alias cfg_idleTimer: idleTimerSpinbox.value
property alias cfg_hideToolTip: hideToolTipCheckbox.checked
property alias cfg_disableLeftClick: disableLeftClickCheckbox.checked
Kirigami.FormLayout {
id: parentLayout
Layout.fillWidth: true
RowLayout {
Kirigami.FormData.label: i18n("Auto-hide when idle:")
CheckBox {
id: hideWhenIdleCheckbox
}
Label {
text: i18n("After")
}
SpinBox {
id: idleTimerSpinbox
enabled: hideWhenIdleCheckbox.checked
from: 1
to: 60
}
Label {
text: i18n("seconds")
}
}
RadioButton {
Kirigami.FormData.label: i18n("Desktop background:")
text: i18n("Default")
checked: desktopWidgetBackgroundRadio.value == PlasmaCore.Types.StandardBackground
onCheckedChanged: () => {
if (checked) {
desktopWidgetBackgroundRadio.value = PlasmaCore.Types.StandardBackground;
}
}
ButtonGroup.group: desktopWidgetBackgroundRadio
}
RadioButton {
text: i18n("Transparent")
checked: desktopWidgetBackgroundRadio.value == PlasmaCore.Types.NoBackground
onCheckedChanged: () => {
if (checked) {
desktopWidgetBackgroundRadio.value = PlasmaCore.Types.NoBackground;
}
}
ButtonGroup.group: desktopWidgetBackgroundRadio
}
RowLayout {
RadioButton {
text: i18n("Transparent with shadow")
checked: desktopWidgetBackgroundRadio.value == PlasmaCore.Types.ShadowBackground
onCheckedChanged: () => {
if (checked) {
desktopWidgetBackgroundRadio.value = PlasmaCore.Types.ShadowBackground;
}
}
ButtonGroup.group: desktopWidgetBackgroundRadio
}
}
ButtonGroup {
id: desktopWidgetBackgroundRadio
property int value: PlasmaCore.Types.StandardBackground
}
RowLayout {
Kirigami.FormData.label: i18n("Disable tooltip:")
CheckBox {
id: hideToolTipCheckbox
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Disable ToolTip that shows the widget name and description.")
}
}
RowLayout {
Kirigami.FormData.label: i18n("Disable left click:")
CheckBox {
id: disableLeftClickCheckbox
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Applet popup will still be accessible from the right click menu.")
}
}
Kirigami.Separator {
Kirigami.FormData.isSection: true
Kirigami.FormData.label: i18n("Troubleshooting")
}
RowLayout {
Kirigami.FormData.label: i18n("Debug mode:")
CheckBox {
id: debugModeCheckbox
}
Kirigami.ContextualHelpButton {
toolTipText: i18n("Print additional messages to the system log. To make it easier to spot sizing issues, this will also force full height to the first visualizer bar and displays a grid over it.")
}
}
}
}
@@ -0,0 +1,225 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasmoid
import "components" as Components
import "code/enum.js" as Enum
import "code/utils.js" as Utils
KCM.SimpleKCM {
id: root
property alias cfg_barGap: barGapSpinbox.value
property alias cfg_barWidth: barWidthSpinbox.value
property alias cfg_blockHeight: blockHeightSpinbox.value
property alias cfg_blockSpacing: blockSpacing.value
property alias cfg_centeredBars: centeredBarsCheckbox.checked
property alias cfg_roundedBars: roundedBarsCheckbox.checked
// fill panel thickness
property alias cfg_fillPanel: fillPanelCheckbox.checked
property int cfg_visualizerStyle
property alias cfg_circleMode: circleMode.checked
property real cfg_circleModeSize
property string cfg_barColors
property string cfg_waveFillColors
property string cfg_inactiveBlockColors
property alias cfg_drawInactiveBlocks: drawInactiveBlocks.checked
property alias cfg_fillWave: fillWaveCheckbox.checked
// take all the available space in the panel
property alias cfg_expanding: expandingCheckbox.checked
property alias cfg_length: lengthSpinbox.value
property int cfg_orientation
readonly property bool vertical: {
if (Plasmoid.formFactor == PlasmaCore.Types.Vertical) {
return true;
}
return false;
}
readonly property string dimensionStr: vertical ? i18n("height") : i18n("width")
ColumnLayout {
Kirigami.FormLayout {
id: parentLayout
Layout.fillWidth: true
ComboBox {
id: visualizerStyleCombobox
Kirigami.FormData.label: i18n("Style:")
textRole: "label"
valueRole: "value"
model: [
{
"label": i18n("Bars"),
"value": Enum.VisualizerStyles.Bars
},
{
"label": i18n("Wave"),
"value": Enum.VisualizerStyles.Wave
},
{
"label": i18n("Blocks"),
"value": Enum.VisualizerStyles.Blocks
},
]
onActivated: {
root.cfg_visualizerStyle = currentValue;
}
Component.onCompleted: {
currentIndex = indexOfValue(root.cfg_visualizerStyle);
}
}
CheckBox {
id: fillWaveCheckbox
text: i18n("Fill wave")
enabled: root.cfg_visualizerStyle === Enum.VisualizerStyles.Wave
}
CheckBox {
id: drawInactiveBlocks
text: i18n("Draw inactive blocks")
enabled: root.cfg_visualizerStyle === Enum.VisualizerStyles.Blocks
}
CheckBox {
id: circleMode
Kirigami.FormData.label: i18n("Circle mode:")
}
Components.DoubleSpinBox {
id: circleModeSize
Kirigami.FormData.label: i18n("Circle size:")
from: 0 * multiplier
to: 0.99 * multiplier
value: cfg_circleModeSize * multiplier
onValueModified: {
cfg_circleModeSize = value / circleModeSize.multiplier;
}
}
ComboBox {
id: orientationCombobox
Kirigami.FormData.label: i18n("Orientation:")
valueRole: "value"
textRole: "label"
model: [
{
label: i18n("Top"),
value: Enum.Orientation.Top
},
{
label: i18n("Bottom"),
value: Enum.Orientation.Bottom
},
{
label: i18n("Left"),
value: Enum.Orientation.Left
},
{
label: i18n("Right"),
value: Enum.Orientation.Right
}
]
onActivated: root.cfg_orientation = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.cfg_orientation)
}
CheckBox {
id: expandingCheckbox
visible: !(Plasmoid.location === PlasmaCore.Types.Floating)
Kirigami.FormData.label: i18n("Fill panel %1:", root.dimensionStr)
}
SpinBox {
id: lengthSpinbox
visible: !(Plasmoid.location === PlasmaCore.Types.Floating)
enabled: !expandingCheckbox.checked
Kirigami.FormData.label: i18n("Fixed %1:", root.dimensionStr)
from: 1
to: 9999
}
CheckBox {
id: fillPanelCheckbox
visible: !(Plasmoid.location === PlasmaCore.Types.Floating)
Kirigami.FormData.label: i18n("Fill panel thickness:")
}
CheckBox {
id: centeredBarsCheckbox
Kirigami.FormData.label: i18n("Centered bars:")
}
CheckBox {
id: roundedBarsCheckbox
Kirigami.FormData.label: i18n("Rounded bars:")
}
SpinBox {
id: barWidthSpinbox
Kirigami.FormData.label: root.cfg_visualizerStyle === Enum.VisualizerStyles.Wave ? i18n("Line width:") : i18n("Bar width:")
from: 1
to: 999
}
SpinBox {
id: barGapSpinbox
Kirigami.FormData.label: i18n("Bar gap:")
from: root.cfg_visualizerStyle === Enum.VisualizerStyles.Wave ? 1 : 0
to: 999
}
SpinBox {
id: blockHeightSpinbox
Kirigami.FormData.label: i18n("Block height:")
from: 1
to: 999
enabled: root.cfg_visualizerStyle === Enum.VisualizerStyles.Blocks
}
SpinBox {
id: blockSpacing
Kirigami.FormData.label: i18n("Block gap:")
from: 0
to: 999
enabled: root.cfg_visualizerStyle === Enum.VisualizerStyles.Blocks
}
}
Components.FormColors {
configString: root.cfg_barColors
handleString: true
onUpdateConfigString: (newString, newConfig) => {
root.cfg_barColors = JSON.stringify(newConfig);
}
sectionName: root.cfg_visualizerStyle === Enum.VisualizerStyles.Wave ? i18n("Wave Color") : i18n("Bar Color")
multiColor: true
}
Components.FormColors {
configString: root.cfg_inactiveBlockColors
handleString: true
onUpdateConfigString: (newString, newConfig) => {
root.cfg_inactiveBlockColors = JSON.stringify(newConfig);
}
sectionName: i18n("Inactive block color")
multiColor: true
visible: root.cfg_visualizerStyle === Enum.VisualizerStyles.Blocks && drawInactiveBlocks.checked
}
Components.FormColors {
configString: root.cfg_waveFillColors
handleString: true
onUpdateConfigString: (newString, newConfig) => {
root.cfg_waveFillColors = JSON.stringify(newConfig);
}
sectionName: i18n("Wave Fill Color")
multiColor: true
visible: root.cfg_visualizerStyle === Enum.VisualizerStyles.Wave && fillWaveCheckbox.checked
}
}
}
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="32"
height="32"
version="1.1"
id="svg2"
sodipodi:docname="error.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.5459416"
inkscape:cx="20.322773"
inkscape:cy="22.365525"
inkscape:window-width="1920"
inkscape:window-height="1048"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="22-22-material-you" />
<defs
id="materia">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Background {color:#181818; } .ColorScheme-ButtonBackground { color:#343434; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-ViewBackground { color:#242424; } .ColorScheme-NegativeText { color:#f44336; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NeutralText { color:#ff9800; }
</style>
</defs>
<g
id="22-22-material-you"
transform="translate(27,5.0000005)"
style="opacity:1;fill:currentColor"
class="ColorScheme-Text">
<rect
style="opacity:0.00100002"
width="22"
height="22"
x="4.7904868"
y="5"
id="rect1" />
<path
d="m 12.474932,8 -4.6844452,4.684445 v 6.63111 L 12.474932,24 h 6.63111 l 4.684445,-4.684445 v -6.63111 L 19.106042,8 m -6.506666,3.555555 3.191111,3.191112 3.191111,-3.191112 1.253334,1.253334 L 17.04382,16 20.234932,19.191111 18.981598,20.444445 15.790487,17.253333 12.599376,20.444445 11.346042,19.191111 14.537154,16 11.346042,12.808889"
id="path1-3-6"
style="fill:currentColor;stroke-width:0.888887" />
</g>
<g
id="material-you"
class="ColorScheme-Text"
style="opacity:1;fill:currentColor"
transform="translate(35.302961,-8.4852813)">
<rect
style="opacity:0.00100002"
width="32"
height="32"
x="-35.302959"
y="8.485281"
id="rect2" />
<path
d="m -24.276292,12.485281 -7.026668,7.026667 v 9.946666 l 7.026668,7.026667 h 9.946666 l 7.0266667,-7.026667 v -9.946666 l -7.0266667,-7.026667 m -9.76,5.333333 4.786667,4.786667 4.786667,-4.786667 1.88,1.88 -4.786667,4.786667 4.786667,4.786667 -1.88,1.88 -4.786667,-4.786667 -4.786667,4.786667 -1.880001,-1.88 4.786668,-4.786667 -4.786668,-4.786667"
id="path1-3"
style="stroke-width:1.33333" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,208 @@
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasmoid
import "code/enum.js" as Enum
import "code/utils.js" as Utils
PlasmoidItem {
id: main
Plasmoid.backgroundHints: editMode ? PlasmaCore.Types.StandardBackground : plasmoid.configuration.desktopWidgetBg
Plasmoid.constraintHints: Plasmoid.configuration.fillPanel ? Plasmoid.CanFillArea : Plasmoid.NoHint
property bool editMode: Plasmoid.containment.corona?.editMode ?? false
property bool onDesktop: Plasmoid.location === PlasmaCore.Types.Floating
property bool horizontal: Plasmoid.formFactor !== PlasmaCore.Types.Vertical
property int orientation: Plasmoid.configuration.orientation
property bool stopCava: Plasmoid.configuration._stopCava
property bool disableLeftClick: Plasmoid.configuration.disableLeftClick
property int barGap: {
if (Plasmoid.configuration.visualizerStyle === Enum.VisualizerStyles.Wave) {
return Math.max(1, Plasmoid.configuration.barGap);
}
return Plasmoid.configuration.barGap;
}
property int barCount: {
let bars = 1;
let width;
if (Plasmoid.configuration.circleMode) {
width = Math.min(main.width, main.height);
} else {
width = [Enum.Orientation.Left, Enum.Orientation.Right].includes(Plasmoid.configuration.orientation) ? main.height : main.width;
}
if (Plasmoid.configuration.visualizerStyle === Enum.VisualizerStyles.Wave) {
bars = Math.floor((width + barGap) / barGap);
} else {
bars = Math.floor((width + barGap) / (Plasmoid.configuration.barWidth + barGap));
}
if (Plasmoid.configuration.outputChannels === "stereo") {
bars = Utils.makeEven(bars);
}
if (Plasmoid.configuration.visualizerStyle === Enum.VisualizerStyles.Wave) {
bars = Math.max(2, bars);
}
return bars;
}
property int asciiMaxRange: [Enum.Orientation.Left, Enum.Orientation.Right].includes(Plasmoid.configuration.orientation) ? main.width : main.height
property var logger: Logger.create(Plasmoid.configuration.debugMode ? LoggingCategory.Debug : LoggingCategory.Info)
property bool hideWhenIdle: Plasmoid.configuration.hideWhenIdle
property bool pauseFullScreen: Plasmoid.configuration.pauseOnFullScreenWindow
property bool pauseMaximized: Plasmoid.configuration.pauseOnMaximizedWindow
property bool pauseByWindow: (pauseMaximized && tasksModel.maximizedExists) || (pauseFullScreen && tasksModel.fullScreenExists)
Plasmoid.status: PlasmaCore.Types.ActiveStatus
function updateStatus() {
logger.debug("Plasmoid.status:", Plasmoid.status);
// HACK: without this delay there is a visible margin on the right side when expanding == true
Utils.delay(10, () => {
if (Plasmoid.status === PlasmaCore.Types.RequiresAttentionStatus) {
return;
}
Plasmoid.status = (hideWhenIdle && cava.idle || !cava.running) && !Plasmoid.expanded && !editMode && !cava.hasError ? PlasmaCore.Types.HiddenStatus : PlasmaCore.Types.ActiveStatus;
logger.debug("Plasmoid.status:", Plasmoid.status);
}, main);
}
onExpandedChanged: expanded => {
logger.debug("expanded:", expanded);
Utils.delay(1000, updateStatus, main);
}
onBarCountChanged: {
if (editMode && stopCava) {
return;
}
Qt.callLater(() => {
resizeDebounce.restart();
});
}
onAsciiMaxRangeChanged: {
if (editMode && stopCava) {
return;
}
Qt.callLater(() => {
resizeDebounce.restart();
});
}
Timer {
id: resizeDebounce
interval: 50
onTriggered: {
cava.barCount = main.barCount;
cava.asciiMaxRange = main.asciiMaxRange;
logger.debug("barCount:", barCount, "asciiMaxRange:", asciiMaxRange);
}
}
onEditModeChanged: {
logger.debug("editMode:", editMode);
updateStatus();
}
Cava {
id: cava
framerate: Plasmoid.configuration.framerate
noiseReduction: Plasmoid.configuration.noiseReduction
monstercat: Plasmoid.configuration.monstercat
waves: Plasmoid.configuration.waves
autoSensitivity: Plasmoid.configuration.autoSensitivity
sensitivityEnabled: Plasmoid.configuration.sensitivityEnabled
sensitivity: Plasmoid.configuration.sensitivity
lowerCutoffFreq: Plasmoid.configuration.lowerCutoffFreq
higherCutoffFreq: Plasmoid.configuration.higherCutoffFreq
inputMethod: Plasmoid.configuration.inputMethod
inputSource: Plasmoid.configuration.inputSource
sampleRate: Plasmoid.configuration.sampleRate
sampleBits: Plasmoid.configuration.sampleBits
inputChannels: Plasmoid.configuration.inputChannels
autoconnect: Plasmoid.configuration.autoconnect
active: Plasmoid.configuration.active
remix: Plasmoid.configuration.remix
virtual: Plasmoid.configuration.virtual
outputChannels: Plasmoid.configuration.outputChannels
monoOption: Plasmoid.configuration.monoOption
reverse: Plasmoid.configuration.reverse
eqEnabled: Plasmoid.configuration.eqEnabled
eq: Plasmoid.configuration.eq
idleCheck: main.hideWhenIdle
idleTimer: Plasmoid.configuration.idleTimer
cavaSleepTimer: Plasmoid.configuration.cavaSleepTimer
onIdleChanged: {
main.logger.info("cava.idle:", idle);
main.updateStatus();
}
onHasErrorChanged: {
main.logger.error("cava.hasError:", hasError, error);
main.updateStatus();
}
onRunningChanged: {
main.logger.info("cava.running:", running);
main.updateStatus();
}
}
preferredRepresentation: compactRepresentation
compactRepresentation: CompactRepresentation {}
fullRepresentation: FullRepresentation {}
onStopCavaChanged: {
logger.debug("stopCava:", stopCava);
if (stopCava) {
cava.stop();
} else {
cava.start();
}
}
Plasmoid.contextualActions: [
PlasmaCore.Action {
text: cava.running ? i18n("Stop CAVA") : i18n("Start CAVA")
icon.name: "waveform-symbolic"
onTriggered: {
Plasmoid.configuration._stopCava = !Plasmoid.configuration._stopCava;
Plasmoid.configuration.writeConfig();
}
},
PlasmaCore.Action {
text: i18n("Show support information")
icon.name: "info-symbolic"
onTriggered: {
main.expanded = !main.expanded;
}
visible: main.disableLeftClick
}
]
// hide default tooltip
toolTipMainText: ""
toolTipSubText: ""
Connections {
target: Qt.application
function onAboutToQuit() {
cava.stop();
}
}
TasksModel {
id: tasksModel
screenGeometry: Plasmoid.containment.screenGeometry
}
onPauseByWindowChanged: {
if (Plasmoid.configuration._stopCava) {
return;
}
if (pauseByWindow) {
cava.stop();
} else {
cava.start();
}
}
}
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
import os
import asyncio
import sys
from signal import SIGTERM
import websockets
async def monitor_command_output(cmd: str, ws_url: str) -> None:
"""Run a command and send the PID and live output to a websocket
Args:
cmd (str): command
ws_url (str): websocket url
"""
proc = None
try:
async with websockets.connect(ws_url) as ws:
await ws.send(f"PID: {os.getpid()}")
print(f"Starting process {cmd} ...")
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
async with websockets.connect(ws_url) as ws:
assert proc.stdout is not None
async for line in proc.stdout:
await ws.send(line.decode().rstrip())
assert proc.stderr is not None
async for line in proc.stderr:
await ws.send(f"ERROR: {line.decode().rstrip()}")
await proc.wait()
except asyncio.CancelledError:
if proc is not None and proc.returncode is None:
print(f"Stopping process pid: {proc.pid} {cmd} ...")
proc.kill()
await proc.wait()
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"Usage: {os.path.basename(__file__)} <ws-url> <command>")
sys.exit(1)
WS_URL = sys.argv[1]
COMMAND = " ".join(sys.argv[2:])
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
main_task = asyncio.ensure_future(
monitor_command_output(COMMAND, WS_URL), loop=loop
)
loop.add_signal_handler(SIGTERM, main_task.cancel)
try:
loop.run_until_complete(main_task)
finally:
loop.close()
@@ -0,0 +1,30 @@
{
"KPackageStructure": "Plasma/Applet",
"KPlugin": {
"Authors": [
{
"Email": "luisbocanegra17b@gmail.com",
"Name": "Luis Bocanegra"
}
],
"BugReportUrl": "https://github.com/luisbocanegra/kurve/issues",
"Category": "System Information",
"Description": "Audio visualizer powered by CAVA",
"Description[nl]": "Audiovisualisatie, gebaseerd op CAVA",
"Description[ru]": "Аудио-визуализатор на базе CAVA",
"Description[sv]": "Ljudvisualisering som använder CAVA",
"Description[zh_CN]": "基于CAVA的音频可视化器",
"EnabledByDefault": false,
"Icon": "waveform-symbolic",
"Id": "luisbocanegra.audio.visualizer",
"License": "GPL-3.0",
"Name": "Kurve",
"Name[nl]": "Kurve",
"Name[ru]": "Kurve",
"Name[sv]": "Kurve",
"Name[zh_CN]": "Kurve",
"Version": "3.3.0",
"Website": "https://github.com/luisbocanegra/kurve"
},
"X-Plasma-API-Minimum-Version": "6.0"
}
@@ -0,0 +1,53 @@
# Translate
## Status
| Locale | Lines | % Done|
|----------|---------|-------|
| Template | 156 | |
| nl | 148/156 | 94% |
| ru | 153/156 | 98% |
| sv | 148/156 | 94% |
| zh_CN | 156/156 | 100% |
## New Translations
* Fill out [`template.pot`](template.pot) with your translations then open a [new issue](https://github.com/luisbocanegra/kurve/issues/new), name the file `spanish.txt`, attach the txt file to the issue (drag and drop).
Or if you know how to make a pull request
* Copy the `template.pot` file and name it your locale's code (Eg: `en`/`de`/`fr`) with the extension `.po`. Then fill out all the `msgstr ""`.
* Your region's locale code can be found at: https://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes/28357857#28357857
## Scripts
Zren's `kpac` script can easily run the `gettext` commands for you, parsing the `metadata.json` and filling out any placeholders for you. `kpac` can be [downloaded here](https://github.com/Zren/plasma-applet-lib/blob/master/kpac) and should be placed at `~/Code/plasmoid-widgetname/kpac` to edit translations at `~/Code/plasmoid-widgetname/package/translate/`.
* `python3 ./kpac i18n` will parse the `i18n()` calls in the `*.qml` files and write it to the `template.pot` file. Then it will merge any changes into the `*.po` language files. Then it converts the `*.po` files to it's binary `*.mo` version and move it to `contents/locale/...` which will bundle the translations in the `*.plasmoid` without needing the user to manually install them.
* `python3 ./kpac localetest` will convert the `.po` to the `*.mo` files then run `plasmoidviewer` (part of `plasma-sdk`).
## How it works
Since KDE Frameworks v5.37, translations can be bundled with the zipped `*.plasmoid` file downloaded from the store.
* `xgettext` extracts the messages from the source code into a `template.pot`.
* Translators copy the `template.pot` to `fr.po` to translate the French language.
* When the source code is updated, we use `msgmerge` to update the `fr.po` based on the updated `template.pot`.
* When testing or releasing the widget, we convert the `.po` files to their binary `.mo` form with `msgfmt`.
The binary `.mo` translation files are placed in `package/contents/locale/` so you may want to add `*.mo` to your `.gitignore`.
```
package/contents/locale/fr/LC_MESSAGES/plasma_applet_luisbocanegra.audio.visualizer.mo
```
## Links
* https://develop.kde.org/docs/plasma/widget/translations-i18n/
* https://l10n.kde.org/stats/gui/trunk-kf5/team/fr/plasma-desktop/
* https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems
* https://api.kde.org/frameworks/ki18n/html/prg_guide.html
> Version 8 of [Zren's i18n scripts](https://github.com/Zren/plasma-applet-lib).
@@ -0,0 +1,776 @@
# Translation of visualizer in nl
# Copyright (C) 2025
# This file is distributed under the same license as the visualizer package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: visualizer\n"
"Report-Msgid-Bugs-To: https://github.com/luisbocanegra/kurve\n"
"POT-Creation-Date: 2025-12-13 18:30+0000\n"
"PO-Revision-Date: 2025-11-12 10:33+0100\n"
"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
"Language-Team: \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.8\n"
#: ../metadata.json
msgid "Kurve"
msgstr "Kurve"
#: ../metadata.json
msgid "Audio visualizer powered by CAVA"
msgstr "Audiovisualisatie, gebaseerd op CAVA"
#: ../contents/config/config.qml
msgid "Visualizer"
msgstr "Visualisatie"
#: ../contents/config/config.qml ../contents/ui/configCava.qml
msgid "General"
msgstr "Algemeen"
#: ../contents/ui/FullRepresentation.qml
msgid "Oh no! Something went wrong"
msgstr "Oeps, er is iets misgegaan…"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Stop CAVA"
msgstr "CAVA stoppen"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Start CAVA"
msgstr "CAVA starten"
#: ../contents/ui/FullRepresentation.qml
msgid "Copy to clipboard"
msgstr "Kopiëren naar klembord"
#: ../contents/ui/components/ColorPickerGradientList.qml
#: ../contents/ui/components/ColorPickerList.qml
#: ../contents/ui/components/FormColors.qml
msgid "Widget background"
msgstr "Widgetachtergrond"
#: ../contents/ui/components/Eq.qml
msgid "Remove band"
msgstr "Bandbreedte verwijderen"
#: ../contents/ui/components/Eq.qml
msgid "Add band"
msgstr "Bandbreedte toevoegen"
#: ../contents/ui/components/Eq.qml
msgid "Flat response"
msgstr "Plat antwoord"
#: ../contents/ui/components/FormColors.qml
msgid "Color"
msgstr "Kleur"
#: ../contents/ui/components/FormColors.qml
msgid "Animation:"
msgstr "Animatie:"
#: ../contents/ui/components/FormColors.qml
msgid "Interval (ms):"
msgstr "Tussenpoos (in ms):"
#: ../contents/ui/components/FormColors.qml
msgid "Smoothing (ms):"
msgstr "Vloeiende overgangen (in ms):"
#: ../contents/ui/components/FormColors.qml
msgid "Color source:"
msgstr "Kleurbron:"
#: ../contents/ui/components/FormColors.qml
msgid "Custom"
msgstr "Zelfgekozen"
#: ../contents/ui/components/FormColors.qml
msgid "A fixed custom color"
msgstr "Een zelfgekozen kleur"
#: ../contents/ui/components/FormColors.qml
msgid "System"
msgstr "Systeemthema"
#: ../contents/ui/components/FormColors.qml
msgid "Color that updates with your System theme"
msgstr "De kleur van het systeemthema"
#: ../contents/ui/components/FormColors.qml
msgid "Custom list"
msgstr "Zelfgemaakte lijst"
#: ../contents/ui/components/FormColors.qml
msgid ""
"Define a list of colors that will be applied to all the elements, wraps "
"around if there are more elements than colors"
msgstr ""
"Stel een lijst samen met kleuren die worden toegepast op alle elementen - in "
"herhaalde volgorde indien er meer elementen dat kleuren zijn"
#: ../contents/ui/components/FormColors.qml
msgid "Random"
msgstr "Willekeurig"
#: ../contents/ui/components/FormColors.qml
msgid "Random color for each element"
msgstr "Gebruik een willekeurige kleur per element"
#: ../contents/ui/components/FormColors.qml
msgid "Follow"
msgstr "Volgen"
#: ../contents/ui/components/FormColors.qml
msgid "Follow the color of a parent element"
msgstr "Gebruik de kleur van het hoofdelement"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient"
msgstr "Verloop"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal or vertical customizable color gradient"
msgstr "Een horizontaal of verticaal kleurverloop, naar eigen inzicht"
#: ../contents/ui/components/FormColors.qml
msgid "Image"
msgstr "Afbeelding"
#: ../contents/ui/components/FormColors.qml
msgid ""
"High resolution images can slow down the desktop when the panel or widget "
"size changes!"
msgstr ""
"Let op: afbeeldingen met een hoge resolutie kunnen voor vertraging zorgen!"
#: ../contents/ui/components/FormColors.qml
msgid "Hue"
msgstr "Tint"
#: ../contents/ui/components/FormColors.qml
msgid "Automatically shifted color for each bar"
msgstr "Voorzie elke balk van een eigen tint"
#: ../contents/ui/components/FormColors.qml
msgid "Element:"
msgstr "Element:"
#: ../contents/ui/components/FormColors.qml
msgid "Panel background"
msgstr "Paneelachtergrond"
#: ../contents/ui/components/FormColors.qml
msgid "Tray widget background"
msgstr "Systeemvakachtergrond"
#: ../contents/ui/components/FormColors.qml
msgid "Positioning:"
msgstr "Uitlijning:"
#: ../contents/ui/components/FormColors.qml
msgid "Stretch"
msgstr "Uitgerekt"
#: ../contents/ui/components/FormColors.qml
msgid "Tile"
msgstr "Tegelen"
#: ../contents/ui/components/FormColors.qml
msgid "Scaled and Cropped"
msgstr "Bijsnijden en inpassen"
#: ../contents/ui/components/FormColors.qml
msgid "Image:"
msgstr "Afbeelding:"
#: ../contents/ui/components/FormColors.qml
msgid "Pick a image file"
msgstr "Kies een afbeelding"
#: ../contents/ui/components/FormColors.qml
msgid "Image files"
msgstr "Afbeeldingsbestanden"
#: ../contents/ui/components/FormColors.qml
msgid "All files"
msgstr "Alle bestanden"
#: ../contents/ui/components/FormColors.qml
msgid "Color:"
msgstr "Kleur:"
#: ../contents/ui/components/FormColors.qml
msgid "Color set:"
msgstr "Kleurenschema:"
#: ../contents/ui/components/FormColors.qml
msgid "Colors:"
msgstr "Kleuren:"
#: ../contents/ui/components/FormColors.qml
msgid "Range:"
msgstr "Bereik:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configVisualizer.qml
msgid "Orientation:"
msgstr "Oriëntatie:"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal"
msgstr "Horizontaal"
#: ../contents/ui/components/FormColors.qml
msgid "Vertical"
msgstr "Verticaal"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient:"
msgstr "Verloop:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configCava.qml
msgid "Reverse:"
msgstr "Verwisselen:"
#: ../contents/ui/components/FormColors.qml
msgid "Display colors the other way around"
msgstr "Kleuren omkeren"
#: ../contents/ui/components/FormColors.qml
msgid "Alpha:"
msgstr "Alfa:"
#: ../contents/ui/components/FormColors.qml
msgid "Contrast Correction"
msgstr "Contrastaanpassing"
#: ../contents/ui/components/FormColors.qml
msgid "Saturation:"
msgstr "Verzadiging:"
#: ../contents/ui/components/FormColors.qml
msgid "Lightness:"
msgstr "Helderheid:"
#: ../contents/ui/configCava.qml
msgid "Framerate:"
msgstr "Framesnelheid:"
#: ../contents/ui/configCava.qml
msgid "Number of bars:"
msgstr "Aantal balken:"
#: ../contents/ui/configCava.qml
msgid "Automatically calculated for the current visualizer style."
msgstr ""
"Dit wordt automatisch berekend aan de hand van de huidige visualisatie."
#: ../contents/ui/configCava.qml
msgid "Automatic sensitivity:"
msgstr "Automatische gevoeligheid:"
#: ../contents/ui/configCava.qml
msgid "Attempt to decrease sensitivity if the bars peak."
msgstr ""
"Probeer de gevoeligheid te verlagen zodra het maximale aantal balken bereikt "
"is."
#: ../contents/ui/configCava.qml
msgid "Sensitivity:"
msgstr "Gevoeligheidsgraad:"
#: ../contents/ui/configCava.qml
msgid ""
"Manual sensitivity in %.\n"
"If autosens is enabled, this will only be the initial value"
msgstr ""
"De handmatige gevoeligheidsgraad (in %).\n"
"Als de automatische modus is ingeschakeld, dan wordt dit als startwaarde "
"gebruikt."
#: ../contents/ui/configCava.qml
msgid "Frequency range (Hz):"
msgstr "Frequentiebereik (in Hz):"
#: ../contents/ui/configCava.qml
msgid "Min"
msgstr "Min."
#: ../contents/ui/configCava.qml
msgid "Max"
msgstr "Max."
#: ../contents/ui/configCava.qml
msgid ""
"Lower and higher cutoff frequencies for lowest and highest bars, the "
"bandwidth of the visualizer.\n"
"Note: Cava will automatically increase the higher cutoff if a too low band "
"is specified."
msgstr ""
"Lagere en hogere afbreekfrequenties voor de laagste en hoogste balken, en de "
"bandbreedte van de visualisatie.\n"
"Let op: Cava zal deze waarde automatisch verhogen als een te lage band is "
"opgegeven."
#: ../contents/ui/configCava.qml
msgid "Sleep timer (seconds):"
msgstr "Slapen na (in seconden):"
#: ../contents/ui/configCava.qml
msgid ""
"Seconds with no input before cava goes to sleep mode.\n"
"Cava will not perform FFT and only check for input once per second."
msgstr ""
"Het aantal seconden zonder invoer, waarna Cava in de slaapmodus gaat.\n"
"Cava zal geen FFT uitvoeren en controleert de invoer slechts eenmaal per "
"seconde."
#: ../contents/ui/configCava.qml
msgid "Pause on:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Full screen window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Maximized window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Input"
msgstr "Invoer"
#: ../contents/ui/configCava.qml
msgid "Method:"
msgstr "Methode:"
#: ../contents/ui/configCava.qml ../contents/ui/configGeneral.qml
msgid "Default"
msgstr "Standaard"
#: ../contents/ui/configCava.qml
msgid ""
"Audio capturing method.\n"
"Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' "
"or 'fifo', in that order, dependent on what support cava was built with.\n"
msgstr ""
"De audio-invoermethode.\n"
"Standaard wordt oss, pipewire, sndio, jack, pulse, alsa, "
"portaudio of fifo in vaste volgorde gebruikt. De gekozen methode is "
"afhankelijk van waar Cava mee gebouwd is.\r\n"
#: ../contents/ui/configCava.qml
msgid "Source:"
msgstr "Bron:"
#: ../contents/ui/configCava.qml
msgid ""
"For pulseaudio and pipewire 'source' will be the source. Default: 'auto', "
"which uses the monitor source of the default sink (all pulseaudio "
"sinks(outputs) have 'monitor' sources(inputs) associated with them)."
msgstr ""
"Voor PulseAudio en PipeWire wordt source gebruikt als de bron. Standaard "
"staat deze waarde op auto, welke de bron van de standaardsink zal "
"raadplegen (alle PulseAudio-sinks (uitvoeren) hebben monitor-bronnen "
"(invoeren))."
#: ../contents/ui/configCava.qml
msgid ""
"For pipewire 'source' will be the object name or object.serial of the device "
"to capture from. Both input and output devices are supported."
msgstr ""
"Voor PipeWire zal source de objectnaam of object.serial zijn van het te "
"gebruiken apparaat. Zowel in- als uitvoerapparaten kunnen worden gebruikt."
#: ../contents/ui/configCava.qml
msgid "For alsa 'source' will be the capture device."
msgstr "Voor Alsa zal source het opnameapparaat zijn."
#: ../contents/ui/configCava.qml
msgid "For fifo 'source' will be the path to fifo-file."
msgstr "Voor fifo zal source de locatie van het fifo-bestand zijn."
#: ../contents/ui/configCava.qml
msgid ""
"For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:"
"EE:FF' will be squeezelite's MAC address."
msgstr ""
"Voor shmem zal source /squeezelite-AA:BB:CC:DD:EE:FF zijn, waar AA:BB:CC:"
"DD:EE:FF staat voor squeezelites MAC-adres."
#: ../contents/ui/configCava.qml
msgid ""
"For sndio 'source' will be a raw recording audio descriptor or a monitoring "
"sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'."
msgstr ""
"Voor sndio zal source een opname-audiobeschrijving of een monitorend "
"subapparaat zijn, bijv. rsnd/2 of snd/1. Standaardwaarde: default."
#: ../contents/ui/configCava.qml
msgid ""
"For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. "
"Default: '/dev/dsp', i.e. the default audio device."
msgstr ""
"Voor oss zal source de locatie van een audioapparaat zijn, bijv. /dev/"
"dsp2. Standaardwaarde: /dev/dsp, oftewel het standaard audioapparaat."
#: ../contents/ui/configCava.qml
msgid ""
"For jack 'source' will be the name of the JACK server to connect to, e.g. "
"'foobar'. Default: 'default'."
msgstr ""
"Voor jack zal source de naam van de JACK-server zijn, bijv. foobar. "
"Standaardwaarde: default."
#: ../contents/ui/configCava.qml
msgid "Sample rate:"
msgstr "Samplesnelheid:"
#: ../contents/ui/configCava.qml
msgid "Only for fifo, pipewire, sndio, oss"
msgstr "Alleen van toepassing op fifo, pipewire, sndio en oss"
#: ../contents/ui/configCava.qml
msgid "Sample bits:"
msgstr "Samplebits:"
#: ../contents/ui/configCava.qml
msgid "Channels:"
msgstr "Aantal kanalen:"
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6, sndio, oss, jack"
msgstr "Alleen van toepassing op pipewire en cava>=0.10.6, sndio, oss en jack"
#: ../contents/ui/configCava.qml
msgid "Auto connect:"
msgstr "Automatisch verbinden:"
#: ../contents/ui/configCava.qml
msgid "Only for jack"
msgstr "Alleen van toepassing op jack"
#: ../contents/ui/configCava.qml
msgid "Active:"
msgstr "Actief:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Force the node to always process. Useful "
"for monitoring sources when no other application is active."
msgstr ""
"Alleen van toepassing op pipewire en cava>=0.10.6. Dwing het bijwerken van "
"het knooppunt af. Handig voor het monitoren als er geen ander programma "
"geopend is."
#: ../contents/ui/configCava.qml
msgid "Remix:"
msgstr "Remixen:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Allow to remix audio channels to match "
"cava's channel count. Useful for surround sound."
msgstr ""
"Alleen van toepassing op pipewire en cava>=0.10.6. Sta het remixen van "
"audiokanalen toe om overeen te komen met cava's kanalen. Handig voor "
"surroundsound."
#: ../contents/ui/configCava.qml
msgid "Virtual:"
msgstr "Virtueel:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Set the node to virtual, to avoid "
"recording notifications from the DE."
msgstr ""
"Alleen van toepassing op pipewire en cava>=0.10.6. Maak het knooppunt "
"virtueel om opnamemeldingen van de werkomgeving te onderdrukken."
#: ../contents/ui/configCava.qml
msgid "Output"
msgstr "Uitvoer"
#: ../contents/ui/configCava.qml
msgid "Visual channels:"
msgstr "Visuele kanalen:"
#: ../contents/ui/configCava.qml
msgid ""
"'mono' outputs left to right lowest to highest frequencies.\n"
"'stereo' mirrors both channels with low frequencies in center."
msgstr ""
"mono geeft uitvoer van links naar rechts, met de laagste tot hoogste "
"frequenties;\n"
"stereo geeft uitvoer op beide kanalen, met de laatste frequenties in het "
"midden."
#: ../contents/ui/configCava.qml
msgid "Mono channel:"
msgstr "Monokanaal:"
#: ../contents/ui/configCava.qml
msgid "Display frequencies the other way around"
msgstr "Verwissel de frequenties van plek"
#: ../contents/ui/configCava.qml
msgid "Smoothing"
msgstr "Vloeiendheid"
#: ../contents/ui/configCava.qml
msgid "Noise reduction:"
msgstr "Ruisonderdrukking:"
#: ../contents/ui/configCava.qml
msgid ""
"The raw visualization is very noisy, this factor adjusts the integral and "
"gravity filters to keep the signal smooth.\n"
"100 will be very slow and smooth, 0 will be fast but noisy."
msgstr ""
"De standaardvisualisatie bevat erg veel ruis. Met behulp van deze factor kun "
"je de filters afstellen om het signaal vloeiend door te laten komen.\n"
"100 is erg langzaam, maar wel vloeiend; 0 is snel, maar met veel ruis."
#: ../contents/ui/configCava.qml
msgid "Monstercat:"
msgstr "Monstercat:"
#: ../contents/ui/configCava.qml
msgid ""
"Enable the so-called \"Monstercat smoothing\" with or without \"waves\"."
msgstr ""
"Schakel de zogeheten Monstercat-vloeiendheid in, met of zonder golven."
#: ../contents/ui/configCava.qml
msgid "Waves"
msgstr "Golven"
#: ../contents/ui/configCava.qml
msgid "Equalizer"
msgstr "Equalizer"
#: ../contents/ui/configCava.qml
msgid "Enabled:"
msgstr "Ingeschakeld:"
#: ../contents/ui/configCava.qml
msgid ""
"Adjust frequencies by a multiplication factor, more bands equals more "
"precision."
msgstr ""
"Pas frequenties aan de hand van een vermenigvuldigingsfactoor aan. Meer "
"banden = preciezer."
#: ../contents/ui/configGeneral.qml
msgid "Auto-hide when idle:"
msgstr "Automatisch verbergen na inactiviteit:"
#: ../contents/ui/configGeneral.qml
msgid "After"
msgstr "Na"
#: ../contents/ui/configGeneral.qml
msgid "seconds"
msgstr "seconden"
#: ../contents/ui/configGeneral.qml
msgid "Desktop background:"
msgstr "Bureaubladachtergrond:"
#: ../contents/ui/configGeneral.qml
msgid "Transparent"
msgstr "Doorzichtig"
#: ../contents/ui/configGeneral.qml
msgid "Transparent with shadow"
msgstr "Doorzichtig, met schaduw"
#: ../contents/ui/configGeneral.qml
msgid "Disable tooltip:"
msgstr "Hulpballon uitschakelen"
#: ../contents/ui/configGeneral.qml
msgid "Disable ToolTip that shows the widget name and description."
msgstr "Schakel de hulpballon uit de de widgetnaam en -beschrijving toont."
#: ../contents/ui/configGeneral.qml
msgid "Disable left click:"
msgstr "Linksklikken uitschakelen:"
#: ../contents/ui/configGeneral.qml
msgid "Applet popup will still be accessible from the right click menu."
msgstr "De pop-up is nog toegankelijk via het rechtermuisknopmenu."
#: ../contents/ui/configGeneral.qml
msgid "Troubleshooting"
msgstr "Probleemoplossing"
#: ../contents/ui/configGeneral.qml
msgid "Debug mode:"
msgstr "Foutopsporingsmodus:"
#: ../contents/ui/configGeneral.qml
msgid ""
"Print additional messages to the system log. To make it easier to spot "
"sizing issues, this will also force full height to the first visualizer bar "
"and displays a grid over it."
msgstr ""
"Toon aanvullende berichten in het systeemlogboek. Ook wordt de eerste "
"visualisatiebalk groter gemaakt en voorzien van een rooster, zodat problemen "
"makkelijker te zien zijn."
#: ../contents/ui/configVisualizer.qml
msgid "height"
msgstr "hoogte"
#: ../contents/ui/configVisualizer.qml
msgid "width"
msgstr "breedte"
#: ../contents/ui/configVisualizer.qml
msgid "Style:"
msgstr "Stijl:"
#: ../contents/ui/configVisualizer.qml
msgid "Bars"
msgstr "Balken"
#: ../contents/ui/configVisualizer.qml
msgid "Wave"
msgstr "Golf"
#: ../contents/ui/configVisualizer.qml
msgid "Blocks"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Fill wave"
msgstr "Opgevulde golf"
#: ../contents/ui/configVisualizer.qml
msgid "Draw inactive blocks"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Circle mode:"
msgstr "Cirkelmodus:"
#: ../contents/ui/configVisualizer.qml
msgid "Circle size:"
msgstr "Cirkelomvang:"
#: ../contents/ui/configVisualizer.qml
msgid "Top"
msgstr "Bovenkant"
#: ../contents/ui/configVisualizer.qml
msgid "Bottom"
msgstr "Onderkant"
#: ../contents/ui/configVisualizer.qml
msgid "Left"
msgstr "Links"
#: ../contents/ui/configVisualizer.qml
msgid "Right"
msgstr "Rechts"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel %1:"
msgstr "Paneel %1 opvullen:"
#: ../contents/ui/configVisualizer.qml
msgid "Fixed %1:"
msgstr "Vaste %1:"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel thickness:"
msgstr "Paneelopvulling:"
#: ../contents/ui/configVisualizer.qml
msgid "Centered bars:"
msgstr "Gecentreerde balken:"
#: ../contents/ui/configVisualizer.qml
msgid "Rounded bars:"
msgstr "Afgeronde balken:"
#: ../contents/ui/configVisualizer.qml
msgid "Line width:"
msgstr "Lijnbreedte:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar width:"
msgstr "Balkbreedte:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar gap:"
msgstr "Ruimte tussen balken:"
#: ../contents/ui/configVisualizer.qml
msgid "Block height:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Block gap:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave Color"
msgstr "Golfkleur"
#: ../contents/ui/configVisualizer.qml
msgid "Bar Color"
msgstr "Balkkleur"
#: ../contents/ui/configVisualizer.qml
msgid "Inactive block color"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave Fill Color"
msgstr "Opgevulde golfkleur"
#: ../contents/ui/main.qml
msgid "Show support information"
msgstr "Ondersteuningsinformatie tonen"
#~ msgid ""
#~ "The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' "
#~ "can be configured for some input methods:"
#~ msgstr ""
#~ "De opties sample_rate, sample_bits, channels en autoconnect "
#~ "kunnen bij sommige invoermethoden nauwkeurig worden afgesteld:"
#~ msgid "Other methods ignore these settings."
#~ msgstr "Andere methoden negeren deze instellingen."
#~ msgid ""
#~ "For 'sndio' and 'oss' they are only preferred values, i.e. if the values "
#~ "are not supported by the chosen audio device, the device will use other "
#~ "supported values instead."
#~ msgstr ""
#~ "Voor sndio en oss zijn dit slechts voorkeurswaarden. Als de waarden "
#~ "niet ondersteund worden door het gekozen audioapparaat, zullen de andere, "
#~ "ondersteunde waarden worden gebruikt."
#~ msgid ""
#~ "Adjust frequencies by a multiplication factor, more bars equals more "
#~ "precision."
#~ msgstr ""
#~ "Pas de frequenties aan met een vermenigvuldigingsfactor. Meer balken = "
#~ "meer precisie."
#~ msgid "Waves:"
#~ msgstr "Golven:"
@@ -0,0 +1,747 @@
# Translation of visualizer in ru
# Copyright (C) 2025
# This file is distributed under the same license as the visualizer package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: visualizer\n"
"Report-Msgid-Bugs-To: https://github.com/luisbocanegra/kurve\n"
"POT-Creation-Date: 2025-12-13 18:30+0000\n"
"PO-Revision-Date: 2025-11-15 03:51+0200\n"
"Last-Translator: Mykjailo Aleksieiev <mishanya.bigimot@gmail.com>\n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.8\n"
#: ../metadata.json
msgid "Kurve"
msgstr "Kurve"
#: ../metadata.json
msgid "Audio visualizer powered by CAVA"
msgstr "Аудио-визуализатор на базе CAVA"
#: ../contents/config/config.qml
msgid "Visualizer"
msgstr "Визуализатор"
#: ../contents/config/config.qml ../contents/ui/configCava.qml
msgid "General"
msgstr "Основное"
#: ../contents/ui/FullRepresentation.qml
msgid "Oh no! Something went wrong"
msgstr "О нет! Что-то пошло не так"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Stop CAVA"
msgstr "Остановить CAVA"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Start CAVA"
msgstr "Запустить CAVA"
#: ../contents/ui/FullRepresentation.qml
msgid "Copy to clipboard"
msgstr "Скопировать в буфер обмена"
#: ../contents/ui/components/ColorPickerGradientList.qml
#: ../contents/ui/components/ColorPickerList.qml
#: ../contents/ui/components/FormColors.qml
msgid "Widget background"
msgstr "Задний фон виджета"
#: ../contents/ui/components/Eq.qml
msgid "Remove band"
msgstr "Удалить полосу"
#: ../contents/ui/components/Eq.qml
msgid "Add band"
msgstr "Добавить полосу"
#: ../contents/ui/components/Eq.qml
msgid "Flat response"
msgstr "Плоская АЧХ"
#: ../contents/ui/components/FormColors.qml
msgid "Color"
msgstr "Цвет"
#: ../contents/ui/components/FormColors.qml
msgid "Animation:"
msgstr "Анимация:"
#: ../contents/ui/components/FormColors.qml
msgid "Interval (ms):"
msgstr "Интервал (мс):"
#: ../contents/ui/components/FormColors.qml
msgid "Smoothing (ms):"
msgstr "Сглаживание (мс):"
#: ../contents/ui/components/FormColors.qml
msgid "Color source:"
msgstr "Источник цвета:"
#: ../contents/ui/components/FormColors.qml
msgid "Custom"
msgstr "Свой источник"
#: ../contents/ui/components/FormColors.qml
msgid "A fixed custom color"
msgstr "Свой фиксированный цвет"
#: ../contents/ui/components/FormColors.qml
msgid "System"
msgstr "Система"
#: ../contents/ui/components/FormColors.qml
msgid "Color that updates with your System theme"
msgstr "Цвет который обновляется вместе с вашей темой системы"
#: ../contents/ui/components/FormColors.qml
msgid "Custom list"
msgstr "Свой список"
#: ../contents/ui/components/FormColors.qml
msgid ""
"Define a list of colors that will be applied to all the elements, wraps "
"around if there are more elements than colors"
msgstr ""
"Задайте список цветов, которые будут применяться ко всем элементам; если "
"элементов больше, чем цветов, список повторяется"
#: ../contents/ui/components/FormColors.qml
msgid "Random"
msgstr "Случайный"
#: ../contents/ui/components/FormColors.qml
msgid "Random color for each element"
msgstr "Случайный цвет для каждого элемента"
#: ../contents/ui/components/FormColors.qml
msgid "Follow"
msgstr "Следовать"
#: ../contents/ui/components/FormColors.qml
msgid "Follow the color of a parent element"
msgstr "Следовать цвету родительского элемента"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient"
msgstr "Градиент"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal or vertical customizable color gradient"
msgstr "Горизонтальный или вертикальный цветовой градиент"
#: ../contents/ui/components/FormColors.qml
msgid "Image"
msgstr "Изображение"
#: ../contents/ui/components/FormColors.qml
msgid ""
"High resolution images can slow down the desktop when the panel or widget "
"size changes!"
msgstr ""
"Изображения высокого качества могут замедлить рабочий стол, когда размер "
"панели или виджета меняется!"
#: ../contents/ui/components/FormColors.qml
msgid "Hue"
msgstr "Оттенок"
#: ../contents/ui/components/FormColors.qml
msgid "Automatically shifted color for each bar"
msgstr "Автоматически меняющийся цвет для каждого столбика"
#: ../contents/ui/components/FormColors.qml
msgid "Element:"
msgstr "Элемент:"
#: ../contents/ui/components/FormColors.qml
msgid "Panel background"
msgstr "Задний фон панели"
#: ../contents/ui/components/FormColors.qml
msgid "Tray widget background"
msgstr "Задний фон виджета в трее"
#: ../contents/ui/components/FormColors.qml
msgid "Positioning:"
msgstr "Расположение:"
#: ../contents/ui/components/FormColors.qml
msgid "Stretch"
msgstr "Растянутое"
#: ../contents/ui/components/FormColors.qml
msgid "Tile"
msgstr "Плитка"
#: ../contents/ui/components/FormColors.qml
msgid "Scaled and Cropped"
msgstr "Масштабированный и обрезанный"
#: ../contents/ui/components/FormColors.qml
msgid "Image:"
msgstr "Изображение:"
#: ../contents/ui/components/FormColors.qml
msgid "Pick a image file"
msgstr "Выберите файл изображения"
#: ../contents/ui/components/FormColors.qml
msgid "Image files"
msgstr "Файлы изображений"
#: ../contents/ui/components/FormColors.qml
msgid "All files"
msgstr "Все файлы"
#: ../contents/ui/components/FormColors.qml
msgid "Color:"
msgstr "Цвет:"
#: ../contents/ui/components/FormColors.qml
msgid "Color set:"
msgstr "Набор цветов:"
#: ../contents/ui/components/FormColors.qml
msgid "Colors:"
msgstr "Цвета:"
#: ../contents/ui/components/FormColors.qml
msgid "Range:"
msgstr "Диапазон:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configVisualizer.qml
msgid "Orientation:"
msgstr "Ориентация:"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal"
msgstr "Горизонтальная"
#: ../contents/ui/components/FormColors.qml
msgid "Vertical"
msgstr "Вертикальная"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient:"
msgstr "Градиент:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configCava.qml
msgid "Reverse:"
msgstr "Реверс:"
#: ../contents/ui/components/FormColors.qml
msgid "Display colors the other way around"
msgstr "Отображает цвета в обратном порядке"
#: ../contents/ui/components/FormColors.qml
msgid "Alpha:"
msgstr "Прозрачность:"
#: ../contents/ui/components/FormColors.qml
msgid "Contrast Correction"
msgstr "Коррекция контраста"
#: ../contents/ui/components/FormColors.qml
msgid "Saturation:"
msgstr "Насыщенность:"
#: ../contents/ui/components/FormColors.qml
msgid "Lightness:"
msgstr "Яркость:"
#: ../contents/ui/configCava.qml
msgid "Framerate:"
msgstr "Частота кадров:"
#: ../contents/ui/configCava.qml
msgid "Number of bars:"
msgstr "Количество столбиков:"
#: ../contents/ui/configCava.qml
msgid "Automatically calculated for the current visualizer style."
msgstr "Автоматически рассчитано под текущий стиль визуализатора."
#: ../contents/ui/configCava.qml
msgid "Automatic sensitivity:"
msgstr "Авто-чувствительность:"
#: ../contents/ui/configCava.qml
msgid "Attempt to decrease sensitivity if the bars peak."
msgstr ""
"Пытается понизить чувствительность, если все столбики на пиковой высоте."
#: ../contents/ui/configCava.qml
msgid "Sensitivity:"
msgstr "Чувствительность:"
#: ../contents/ui/configCava.qml
msgid ""
"Manual sensitivity in %.\n"
"If autosens is enabled, this will only be the initial value"
msgstr ""
"Ручная чувствительность в %\n"
"Если включена авто-чувствительность, это будет лишь изначальным значением"
#: ../contents/ui/configCava.qml
msgid "Frequency range (Hz):"
msgstr "Частотный диапазон (Гц):"
#: ../contents/ui/configCava.qml
msgid "Min"
msgstr "Мин"
#: ../contents/ui/configCava.qml
msgid "Max"
msgstr "Макс"
#: ../contents/ui/configCava.qml
msgid ""
"Lower and higher cutoff frequencies for lowest and highest bars, the "
"bandwidth of the visualizer.\n"
"Note: Cava will automatically increase the higher cutoff if a too low band "
"is specified."
msgstr ""
"Нижняя и верхняя частоты среза для самой низкой и самой высокой полосы, "
"ширина полос визуализатора.\n"
"Примечание: Cava автоматически увеличит верхнюю частоту среза, если указана "
"слишком низкая полоса."
#: ../contents/ui/configCava.qml
msgid "Sleep timer (seconds):"
msgstr "Таймер сна (в секундах):"
#: ../contents/ui/configCava.qml
msgid ""
"Seconds with no input before cava goes to sleep mode.\n"
"Cava will not perform FFT and only check for input once per second."
msgstr ""
"Время в секундах без ввода, после которого CAVA отправится в режим сна.\n"
"CAVA не будет осуществлять БПФ, и просто проверяет ввод раз в секунду."
#: ../contents/ui/configCava.qml
msgid "Pause on:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Full screen window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Maximized window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Input"
msgstr "Ввод"
#: ../contents/ui/configCava.qml
msgid "Method:"
msgstr "Метод:"
#: ../contents/ui/configCava.qml ../contents/ui/configGeneral.qml
msgid "Default"
msgstr "По умолчанию"
#: ../contents/ui/configCava.qml
msgid ""
"Audio capturing method.\n"
"Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' "
"or 'fifo', in that order, dependent on what support cava was built with.\n"
msgstr ""
"Метод захвата аудио.\n"
"По умолчанию будет выбран 'oss', 'pipewire', 'sndio', 'jack', 'pulse', "
"'alsa', 'portaudio' или 'fifo', в таком порядке, в зависимости от опций, с "
"какими был собран CAVA.\n"
#: ../contents/ui/configCava.qml
msgid "Source:"
msgstr "Источник:"
#: ../contents/ui/configCava.qml
msgid ""
"For pulseaudio and pipewire 'source' will be the source. Default: 'auto', "
"which uses the monitor source of the default sink (all pulseaudio "
"sinks(outputs) have 'monitor' sources(inputs) associated with them)."
msgstr ""
"Для pulseaudio и pipewire source’ будет являться источником. По умолчанию: "
"‘auto’, что использует мониторинг источника стандартного выхода (у всех "
"выходов PulseAudio есть соответствующие ‘monitor’ источники(вводы))."
#: ../contents/ui/configCava.qml
msgid ""
"For pipewire 'source' will be the object name or object.serial of the device "
"to capture from. Both input and output devices are supported."
msgstr ""
"Для pipewire source’ будет именем объекта или object.serial устройства, с "
"которого осуществляется захват. Поддерживаются как входные, так и выходные "
"устройства."
#: ../contents/ui/configCava.qml
msgid "For alsa 'source' will be the capture device."
msgstr "Для alsa 'source' будет устройством захвата."
#: ../contents/ui/configCava.qml
msgid "For fifo 'source' will be the path to fifo-file."
msgstr "Для fifo 'source' будет путём к fifo-файлу."
#: ../contents/ui/configCava.qml
msgid ""
"For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:"
"EE:FF' will be squeezelite's MAC address."
msgstr ""
"Для shmem 'source' будет /squeezelite-AA:BB:CC:DD:EE:FF, где AA:BB:CC:DD:EE:"
"FF' это MAC-адрес squeezelite."
#: ../contents/ui/configCava.qml
msgid ""
"For sndio 'source' will be a raw recording audio descriptor or a monitoring "
"sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'."
msgstr ""
"Для sndio source’ — это дескриптор записи или подустройство мониторинга, "
"например, 'rsnd/2' или 'snd/1'. По умолчанию: 'default'."
#: ../contents/ui/configCava.qml
msgid ""
"For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. "
"Default: '/dev/dsp', i.e. the default audio device."
msgstr ""
"Для oss 'source' будет путём к устройству аудио, например '/dev/dsp2'. По "
"умолчанию: '/dev/dsp', или устройство аудио по умолчанию."
#: ../contents/ui/configCava.qml
msgid ""
"For jack 'source' will be the name of the JACK server to connect to, e.g. "
"'foobar'. Default: 'default'."
msgstr ""
"Для jack 'source' будет именем сервера JACK к котому надо подключиться, "
"например 'foobar'. По умолчанию: 'default'."
#: ../contents/ui/configCava.qml
msgid "Sample rate:"
msgstr "Частота дискретизации:"
#: ../contents/ui/configCava.qml
msgid "Only for fifo, pipewire, sndio, oss"
msgstr "Только для fifo, pipewire, sndio, oss"
#: ../contents/ui/configCava.qml
msgid "Sample bits:"
msgstr "Битовая глубина:"
#: ../contents/ui/configCava.qml
msgid "Channels:"
msgstr "Каналы:"
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6, sndio, oss, jack"
msgstr "Только для pipewire & cava>=0.10.6, sndio, jack"
#: ../contents/ui/configCava.qml
msgid "Auto connect:"
msgstr "Авто-подключение:"
#: ../contents/ui/configCava.qml
msgid "Only for jack"
msgstr "Только для jack"
#: ../contents/ui/configCava.qml
msgid "Active:"
msgstr "Активный:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Force the node to always process. Useful "
"for monitoring sources when no other application is active."
msgstr ""
"Только для pipewire & cava>=0.10.6. Принудительно заставляет узел (node) "
"всегда обрабатывать сигнал. Полезно для мониторинга источников, когда другие "
"приложения не активны."
#: ../contents/ui/configCava.qml
msgid "Remix:"
msgstr "Ремикс:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Allow to remix audio channels to match "
"cava's channel count. Useful for surround sound."
msgstr ""
"Только для pipewire & cava>=0.10.6. Разрешить перемешивание аудиоканалов для "
"соответствия количеству каналов Cava. Полезно для многоканального звука."
#: ../contents/ui/configCava.qml
msgid "Virtual:"
msgstr "Виртуальный:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Set the node to virtual, to avoid "
"recording notifications from the DE."
msgstr ""
"Только для pipewire & cava>=0.10.6. Переключить узел в виртуальный, чтобы "
"избежать записи звука уведомлений."
#: ../contents/ui/configCava.qml
msgid "Output"
msgstr "Вывод"
#: ../contents/ui/configCava.qml
msgid "Visual channels:"
msgstr "Визуальные каналы:"
#: ../contents/ui/configCava.qml
msgid ""
"'mono' outputs left to right lowest to highest frequencies.\n"
"'stereo' mirrors both channels with low frequencies in center."
msgstr ""
"В режиме ‘mono’ выводится слева направо от низких к высоким частотам.\n"
"В режиме ‘stereo’ оба канала отображаются зеркально, низкие частоты "
"находятся по центру."
#: ../contents/ui/configCava.qml
msgid "Mono channel:"
msgstr "Моно-канал:"
#: ../contents/ui/configCava.qml
msgid "Display frequencies the other way around"
msgstr "Отображать частоты наоборот"
#: ../contents/ui/configCava.qml
msgid "Smoothing"
msgstr "Сглаживание"
#: ../contents/ui/configCava.qml
msgid "Noise reduction:"
msgstr "Шумоподавление:"
#: ../contents/ui/configCava.qml
msgid ""
"The raw visualization is very noisy, this factor adjusts the integral and "
"gravity filters to keep the signal smooth.\n"
"100 will be very slow and smooth, 0 will be fast but noisy."
msgstr ""
"Сырая визуализация очень шумная, этот параметр настраивает интегральный и "
"гравитационный фильтры, чтобы удерживать сигнал плавным.\n"
"Значение 100 сделает движение очень медленным и гладким, значение 0 — "
"быстрым, но шумным."
#: ../contents/ui/configCava.qml
msgid "Monstercat:"
msgstr "Monstercat:"
#: ../contents/ui/configCava.qml
msgid ""
"Enable the so-called \"Monstercat smoothing\" with or without \"waves\"."
msgstr "Включает так называемое \"Monstercat сглаживание\" с или без \"волн\"."
#: ../contents/ui/configCava.qml
msgid "Waves"
msgstr "Волны"
#: ../contents/ui/configCava.qml
msgid "Equalizer"
msgstr "Эквалайзер"
#: ../contents/ui/configCava.qml
msgid "Enabled:"
msgstr "Включён:"
#: ../contents/ui/configCava.qml
msgid ""
"Adjust frequencies by a multiplication factor, more bands equals more "
"precision."
msgstr ""
"Корректирует частоты с помощью коэффициента умножения; большее число полос "
"означает более высокую точность."
#: ../contents/ui/configGeneral.qml
msgid "Auto-hide when idle:"
msgstr "Автосокрытие при неактивности:"
#: ../contents/ui/configGeneral.qml
msgid "After"
msgstr "После"
#: ../contents/ui/configGeneral.qml
msgid "seconds"
msgstr "секунд"
#: ../contents/ui/configGeneral.qml
msgid "Desktop background:"
msgstr "Задний фон рабочего стола:"
#: ../contents/ui/configGeneral.qml
msgid "Transparent"
msgstr "Прозрачный"
#: ../contents/ui/configGeneral.qml
msgid "Transparent with shadow"
msgstr "Прозрачный с тенью"
#: ../contents/ui/configGeneral.qml
msgid "Disable tooltip:"
msgstr "Отключить всплывающую подсказку:"
#: ../contents/ui/configGeneral.qml
msgid "Disable ToolTip that shows the widget name and description."
msgstr ""
"Отключает всплывающую подсказку, которая показывает имя виджета и его "
"описание."
#: ../contents/ui/configGeneral.qml
msgid "Disable left click:"
msgstr "Отключить левый клик:"
#: ../contents/ui/configGeneral.qml
msgid "Applet popup will still be accessible from the right click menu."
msgstr ""
"Всплывающее окно апплета всё ещё будет доступно из контекстного меню по "
"правому клику."
#: ../contents/ui/configGeneral.qml
msgid "Troubleshooting"
msgstr "Решение проблем"
#: ../contents/ui/configGeneral.qml
msgid "Debug mode:"
msgstr "Режим отладки:"
#: ../contents/ui/configGeneral.qml
msgid ""
"Print additional messages to the system log. To make it easier to spot "
"sizing issues, this will also force full height to the first visualizer bar "
"and displays a grid over it."
msgstr ""
"Выводить дополнительные сообщения в системный журнал. Чтобы упростить "
"обнаружение проблем с размерами, этот режим также принудительно "
"устанавливает полную высоту для первой полосы визуализатора и отображает "
"поверх неё сетку."
#: ../contents/ui/configVisualizer.qml
msgid "height"
msgstr "высота"
#: ../contents/ui/configVisualizer.qml
msgid "width"
msgstr "ширина"
#: ../contents/ui/configVisualizer.qml
msgid "Style:"
msgstr "Стиль:"
#: ../contents/ui/configVisualizer.qml
msgid "Bars"
msgstr "Столбики"
#: ../contents/ui/configVisualizer.qml
msgid "Wave"
msgstr "Волны"
#: ../contents/ui/configVisualizer.qml
msgid "Blocks"
msgstr "Блоки"
#: ../contents/ui/configVisualizer.qml
msgid "Fill wave"
msgstr "Заполнить волну"
#: ../contents/ui/configVisualizer.qml
msgid "Draw inactive blocks"
msgstr "Рисовать неактивные блоки"
#: ../contents/ui/configVisualizer.qml
msgid "Circle mode:"
msgstr "Круговой режим:"
#: ../contents/ui/configVisualizer.qml
msgid "Circle size:"
msgstr "Размер круга:"
#: ../contents/ui/configVisualizer.qml
msgid "Top"
msgstr "Сверху"
#: ../contents/ui/configVisualizer.qml
msgid "Bottom"
msgstr "Снизу"
#: ../contents/ui/configVisualizer.qml
msgid "Left"
msgstr "Слева"
#: ../contents/ui/configVisualizer.qml
msgid "Right"
msgstr "Справа"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel %1:"
msgstr "Заполнить панель %1:"
#: ../contents/ui/configVisualizer.qml
msgid "Fixed %1:"
msgstr "Фиксированная %1:"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel thickness:"
msgstr "Заполнить толщину панели:"
#: ../contents/ui/configVisualizer.qml
msgid "Centered bars:"
msgstr "Центрированные столбики:"
#: ../contents/ui/configVisualizer.qml
msgid "Rounded bars:"
msgstr "Закруглённые столбики:"
#: ../contents/ui/configVisualizer.qml
msgid "Line width:"
msgstr "Ширина линии:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar width:"
msgstr "Ширина столбика:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar gap:"
msgstr "Расстояние между столбиками:"
#: ../contents/ui/configVisualizer.qml
msgid "Block height:"
msgstr "Высота блока:"
#: ../contents/ui/configVisualizer.qml
msgid "Block gap:"
msgstr "Расстояние между блоками:"
#: ../contents/ui/configVisualizer.qml
msgid "Wave Color"
msgstr "Цвет волны"
#: ../contents/ui/configVisualizer.qml
msgid "Bar Color"
msgstr "Цвет столбика"
#: ../contents/ui/configVisualizer.qml
msgid "Inactive block color"
msgstr "Цвет неактивного блока"
#: ../contents/ui/configVisualizer.qml
msgid "Wave Fill Color"
msgstr "Цвет заливки волны"
#: ../contents/ui/main.qml
msgid "Show support information"
msgstr "Отображать информацию поддержки"
@@ -0,0 +1,768 @@
# Translation of visualizer in sv
# Copyright (C) 2025
# This file is distributed under the same license as the visualizer package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: visualizer\n"
"Report-Msgid-Bugs-To: https://github.com/luisbocanegra/kurve\n"
"POT-Creation-Date: 2025-12-13 18:30+0000\n"
"PO-Revision-Date: 2025-11-07 15:03+0000\n"
"Last-Translator: Mathias Mårtensson <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.6\n"
#: ../metadata.json
msgid "Kurve"
msgstr "Kurve"
#: ../metadata.json
msgid "Audio visualizer powered by CAVA"
msgstr "Ljudvisualisering som använder CAVA"
#: ../contents/config/config.qml
msgid "Visualizer"
msgstr "Visualiserare"
#: ../contents/config/config.qml ../contents/ui/configCava.qml
msgid "General"
msgstr "Allmänt"
#: ../contents/ui/FullRepresentation.qml
msgid "Oh no! Something went wrong"
msgstr "Åh nej! Något gick fel"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Stop CAVA"
msgstr "Stoppa CAVA"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Start CAVA"
msgstr "Starta CAVA"
#: ../contents/ui/FullRepresentation.qml
msgid "Copy to clipboard"
msgstr "Kopiera till klippbordet"
#: ../contents/ui/components/ColorPickerGradientList.qml
#: ../contents/ui/components/ColorPickerList.qml
#: ../contents/ui/components/FormColors.qml
msgid "Widget background"
msgstr "Widgetbakgrund"
#: ../contents/ui/components/Eq.qml
msgid "Remove band"
msgstr "Ta bort band"
#: ../contents/ui/components/Eq.qml
msgid "Add band"
msgstr "Lägg till band"
#: ../contents/ui/components/Eq.qml
msgid "Flat response"
msgstr "Platt kurva"
#: ../contents/ui/components/FormColors.qml
msgid "Color"
msgstr "Färg"
#: ../contents/ui/components/FormColors.qml
msgid "Animation:"
msgstr "Animation:"
#: ../contents/ui/components/FormColors.qml
msgid "Interval (ms):"
msgstr "Intervall (ms):"
#: ../contents/ui/components/FormColors.qml
msgid "Smoothing (ms):"
msgstr "Utjämning (ms):"
#: ../contents/ui/components/FormColors.qml
msgid "Color source:"
msgstr "Färgkälla:"
#: ../contents/ui/components/FormColors.qml
msgid "Custom"
msgstr "Anpassad"
#: ../contents/ui/components/FormColors.qml
msgid "A fixed custom color"
msgstr "En fast anpassad färg"
#: ../contents/ui/components/FormColors.qml
msgid "System"
msgstr "System"
#: ../contents/ui/components/FormColors.qml
msgid "Color that updates with your System theme"
msgstr "Färg som följer ditt systemtema"
#: ../contents/ui/components/FormColors.qml
msgid "Custom list"
msgstr "Anpassad lista"
#: ../contents/ui/components/FormColors.qml
msgid ""
"Define a list of colors that will be applied to all the elements, wraps "
"around if there are more elements than colors"
msgstr ""
"Definierar en lista av färger som appliceras på alla element, upprepar om "
"det finns fler element än färger"
#: ../contents/ui/components/FormColors.qml
msgid "Random"
msgstr "Slumpmässig"
#: ../contents/ui/components/FormColors.qml
msgid "Random color for each element"
msgstr "Slumpmässig färg för varje element"
#: ../contents/ui/components/FormColors.qml
msgid "Follow"
msgstr "Följer"
#: ../contents/ui/components/FormColors.qml
msgid "Follow the color of a parent element"
msgstr "Följer färgen på ett överordnat element"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient"
msgstr "Färgövergång"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal or vertical customizable color gradient"
msgstr "Horisontala eller vertikala anpassade färgövergångar"
#: ../contents/ui/components/FormColors.qml
msgid "Image"
msgstr "Bild"
#: ../contents/ui/components/FormColors.qml
msgid ""
"High resolution images can slow down the desktop when the panel or widget "
"size changes!"
msgstr ""
"Hög upplösning på bilder kan ge sämre prestanda på skrivbordet när panelens "
"eller widgetens storlek ändras!"
#: ../contents/ui/components/FormColors.qml
msgid "Hue"
msgstr "Nyans"
#: ../contents/ui/components/FormColors.qml
msgid "Automatically shifted color for each bar"
msgstr "Automatiskt ändrar färg för varje band"
#: ../contents/ui/components/FormColors.qml
msgid "Element:"
msgstr "Element:"
#: ../contents/ui/components/FormColors.qml
msgid "Panel background"
msgstr "Panelbakgrund"
#: ../contents/ui/components/FormColors.qml
msgid "Tray widget background"
msgstr "Tray Widgetbakgrund"
#: ../contents/ui/components/FormColors.qml
msgid "Positioning:"
msgstr "Positionering:"
#: ../contents/ui/components/FormColors.qml
msgid "Stretch"
msgstr "Sträck"
#: ../contents/ui/components/FormColors.qml
msgid "Tile"
msgstr "Bricka"
#: ../contents/ui/components/FormColors.qml
msgid "Scaled and Cropped"
msgstr "Skalad och beskuren"
#: ../contents/ui/components/FormColors.qml
msgid "Image:"
msgstr "Bild:"
#: ../contents/ui/components/FormColors.qml
msgid "Pick a image file"
msgstr "Välj en bildfil"
#: ../contents/ui/components/FormColors.qml
msgid "Image files"
msgstr "Bildfiler"
#: ../contents/ui/components/FormColors.qml
msgid "All files"
msgstr "Alla filer"
#: ../contents/ui/components/FormColors.qml
msgid "Color:"
msgstr "Färg:"
#: ../contents/ui/components/FormColors.qml
msgid "Color set:"
msgstr "Färguppsättning:"
#: ../contents/ui/components/FormColors.qml
msgid "Colors:"
msgstr "Färger:"
#: ../contents/ui/components/FormColors.qml
msgid "Range:"
msgstr "Räckvidd:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configVisualizer.qml
msgid "Orientation:"
msgstr "Orientering:"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal"
msgstr "Horisontell"
#: ../contents/ui/components/FormColors.qml
msgid "Vertical"
msgstr "Vertikal"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient:"
msgstr "Färgövergång:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configCava.qml
msgid "Reverse:"
msgstr "Omvänd:"
#: ../contents/ui/components/FormColors.qml
msgid "Display colors the other way around"
msgstr "Visa färger i omvänd ordning"
#: ../contents/ui/components/FormColors.qml
msgid "Alpha:"
msgstr "Alfa:"
#: ../contents/ui/components/FormColors.qml
msgid "Contrast Correction"
msgstr "Kontrastkorrigering"
#: ../contents/ui/components/FormColors.qml
msgid "Saturation:"
msgstr "Mättnad:"
#: ../contents/ui/components/FormColors.qml
msgid "Lightness:"
msgstr "Ljusstyrka:"
#: ../contents/ui/configCava.qml
msgid "Framerate:"
msgstr "Bildfrekvens:"
#: ../contents/ui/configCava.qml
msgid "Number of bars:"
msgstr "Antal band:"
#: ../contents/ui/configCava.qml
msgid "Automatically calculated for the current visualizer style."
msgstr "Automatiskt uträknad för den valda visualiseringsstilen."
#: ../contents/ui/configCava.qml
msgid "Automatic sensitivity:"
msgstr "Automatisk känslighet:"
#: ../contents/ui/configCava.qml
msgid "Attempt to decrease sensitivity if the bars peak."
msgstr "Försöker att minska känsligheten om banden når sin topp."
#: ../contents/ui/configCava.qml
msgid "Sensitivity:"
msgstr "Känslighet:"
#: ../contents/ui/configCava.qml
msgid ""
"Manual sensitivity in %.\n"
"If autosens is enabled, this will only be the initial value"
msgstr ""
"Manuell känslighet i %.\n"
"Om automatisk känslighet är aktiverat kommer detta endast att vara det "
"initiala värdet"
#: ../contents/ui/configCava.qml
msgid "Frequency range (Hz):"
msgstr "Frekvensomfång (Hz):"
#: ../contents/ui/configCava.qml
msgid "Min"
msgstr "Min"
#: ../contents/ui/configCava.qml
msgid "Max"
msgstr "Max"
#: ../contents/ui/configCava.qml
msgid ""
"Lower and higher cutoff frequencies for lowest and highest bars, the "
"bandwidth of the visualizer.\n"
"Note: Cava will automatically increase the higher cutoff if a too low band "
"is specified."
msgstr ""
" Visualiserarens bandbredd för lägsta och högsta gränsfrekvenser för lägsta "
"och högsta band.\n"
"Observera att Cava automatiskt höjer till en högre gränsfrekvens om ett för "
"lågt frekvensband läggs till."
#: ../contents/ui/configCava.qml
msgid "Sleep timer (seconds):"
msgstr "Sleep timer (sekunder):"
#: ../contents/ui/configCava.qml
msgid ""
"Seconds with no input before cava goes to sleep mode.\n"
"Cava will not perform FFT and only check for input once per second."
msgstr ""
"Sekunder utan inmatning innan Cava går in i viloläge.\n"
"Cava utför inte FFT och kontrollerar bara inmatning en gång per sekund."
#: ../contents/ui/configCava.qml
msgid "Pause on:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Full screen window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Maximized window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Input"
msgstr "Indata"
#: ../contents/ui/configCava.qml
msgid "Method:"
msgstr "Metod:"
#: ../contents/ui/configCava.qml ../contents/ui/configGeneral.qml
msgid "Default"
msgstr "Förvald"
#: ../contents/ui/configCava.qml
msgid ""
"Audio capturing method.\n"
"Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' "
"or 'fifo', in that order, dependent on what support cava was built with.\n"
msgstr ""
"Ljudinspelningsmetod.\n"
"Standardvärdena är 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', "
"'portaudio' eller 'fifo', i den ordningen, beroende på vilket stöd Cava "
"kompilerats för.\n"
#: ../contents/ui/configCava.qml
msgid "Source:"
msgstr "Källa:"
#: ../contents/ui/configCava.qml
msgid ""
"For pulseaudio and pipewire 'source' will be the source. Default: 'auto', "
"which uses the monitor source of the default sink (all pulseaudio "
"sinks(outputs) have 'monitor' sources(inputs) associated with them)."
msgstr ""
"För pulseaudio och pipewire kommer 'source' att vara källan. Förvald: "
"'auto', som använder sig av källan för förvald sink (alla pulseaudio-sinkar "
"(utgångar) har 'monitor'-källor (ingångar) associerade med sig)."
#: ../contents/ui/configCava.qml
msgid ""
"For pipewire 'source' will be the object name or object.serial of the device "
"to capture from. Both input and output devices are supported."
msgstr ""
"För pipewire kommer 'source' att vara objektnamnet eller object.serial för "
"den enhet som det hämtas från. Både in- och utdataenheter stöds."
#: ../contents/ui/configCava.qml
msgid "For alsa 'source' will be the capture device."
msgstr "För alsa kommer 'source' att vara inspelningsenheten."
#: ../contents/ui/configCava.qml
msgid "For fifo 'source' will be the path to fifo-file."
msgstr "För fifo kommer 'source' att vara sökvägen till fifo-filen."
#: ../contents/ui/configCava.qml
msgid ""
"For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:"
"EE:FF' will be squeezelite's MAC address."
msgstr ""
"För shmem kommer 'source' att vara /squeezelite-AA:BB:CC:DD:EE:FF där 'AA:BB:"
"CC:DD:EE:FF' är squeezelites MAC-adress."
#: ../contents/ui/configCava.qml
msgid ""
"For sndio 'source' will be a raw recording audio descriptor or a monitoring "
"sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'."
msgstr ""
"För sndio kommer 'source' att vara en rå inspelningsenhet eller en "
"underordnad övervakningsenhet, t.ex. 'rsnd/2' eller 'snd/1'. Standard: "
"'default'."
#: ../contents/ui/configCava.qml
msgid ""
"For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. "
"Default: '/dev/dsp', i.e. the default audio device."
msgstr ""
"För oss kommer 'source' att vara sökvägen till en ljudenhet, t.ex. '/dev/"
"dsp2'. Standard: '/dev/dsp', d.v.s. standardljudenheten."
#: ../contents/ui/configCava.qml
msgid ""
"For jack 'source' will be the name of the JACK server to connect to, e.g. "
"'foobar'. Default: 'default'."
msgstr ""
"För jack kommer 'source' att vara namnet på JACK-servern att ansluta till, t."
"ex. 'foobar'. Standard: 'default'."
#: ../contents/ui/configCava.qml
msgid "Sample rate:"
msgstr "Samplingsfrekvens:"
#: ../contents/ui/configCava.qml
msgid "Only for fifo, pipewire, sndio, oss"
msgstr "Endast för fifo, pipewire, sndio, oss"
#: ../contents/ui/configCava.qml
msgid "Sample bits:"
msgstr "bitdjup:"
#: ../contents/ui/configCava.qml
msgid "Channels:"
msgstr "Kanaler:"
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6, sndio, oss, jack"
msgstr "Endast för pipewire & cava>=0.10.6, sndio, oss, jack"
#: ../contents/ui/configCava.qml
msgid "Auto connect:"
msgstr "Automatisk anslutning:"
#: ../contents/ui/configCava.qml
msgid "Only for jack"
msgstr "Endast för jack"
#: ../contents/ui/configCava.qml
msgid "Active:"
msgstr "Aktiv:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Force the node to always process. Useful "
"for monitoring sources when no other application is active."
msgstr ""
"Endast för pipewire & cava>=0.10.6. Tvinga noden att alltid bearbeta. "
"Användbart för att övervaka källor när ingen annan applikation är aktiv."
#: ../contents/ui/configCava.qml
msgid "Remix:"
msgstr "Remix:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Allow to remix audio channels to match "
"cava's channel count. Useful for surround sound."
msgstr ""
"Endast för pipewire & cava>=0.10.6. Tillåter att remixa ljudkanaler för att "
"matcha Cavas kanalantal. Användbart för surroundljud."
#: ../contents/ui/configCava.qml
msgid "Virtual:"
msgstr "Virtuell:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Set the node to virtual, to avoid "
"recording notifications from the DE."
msgstr ""
"Endast för pipewire & cava>=0.10.6. Ställ in noden på virtuell för att "
"undvika att spela in aviseringar från skrivbordsmiljön."
#: ../contents/ui/configCava.qml
msgid "Output"
msgstr "Utdataenhet"
#: ../contents/ui/configCava.qml
msgid "Visual channels:"
msgstr "Visuella kanaler:"
#: ../contents/ui/configCava.qml
msgid ""
"'mono' outputs left to right lowest to highest frequencies.\n"
"'stereo' mirrors both channels with low frequencies in center."
msgstr ""
"'mono' återger från vänster till höger, lägsta till högsta frekvenser.\n"
"'stereo' speglar båda kanalerna med de låga frekvenser i mitten."
#: ../contents/ui/configCava.qml
msgid "Mono channel:"
msgstr "Monokanal:"
#: ../contents/ui/configCava.qml
msgid "Display frequencies the other way around"
msgstr "Återger frekvenser omvänt"
#: ../contents/ui/configCava.qml
msgid "Smoothing"
msgstr "Utjämning"
#: ../contents/ui/configCava.qml
msgid "Noise reduction:"
msgstr "Brusreducering:"
#: ../contents/ui/configCava.qml
msgid ""
"The raw visualization is very noisy, this factor adjusts the integral and "
"gravity filters to keep the signal smooth.\n"
"100 will be very slow and smooth, 0 will be fast but noisy."
msgstr ""
"Den råa visualiseringen är mycket brusig, denna faktor justerar integral- "
"och gravitationsfiltren så att signalen hålls jämn.\n"
"100 kommer att vara väldigt långsam och jämn, 0 kommer att vara snabb men "
"brusig."
#: ../contents/ui/configCava.qml
msgid "Monstercat:"
msgstr "Monstercat:"
#: ../contents/ui/configCava.qml
msgid ""
"Enable the so-called \"Monstercat smoothing\" with or without \"waves\"."
msgstr ""
"Aktivera den så kallade \"Monstercat-utjämningen\" med eller utan \"Vågor\"."
#: ../contents/ui/configCava.qml
msgid "Waves"
msgstr "Vågor"
#: ../contents/ui/configCava.qml
msgid "Equalizer"
msgstr "Equalizer"
#: ../contents/ui/configCava.qml
msgid "Enabled:"
msgstr "Aktiverad:"
#: ../contents/ui/configCava.qml
msgid ""
"Adjust frequencies by a multiplication factor, more bands equals more "
"precision."
msgstr ""
"Justera frekvenserna med en multiplikationsfaktor, desto fler band, desto "
"högre precision."
#: ../contents/ui/configGeneral.qml
msgid "Auto-hide when idle:"
msgstr "Dölj automatiskt vid inaktivitet:"
#: ../contents/ui/configGeneral.qml
msgid "After"
msgstr "Efter"
#: ../contents/ui/configGeneral.qml
msgid "seconds"
msgstr "sekunder"
#: ../contents/ui/configGeneral.qml
msgid "Desktop background:"
msgstr "Bakgrundsfärg:"
#: ../contents/ui/configGeneral.qml
msgid "Transparent"
msgstr "Transparent"
#: ../contents/ui/configGeneral.qml
msgid "Transparent with shadow"
msgstr "Transparent med skugga"
#: ../contents/ui/configGeneral.qml
msgid "Disable tooltip:"
msgstr "Inaktivera verktygstips:"
#: ../contents/ui/configGeneral.qml
msgid "Disable ToolTip that shows the widget name and description."
msgstr "Inaktivera verktygstips som visar widgetens namn och beskrivning."
#: ../contents/ui/configGeneral.qml
msgid "Disable left click:"
msgstr "Inaktivera vänsterklick:"
#: ../contents/ui/configGeneral.qml
msgid "Applet popup will still be accessible from the right click menu."
msgstr ""
"Applet-popupen kommer fortfarande att vara tillgänglig från högerklicksmenyn."
#: ../contents/ui/configGeneral.qml
msgid "Troubleshooting"
msgstr "Felsökning"
#: ../contents/ui/configGeneral.qml
msgid "Debug mode:"
msgstr "Felsökningsläge:"
#: ../contents/ui/configGeneral.qml
msgid ""
"Print additional messages to the system log. To make it easier to spot "
"sizing issues, this will also force full height to the first visualizer bar "
"and displays a grid over it."
msgstr ""
"Skriv ut ytterligare meddelanden till systemloggen. För att göra det lättare "
"att upptäcka storleksproblem, detta kommer också att tvinga fram full höjd "
"på det första visualiseringsfältet och visar ett rutnät över det."
#: ../contents/ui/configVisualizer.qml
msgid "height"
msgstr "höjd"
#: ../contents/ui/configVisualizer.qml
msgid "width"
msgstr "bredd"
#: ../contents/ui/configVisualizer.qml
msgid "Style:"
msgstr "Stil:"
#: ../contents/ui/configVisualizer.qml
msgid "Bars"
msgstr "Band"
#: ../contents/ui/configVisualizer.qml
msgid "Wave"
msgstr "Vågor"
#: ../contents/ui/configVisualizer.qml
msgid "Blocks"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Fill wave"
msgstr "Fyll vågor"
#: ../contents/ui/configVisualizer.qml
msgid "Draw inactive blocks"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Circle mode:"
msgstr "Cirkelläge:"
#: ../contents/ui/configVisualizer.qml
msgid "Circle size:"
msgstr "Cirkelstorlek:"
#: ../contents/ui/configVisualizer.qml
msgid "Top"
msgstr "Topp"
#: ../contents/ui/configVisualizer.qml
msgid "Bottom"
msgstr "Botten"
#: ../contents/ui/configVisualizer.qml
msgid "Left"
msgstr "Vänster"
#: ../contents/ui/configVisualizer.qml
msgid "Right"
msgstr "Höger"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel %1:"
msgstr "Fyll panelens %1:"
#: ../contents/ui/configVisualizer.qml
msgid "Fixed %1:"
msgstr "Fixerad %1:"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel thickness:"
msgstr "Fyll panelens tjocklek:"
#: ../contents/ui/configVisualizer.qml
msgid "Centered bars:"
msgstr "Centrerade band:"
#: ../contents/ui/configVisualizer.qml
msgid "Rounded bars:"
msgstr "Rundade band:"
#: ../contents/ui/configVisualizer.qml
msgid "Line width:"
msgstr "Linjebredd:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar width:"
msgstr "Bandens bredd:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar gap:"
msgstr "Bandens mellanrum:"
#: ../contents/ui/configVisualizer.qml
msgid "Block height:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Block gap:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave Color"
msgstr "Vågfärg"
#: ../contents/ui/configVisualizer.qml
msgid "Bar Color"
msgstr "Bandfärg"
#: ../contents/ui/configVisualizer.qml
msgid "Inactive block color"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave Fill Color"
msgstr "Vågfyllningsfärg"
#: ../contents/ui/main.qml
msgid "Show support information"
msgstr "Visa supportinformation"
#~ msgid ""
#~ "The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' "
#~ "can be configured for some input methods:"
#~ msgstr ""
#~ "Alternativen 'sample_rate', 'sample_bits', 'channels' och 'autoconnect' "
#~ "kan konfigureras för vissa inmatningsmetoder:"
#~ msgid "Other methods ignore these settings."
#~ msgstr "Andra metoder ignorerar dessa inställningar."
#~ msgid ""
#~ "For 'sndio' and 'oss' they are only preferred values, i.e. if the values "
#~ "are not supported by the chosen audio device, the device will use other "
#~ "supported values instead."
#~ msgstr ""
#~ "För 'sndio' och 'oss' är endast föredragna värden, d.v.s. om värdena inte "
#~ "stöds av den valda ljudenheten kommer enheten att använda andra värden "
#~ "som stöds istället."
#~ msgid ""
#~ "Adjust frequencies by a multiplication factor, more bars equals more "
#~ "precision."
#~ msgstr ""
#~ "Justera frekvenserna med en multiplikationsfaktor, desto fler band, desto "
#~ "högre precision."
@@ -0,0 +1,656 @@
# Translation of visualizer in LANGUAGE
# Copyright (C) 2025
# This file is distributed under the same license as the visualizer package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: visualizer\n"
"Report-Msgid-Bugs-To: https://github.com/luisbocanegra/kurve\n"
"POT-Creation-Date: 2025-12-13 18:30+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../metadata.json
msgid "Kurve"
msgstr ""
#: ../metadata.json
msgid "Audio visualizer powered by CAVA"
msgstr ""
#: ../contents/config/config.qml
msgid "Visualizer"
msgstr ""
#: ../contents/config/config.qml ../contents/ui/configCava.qml
msgid "General"
msgstr ""
#: ../contents/ui/FullRepresentation.qml
msgid "Oh no! Something went wrong"
msgstr ""
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml ../contents/ui/main.qml
msgid "Stop CAVA"
msgstr ""
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml ../contents/ui/main.qml
msgid "Start CAVA"
msgstr ""
#: ../contents/ui/FullRepresentation.qml
msgid "Copy to clipboard"
msgstr ""
#: ../contents/ui/components/ColorPickerGradientList.qml ../contents/ui/components/ColorPickerList.qml ../contents/ui/components/FormColors.qml
msgid "Widget background"
msgstr ""
#: ../contents/ui/components/Eq.qml
msgid "Remove band"
msgstr ""
#: ../contents/ui/components/Eq.qml
msgid "Add band"
msgstr ""
#: ../contents/ui/components/Eq.qml
msgid "Flat response"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Color"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Animation:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Interval (ms):"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Smoothing (ms):"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Color source:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Custom"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "A fixed custom color"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "System"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Color that updates with your System theme"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Custom list"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Define a list of colors that will be applied to all the elements, wraps around if there are more elements than colors"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Random"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Random color for each element"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Follow"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Follow the color of a parent element"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Gradient"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal or vertical customizable color gradient"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Image"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "High resolution images can slow down the desktop when the panel or widget size changes!"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Hue"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Automatically shifted color for each bar"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Element:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Panel background"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Tray widget background"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Positioning:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Stretch"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Tile"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Scaled and Cropped"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Image:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Pick a image file"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Image files"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "All files"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Color:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Color set:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Colors:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Range:"
msgstr ""
#: ../contents/ui/components/FormColors.qml ../contents/ui/configVisualizer.qml
msgid "Orientation:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Vertical"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Gradient:"
msgstr ""
#: ../contents/ui/components/FormColors.qml ../contents/ui/configCava.qml
msgid "Reverse:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Display colors the other way around"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Alpha:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Contrast Correction"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Saturation:"
msgstr ""
#: ../contents/ui/components/FormColors.qml
msgid "Lightness:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Framerate:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Number of bars:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Automatically calculated for the current visualizer style."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Automatic sensitivity:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Attempt to decrease sensitivity if the bars peak."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Sensitivity:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"Manual sensitivity in %.\n"
"If autosens is enabled, this will only be the initial value"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Frequency range (Hz):"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Min"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Max"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"Lower and higher cutoff frequencies for lowest and highest bars, the bandwidth of the visualizer.\n"
"Note: Cava will automatically increase the higher cutoff if a too low band is specified."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Sleep timer (seconds):"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"Seconds with no input before cava goes to sleep mode.\n"
"Cava will not perform FFT and only check for input once per second."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Pause on:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Full screen window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Maximized window"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Input"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Method:"
msgstr ""
#: ../contents/ui/configCava.qml ../contents/ui/configGeneral.qml
msgid "Default"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"Audio capturing method.\n"
"Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.\n"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Source:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated "
"with them)."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For pipewire 'source' will be the object name or object.serial of the device to capture from. Both input and output devices are supported."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For alsa 'source' will be the capture device."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For fifo 'source' will be the path to fifo-file."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Sample rate:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Only for fifo, pipewire, sndio, oss"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Sample bits:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Channels:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6, sndio, oss, jack"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Auto connect:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Only for jack"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Active:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6. Force the node to always process. Useful for monitoring sources when no other application is active."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Remix:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6. Allow to remix audio channels to match cava's channel count. Useful for surround sound."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Virtual:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6. Set the node to virtual, to avoid recording notifications from the DE."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Output"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Visual channels:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"'mono' outputs left to right lowest to highest frequencies.\n"
"'stereo' mirrors both channels with low frequencies in center."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Mono channel:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Display frequencies the other way around"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Smoothing"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Noise reduction:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid ""
"The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth.\n"
"100 will be very slow and smooth, 0 will be fast but noisy."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Monstercat:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Enable the so-called \"Monstercat smoothing\" with or without \"waves\"."
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Waves"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Equalizer"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Enabled:"
msgstr ""
#: ../contents/ui/configCava.qml
msgid "Adjust frequencies by a multiplication factor, more bands equals more precision."
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Auto-hide when idle:"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "After"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "seconds"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Desktop background:"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Transparent"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Transparent with shadow"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Disable tooltip:"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Disable ToolTip that shows the widget name and description."
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Disable left click:"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Applet popup will still be accessible from the right click menu."
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Troubleshooting"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Debug mode:"
msgstr ""
#: ../contents/ui/configGeneral.qml
msgid "Print additional messages to the system log. To make it easier to spot sizing issues, this will also force full height to the first visualizer bar and displays a grid over it."
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "height"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "width"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Style:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Bars"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Blocks"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Fill wave"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Draw inactive blocks"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Circle mode:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Circle size:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Top"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Bottom"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Left"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Right"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel %1:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Fixed %1:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel thickness:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Centered bars:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Rounded bars:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Line width:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Bar width:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Bar gap:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Block height:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Block gap:"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave Color"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Bar Color"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Inactive block color"
msgstr ""
#: ../contents/ui/configVisualizer.qml
msgid "Wave Fill Color"
msgstr ""
#: ../contents/ui/main.qml
msgid "Show support information"
msgstr ""
@@ -0,0 +1,725 @@
# Translation of visualizer in Chinese (Simplified)
# Copyright (C) 2025
# This file is distributed under the same license as the visualizer package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: visualizer\n"
"Report-Msgid-Bugs-To: https://github.com/luisbocanegra/kurve\n"
"POT-Creation-Date: 2025-12-13 18:30+0000\n"
"PO-Revision-Date: 2025-11-25 00:39+0800\n"
"Last-Translator: BCTaoTao <165790460+BCTaoTao@users.noreply.github.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.6\n"
#: ../metadata.json
msgid "Kurve"
msgstr "Kurve"
#: ../metadata.json
msgid "Audio visualizer powered by CAVA"
msgstr "基于CAVA的音频可视化器"
#: ../contents/config/config.qml
msgid "Visualizer"
msgstr "可视化器"
#: ../contents/config/config.qml ../contents/ui/configCava.qml
msgid "General"
msgstr "常规"
#: ../contents/ui/FullRepresentation.qml
msgid "Oh no! Something went wrong"
msgstr "糟糕!出错了"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Stop CAVA"
msgstr "停止 CAVA"
#: ../contents/ui/FullRepresentation.qml ../contents/ui/configCava.qml
#: ../contents/ui/main.qml
msgid "Start CAVA"
msgstr "启动 CAVA"
#: ../contents/ui/FullRepresentation.qml
msgid "Copy to clipboard"
msgstr "复制到剪贴板"
#: ../contents/ui/components/ColorPickerGradientList.qml
#: ../contents/ui/components/ColorPickerList.qml
#: ../contents/ui/components/FormColors.qml
msgid "Widget background"
msgstr "小部件背景"
#: ../contents/ui/components/Eq.qml
msgid "Remove band"
msgstr "移除频段"
#: ../contents/ui/components/Eq.qml
msgid "Add band"
msgstr "添加频段"
#: ../contents/ui/components/Eq.qml
msgid "Flat response"
msgstr "平直响应"
#: ../contents/ui/components/FormColors.qml
msgid "Color"
msgstr "颜色"
#: ../contents/ui/components/FormColors.qml
msgid "Animation:"
msgstr "动画:"
#: ../contents/ui/components/FormColors.qml
msgid "Interval (ms):"
msgstr "间隔(毫秒):"
#: ../contents/ui/components/FormColors.qml
msgid "Smoothing (ms):"
msgstr "平滑(毫秒):"
#: ../contents/ui/components/FormColors.qml
msgid "Color source:"
msgstr "颜色来源:"
#: ../contents/ui/components/FormColors.qml
msgid "Custom"
msgstr "自定义"
#: ../contents/ui/components/FormColors.qml
msgid "A fixed custom color"
msgstr "固定的自定义颜色"
#: ../contents/ui/components/FormColors.qml
msgid "System"
msgstr "系统"
#: ../contents/ui/components/FormColors.qml
msgid "Color that updates with your System theme"
msgstr "随系统主题自动更新的颜色"
#: ../contents/ui/components/FormColors.qml
msgid "Custom list"
msgstr "自定义颜色列表"
#: ../contents/ui/components/FormColors.qml
msgid ""
"Define a list of colors that will be applied to all the elements, wraps "
"around if there are more elements than colors"
msgstr ""
"定义一组颜色列表,依次应用于所有元素;若元素数量多于颜色数量,则循环使用"
#: ../contents/ui/components/FormColors.qml
msgid "Random"
msgstr "随机"
#: ../contents/ui/components/FormColors.qml
msgid "Random color for each element"
msgstr "为每个元素分配随机颜色"
#: ../contents/ui/components/FormColors.qml
msgid "Follow"
msgstr "跟随"
#: ../contents/ui/components/FormColors.qml
msgid "Follow the color of a parent element"
msgstr "跟随父级元素的颜色"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient"
msgstr "渐变"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal or vertical customizable color gradient"
msgstr "可自定义的水平或垂直颜色渐变"
#: ../contents/ui/components/FormColors.qml
msgid "Image"
msgstr "图像"
#: ../contents/ui/components/FormColors.qml
msgid ""
"High resolution images can slow down the desktop when the panel or widget "
"size changes!"
msgstr "高分辨率图像在面板或小部件尺寸变化时可能导致桌面变慢!"
#: ../contents/ui/components/FormColors.qml
msgid "Hue"
msgstr "色相"
#: ../contents/ui/components/FormColors.qml
msgid "Automatically shifted color for each bar"
msgstr "为每根频谱柱自动偏移色相"
#: ../contents/ui/components/FormColors.qml
msgid "Element:"
msgstr "元素:"
#: ../contents/ui/components/FormColors.qml
msgid "Panel background"
msgstr "面板背景"
#: ../contents/ui/components/FormColors.qml
msgid "Tray widget background"
msgstr "托盘小部件背景"
#: ../contents/ui/components/FormColors.qml
msgid "Positioning:"
msgstr "定位方式:"
#: ../contents/ui/components/FormColors.qml
msgid "Stretch"
msgstr "拉伸"
#: ../contents/ui/components/FormColors.qml
msgid "Tile"
msgstr "平铺"
#: ../contents/ui/components/FormColors.qml
msgid "Scaled and Cropped"
msgstr "缩放并裁剪"
#: ../contents/ui/components/FormColors.qml
msgid "Image:"
msgstr "图像:"
#: ../contents/ui/components/FormColors.qml
msgid "Pick a image file"
msgstr "选择图像文件"
#: ../contents/ui/components/FormColors.qml
msgid "Image files"
msgstr "图像文件"
#: ../contents/ui/components/FormColors.qml
msgid "All files"
msgstr "所有文件"
#: ../contents/ui/components/FormColors.qml
msgid "Color:"
msgstr "颜色:"
#: ../contents/ui/components/FormColors.qml
msgid "Color set:"
msgstr "颜色集:"
#: ../contents/ui/components/FormColors.qml
msgid "Colors:"
msgstr "颜色:"
#: ../contents/ui/components/FormColors.qml
msgid "Range:"
msgstr "范围:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configVisualizer.qml
msgid "Orientation:"
msgstr "方向:"
#: ../contents/ui/components/FormColors.qml
msgid "Horizontal"
msgstr "水平"
#: ../contents/ui/components/FormColors.qml
msgid "Vertical"
msgstr "垂直"
#: ../contents/ui/components/FormColors.qml
msgid "Gradient:"
msgstr "渐变:"
#: ../contents/ui/components/FormColors.qml ../contents/ui/configCava.qml
msgid "Reverse:"
msgstr "反转:"
#: ../contents/ui/components/FormColors.qml
msgid "Display colors the other way around"
msgstr "以相反顺序显示颜色"
#: ../contents/ui/components/FormColors.qml
msgid "Alpha:"
msgstr "透明度:"
#: ../contents/ui/components/FormColors.qml
msgid "Contrast Correction"
msgstr "对比度校正"
#: ../contents/ui/components/FormColors.qml
msgid "Saturation:"
msgstr "饱和度:"
#: ../contents/ui/components/FormColors.qml
msgid "Lightness:"
msgstr "亮度:"
#: ../contents/ui/configCava.qml
msgid "Framerate:"
msgstr "帧率:"
#: ../contents/ui/configCava.qml
msgid "Number of bars:"
msgstr "频谱柱数量:"
#: ../contents/ui/configCava.qml
msgid "Automatically calculated for the current visualizer style."
msgstr "根据当前可视化样式自动计算。"
#: ../contents/ui/configCava.qml
msgid "Automatic sensitivity:"
msgstr "自动灵敏度:"
#: ../contents/ui/configCava.qml
msgid "Attempt to decrease sensitivity if the bars peak."
msgstr "当频谱柱达到峰值时,尝试降低灵敏度。"
#: ../contents/ui/configCava.qml
msgid "Sensitivity:"
msgstr "灵敏度:"
#: ../contents/ui/configCava.qml
msgid ""
"Manual sensitivity in %.\n"
"If autosens is enabled, this will only be the initial value"
msgstr ""
"手动灵敏度(百分比)。\n"
"若启用自动灵敏度,此值仅作为初始值。"
#: ../contents/ui/configCava.qml
msgid "Frequency range (Hz):"
msgstr "频率范围(Hz):"
#: ../contents/ui/configCava.qml
msgid "Min"
msgstr "最小"
#: ../contents/ui/configCava.qml
msgid "Max"
msgstr "最大"
#: ../contents/ui/configCava.qml
msgid ""
"Lower and higher cutoff frequencies for lowest and highest bars, the "
"bandwidth of the visualizer.\n"
"Note: Cava will automatically increase the higher cutoff if a too low band "
"is specified."
msgstr ""
"最低和最高频谱柱对应的截止频率,即可视化器的带宽。\n"
"注意:若指定的频带过窄,CAVA会自动提高高频截止值。"
#: ../contents/ui/configCava.qml
msgid "Sleep timer (seconds):"
msgstr "休眠计时器(秒):"
#: ../contents/ui/configCava.qml
msgid ""
"Seconds with no input before cava goes to sleep mode.\n"
"Cava will not perform FFT and only check for input once per second."
msgstr ""
"无音频输入多少秒后进入休眠模式。\n"
"休眠期间CAVA不执行FFT,仅每秒检查一次输入。"
#: ../contents/ui/configCava.qml
msgid "Pause on:"
msgstr "何时暂停:"
#: ../contents/ui/configCava.qml
msgid "Full screen window"
msgstr "全屏窗口时"
#: ../contents/ui/configCava.qml
msgid "Maximized window"
msgstr "最大化窗口时"
#: ../contents/ui/configCava.qml
msgid "Input"
msgstr "输入"
#: ../contents/ui/configCava.qml
msgid "Method:"
msgstr "方法:"
#: ../contents/ui/configCava.qml ../contents/ui/configGeneral.qml
msgid "Default"
msgstr "默认"
#: ../contents/ui/configCava.qml
msgid ""
"Audio capturing method.\n"
"Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' "
"or 'fifo', in that order, dependent on what support cava was built with.\n"
msgstr ""
"音频捕获方法。\n"
"默认按以下顺序尝试:\n"
"'oss'、'pipewire'、'sndio'、'jack'、'pulse'、'alsa'、'portaudio'或'fifo'\n"
"具体取决于CAVA编译时所包含的支持。\n"
#: ../contents/ui/configCava.qml
msgid "Source:"
msgstr "源:"
#: ../contents/ui/configCava.qml
msgid ""
"For pulseaudio and pipewire 'source' will be the source. Default: 'auto', "
"which uses the monitor source of the default sink (all pulseaudio "
"sinks(outputs) have 'monitor' sources(inputs) associated with them)."
msgstr ""
"对于PulseAudio和PipeWire'source'指音频源。默认为'auto',即使用默认输出设备"
"的监听源(所有PulseAudio输出设备均有对应的监听输入源)。"
#: ../contents/ui/configCava.qml
msgid ""
"For pipewire 'source' will be the object name or object.serial of the device "
"to capture from. Both input and output devices are supported."
msgstr ""
"对于PipeWire'source'可为设备的对象名称或object.serial,支持输入和输出设备。"
#: ../contents/ui/configCava.qml
msgid "For alsa 'source' will be the capture device."
msgstr "对于ALSA'source'为录音设备。"
#: ../contents/ui/configCava.qml
msgid "For fifo 'source' will be the path to fifo-file."
msgstr "对于FIFO'source'为FIFO文件路径。"
#: ../contents/ui/configCava.qml
msgid ""
"For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:"
"EE:FF' will be squeezelite's MAC address."
msgstr ""
"对于shmem'source'为/squeezelite-AA:BB:CC:DD:EE:FF,其中'AA:BB:CC:DD:EE:"
"FF'为squeezelite的MAC地址。"
#: ../contents/ui/configCava.qml
msgid ""
"For sndio 'source' will be a raw recording audio descriptor or a monitoring "
"sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'."
msgstr ""
"对于sndio'source'为原始录音音频描述符或监听子设备(如'rsnd/2'或'snd/1')。"
"默认值:'default'。"
#: ../contents/ui/configCava.qml
msgid ""
"For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. "
"Default: '/dev/dsp', i.e. the default audio device."
msgstr ""
"对于OSS'source'为音频设备路径(如'/dev/dsp2')。默认值:'/dev/dsp'(即默认"
"音频设备)。"
#: ../contents/ui/configCava.qml
msgid ""
"For jack 'source' will be the name of the JACK server to connect to, e.g. "
"'foobar'. Default: 'default'."
msgstr ""
"对于JACK'source'为要连接的JACK服务器名称(如'foobar')。默认值:'default'。"
#: ../contents/ui/configCava.qml
msgid "Sample rate:"
msgstr "采样率:"
#: ../contents/ui/configCava.qml
msgid "Only for fifo, pipewire, sndio, oss"
msgstr "仅适用于fifo、pipewire、sndio、oss"
#: ../contents/ui/configCava.qml
msgid "Sample bits:"
msgstr "采样位深:"
#: ../contents/ui/configCava.qml
msgid "Channels:"
msgstr "声道数:"
#: ../contents/ui/configCava.qml
msgid "Only for pipewire & cava>=0.10.6, sndio, oss, jack"
msgstr "仅适用于pipewireCAVA ≥ 0.10.6)、sndio、oss、jack"
#: ../contents/ui/configCava.qml
msgid "Auto connect:"
msgstr "自动连接:"
#: ../contents/ui/configCava.qml
msgid "Only for jack"
msgstr "仅适用于jack"
#: ../contents/ui/configCava.qml
msgid "Active:"
msgstr "激活:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Force the node to always process. Useful "
"for monitoring sources when no other application is active."
msgstr ""
"仅适用于pipewireCAVA ≥ 0.10.6)。强制节点始终处理音频,适用于无其他应用活跃"
"时监听音频源。"
#: ../contents/ui/configCava.qml
msgid "Remix:"
msgstr "混音:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Allow to remix audio channels to match "
"cava's channel count. Useful for surround sound."
msgstr ""
"仅适用于pipewireCAVA ≥ 0.10.6)。允许重混音频声道以匹配CAVA的声道数,适用于"
"环绕声。"
#: ../contents/ui/configCava.qml
msgid "Virtual:"
msgstr "虚拟:"
#: ../contents/ui/configCava.qml
msgid ""
"Only for pipewire & cava>=0.10.6. Set the node to virtual, to avoid "
"recording notifications from the DE."
msgstr ""
"仅适用于pipewireCAVA ≥ 0.10.6)。将节点设为虚拟,避免桌面环境弹出录音通知。"
#: ../contents/ui/configCava.qml
msgid "Output"
msgstr "输出"
#: ../contents/ui/configCava.qml
msgid "Visual channels:"
msgstr "可视化声道:"
#: ../contents/ui/configCava.qml
msgid ""
"'mono' outputs left to right lowest to highest frequencies.\n"
"'stereo' mirrors both channels with low frequencies in center."
msgstr ""
"“单声道(mono)”从左到右显示从低到高的频率。\n"
"“立体声(stereo)”镜像显示双声道,低频位于中央。"
#: ../contents/ui/configCava.qml
msgid "Mono channel:"
msgstr "单声道选项:"
#: ../contents/ui/configCava.qml
msgid "Display frequencies the other way around"
msgstr "以相反顺序显示频率"
#: ../contents/ui/configCava.qml
msgid "Smoothing"
msgstr "平滑"
#: ../contents/ui/configCava.qml
msgid "Noise reduction:"
msgstr "降噪:"
#: ../contents/ui/configCava.qml
msgid ""
"The raw visualization is very noisy, this factor adjusts the integral and "
"gravity filters to keep the signal smooth.\n"
"100 will be very slow and smooth, 0 will be fast but noisy."
msgstr ""
"原始可视化噪声较大,此参数通过积分与重力滤波器使信号平滑。\n"
"100表示极慢且平滑,0表示快速但噪声明显。"
#: ../contents/ui/configCava.qml
msgid "Monstercat:"
msgstr "Monstercat平滑:"
#: ../contents/ui/configCava.qml
msgid ""
"Enable the so-called \"Monstercat smoothing\" with or without \"waves\"."
msgstr "启用被称为“Monstercat平滑”的效果,可选择是否同时启用“波浪效果”。"
#: ../contents/ui/configCava.qml
msgid "Waves"
msgstr "波浪效果"
#: ../contents/ui/configCava.qml
msgid "Equalizer"
msgstr "均衡器"
#: ../contents/ui/configCava.qml
msgid "Enabled:"
msgstr "启用:"
#: ../contents/ui/configCava.qml
msgid ""
"Adjust frequencies by a multiplication factor, more bands equals more "
"precision."
msgstr "通过乘法因子调整频率,频段越多,精度越高。"
#: ../contents/ui/configGeneral.qml
msgid "Auto-hide when idle:"
msgstr "空闲时自动隐藏:"
#: ../contents/ui/configGeneral.qml
msgid "After"
msgstr "在"
#: ../contents/ui/configGeneral.qml
msgid "seconds"
msgstr "秒后"
#: ../contents/ui/configGeneral.qml
msgid "Desktop background:"
msgstr "桌面背景:"
#: ../contents/ui/configGeneral.qml
msgid "Transparent"
msgstr "透明"
#: ../contents/ui/configGeneral.qml
msgid "Transparent with shadow"
msgstr "透明带阴影"
#: ../contents/ui/configGeneral.qml
msgid "Disable tooltip:"
msgstr "禁用工具提示:"
#: ../contents/ui/configGeneral.qml
msgid "Disable ToolTip that shows the widget name and description."
msgstr "禁用显示小部件名称和描述的工具提示。"
#: ../contents/ui/configGeneral.qml
msgid "Disable left click:"
msgstr "禁用左键点击:"
#: ../contents/ui/configGeneral.qml
msgid "Applet popup will still be accessible from the right click menu."
msgstr "仍可通过右键菜单访问小部件弹出窗口。"
#: ../contents/ui/configGeneral.qml
msgid "Troubleshooting"
msgstr "故障排除"
#: ../contents/ui/configGeneral.qml
msgid "Debug mode:"
msgstr "调试模式:"
#: ../contents/ui/configGeneral.qml
msgid ""
"Print additional messages to the system log. To make it easier to spot "
"sizing issues, this will also force full height to the first visualizer bar "
"and displays a grid over it."
msgstr ""
"向系统日志输出额外信息。\n"
"为便于发现尺寸问题,此选项还会强制将第一条频谱柱设为最大高度,并在整个区域上"
"显示网格。"
#: ../contents/ui/configVisualizer.qml
msgid "height"
msgstr "高度"
#: ../contents/ui/configVisualizer.qml
msgid "width"
msgstr "宽度"
#: ../contents/ui/configVisualizer.qml
msgid "Style:"
msgstr "样式:"
#: ../contents/ui/configVisualizer.qml
msgid "Bars"
msgstr "柱状"
#: ../contents/ui/configVisualizer.qml
msgid "Wave"
msgstr "波形"
#: ../contents/ui/configVisualizer.qml
msgid "Blocks"
msgstr "块状"
#: ../contents/ui/configVisualizer.qml
msgid "Fill wave"
msgstr "填充波形"
#: ../contents/ui/configVisualizer.qml
msgid "Draw inactive blocks"
msgstr "绘制非活动块"
#: ../contents/ui/configVisualizer.qml
msgid "Circle mode:"
msgstr "环形模式"
#: ../contents/ui/configVisualizer.qml
msgid "Circle size:"
msgstr "内环大小"
#: ../contents/ui/configVisualizer.qml
msgid "Top"
msgstr "顶部"
#: ../contents/ui/configVisualizer.qml
msgid "Bottom"
msgstr "底部"
#: ../contents/ui/configVisualizer.qml
msgid "Left"
msgstr "左侧"
#: ../contents/ui/configVisualizer.qml
msgid "Right"
msgstr "右侧"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel %1:"
msgstr "填充面板%1"
#: ../contents/ui/configVisualizer.qml
msgid "Fixed %1:"
msgstr "固定%1"
#: ../contents/ui/configVisualizer.qml
msgid "Fill panel thickness:"
msgstr "填充面板厚度:"
#: ../contents/ui/configVisualizer.qml
msgid "Centered bars:"
msgstr "居中柱:"
#: ../contents/ui/configVisualizer.qml
msgid "Rounded bars:"
msgstr "圆角柱:"
#: ../contents/ui/configVisualizer.qml
msgid "Line width:"
msgstr "线条宽度:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar width:"
msgstr "列宽度:"
#: ../contents/ui/configVisualizer.qml
msgid "Bar gap:"
msgstr "列间距:"
#: ../contents/ui/configVisualizer.qml
msgid "Block height:"
msgstr "块高度:"
#: ../contents/ui/configVisualizer.qml
msgid "Block gap:"
msgstr "块间距:"
#: ../contents/ui/configVisualizer.qml
msgid "Wave Color"
msgstr "波形颜色"
#: ../contents/ui/configVisualizer.qml
msgid "Bar Color"
msgstr "频谱柱颜色"
#: ../contents/ui/configVisualizer.qml
msgid "Inactive block color"
msgstr "非活动块颜色"
#: ../contents/ui/configVisualizer.qml
msgid "Wave Fill Color"
msgstr "波形填充颜色"
#: ../contents/ui/main.qml
msgid "Show support information"
msgstr "显示支持信息"