plasmoids: añade commandoutput y codexbar al tracking
Ambos ya estaban puestos en un panel real pero no viajaban a la otra máquina -- detectado en la misma auditoría que quitó claudeusageswitcher y tt.launchpadPlasma (sin usar). Sin esto, una reinstalación desde arch-setup dejaría el panel sin estos dos widgets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d434a3c635
commit
b98999cde9
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
import org.kde.plasma.configuration
|
||||||
|
|
||||||
|
ConfigModel {
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("Command")
|
||||||
|
icon: "configure"
|
||||||
|
source: "config/ConfigCommand.qml"
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("Appearance")
|
||||||
|
icon: "preferences-desktop-color"
|
||||||
|
source: "config/ConfigAppearance.qml"
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("Actions")
|
||||||
|
icon: "preferences-desktop-mouse"
|
||||||
|
source: "config/ConfigActions.qml"
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
<?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">
|
||||||
|
<entry name="interval" type="int">
|
||||||
|
<default>1000</default>
|
||||||
|
<!-- <default>60000</default> -->
|
||||||
|
</entry>
|
||||||
|
<entry name="command" type="string">
|
||||||
|
<default>echo "Test $(date +%S)"</default>
|
||||||
|
<!-- <default>echo "<a href=\"https://google.com\">Test $(date +%S)</a>"</default> -->
|
||||||
|
<!-- <default>SYSTEMD_COLORS=1 journalctl -q -b0 -n20</default> -->
|
||||||
|
</entry>
|
||||||
|
<entry name="tooltipCommand" type="string">
|
||||||
|
<default></default>
|
||||||
|
<!-- <default>echo "Tooltip test $(date +%S)"</default> -->
|
||||||
|
</entry>
|
||||||
|
<entry name="waitForCompletion" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="fontSize" type="int">
|
||||||
|
<default>13</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="fontFamily" type="string">
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="bold" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="italic" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="underline" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="textAlign" type="int">
|
||||||
|
<!-- Text.AlignLeft: 1 -->
|
||||||
|
<!-- Text.AlignHCenter: 4 -->
|
||||||
|
<!-- Text.AlignRight: 2 -->
|
||||||
|
<!-- Text.AlignJustify: 8 -->
|
||||||
|
<default>1</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="vertAlign" type="int">
|
||||||
|
<!-- Text.AlignTop: 32 -->
|
||||||
|
<!-- Text.AlignVCenter: 128 -->
|
||||||
|
<!-- Text.AlignBottom: 64 -->
|
||||||
|
<default>128</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="textColor" type="string">
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="outlineColor" type="string">
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showOutline" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showBackground" type="bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="clickCommand" type="string">
|
||||||
|
<default></default>
|
||||||
|
<!-- <default>kdialog --yesno "test"</default> -->
|
||||||
|
</entry>
|
||||||
|
<entry name="mousewheelUpCommand" type="string">
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="mousewheelDownCommand" type="string">
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="useFixedWidth" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="fixedWidth" type="int">
|
||||||
|
<default>100</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="useFixedHeight" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="fixedHeight" type="int">
|
||||||
|
<default>100</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="replaceAllNewlines" type="bool">
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</kcfg>
|
||||||
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+83
@@ -0,0 +1,83 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
|
|
||||||
|
import "../libconfig" as LibConfig
|
||||||
|
|
||||||
|
LibConfig.FormKCM {
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Tooltip")
|
||||||
|
label.horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
|
Layout.preferredWidth: 360 * Screen.devicePixelRatio
|
||||||
|
spacing: 0
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Hover Command:")
|
||||||
|
}
|
||||||
|
LibConfig.TextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
configKey: 'tooltipCommand'
|
||||||
|
wrapMode: LibConfig.TextField.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Click")
|
||||||
|
label.horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
|
Layout.preferredWidth: 360 * Screen.devicePixelRatio
|
||||||
|
spacing: 0
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Run Command:")
|
||||||
|
}
|
||||||
|
LibConfig.TextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
configKey: 'clickCommand'
|
||||||
|
wrapMode: LibConfig.TextField.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Mouse Wheel")
|
||||||
|
label.horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
|
Layout.preferredWidth: 360 * Screen.devicePixelRatio
|
||||||
|
spacing: 0
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Scroll Up:")
|
||||||
|
}
|
||||||
|
LibConfig.TextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
configKey: 'mousewheelUpCommand'
|
||||||
|
wrapMode: LibConfig.TextField.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
|
Layout.preferredWidth: 360 * Screen.devicePixelRatio
|
||||||
|
spacing: 0
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Scroll Down:")
|
||||||
|
}
|
||||||
|
LibConfig.TextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
configKey: 'mousewheelDownCommand'
|
||||||
|
wrapMode: LibConfig.TextField.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
|
|
||||||
|
import "../libconfig" as LibConfig
|
||||||
|
|
||||||
|
LibConfig.FormKCM {
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Font")
|
||||||
|
}
|
||||||
|
LibConfig.FontFamily {
|
||||||
|
Kirigami.FormData.label: i18n("Font Family:")
|
||||||
|
configKey: 'fontFamily'
|
||||||
|
}
|
||||||
|
LibConfig.SpinBox {
|
||||||
|
Kirigami.FormData.label: i18n("Font Size:")
|
||||||
|
configKey: 'fontSize'
|
||||||
|
suffix: i18n("px")
|
||||||
|
}
|
||||||
|
LibConfig.TextFormat {
|
||||||
|
boldConfigKey: 'bold'
|
||||||
|
italicConfigKey: 'italic'
|
||||||
|
underlineConfigKey: 'underline'
|
||||||
|
alignConfigKey: 'textAlign'
|
||||||
|
vertAlignConfigKey: 'vertAlign'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Colors")
|
||||||
|
}
|
||||||
|
LibConfig.ColorField {
|
||||||
|
Kirigami.FormData.label: i18n("Text:")
|
||||||
|
configKey: 'textColor'
|
||||||
|
defaultColor: PlasmaCore.Theme.textColor
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: i18n("Outline:")
|
||||||
|
spacing: 0
|
||||||
|
LibConfig.CheckBox {
|
||||||
|
configKey: 'showOutline'
|
||||||
|
}
|
||||||
|
LibConfig.ColorField {
|
||||||
|
configKey: 'outlineColor'
|
||||||
|
defaultColor: PlasmaCore.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Misc")
|
||||||
|
}
|
||||||
|
LibConfig.CheckBox {
|
||||||
|
Kirigami.FormData.label: i18n("Desktop Widget:")
|
||||||
|
configKey: 'showBackground'
|
||||||
|
text: i18n("Show background")
|
||||||
|
visible: plasmoid.location == PlasmaCore.Types.Floating
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: i18n("Fixed Width:")
|
||||||
|
spacing: 0
|
||||||
|
visible: plasmoid.formFactor == PlasmaCore.Types.Horizontal
|
||||||
|
LibConfig.CheckBox {
|
||||||
|
configKey: 'useFixedWidth'
|
||||||
|
}
|
||||||
|
LibConfig.SpinBox {
|
||||||
|
configKey: 'fixedWidth'
|
||||||
|
suffix: i18n("px")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: i18n("Fixed Height:")
|
||||||
|
spacing: 0
|
||||||
|
visible: plasmoid.formFactor == PlasmaCore.Types.Vertical
|
||||||
|
LibConfig.CheckBox {
|
||||||
|
configKey: 'useFixedHeight'
|
||||||
|
}
|
||||||
|
LibConfig.SpinBox {
|
||||||
|
configKey: 'fixedHeight'
|
||||||
|
suffix: i18n("px")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LibConfig.CheckBox {
|
||||||
|
Kirigami.FormData.label: i18n("Replace all newlines with spaces")
|
||||||
|
configKey: 'replaceAllNewlines'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
|
|
||||||
|
import "../libconfig" as LibConfig
|
||||||
|
|
||||||
|
LibConfig.FormKCM {
|
||||||
|
|
||||||
|
property alias cfg_command: command.text
|
||||||
|
property alias cfg_interval: interval.value
|
||||||
|
property alias cfg_waitForCompletion: waitForCompletion.checked
|
||||||
|
|
||||||
|
//-------------------------------------------------------
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("Command")
|
||||||
|
}
|
||||||
|
LibConfig.TextField {
|
||||||
|
id: command
|
||||||
|
// configKey: 'command'
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: 240 * Screen.devicePixelRatio // + ~180px from label column
|
||||||
|
wrapMode: LibConfig.TextField.Wrap
|
||||||
|
}
|
||||||
|
LibConfig.SpinBox {
|
||||||
|
id: interval
|
||||||
|
Kirigami.FormData.label: i18n("Run every ")
|
||||||
|
configKey: 'interval'
|
||||||
|
suffix: i18n("ms")
|
||||||
|
stepSize: 500
|
||||||
|
}
|
||||||
|
LibConfig.CheckBox {
|
||||||
|
id: waitForCompletion
|
||||||
|
Kirigami.FormData.label: i18n("Wait for completion")
|
||||||
|
// configKey: 'waitForCompletion'
|
||||||
|
text: i18n("Enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// Version 5
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: configCheckBox
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
checked: plasmoid.configuration[configKey]
|
||||||
|
onClicked: plasmoid.configuration[configKey] = !plasmoid.configuration[configKey]
|
||||||
|
}
|
||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
// Version 8
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Dialogs as QtDialogs
|
||||||
|
import QtQuick.Window
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
// https://doc.qt.io/qt-6/qtgraphicaleffects5-index.html
|
||||||
|
import Qt5Compat.GraphicalEffects as QtGraphicalEffects // TODO Deprecated in Qt6
|
||||||
|
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: colorField
|
||||||
|
font.family: "monospace"
|
||||||
|
readonly property string defaultText: "#AARRGGBB"
|
||||||
|
placeholderText: defaultColor ? defaultColor : defaultText
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
// Make sure the text is:
|
||||||
|
// Empty (use default)
|
||||||
|
// or #123 or #112233 or #11223344 before applying the color.
|
||||||
|
if (text.length === 0
|
||||||
|
|| (text.indexOf('#') === 0 && (text.length == 4 || text.length == 7 || text.length == 9))
|
||||||
|
) {
|
||||||
|
colorField.value = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool showAlphaChannel: true
|
||||||
|
property bool showPreviewBg: true
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
property string defaultColor: ''
|
||||||
|
property string value: {
|
||||||
|
if (configKey) {
|
||||||
|
return plasmoid.configuration[configKey]
|
||||||
|
} else {
|
||||||
|
return "#000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color defaultColorValue: defaultColor
|
||||||
|
readonly property color valueColor: {
|
||||||
|
if (value == '' && defaultColor) {
|
||||||
|
return defaultColor
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChanged: {
|
||||||
|
if (!activeFocus) {
|
||||||
|
text = colorField.value
|
||||||
|
}
|
||||||
|
if (configKey) {
|
||||||
|
if (value == defaultColorValue) {
|
||||||
|
plasmoid.configuration[configKey] = ""
|
||||||
|
} else {
|
||||||
|
plasmoid.configuration[configKey] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leftPadding: rightPadding + mouseArea.height + rightPadding
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
font.family: colorField.font.family
|
||||||
|
font.italic: colorField.font.italic
|
||||||
|
font.pointSize: colorField.font.pointSize
|
||||||
|
font.pixelSize: colorField.font.pixelSize
|
||||||
|
font.weight: colorField.font.weight
|
||||||
|
}
|
||||||
|
readonly property int defaultWidth: Math.ceil(fontMetrics.advanceWidth(defaultText))
|
||||||
|
implicitWidth: rightPadding + Math.max(defaultWidth, contentWidth) + leftPadding
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.leftMargin: parent.rightPadding
|
||||||
|
anchors.topMargin: parent.topPadding
|
||||||
|
anchors.bottomMargin: parent.bottomPadding
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: height
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: dialogLoader.active = true
|
||||||
|
|
||||||
|
// Color Preview Circle
|
||||||
|
Rectangle {
|
||||||
|
id: previewBgMask
|
||||||
|
visible: false
|
||||||
|
anchors.fill: parent
|
||||||
|
border.width: 1 * Screen.devicePixelRatio
|
||||||
|
border.color: "transparent"
|
||||||
|
radius: width / 2
|
||||||
|
}
|
||||||
|
QtGraphicalEffects.ConicalGradient {
|
||||||
|
id: previewBgGradient
|
||||||
|
visible: colorField.showPreviewBg
|
||||||
|
anchors.fill: parent
|
||||||
|
angle: 0.0
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.00; color: "white" }
|
||||||
|
GradientStop { position: 0.24; color: "white" }
|
||||||
|
GradientStop { position: 0.25; color: "#cccccc" }
|
||||||
|
GradientStop { position: 0.49; color: "#cccccc" }
|
||||||
|
GradientStop { position: 0.50; color: "white" }
|
||||||
|
GradientStop { position: 0.74; color: "white" }
|
||||||
|
GradientStop { position: 0.75; color: "#cccccc" }
|
||||||
|
GradientStop { position: 1.00; color: "#cccccc" }
|
||||||
|
}
|
||||||
|
source: previewBgMask
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: previewFill
|
||||||
|
anchors.fill: parent
|
||||||
|
color: colorField.valueColor
|
||||||
|
border.width: 1 * Screen.devicePixelRatio
|
||||||
|
border.color: Kirigami.ColorUtils.linearInterpolation(color, Kirigami.Theme.textColor, 0.5)
|
||||||
|
radius: width / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: dialogLoader
|
||||||
|
active: false
|
||||||
|
sourceComponent: QtDialogs.ColorDialog {
|
||||||
|
id: dialog
|
||||||
|
visible: true
|
||||||
|
modality: Qt.WindowModal
|
||||||
|
options: colorField.showAlphaChannel ? QtDialogs.ColorDialog.ShowAlphaChannel : 0
|
||||||
|
selectedColor: colorField.valueColor
|
||||||
|
onSelectedColorChanged: {
|
||||||
|
if (visible) {
|
||||||
|
colorField.text = selectedColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onAccepted: {
|
||||||
|
colorField.text = selectedColor
|
||||||
|
dialogLoader.active = false
|
||||||
|
}
|
||||||
|
onRejected: {
|
||||||
|
// This event is also triggered when the user clicks outside the popup modal.
|
||||||
|
// TODO Find a way to only trigger when Cancel is clicked.
|
||||||
|
colorField.text = initColor
|
||||||
|
dialogLoader.active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
property color initColor
|
||||||
|
Component.onCompleted: {
|
||||||
|
initColor = colorField.valueColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
// Version 8
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Example:
|
||||||
|
**
|
||||||
|
import './libconfig' as LibConfig
|
||||||
|
LibConfig.ComboBox {
|
||||||
|
configKey: "appDescription"
|
||||||
|
model: [
|
||||||
|
{ value: "a", text: i18n("A") },
|
||||||
|
{ value: "b", text: i18n("B") },
|
||||||
|
{ value: "c", text: i18n("C") },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
LibConfig.ComboBox {
|
||||||
|
configKey: "appDescription"
|
||||||
|
populated: false
|
||||||
|
onPopulate: {
|
||||||
|
model = [
|
||||||
|
{ value: "a", text: i18n("A") },
|
||||||
|
{ value: "b", text: i18n("B") },
|
||||||
|
{ value: "c", text: i18n("C") },
|
||||||
|
]
|
||||||
|
populated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: configComboBox
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
|
||||||
|
onConfigValueChanged: {
|
||||||
|
if (!focus && value != configValue) {
|
||||||
|
selectValue(configValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var currentItem: currentIndex >= 0 ? model[currentIndex] : null
|
||||||
|
|
||||||
|
textRole: "text" // Doesn't autodeduce from model if we manually populate it
|
||||||
|
|
||||||
|
// Note that ComboBox.valueRole and ComboBox.currentValue was introduced in Qt 5.14.
|
||||||
|
// Ubuntu 20.04 only has Qt 5.12. We cannot define a currentValue property or it will
|
||||||
|
// break when users upgrade to Qt 5.14.
|
||||||
|
property string _valueRole: "value"
|
||||||
|
readonly property var _currentValue: _valueRole && currentIndex >= 0 ? model[currentIndex][_valueRole] : null
|
||||||
|
readonly property alias value: configComboBox._currentValue
|
||||||
|
|
||||||
|
model: []
|
||||||
|
|
||||||
|
signal populate()
|
||||||
|
property bool populated: true
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
populate()
|
||||||
|
selectValue(configValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
if (typeof model !== 'number' && 0 <= currentIndex && currentIndex < count) {
|
||||||
|
var item = model[currentIndex]
|
||||||
|
if (typeof item !== "undefined") {
|
||||||
|
var val = item[_valueRole]
|
||||||
|
if (configKey && (typeof val !== "undefined") && populated) {
|
||||||
|
plasmoid.configuration[configKey] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function size() {
|
||||||
|
if (typeof model === "number") {
|
||||||
|
return model
|
||||||
|
} else if (typeof model.count === "number") {
|
||||||
|
return model.count
|
||||||
|
} else if (typeof model.length === "number") {
|
||||||
|
return model.length
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findValue(val) {
|
||||||
|
for (var i = 0; i < size(); i++) {
|
||||||
|
if (model[i][_valueRole] == val) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectValue(val) {
|
||||||
|
var index = findValue(val)
|
||||||
|
if (index >= 0) {
|
||||||
|
currentIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
// Version 4
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
import "." as LibConfig
|
||||||
|
|
||||||
|
LibConfig.ComboBox {
|
||||||
|
id: configFontFamily
|
||||||
|
|
||||||
|
populated: false
|
||||||
|
|
||||||
|
// Based on: org.kde.plasma.digitalclock
|
||||||
|
onPopulate: {
|
||||||
|
var arr = [] // Use temp array to avoid constant binding stuff
|
||||||
|
arr.push({ text: i18nc("Use default font", "Default"), value: "" })
|
||||||
|
|
||||||
|
var fonts = Qt.fontFamilies()
|
||||||
|
for (var i = 0; i < fonts.length; i++) {
|
||||||
|
arr.push({ text: fonts[i], value: fonts[i] })
|
||||||
|
}
|
||||||
|
model = arr
|
||||||
|
populated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
// Version 3
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Window
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kcmutils as KCM
|
||||||
|
|
||||||
|
KCM.SimpleKCM {
|
||||||
|
id: simpleKCM
|
||||||
|
default property alias _formChildren: formLayout.data
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
id: formLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://invent.kde.org/plasma/plasma-desktop/-/blame/master/desktoppackage/contents/configuration/AppletConfiguration.qml
|
||||||
|
// AppletConfiguration.implicitWidth: Kirigami.Units.gridUnit * 40 = 720
|
||||||
|
// AppletConfiguration.Layout.minimumWidth: Kirigami.Units.gridUnit * 30 = 540
|
||||||
|
// In practice, Window.width = 744px is a typical FormLayout.wideMode switchWidth
|
||||||
|
// A rough guess is 128+24+180+10+360+24+20 = 746px
|
||||||
|
// TabSidebar=128x, Padding=24px, Labels=180px, Spacing=10px, Controls=360px, Padding=24px, Scrollbar=20px
|
||||||
|
// However the default is only 720px. So we'll set it to a 800px minimum to avoid wideMode=false
|
||||||
|
property int wideModeMinWidth: 800 * Screen.devicePixelRatio
|
||||||
|
Window.onWindowChanged: {
|
||||||
|
if (Window.window) {
|
||||||
|
Window.window.visibleChanged.connect(function(){
|
||||||
|
if (Window.window && Window.window.visible && Window.window.width < wideModeMinWidth) {
|
||||||
|
Window.window.width = wideModeMinWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
// Version 6
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Example:
|
||||||
|
**
|
||||||
|
import './libconfig' as LibConfig
|
||||||
|
LibConfig.Heading {
|
||||||
|
text: i18n("SpinBox (Double)")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// While the following Kirigami is very simple:
|
||||||
|
// Kirigami.Separator {
|
||||||
|
// Kirigami.FormData.label: "Heading"
|
||||||
|
// Kirigami.FormData.isSection: true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// I want to be able to adjust the label size and make it bold.
|
||||||
|
// Kirigami's buddy Heading is level=3, which does not stand out
|
||||||
|
// very well. I also want to center the heading.
|
||||||
|
// Since we can't access the Heading in the buddy component, we
|
||||||
|
// need to make sure the Heading has no text, and draw our own.
|
||||||
|
ColumnLayout {
|
||||||
|
id: heading
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
property string text: ""
|
||||||
|
property alias separator: separator
|
||||||
|
property alias label: label
|
||||||
|
property bool useThickTopMargin: true
|
||||||
|
|
||||||
|
property Item __formLayout: {
|
||||||
|
if (parent && typeof parent.wideMode === 'boolean') {
|
||||||
|
return parent
|
||||||
|
} else if (typeof formLayout !== 'undefined' && typeof formLayout.wideMode === 'boolean') {
|
||||||
|
return formLayout
|
||||||
|
} else if (typeof page !== 'undefined' && typeof page.wideMode === 'boolean') {
|
||||||
|
return page
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
// Kirigami.FormData.isSection: true
|
||||||
|
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.FormLabel
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
id: separator
|
||||||
|
visible: false
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
id: label
|
||||||
|
Layout.topMargin: useThickTopMargin ? Kirigami.Units.largeSpacing * 3 : Kirigami.Units.largeSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: heading.text
|
||||||
|
level: 1
|
||||||
|
font.weight: Font.Bold
|
||||||
|
// horizontalAlignment: (!__formLayout || __formLayout.wideMode) ? Text.AlignHCenter : Text.AlignLeft
|
||||||
|
verticalAlignment: (!__formLayout || __formLayout.wideMode) ? Text.AlignVCenter : Text.AlignBottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--- Test Default Kirigami Heading
|
||||||
|
// Kirigami.Separator {
|
||||||
|
// property string text: ""
|
||||||
|
// Kirigami.FormData.label: text
|
||||||
|
// Kirigami.FormData.isSection: true
|
||||||
|
// property alias separator: separator
|
||||||
|
// Item {
|
||||||
|
// id: separator
|
||||||
|
// }
|
||||||
|
// }
|
||||||
+225
@@ -0,0 +1,225 @@
|
|||||||
|
// Version 7
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Example:
|
||||||
|
**
|
||||||
|
import './libconfig' as LibConfig
|
||||||
|
// Integer
|
||||||
|
LibConfig.SpinBox {
|
||||||
|
configKey: "leftPadding"
|
||||||
|
suffix: "px"
|
||||||
|
from: 0
|
||||||
|
to: 1000
|
||||||
|
stepSize: 5
|
||||||
|
}
|
||||||
|
// Double
|
||||||
|
LibConfig.SpinBox {
|
||||||
|
configKey: "distance"
|
||||||
|
decimals: 3
|
||||||
|
suffix: "m"
|
||||||
|
minimumValue: 0.0
|
||||||
|
maximumValue: 1000.0
|
||||||
|
stepSize: Math.round(0.5 * factor)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// QQC1.SpinBox: https://github.com/qt/qtquickcontrols/blob/dev/src/controls/SpinBox.qml
|
||||||
|
// QQC2.SpinBox: https://github.com/qt/qtquickcontrols2/blob/5.15/src/imports/controls/SpinBox.qml
|
||||||
|
// KDE Config Theme: https://invent.kde.org/frameworks/qqc2-desktop-style/-/blob/master/org.kde.desktop/SpinBox.qml
|
||||||
|
// Qt6 QQC2.SpinBox: https://github.com/qt/qtquickcontrols2/blob/dev/src/imports/controls/basic/SpinBox.qml
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: spinBox
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
readonly property var configValue: configKey ? plasmoid.configuration[configKey] : 0
|
||||||
|
|
||||||
|
readonly property real factor: Math.pow(10, decimals)
|
||||||
|
readonly property real valueReal: value / factor
|
||||||
|
value: Math.round(configValue * factor)
|
||||||
|
onValueRealChanged: serializeTimer.start()
|
||||||
|
|
||||||
|
readonly property int spinBox_MININT: Math.ceil(-2147483648 / factor)
|
||||||
|
readonly property int spinBox_MAXINT: Math.floor(2147483647 / factor)
|
||||||
|
from: Math.round(minimumValue * factor)
|
||||||
|
to: Math.round(maximumValue * factor)
|
||||||
|
|
||||||
|
// Reimplement QQC1 properties
|
||||||
|
// https://github.com/qt/qtquickcontrols/blob/dev/src/controls/SpinBox.qml
|
||||||
|
property int decimals: 0
|
||||||
|
property alias prefix: prefixLabel.text
|
||||||
|
property alias suffix: suffixLabel.text
|
||||||
|
property real minimumValue: 0
|
||||||
|
property real maximumValue: spinBox_MAXINT
|
||||||
|
|
||||||
|
// Avoid selecting prefix/suffix by drawing them overlayed on top.
|
||||||
|
// As a bonus, we can draw with in a different color (textColor at 60% opacity).
|
||||||
|
QQC2.Label {
|
||||||
|
id: prefixLabel
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.leftMargin: spinBox.leftPadding
|
||||||
|
anchors.topMargin: spinBox.topPadding
|
||||||
|
anchors.bottomMargin: spinBox.bottomPadding
|
||||||
|
font: spinBox.font
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
verticalAlignment: Qt.AlignVCenter
|
||||||
|
color: spinBox.palette.text
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
id: suffixLabel
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.rightMargin: spinBox.rightPadding
|
||||||
|
anchors.topMargin: spinBox.topPadding
|
||||||
|
anchors.bottomMargin: spinBox.bottomPadding
|
||||||
|
font: spinBox.font
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
verticalAlignment: Qt.AlignVCenter
|
||||||
|
color: spinBox.palette.text
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer { // throttle
|
||||||
|
id: serializeTimer
|
||||||
|
interval: 300
|
||||||
|
onTriggered: {
|
||||||
|
if (configKey) {
|
||||||
|
if (decimals == 0) {
|
||||||
|
plasmoid.configuration[configKey] = spinBox.value
|
||||||
|
} else {
|
||||||
|
plasmoid.configuration[configKey] = spinBox.valueReal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Qt5 used RegExpValidator { regExp: /[\-\.\d]+/ }
|
||||||
|
// validator: RegularExpressionValidator {
|
||||||
|
// regularExpression: /[\-\.\d]+/
|
||||||
|
// }
|
||||||
|
validator: DoubleValidator {
|
||||||
|
bottom: Math.min(spinBox.from, spinBox.to)
|
||||||
|
top: Math.max(spinBox.from, spinBox.to)
|
||||||
|
decimals: spinBox.decimals
|
||||||
|
notation: DoubleValidator.StandardNotation
|
||||||
|
}
|
||||||
|
|
||||||
|
textFromValue: function(value, locale) {
|
||||||
|
return Number(value / factor).toFixed(decimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueFromText: function(text, locale) {
|
||||||
|
var text2 = text
|
||||||
|
.replace(/[^\-\.\d]/g, '') // Remove non digit characters
|
||||||
|
.replace(/\.+/g, '.') // Allow user to type '.' instead of RightArrow to enter to decimals
|
||||||
|
var val = Number(text2)
|
||||||
|
if (isNaN(val)) {
|
||||||
|
val = -0
|
||||||
|
}
|
||||||
|
// console.log('valueFromText', text, val)
|
||||||
|
return Math.round(val * factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select value on foucs
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (activeFocus) {
|
||||||
|
selectValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function selectValue() {
|
||||||
|
// Check if SpinBox.contentItem == TextInput
|
||||||
|
// https://invent.kde.org/frameworks/qqc2-desktop-style/-/blob/master/org.kde.desktop/SpinBox.qml
|
||||||
|
// https://doc.qt.io/qt-5/qml-qtquick-textinput.html#select-method
|
||||||
|
if (contentItem && contentItem instanceof TextInput) {
|
||||||
|
contentItem.selectAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixMinus(str) {
|
||||||
|
var minusIndex = str.indexOf('-')
|
||||||
|
if (minusIndex >= 0) {
|
||||||
|
var a = str.substr(0, minusIndex)
|
||||||
|
var b = str.substr(minusIndex+1)
|
||||||
|
console.log('a', a, 'b', b)
|
||||||
|
|
||||||
|
return '-' + a + b
|
||||||
|
} else {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function fixDecimals(str) {
|
||||||
|
var periodIndex = str.indexOf('.')
|
||||||
|
var a = str.substr(0, periodIndex+1)
|
||||||
|
var b = str.substr(periodIndex+1)
|
||||||
|
return a + b.replace(/\.+/g, '') // Remove extra periods
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixText(str) {
|
||||||
|
return fixMinus(fixDecimals(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTextEdited() {
|
||||||
|
var oldText = spinBox.contentItem.text
|
||||||
|
// console.log('onTextEdited', 'oldText1', oldText)
|
||||||
|
oldText = fixText(oldText)
|
||||||
|
// console.log('onTextEdited', 'oldText2', oldText)
|
||||||
|
var oldPeriodIndex = oldText.indexOf('.')
|
||||||
|
if (oldPeriodIndex == -1) {
|
||||||
|
oldPeriodIndex = oldText.length
|
||||||
|
}
|
||||||
|
var oldCursorPosition = spinBox.contentItem.cursorPosition
|
||||||
|
var oldCursorDelta = oldPeriodIndex - oldCursorPosition
|
||||||
|
|
||||||
|
spinBox.value = spinBox.valueFromText(oldText, spinBox.locale)
|
||||||
|
spinBox.valueModified()
|
||||||
|
|
||||||
|
var newText = spinBox.contentItem.text
|
||||||
|
// console.log('onTextEdited', 'newText1', newText)
|
||||||
|
newText = fixText(newText)
|
||||||
|
// console.log('onTextEdited', 'newText2', newText)
|
||||||
|
var newPeriodIndex = newText.indexOf('.')
|
||||||
|
if (newPeriodIndex == -1) {
|
||||||
|
newPeriodIndex = newText.length
|
||||||
|
}
|
||||||
|
if (newText != spinBox.contentItem.text) {
|
||||||
|
spinBox.contentItem.text = Qt.binding(function(){
|
||||||
|
return spinBox.textFromValue(spinBox.value, spinBox.locale)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
spinBox.contentItem.cursorPosition = newPeriodIndex - oldCursorDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindContentItem() {
|
||||||
|
if (contentItem && contentItem instanceof TextInput) {
|
||||||
|
// We bind the left/right padding in the TextInput so that
|
||||||
|
// clicking the prefix/suffix will focus the TextInput. If we set
|
||||||
|
// the SpinBox left/right padding, then they do not focus the TextInput.
|
||||||
|
contentItem.leftPadding = Qt.binding(function(){ return prefixLabel.implicitWidth })
|
||||||
|
contentItem.rightPadding = Qt.binding(function(){ return suffixLabel.implicitWidth })
|
||||||
|
|
||||||
|
// Bind value update on keypress, while retaining cursor position
|
||||||
|
spinBox.contentItem.textEdited.connect(spinBox.onTextEdited)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentItemChanged: {
|
||||||
|
bindContentItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
if (data[i] instanceof Connections) {
|
||||||
|
// Remove the Connections where it changes the text/cursor when typing.
|
||||||
|
// onTextEdited { value = valueFromText() }
|
||||||
|
data[i].destroy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bindContentItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
// Version 4
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: configTextAlign
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
|
||||||
|
|
||||||
|
function setValue(val) {
|
||||||
|
if (configKey) {
|
||||||
|
plasmoid.configuration[configKey] = val
|
||||||
|
}
|
||||||
|
updateChecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChecked() {
|
||||||
|
// button.checked bindings are unbound when clicked
|
||||||
|
justifyLeftButton.checked = Qt.binding(function(){ return configValue == Text.AlignLeft })
|
||||||
|
justifyCenterButton.checked = Qt.binding(function(){ return configValue == Text.AlignHCenter })
|
||||||
|
justifyRightButton.checked = Qt.binding(function(){ return configValue == Text.AlignRight })
|
||||||
|
justifyFillButton.checked = Qt.binding(function(){ return configValue == Text.AlignJustify })
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: updateChecked()
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: justifyLeftButton
|
||||||
|
icon.name: "format-justify-left-symbolic"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: justifyCenterButton
|
||||||
|
icon.name: "format-justify-center-symbolic"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignHCenter)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: justifyRightButton
|
||||||
|
icon.name: "format-justify-right-symbolic"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: justifyFillButton
|
||||||
|
icon.name: "format-justify-fill-symbolic"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignJustify)
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// Version 6
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: textField
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
|
||||||
|
onConfigValueChanged: {
|
||||||
|
if (!focus && text != configValue) {
|
||||||
|
text = configValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text: configValue
|
||||||
|
onTextChanged: serializeTimer.start()
|
||||||
|
|
||||||
|
property string defaultValue: ""
|
||||||
|
|
||||||
|
rightPadding: clearButton.width + Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
property alias clearButton: clearButton
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: clearButton
|
||||||
|
visible: textField.text != textField.defaultValue
|
||||||
|
icon.name: "edit-clear"
|
||||||
|
onClicked: textField.text = defaultValue
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
width: implicitBackgroundHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer { // throttle
|
||||||
|
id: serializeTimer
|
||||||
|
interval: 300
|
||||||
|
onTriggered: {
|
||||||
|
if (configKey) {
|
||||||
|
plasmoid.configuration[configKey] = textField.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
// Version 6
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
import "." as LibConfig
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
id: configTextFormat
|
||||||
|
|
||||||
|
spacing: Kirigami.Units.smallSpacing * 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
property alias boldConfigKey: configBold.configKey
|
||||||
|
property alias italicConfigKey: configItalic.configKey
|
||||||
|
property alias underlineConfigKey: configUnderline.configKey
|
||||||
|
property alias alignConfigKey: configTextAlign.configKey
|
||||||
|
property alias vertAlignConfigKey: configVertAlign.configKey
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
QQC2.Button {
|
||||||
|
id: configBold
|
||||||
|
property string configKey: ''
|
||||||
|
visible: configKey
|
||||||
|
icon.name: 'format-text-bold-symbolic'
|
||||||
|
checkable: true
|
||||||
|
checked: configKey ? plasmoid.configuration[configKey] : false
|
||||||
|
onClicked: plasmoid.configuration[configKey] = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: configItalic
|
||||||
|
property string configKey: ''
|
||||||
|
visible: configKey
|
||||||
|
icon.name: 'format-text-italic-symbolic'
|
||||||
|
checkable: true
|
||||||
|
checked: configKey ? plasmoid.configuration[configKey] : false
|
||||||
|
onClicked: plasmoid.configuration[configKey] = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: configUnderline
|
||||||
|
property string configKey: ''
|
||||||
|
visible: configKey
|
||||||
|
icon.name: 'format-text-underline-symbolic'
|
||||||
|
checkable: true
|
||||||
|
checked: configKey ? plasmoid.configuration[configKey] : false
|
||||||
|
onClicked: plasmoid.configuration[configKey] = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LibConfig.TextAlign {
|
||||||
|
id: configTextAlign
|
||||||
|
visible: configKey
|
||||||
|
}
|
||||||
|
|
||||||
|
LibConfig.VertAlign {
|
||||||
|
id: configVertAlign
|
||||||
|
visible: configKey
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
// Version 5
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: configVertAlign
|
||||||
|
|
||||||
|
property string configKey: ''
|
||||||
|
readonly property string configValue: configKey ? plasmoid.configuration[configKey] : ""
|
||||||
|
|
||||||
|
function setValue(val) {
|
||||||
|
if (configKey) {
|
||||||
|
plasmoid.configuration[configKey] = val
|
||||||
|
}
|
||||||
|
updateChecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChecked() {
|
||||||
|
// button.checked bindings are unbound when clicked
|
||||||
|
vertTopButton.checked = Qt.binding(function(){ return configValue == Text.AlignTop })
|
||||||
|
vertCenterButton.checked = Qt.binding(function(){ return configValue == Text.AlignVCenter })
|
||||||
|
vertBottomButton.checked = Qt.binding(function(){ return configValue == Text.AlignBottom })
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: updateChecked()
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: vertTopButton
|
||||||
|
icon.name: "align-vertical-top"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: vertCenterButton
|
||||||
|
icon.name: "align-vertical-center"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignVCenter)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: vertBottomButton
|
||||||
|
icon.name: "align-vertical-bottom"
|
||||||
|
checkable: true
|
||||||
|
onClicked: setValue(Text.AlignBottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
|
import org.kde.plasma.components as PlasmaComponent
|
||||||
|
import org.kde.plasma.plasma5support as Plasma5Support
|
||||||
|
|
||||||
|
PlasmoidItem {
|
||||||
|
id: widget
|
||||||
|
|
||||||
|
// https://github.com/KDE/plasma-workspace/blob/master/dataengines/executable/executable.h
|
||||||
|
// https://github.com/KDE/plasma-workspace/blob/master/dataengines/executable/executable.cpp
|
||||||
|
// https://github.com/KDE/plasma-framework/blob/master/src/declarativeimports/core/datasource.h
|
||||||
|
// https://github.com/KDE/plasma-framework/blob/master/src/declarativeimports/core/datasource.cpp
|
||||||
|
// https://github.com/KDE/plasma-framework/blob/master/src/plasma/scripting/dataenginescript.cpp
|
||||||
|
Plasma5Support.DataSource {
|
||||||
|
id: executable
|
||||||
|
engine: "executable"
|
||||||
|
connectedSources: []
|
||||||
|
onNewData: (sourceName, data) => {
|
||||||
|
var exitCode = data["exit code"]
|
||||||
|
var exitStatus = data["exit status"]
|
||||||
|
var stdout = data["stdout"]
|
||||||
|
var stderr = data["stderr"]
|
||||||
|
exited(sourceName, exitCode, exitStatus, stdout, stderr)
|
||||||
|
disconnectSource(sourceName) // cmd finished
|
||||||
|
}
|
||||||
|
function exec(cmd) {
|
||||||
|
if (cmd) {
|
||||||
|
connectSource(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signal exited(string cmd, int exitCode, int exitStatus, string stdout, string stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
function performClick() {
|
||||||
|
executable.exec(plasmoid.configuration.clickCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
function performMouseWheelUp() {
|
||||||
|
executable.exec(plasmoid.configuration.mousewheelUpCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
function performMouseWheelDown() {
|
||||||
|
executable.exec(plasmoid.configuration.mousewheelDownCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: config
|
||||||
|
readonly property bool active: !!command
|
||||||
|
readonly property bool waitForCompletion: plasmoid.configuration.waitForCompletion
|
||||||
|
readonly property int interval: Math.max(0, plasmoid.configuration.interval)
|
||||||
|
readonly property string command: plasmoid.configuration.command || ''
|
||||||
|
readonly property string tooltipCommand: plasmoid.configuration.tooltipCommand || ''
|
||||||
|
readonly property bool clickEnabled: !!plasmoid.configuration.clickCommand
|
||||||
|
readonly property bool mousewheelEnabled: (plasmoid.configuration.mousewheelUpCommand || plasmoid.configuration.mousewheelDownCommand)
|
||||||
|
readonly property color textColor: plasmoid.configuration.textColor || Kirigami.Theme.textColor
|
||||||
|
readonly property color outlineColor: plasmoid.configuration.outlineColor || Kirigami.Theme.backgroundColor
|
||||||
|
readonly property bool showOutline: plasmoid.configuration.showOutline
|
||||||
|
|
||||||
|
onCommandChanged: widget.runCommand()
|
||||||
|
onTooltipCommandChanged: widget.runCommand()
|
||||||
|
onIntervalChanged: {
|
||||||
|
// interval=0 stops the timer even with Timer.repeat=true, so we may
|
||||||
|
// need to restart the timer. Might as well restart the interval too.
|
||||||
|
timer.restart()
|
||||||
|
}
|
||||||
|
onWaitForCompletionChanged: {
|
||||||
|
if (!waitForCompletion) {
|
||||||
|
// The timer needs to be restarted in case the timer was already
|
||||||
|
// triggered and the command is running. If we don't restart the
|
||||||
|
// timer, it'll stop forever.
|
||||||
|
timer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
|
||||||
|
property var ansiColors: ({
|
||||||
|
30: '#000000', // Black
|
||||||
|
31: '#aa0000', // Red
|
||||||
|
32: '#00aa00', // Green
|
||||||
|
33: '#aa6500', // Yellow
|
||||||
|
34: '#0000aa', // Blue
|
||||||
|
35: '#aa00aa', // Magenta
|
||||||
|
36: '#00aaaa', // Cyan
|
||||||
|
37: '#aaaaaa', // White
|
||||||
|
90: '#656565', // Bright Black
|
||||||
|
91: '#ff6565', // Bright Red
|
||||||
|
92: '#65ff65', // Bright Green
|
||||||
|
93: '#ffff65', // Bright Yellow
|
||||||
|
94: '#6565ff', // Bright Blue
|
||||||
|
95: '#ff65ff', // Bright Magenta
|
||||||
|
96: '#65ffff', // Bright Cyan
|
||||||
|
97: '#ffffff', // Bright White
|
||||||
|
})
|
||||||
|
function resetState(state) {
|
||||||
|
var out = state.closeTags.join(' ')
|
||||||
|
state.bold = false
|
||||||
|
state.closeTags = []
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
function parseAnsiCode(n, i, tokens, state) {
|
||||||
|
if (n == 0) { // Reset
|
||||||
|
return resetState(state)
|
||||||
|
} else if (n == 1) {
|
||||||
|
state.closeTags.push('</b>')
|
||||||
|
state.bold = true
|
||||||
|
return '<b>'
|
||||||
|
} else if (30 <= n && n <= 37 || 90 <= n && n <= 97) {
|
||||||
|
if (state.bold && 30 <= n && n <= 37) {
|
||||||
|
// Bold also intensifies the colors to "Bright".
|
||||||
|
// 30 => 90
|
||||||
|
n += 60
|
||||||
|
}
|
||||||
|
var hexColor = ansiColors[n]
|
||||||
|
state.closeTags.push('</font>')
|
||||||
|
return '<font color="' + hexColor + '">'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// https://stackoverflow.com/questions/4745317/converting-integers-to-hex-string-in-javascript
|
||||||
|
function formatHexInt(n) {
|
||||||
|
var num = Number(n)
|
||||||
|
if (isNaN(num)) {
|
||||||
|
return "00"
|
||||||
|
}
|
||||||
|
num = Math.max(0, Math.min(num, 255))
|
||||||
|
var str = num.toString(16)
|
||||||
|
return str.length == 1 ? '0' + str : str
|
||||||
|
}
|
||||||
|
function rgbToHex(r, g, b) {
|
||||||
|
return '#' + formatHexInt(r) + formatHexInt(g) + formatHexInt(b)
|
||||||
|
}
|
||||||
|
function parseColorMode(i, tokens) {
|
||||||
|
var colorMode = parseInt(tokens[++i], 10)
|
||||||
|
if (colorMode == 2) { // RGB
|
||||||
|
var r = parseInt(tokens[++i], 10)
|
||||||
|
var g = parseInt(tokens[++i], 10)
|
||||||
|
var b = parseInt(tokens[++i], 10)
|
||||||
|
return rgbToHex(r, g, b)
|
||||||
|
} else if (colorMode == 5) { // Preset of 256 colors
|
||||||
|
// Logic taken from Konsole
|
||||||
|
// https://invent.kde.org/utilities/konsole/-/blob/master/src/autotests/CharacterColorTest.cpp#L159
|
||||||
|
var n = parseInt(tokens[++i], 10)
|
||||||
|
if (0 <= n && n <= 7) { // Normal
|
||||||
|
var u = n + 30
|
||||||
|
return ansiColors[u]
|
||||||
|
} else if (8 <= n && n <= 15) { // Bright
|
||||||
|
var u = n - 8 + 90
|
||||||
|
return ansiColors[u]
|
||||||
|
} else if (16 <= n && n <= 231) { // 212
|
||||||
|
var u = n - 16
|
||||||
|
var r = Math.floor(((u / 36) % 6) != 0 ? (40 * ((u / 36) % 6) + 55) : 0)
|
||||||
|
var g = Math.floor(((u / 6) % 6) != 0 ? (40 * ((u / 6) % 6) + 55) : 0)
|
||||||
|
var b = Math.floor(((u / 1) % 6) != 0 ? (40 * ((u / 1) % 6) + 55) : 0)
|
||||||
|
return rgbToHex(r, g, b)
|
||||||
|
} else if (232 <= n && n <= 255) {
|
||||||
|
var gray = Math.floor((n - 232) * 10 + 8)
|
||||||
|
return rgbToHex(gray, gray, gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
function parseAnsiEscape(codes, state) {
|
||||||
|
var tokens = codes.split(';')
|
||||||
|
var out = ''
|
||||||
|
for (var i = 0; i < tokens.length; i++) {
|
||||||
|
tokens[i] = parseInt(tokens[i], 10)
|
||||||
|
}
|
||||||
|
for (var i = 0; i < tokens.length; i++) {
|
||||||
|
var token = tokens[i]
|
||||||
|
if (token == 38) { // Set FG
|
||||||
|
var hexColor = parseColorMode(i, tokens)
|
||||||
|
if (hexColor) {
|
||||||
|
state.closeTags.push('</font>')
|
||||||
|
out += '<font color="' + hexColor + '">'
|
||||||
|
}
|
||||||
|
} else if (token == 48) { // Set BG
|
||||||
|
var hexColor = parseColorMode(i, tokens)
|
||||||
|
// Ignore
|
||||||
|
} else {
|
||||||
|
out += parseAnsiCode(token, i, tokens, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
property string outputText: ''
|
||||||
|
property string tooltipText: ''
|
||||||
|
|
||||||
|
function formatOutputText(stdout) {
|
||||||
|
var formattedText = stdout
|
||||||
|
|
||||||
|
// Newlines
|
||||||
|
if (plasmoid.configuration.replaceAllNewlines) {
|
||||||
|
formattedText = formattedText.replace(/\n/g, ' ').trim()
|
||||||
|
} else if (formattedText.length >= 1 && formattedText[formattedText.length-1] == '\n') {
|
||||||
|
formattedText = formattedText.substr(0, formattedText.length-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal Colors (Issue #7)
|
||||||
|
var state = {
|
||||||
|
html: false,
|
||||||
|
bold: false,
|
||||||
|
closeTags: [],
|
||||||
|
}
|
||||||
|
formattedText = formattedText.replace(/\033\[(\d+(;\d+)*)?m/g, function(match, p1, p2){
|
||||||
|
state.html = true
|
||||||
|
if (typeof p1 === 'string') {
|
||||||
|
return parseAnsiEscape(p1, state)
|
||||||
|
} else { // \033[m is Reset
|
||||||
|
return parseAnsiEscape('0', state)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
formattedText += resetState(state)
|
||||||
|
|
||||||
|
// Format Newlines when in HTML mode
|
||||||
|
if (state.html) {
|
||||||
|
formattedText = formattedText.replace(/\n/g, '<br>')
|
||||||
|
}
|
||||||
|
return formattedText
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: executable
|
||||||
|
function onExited(cmd, exitCode, exitStatus, stdout, stderr) {
|
||||||
|
if ((cmd == config.command) || (cmd == config.tooltipCommand)) {
|
||||||
|
var formattedText = formatOutputText(stdout)
|
||||||
|
|
||||||
|
// console.log('[commandoutput]', 'stdout', JSON.stringify(stdout))
|
||||||
|
// console.log('[commandoutput]', 'format', JSON.stringify(formattedText))
|
||||||
|
|
||||||
|
if (cmd == config.command) {
|
||||||
|
widget.outputText = formattedText
|
||||||
|
} else if (cmd == config.tooltipCommand) {
|
||||||
|
widget.tooltipText = formattedText
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.waitForCompletion) {
|
||||||
|
timer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runCommand() {
|
||||||
|
// console.log('[commandoutput]', Date.now(), 'runCommand', config.command)
|
||||||
|
executable.exec(config.command)
|
||||||
|
executable.exec(config.tooltipCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: timer
|
||||||
|
interval: config.interval
|
||||||
|
running: true
|
||||||
|
repeat: !config.waitForCompletion
|
||||||
|
onTriggered: widget.runCommand()
|
||||||
|
// onIntervalChanged: console.log('interval', interval)
|
||||||
|
// onRunningChanged: console.log('running', running)
|
||||||
|
// onRepeatChanged: console.log('repeat', repeat)
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// Run right away in case the interval is very long.
|
||||||
|
triggered()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plasmoid.onActivated: widget.performClick()
|
||||||
|
|
||||||
|
Plasmoid.backgroundHints: plasmoid.configuration.showBackground ? PlasmaCore.Types.DefaultBackground : PlasmaCore.Types.NoBackground
|
||||||
|
|
||||||
|
fullRepresentation: Item {
|
||||||
|
id: panelItem
|
||||||
|
|
||||||
|
readonly property bool isHorizontal: plasmoid.formFactor == PlasmaCore.Types.Horizontal
|
||||||
|
readonly property bool isVertical: plasmoid.formFactor == PlasmaCore.Types.Vertical
|
||||||
|
readonly property bool isInPanel: isHorizontal || isVertical
|
||||||
|
readonly property bool isOnDesktop: !isInPanel
|
||||||
|
|
||||||
|
readonly property int itemWidth: {
|
||||||
|
if (isOnDesktop) {
|
||||||
|
return Math.ceil(output.contentWidth)
|
||||||
|
} else if (isHorizontal && plasmoid.configuration.useFixedWidth) {
|
||||||
|
return plasmoid.configuration.fixedWidth * Kirigami.Units.devicePixelRatio
|
||||||
|
} else { // isHorizontal || isVertical
|
||||||
|
return Math.ceil(output.implicitWidth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Layout.minimumWidth: isHorizontal ? itemWidth : -1
|
||||||
|
Layout.fillWidth: isVertical
|
||||||
|
Layout.preferredWidth: itemWidth // Panel widget default
|
||||||
|
// width: itemWidth // Desktop widget default
|
||||||
|
// onItemWidthChanged: console.log('itemWidth', itemWidth, 'implicitWidth', output.implicitWidth, 'contentWidth', output.contentWidth)
|
||||||
|
|
||||||
|
readonly property int itemHeight: {
|
||||||
|
if (isOnDesktop) {
|
||||||
|
return Math.ceil(output.contentHeight)
|
||||||
|
} else if (isVertical && plasmoid.configuration.useFixedHeight) {
|
||||||
|
return plasmoid.configuration.fixedHeight * Kirigami.Units.devicePixelRatio
|
||||||
|
} else { // isHorizontal || isVertical
|
||||||
|
return Math.ceil(output.implicitHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Layout.minimumHeight: isVertical ? itemHeight : -1
|
||||||
|
Layout.fillHeight: isHorizontal
|
||||||
|
Layout.preferredHeight: itemHeight // Panel widget default
|
||||||
|
// height: itemHeight // Desktop widget default
|
||||||
|
// onItemHeightChanged: console.log('itemHeight', itemHeight, 'implicitHeight', output.implicitHeight, 'contentHeight', output.contentHeight)
|
||||||
|
|
||||||
|
|
||||||
|
// Note MouseArea is below the Text so
|
||||||
|
// that we don't eat the link clicks.
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: config.clickEnabled
|
||||||
|
|
||||||
|
cursorShape: output.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
widget.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
property int wheelDelta: 0
|
||||||
|
onWheel: (wheel) => {
|
||||||
|
var delta = wheel.angleDelta.y || wheel.angleDelta.x
|
||||||
|
wheelDelta += delta
|
||||||
|
// Magic number 120 for common "one click"
|
||||||
|
// See: http://qt-project.org/doc/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop
|
||||||
|
while (wheelDelta >= 120) {
|
||||||
|
wheelDelta -= 120
|
||||||
|
widget.performMouseWheelUp()
|
||||||
|
}
|
||||||
|
while (wheelDelta <= -120) {
|
||||||
|
wheelDelta += 120
|
||||||
|
widget.performMouseWheelDown()
|
||||||
|
}
|
||||||
|
wheel.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: output
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
PlasmaCore.ToolTipArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
mainText: widget.tooltipText
|
||||||
|
enabled: widget.tooltipText
|
||||||
|
}
|
||||||
|
|
||||||
|
text: widget.outputText
|
||||||
|
|
||||||
|
color: config.textColor
|
||||||
|
style: config.showOutline ? Text.Outline : Text.Normal
|
||||||
|
styleColor: config.outlineColor
|
||||||
|
|
||||||
|
linkColor: Kirigami.Theme.linkColor
|
||||||
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
|
||||||
|
font.pointSize: plasmoid.configuration.fontSize
|
||||||
|
font.family: plasmoid.configuration.fontFamily || Kirigami.Theme.defaultFont.family
|
||||||
|
font.weight: plasmoid.configuration.bold ? Font.Bold : Font.Normal
|
||||||
|
font.italic: plasmoid.configuration.italic
|
||||||
|
font.underline: plasmoid.configuration.underline
|
||||||
|
fontSizeMode: Text.FixedSize
|
||||||
|
horizontalAlignment: plasmoid.configuration.textAlign
|
||||||
|
verticalAlignment: plasmoid.configuration.vertAlign
|
||||||
|
|
||||||
|
property bool isFixedWidth: {
|
||||||
|
if (plasmoid.formFactor == PlasmaCore.Types.Planar) { // Desktop Widget
|
||||||
|
return true
|
||||||
|
} else if (plasmoid.formFactor == PlasmaCore.Types.Horizontal) {
|
||||||
|
return plasmoid.configuration.useFixedWidth
|
||||||
|
} else if (plasmoid.formFactor == PlasmaCore.Types.Vertical) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: isFixedWidth ? Text.Wrap : Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredRepresentation: fullRepresentation
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"KPackageStructure": "Plasma/Applet",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "zrenfire@gmail.com",
|
||||||
|
"Name": "Chris Holland"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Email": "aricaldeira@gmail.com",
|
||||||
|
"Name": "Ari Caldeira"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Email": "oded@geek.co.il",
|
||||||
|
"Name": "Oded Arbel"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Category": "System Information",
|
||||||
|
"Description": "Run a command periodically and render its output.",
|
||||||
|
"Description[bz]": "Ezekuta un komandu a kada X milisegundus, i mòstra a saida/rezuwtadu",
|
||||||
|
"Description[fr]": "Exécute une commande périodiquement et affiche son retour.",
|
||||||
|
"Description[nl]": "Voer een opdracht periodiek uit en toon de uitvoer ervan.",
|
||||||
|
"Description[pt_br]": "Executa um comando a cada X milissegundos, e mostra a saída/resultado",
|
||||||
|
"Icon": "utilities-terminal",
|
||||||
|
"Id": "com.github.zren.commandoutput",
|
||||||
|
"License": "GPL-2.0+",
|
||||||
|
"Name": "Command Output",
|
||||||
|
"Name[bz]": "Saida di Komandus",
|
||||||
|
"Name[fr]": "Command Output",
|
||||||
|
"Name[nl]": "Opdrachtuitvoer",
|
||||||
|
"Name[pt_br]": "Saída de Comandos",
|
||||||
|
"Version": "14",
|
||||||
|
"Website": "https://github.com/Zren/plasma-applet-commandoutput"
|
||||||
|
},
|
||||||
|
"X-Plasma-API-Minimum-Version": "6.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Translate
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
| Locale | Lines | % Done|
|
||||||
|
|----------|---------|-------|
|
||||||
|
| Template | 30 | |
|
||||||
|
| bz | 26/30 | 86% |
|
||||||
|
| fr | 30/30 | 100% |
|
||||||
|
| nl | 30/30 | 100% |
|
||||||
|
| pt_br | 27/30 | 90% |
|
||||||
|
|
||||||
|
|
||||||
|
## New Translations
|
||||||
|
|
||||||
|
* Fill out [`template.pot`](template.pot) with your translations then open a [new issue](https://github.com/Zren/plasma-applet-commandoutput/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_com.github.zren.commandoutput.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,166 @@
|
|||||||
|
# Translation of commandoutput in fr
|
||||||
|
# Copyright (C) 2020
|
||||||
|
# This file is distributed under the same license as the commandoutput package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: commandoutput\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-commandoutput\n"
|
||||||
|
"POT-Creation-Date: 2024-03-12 16:30-0400\n"
|
||||||
|
"PO-Revision-Date: 2020-10-05 21:55+020\n"
|
||||||
|
"Last-Translator: Arí KAWDERA <aricaldeira@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: bz_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Command Output"
|
||||||
|
msgstr "Saida di Komandus"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Run a command periodically and render its output."
|
||||||
|
msgstr "Ezekuta un komandu a kada X milisegundus, i mòstra a saida/rezuwtadu"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Command"
|
||||||
|
msgstr "Komandu"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Appearance"
|
||||||
|
msgstr "Aparensya"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Asoyns"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Tooltip"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Hover Command:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Click"
|
||||||
|
msgstr "Kliki"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Run Command:"
|
||||||
|
msgstr "Rodar u komandu:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Mouse Wheel"
|
||||||
|
msgstr "Rodiỹa du mawzi"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Up:"
|
||||||
|
msgstr "Rolar pra sima:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Down:"
|
||||||
|
msgstr "Rolar pra baxu:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font"
|
||||||
|
msgstr "Fonti"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Family:"
|
||||||
|
msgstr "Familya:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Size:"
|
||||||
|
msgstr "Tamaỹu:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "px"
|
||||||
|
msgstr "px"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Colors"
|
||||||
|
msgstr "Koris"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Text:"
|
||||||
|
msgstr "Testu:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Outline:"
|
||||||
|
msgstr "Kontornu:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Misc"
|
||||||
|
msgstr "Divèrsus"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Desktop Widget:"
|
||||||
|
msgstr "Widigèti pru dèskitòpi:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Show background"
|
||||||
|
msgstr "nuwn dexar transparenti"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Width:"
|
||||||
|
msgstr "Largura fiksa:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Height:"
|
||||||
|
msgstr "Awtura fiksa:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Replace all newlines with spaces"
|
||||||
|
msgstr "Junta as liỹas numa liỹa sò, separandu kun espasus"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Run every "
|
||||||
|
msgstr "Rodar a kada "
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "ms"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Wait for completion"
|
||||||
|
msgstr "Esperar a ezekusawn terminar"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/libconfig/FontFamily.qml
|
||||||
|
msgctxt "Use default font"
|
||||||
|
msgid "Default"
|
||||||
|
msgstr "Padrawn"
|
||||||
|
|
||||||
|
#~ msgid "<b>Version:</b> %1"
|
||||||
|
#~ msgstr "<b>Versawn:</b> %1"
|
||||||
|
|
||||||
|
#~ msgid "Mouse Click"
|
||||||
|
#~ msgstr "Kliki du mawzi"
|
||||||
|
|
||||||
|
#~ msgid "Command:"
|
||||||
|
#~ msgstr "Komandu:"
|
||||||
|
|
||||||
|
#~ msgid "pt"
|
||||||
|
#~ msgstr "pt"
|
||||||
|
|
||||||
|
#~ msgid "Desktop Widget: Show background"
|
||||||
|
#~ msgstr "Widigèti pru dèskitòpi: nuwn dexar transparenti"
|
||||||
|
|
||||||
|
#~ msgid "Tooltip Command"
|
||||||
|
#~ msgstr "Komandu pra pasajen du mawzi"
|
||||||
|
|
||||||
|
#~ msgid "Run"
|
||||||
|
#~ msgstr "Ezekusawn"
|
||||||
|
|
||||||
|
#~ msgid "Tooltip command"
|
||||||
|
#~ msgstr "Komandu pra pasajen du mawzi"
|
||||||
|
|
||||||
|
#~ msgid "General"
|
||||||
|
#~ msgstr "Jeraw"
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# Translation of commandoutput in fr
|
||||||
|
# Copyright (C) 2020
|
||||||
|
# This file is distributed under the same license as the commandoutput package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: commandoutput\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-commandoutput\n"
|
||||||
|
"POT-Creation-Date: 2024-03-12 16:30-0400\n"
|
||||||
|
"PO-Revision-Date: 2024-03-14 09:56+020\n"
|
||||||
|
"Last-Translator: Sasha MOREL <s.morel@almtesh.net>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Command Output"
|
||||||
|
msgstr "Command Output"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Run a command periodically and render its output."
|
||||||
|
msgstr "Exécute une commande périodiquement et affiche son retour."
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Command"
|
||||||
|
msgstr "Commande"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Appearance"
|
||||||
|
msgstr "Apparence"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Actions"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Tooltip"
|
||||||
|
msgstr "Infobulle"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Hover Command:"
|
||||||
|
msgstr "Commande au survol :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Click"
|
||||||
|
msgstr "Clic"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Run Command:"
|
||||||
|
msgstr "Lancer la commande :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Mouse Wheel"
|
||||||
|
msgstr "Molette de souris"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Up:"
|
||||||
|
msgstr "Défilement vers le haut :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Down:"
|
||||||
|
msgstr "Défilement vers le bas :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font"
|
||||||
|
msgstr "Police"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Family:"
|
||||||
|
msgstr "Famille :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Size:"
|
||||||
|
msgstr "Taille :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "px"
|
||||||
|
msgstr "px"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Colors"
|
||||||
|
msgstr "Couleurs"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Text:"
|
||||||
|
msgstr "Texte :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Outline:"
|
||||||
|
msgstr "Contours :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Misc"
|
||||||
|
msgstr "Divers"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Desktop Widget:"
|
||||||
|
msgstr "Pour le widget de bureau :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Show background"
|
||||||
|
msgstr "afficher un fond"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Width:"
|
||||||
|
msgstr "Largeur fixe :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Height:"
|
||||||
|
msgstr "Hauteur fixe :"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Replace all newlines with spaces"
|
||||||
|
msgstr "Remplacer les sauts de lignes par des espaces"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Run every "
|
||||||
|
msgstr "Exécuter toutes les "
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "ms"
|
||||||
|
msgstr " ms"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Wait for completion"
|
||||||
|
msgstr "Attendre la fin de l'éxécution"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "Activé"
|
||||||
|
|
||||||
|
#: ../contents/ui/libconfig/FontFamily.qml
|
||||||
|
msgctxt "Use default font"
|
||||||
|
msgid "Default"
|
||||||
|
msgstr "Par défaut"
|
||||||
|
|
||||||
|
#~ msgid "<b>Version:</b> %1"
|
||||||
|
#~ msgstr "<b>Version :</b> %1"
|
||||||
|
|
||||||
|
#~ msgid "Desktop Widget: Show background"
|
||||||
|
#~ msgstr "Pour le widget de bureau : afficher un fond"
|
||||||
|
|
||||||
|
#~ msgid "General"
|
||||||
|
#~ msgstr "Général"
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# Translation of commandoutput in nl_NL
|
||||||
|
# Copyright (C) 2019
|
||||||
|
# This file is distributed under the same license as the commandoutput package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: commandoutput\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-commandoutput\n"
|
||||||
|
"POT-Creation-Date: 2024-03-12 16:30-0400\n"
|
||||||
|
"PO-Revision-Date: 2024-03-14 10:27+0100\n"
|
||||||
|
"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
|
||||||
|
"Language-Team: Dutch <vistausss@outlook.com>\n"
|
||||||
|
"Language: nl_NL\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 3.4.2\n"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Command Output"
|
||||||
|
msgstr "Opdrachtuitvoer"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Run a command periodically and render its output."
|
||||||
|
msgstr "Voer een opdracht periodiek uit en toon de uitvoer ervan."
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Command"
|
||||||
|
msgstr "Opdracht"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Appearance"
|
||||||
|
msgstr "Vormgeving"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Acties"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Tooltip"
|
||||||
|
msgstr "Hulpballon"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Hover Command:"
|
||||||
|
msgstr "Opdracht na aanwijzen:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Click"
|
||||||
|
msgstr "Klikken"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Run Command:"
|
||||||
|
msgstr "Opdracht uitvoeren:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Mouse Wheel"
|
||||||
|
msgstr "Muiswiel"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Up:"
|
||||||
|
msgstr "Omhoog scrollen:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Down:"
|
||||||
|
msgstr "Omlaag scrollen:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font"
|
||||||
|
msgstr "Lettertype"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Family:"
|
||||||
|
msgstr "Lettertype:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Size:"
|
||||||
|
msgstr "Tekstgrootte:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "px"
|
||||||
|
msgstr "px"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Colors"
|
||||||
|
msgstr "Kleuren"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Text:"
|
||||||
|
msgstr "Tekst:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Outline:"
|
||||||
|
msgstr "Contour:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Misc"
|
||||||
|
msgstr "Overig"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Desktop Widget:"
|
||||||
|
msgstr "Bureaubladwidget:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Show background"
|
||||||
|
msgstr "Achtergrond tonen"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Width:"
|
||||||
|
msgstr "Vaste breedte:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Height:"
|
||||||
|
msgstr "Vaste hoogte:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Replace all newlines with spaces"
|
||||||
|
msgstr "Alle witregels vervangen door spaties"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Run every "
|
||||||
|
msgstr "Uitvoeren, elke "
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "ms"
|
||||||
|
msgstr "ms"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Wait for completion"
|
||||||
|
msgstr "Wachten tot opdracht is afgerond"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "Ingeschakeld"
|
||||||
|
|
||||||
|
#: ../contents/ui/libconfig/FontFamily.qml
|
||||||
|
msgctxt "Use default font"
|
||||||
|
msgid "Default"
|
||||||
|
msgstr "Standaard"
|
||||||
|
|
||||||
|
#~ msgid "<b>Version:</b> %1"
|
||||||
|
#~ msgstr "<b>Versie:</b> %1"
|
||||||
|
|
||||||
|
#~ msgid "Desktop Widget: Show background"
|
||||||
|
#~ msgstr "Bureaubladwidget: achtergrond tonen"
|
||||||
|
|
||||||
|
#~ msgid "General"
|
||||||
|
#~ msgstr "Algemeen"
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
# Translation of commandoutput in fr
|
||||||
|
# Copyright (C) 2020
|
||||||
|
# This file is distributed under the same license as the commandoutput package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: commandoutput\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-commandoutput\n"
|
||||||
|
"POT-Creation-Date: 2024-03-12 16:30-0400\n"
|
||||||
|
"PO-Revision-Date: 2020-10-05 21:55+020\n"
|
||||||
|
"Last-Translator: Ari CALDEIRA <aricaldeira@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Command Output"
|
||||||
|
msgstr "Saída de Comandos"
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Run a command periodically and render its output."
|
||||||
|
msgstr "Executa um comando a cada X milissegundos, e mostra a saída/resultado"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Command"
|
||||||
|
msgstr "Comando"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Appearance"
|
||||||
|
msgstr "Aparência"
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Ações"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Tooltip"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Hover Command:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Click"
|
||||||
|
msgstr "Clique"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Run Command:"
|
||||||
|
msgstr "Rodar o comando:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Mouse Wheel"
|
||||||
|
msgstr "Rodinha do mouse"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Up:"
|
||||||
|
msgstr "Rolar para cima:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Down:"
|
||||||
|
msgstr "Rolar para baixo:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font"
|
||||||
|
msgstr "Fonte"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Family:"
|
||||||
|
msgstr "Família:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Size:"
|
||||||
|
msgstr "Tamanho:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "px"
|
||||||
|
msgstr "px"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Colors"
|
||||||
|
msgstr "Cores"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Text:"
|
||||||
|
msgstr "Texto:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Outline:"
|
||||||
|
msgstr "Contorno:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Misc"
|
||||||
|
msgstr "Diversos"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Desktop Widget:"
|
||||||
|
msgstr "Widget para Desktop:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Show background"
|
||||||
|
msgstr "não deixa transparente"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Width:"
|
||||||
|
msgstr "Largura fixa:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Height:"
|
||||||
|
msgstr "Altura fixa:"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Replace all newlines with spaces"
|
||||||
|
msgstr "Junta as linhas numa linha só, separando com espaços"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Run every "
|
||||||
|
msgstr "Executar a cada "
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "ms"
|
||||||
|
msgstr "ms"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Wait for completion"
|
||||||
|
msgstr "Esperar a execução terminar"
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/libconfig/FontFamily.qml
|
||||||
|
msgctxt "Use default font"
|
||||||
|
msgid "Default"
|
||||||
|
msgstr "Padrão"
|
||||||
|
|
||||||
|
#~ msgid "<b>Version:</b> %1"
|
||||||
|
#~ msgstr "<b>Versão:</b> %1"
|
||||||
|
|
||||||
|
#~ msgid "Mouse Click"
|
||||||
|
#~ msgstr "Clique do mouse"
|
||||||
|
|
||||||
|
#~ msgid "Command:"
|
||||||
|
#~ msgstr "Comando:"
|
||||||
|
|
||||||
|
#~ msgid "pt"
|
||||||
|
#~ msgstr "pt"
|
||||||
|
|
||||||
|
#~ msgid "Desktop Widget: Show background"
|
||||||
|
#~ msgstr "Widget para Desktop: não deixa transparente"
|
||||||
|
|
||||||
|
#~ msgid "Tooltip Command"
|
||||||
|
#~ msgstr "Comando para a passagem do mouse"
|
||||||
|
|
||||||
|
#~ msgid "Run"
|
||||||
|
#~ msgstr "Execução"
|
||||||
|
|
||||||
|
#~ msgid "Tooltip command"
|
||||||
|
#~ msgstr "Comando para a passagem do mouse"
|
||||||
|
|
||||||
|
#~ msgid "General"
|
||||||
|
#~ msgstr "Geral"
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
# Translation of commandoutput in LANGUAGE
|
||||||
|
# Copyright (C) 2024
|
||||||
|
# This file is distributed under the same license as the commandoutput package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: commandoutput\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-commandoutput\n"
|
||||||
|
"POT-Creation-Date: 2024-03-12 16:30-0400\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 "Command Output"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../metadata.json
|
||||||
|
msgid "Run a command periodically and render its output."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Command"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Appearance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/config/config.qml
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Tooltip"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Hover Command:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Run Command:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Mouse Wheel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Up:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigActions.qml
|
||||||
|
msgid "Scroll Down:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Family:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Font Size:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "px"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Colors"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Text:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Outline:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Misc"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Desktop Widget:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Show background"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Width:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Fixed Height:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigAppearance.qml
|
||||||
|
msgid "Replace all newlines with spaces"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Run every "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "ms"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Wait for completion"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/config/ConfigCommand.qml
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../contents/ui/libconfig/FontFamily.qml
|
||||||
|
msgctxt "Use default font"
|
||||||
|
msgid "Default"
|
||||||
|
msgstr ""
|
||||||
@@ -0,0 +1,548 @@
|
|||||||
|
// Parsing and formatting helpers for the CodexBar plasmoid.
|
||||||
|
// This file is imported both from QML (import "../code/parser.js" as Parser)
|
||||||
|
// and from Node.js unit tests (tests/parser.test.mjs), so it must stay
|
||||||
|
// engine-neutral: no QML pragmas, no Node-only APIs.
|
||||||
|
|
||||||
|
// Catalog captured from `codexbar config providers` (CodexBar CLI v0.42.1).
|
||||||
|
var PROVIDER_CATALOG = [
|
||||||
|
{ id: "codex", name: "Codex" },
|
||||||
|
{ id: "openai", name: "OpenAI" },
|
||||||
|
{ id: "azureopenai", name: "Azure OpenAI" },
|
||||||
|
{ id: "claude", name: "Claude" },
|
||||||
|
{ id: "cursor", name: "Cursor" },
|
||||||
|
{ id: "opencode", name: "OpenCode" },
|
||||||
|
{ id: "opencodego", name: "OpenCode Go" },
|
||||||
|
{ id: "alibaba", name: "Alibaba" },
|
||||||
|
{ id: "alibabatokenplan", name: "Alibaba Token Plan" },
|
||||||
|
{ id: "factory", name: "Droid" },
|
||||||
|
{ id: "gemini", name: "Gemini" },
|
||||||
|
{ id: "antigravity", name: "Antigravity" },
|
||||||
|
{ id: "copilot", name: "Copilot" },
|
||||||
|
{ id: "devin", name: "Devin" },
|
||||||
|
{ id: "zai", name: "z.ai" },
|
||||||
|
{ id: "minimax", name: "MiniMax" },
|
||||||
|
{ id: "manus", name: "Manus" },
|
||||||
|
{ id: "kimi", name: "Kimi" },
|
||||||
|
{ id: "kilo", name: "Kilo" },
|
||||||
|
{ id: "kiro", name: "Kiro" },
|
||||||
|
{ id: "vertexai", name: "Vertex AI" },
|
||||||
|
{ id: "augment", name: "Augment" },
|
||||||
|
{ id: "jetbrains", name: "JetBrains AI" },
|
||||||
|
{ id: "kimik2", name: "Kimi K2 (unofficial)" },
|
||||||
|
{ id: "moonshot", name: "Moonshot / Kimi API" },
|
||||||
|
{ id: "amp", name: "Amp" },
|
||||||
|
{ id: "t3chat", name: "T3 Chat" },
|
||||||
|
{ id: "ollama", name: "Ollama" },
|
||||||
|
{ id: "synthetic", name: "Synthetic" },
|
||||||
|
{ id: "warp", name: "Warp" },
|
||||||
|
{ id: "openrouter", name: "OpenRouter" },
|
||||||
|
{ id: "elevenlabs", name: "ElevenLabs" },
|
||||||
|
{ id: "windsurf", name: "Windsurf" },
|
||||||
|
{ id: "zed", name: "Zed" },
|
||||||
|
{ id: "perplexity", name: "Perplexity" },
|
||||||
|
{ id: "mimo", name: "Xiaomi MiMo" },
|
||||||
|
{ id: "doubao", name: "Doubao" },
|
||||||
|
{ id: "sakana", name: "Sakana AI" },
|
||||||
|
{ id: "abacus", name: "Abacus AI" },
|
||||||
|
{ id: "mistral", name: "Mistral" },
|
||||||
|
{ id: "deepseek", name: "DeepSeek" },
|
||||||
|
{ id: "codebuff", name: "Codebuff" },
|
||||||
|
{ id: "crof", name: "Crof" },
|
||||||
|
{ id: "venice", name: "Venice" },
|
||||||
|
{ id: "commandcode", name: "Command Code" },
|
||||||
|
{ id: "qoder", name: "Qoder" },
|
||||||
|
{ id: "stepfun", name: "StepFun" },
|
||||||
|
{ id: "bedrock", name: "AWS Bedrock" },
|
||||||
|
{ id: "grok", name: "Grok" },
|
||||||
|
{ id: "groq", name: "Groq" },
|
||||||
|
{ id: "llmproxy", name: "LLM Proxy" },
|
||||||
|
{ id: "litellm", name: "LiteLLM" },
|
||||||
|
{ id: "deepgram", name: "Deepgram" },
|
||||||
|
{ id: "poe", name: "Poe" },
|
||||||
|
{ id: "chutes", name: "Chutes" },
|
||||||
|
{ id: "crossmodel", name: "CrossModel" },
|
||||||
|
{ id: "clawrouter", name: "ClawRouter" },
|
||||||
|
{ id: "wayfinder", name: "Wayfinder" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var SHORT_NAMES = {
|
||||||
|
codex: "Cx",
|
||||||
|
openai: "OA",
|
||||||
|
azureopenai: "Az",
|
||||||
|
claude: "Cl",
|
||||||
|
cursor: "Cu",
|
||||||
|
gemini: "Gm",
|
||||||
|
copilot: "Cp",
|
||||||
|
antigravity: "Ag",
|
||||||
|
openrouter: "OR",
|
||||||
|
minimax: "MM",
|
||||||
|
deepseek: "DS",
|
||||||
|
jetbrains: "JB",
|
||||||
|
vertexai: "Vx",
|
||||||
|
bedrock: "BR",
|
||||||
|
litellm: "LL",
|
||||||
|
llmproxy: "LP",
|
||||||
|
groq: "Gq",
|
||||||
|
grok: "Gk",
|
||||||
|
};
|
||||||
|
|
||||||
|
function providerDisplayName(id) {
|
||||||
|
for (var i = 0; i < PROVIDER_CATALOG.length; i++) {
|
||||||
|
if (PROVIDER_CATALOG[i].id === id) {
|
||||||
|
return PROVIDER_CATALOG[i].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortName(id) {
|
||||||
|
if (SHORT_NAMES[id]) {
|
||||||
|
return SHORT_NAMES[id];
|
||||||
|
}
|
||||||
|
if (!id || id.length === 0) {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
var base = id.slice(0, 2);
|
||||||
|
return base.charAt(0).toUpperCase() + base.slice(1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function windowLabel(windowMinutes) {
|
||||||
|
if (windowMinutes === null || windowMinutes === undefined) {
|
||||||
|
return "Usage";
|
||||||
|
}
|
||||||
|
if (windowMinutes === 10080) {
|
||||||
|
return "Weekly";
|
||||||
|
}
|
||||||
|
if (windowMinutes === 43200) {
|
||||||
|
return "Monthly";
|
||||||
|
}
|
||||||
|
if (windowMinutes === 1440) {
|
||||||
|
return "Daily";
|
||||||
|
}
|
||||||
|
if (windowMinutes >= 180 && windowMinutes <= 360) {
|
||||||
|
return "Session";
|
||||||
|
}
|
||||||
|
if (windowMinutes % 1440 === 0) {
|
||||||
|
return (windowMinutes / 1440) + "d";
|
||||||
|
}
|
||||||
|
if (windowMinutes % 60 === 0) {
|
||||||
|
return (windowMinutes / 60) + "h";
|
||||||
|
}
|
||||||
|
return windowMinutes + "m";
|
||||||
|
}
|
||||||
|
|
||||||
|
function usageStage(usedPercent) {
|
||||||
|
if (usedPercent >= 90) {
|
||||||
|
return "crit";
|
||||||
|
}
|
||||||
|
if (usedPercent >= 70) {
|
||||||
|
return "warn";
|
||||||
|
}
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCountdown(resetsAtIso, nowMs) {
|
||||||
|
if (!resetsAtIso) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var target = Date.parse(resetsAtIso);
|
||||||
|
if (isNaN(target)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var diffMinutes = Math.floor((target - nowMs) / 60000);
|
||||||
|
if (diffMinutes <= 0) {
|
||||||
|
return "now";
|
||||||
|
}
|
||||||
|
var days = Math.floor(diffMinutes / 1440);
|
||||||
|
var hours = Math.floor((diffMinutes % 1440) / 60);
|
||||||
|
var minutes = diffMinutes % 60;
|
||||||
|
if (days > 0) {
|
||||||
|
return days + "d " + hours + "h";
|
||||||
|
}
|
||||||
|
if (hours > 0) {
|
||||||
|
return hours + "h " + minutes + "m";
|
||||||
|
}
|
||||||
|
return minutes + "m";
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWindow(key, raw, pace, labelOverride, usageKnown) {
|
||||||
|
var paceSummary = "";
|
||||||
|
if (pace && pace[key] && pace[key].summary) {
|
||||||
|
paceSummary = pace[key].summary;
|
||||||
|
}
|
||||||
|
var minutes = raw.windowMinutes === undefined ? null : raw.windowMinutes;
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
label: labelOverride || windowLabel(minutes),
|
||||||
|
windowMinutes: minutes,
|
||||||
|
usedPercent: Math.round(raw.usedPercent || 0),
|
||||||
|
resetsAt: raw.resetsAt || null,
|
||||||
|
resetDescription: raw.resetDescription || "",
|
||||||
|
paceSummary: paceSummary,
|
||||||
|
usageKnown: usageKnown === undefined ? true : usageKnown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function payloadToModel(payload) {
|
||||||
|
var usage = payload.usage || null;
|
||||||
|
var windows = [];
|
||||||
|
if (usage) {
|
||||||
|
var keys = ["primary", "secondary", "tertiary"];
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var raw = usage[keys[i]];
|
||||||
|
if (!raw || raw.isSyntheticPlaceholder) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
windows.push(makeWindow(keys[i], raw, payload.pace || null));
|
||||||
|
}
|
||||||
|
// Named model-specific lanes (e.g. Codex Spark, Code review).
|
||||||
|
var extras = usage.extraRateWindows || [];
|
||||||
|
for (var j = 0; j < extras.length; j++) {
|
||||||
|
var extra = extras[j];
|
||||||
|
if (!extra || !extra.window || extra.window.isSyntheticPlaceholder) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
windows.push(makeWindow(
|
||||||
|
"extra:" + (extra.id || j),
|
||||||
|
extra.window,
|
||||||
|
null,
|
||||||
|
extra.title || extra.id || "Extra",
|
||||||
|
extra.usageKnown === undefined ? true : extra.usageKnown));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// OpenAI web dashboard exposes code review as a bare remaining percent.
|
||||||
|
var dashboard = payload.openaiDashboard || null;
|
||||||
|
if (dashboard && typeof dashboard.codeReviewRemainingPercent === "number") {
|
||||||
|
var hasReviewLane = windows.some(function(w) {
|
||||||
|
return w.label.toLowerCase().indexOf("code review") !== -1;
|
||||||
|
});
|
||||||
|
if (!hasReviewLane) {
|
||||||
|
windows.push(makeWindow(
|
||||||
|
"extra:code-review",
|
||||||
|
{ usedPercent: 100 - dashboard.codeReviewRemainingPercent, windowMinutes: null, resetsAt: null },
|
||||||
|
null,
|
||||||
|
"Code review",
|
||||||
|
true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var account = null;
|
||||||
|
if (usage && usage.identity && usage.identity.accountEmail) {
|
||||||
|
account = usage.identity.accountEmail;
|
||||||
|
} else if (usage && usage.accountEmail) {
|
||||||
|
account = usage.accountEmail;
|
||||||
|
} else if (payload.account) {
|
||||||
|
account = payload.account;
|
||||||
|
}
|
||||||
|
var plan = null;
|
||||||
|
if (usage && usage.identity && usage.identity.loginMethod) {
|
||||||
|
plan = usage.identity.loginMethod;
|
||||||
|
} else if (usage && usage.loginMethod) {
|
||||||
|
plan = usage.loginMethod;
|
||||||
|
}
|
||||||
|
var credits = null;
|
||||||
|
if (payload.credits && typeof payload.credits.remaining === "number") {
|
||||||
|
credits = payload.credits.remaining;
|
||||||
|
}
|
||||||
|
var status = null;
|
||||||
|
if (payload.status && payload.status.indicator && payload.status.indicator !== "none") {
|
||||||
|
status = {
|
||||||
|
indicator: payload.status.indicator,
|
||||||
|
description: payload.status.description || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: payload.provider || "?",
|
||||||
|
name: providerDisplayName(payload.provider || "?"),
|
||||||
|
source: payload.source || "",
|
||||||
|
version: payload.version || "",
|
||||||
|
account: account,
|
||||||
|
plan: plan,
|
||||||
|
credits: credits,
|
||||||
|
status: status,
|
||||||
|
error: payload.error ? String(payload.error.message || "error") : null,
|
||||||
|
windows: windows,
|
||||||
|
updatedAt: usage && usage.updatedAt ? usage.updatedAt : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseUsageJson(text) {
|
||||||
|
var parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: "Invalid JSON from codexbar: " + e.message, models: [] };
|
||||||
|
}
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
return { ok: false, error: "Unexpected codexbar payload (expected array)", models: [] };
|
||||||
|
}
|
||||||
|
var models = [];
|
||||||
|
for (var i = 0; i < parsed.length; i++) {
|
||||||
|
models.push(payloadToModel(parsed[i]));
|
||||||
|
}
|
||||||
|
return { ok: true, error: "", models: models };
|
||||||
|
}
|
||||||
|
|
||||||
|
function worstUsedPercent(model) {
|
||||||
|
var worst = -1;
|
||||||
|
for (var i = 0; i < model.windows.length; i++) {
|
||||||
|
if (model.windows[i].usedPercent > worst) {
|
||||||
|
worst = model.windows[i].usedPercent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return worst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ring assignment for the radial gauge: outer ring shows the short
|
||||||
|
// (session/daily) window, inner ring the long (weekly/monthly) window.
|
||||||
|
function gaugeRings(model) {
|
||||||
|
var outerIdx = -1;
|
||||||
|
var innerIdx = -1;
|
||||||
|
var firstKnownIdx = -1;
|
||||||
|
for (var i = 0; i < model.windows.length; i++) {
|
||||||
|
if (model.windows[i].usageKnown === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (firstKnownIdx === -1) {
|
||||||
|
firstKnownIdx = i;
|
||||||
|
}
|
||||||
|
var minutes = model.windows[i].windowMinutes;
|
||||||
|
if (minutes !== null && minutes !== undefined && minutes <= 1440 && outerIdx === -1) {
|
||||||
|
outerIdx = i;
|
||||||
|
} else if (minutes !== null && minutes !== undefined && minutes >= 10080 && innerIdx === -1) {
|
||||||
|
innerIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (outerIdx === -1 && innerIdx === -1 && firstKnownIdx !== -1) {
|
||||||
|
outerIdx = firstKnownIdx;
|
||||||
|
}
|
||||||
|
return { outerIdx: outerIdx, innerIdx: innerIdx };
|
||||||
|
}
|
||||||
|
|
||||||
|
function gaugeCenterPercent(model) {
|
||||||
|
var rings = gaugeRings(model);
|
||||||
|
var idx = rings.outerIdx !== -1 ? rings.outerIdx : rings.innerIdx;
|
||||||
|
if (idx === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return Math.max(0, Math.min(100, model.windows[idx].usedPercent));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sweepAngle(percent) {
|
||||||
|
var clamped = Math.max(0, Math.min(100, percent));
|
||||||
|
return clamped * 3.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local token-cost scan payloads from `codexbar cost --format json`.
|
||||||
|
function parseCostJson(text) {
|
||||||
|
var parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: "Invalid cost JSON: " + e.message, byId: {} };
|
||||||
|
}
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
return { ok: false, error: "Unexpected cost payload (expected array)", byId: {} };
|
||||||
|
}
|
||||||
|
var byId = {};
|
||||||
|
for (var i = 0; i < parsed.length; i++) {
|
||||||
|
var p = parsed[i];
|
||||||
|
if (!p || !p.provider) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var daily = [];
|
||||||
|
var rawDaily = p.daily || [];
|
||||||
|
for (var j = 0; j < rawDaily.length; j++) {
|
||||||
|
daily.push({
|
||||||
|
date: rawDaily[j].date || "",
|
||||||
|
totalCost: Number(rawDaily[j].totalCost) || 0,
|
||||||
|
totalTokens: Number(rawDaily[j].totalTokens) || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
byId[p.provider] = {
|
||||||
|
todayCostUSD: typeof p.sessionCostUSD === "number" ? p.sessionCostUSD : null,
|
||||||
|
todayTokens: typeof p.sessionTokens === "number" ? p.sessionTokens : null,
|
||||||
|
month30CostUSD: typeof p.last30DaysCostUSD === "number" ? p.last30DaysCostUSD : null,
|
||||||
|
month30Tokens: typeof p.last30DaysTokens === "number" ? p.last30DaysTokens : null,
|
||||||
|
daily: daily,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ok: true, error: "", byId: byId };
|
||||||
|
}
|
||||||
|
|
||||||
|
function humanTokens(value) {
|
||||||
|
if (value === null || value === undefined || !isFinite(value)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var n = Number(value);
|
||||||
|
if (n < 1000) {
|
||||||
|
return String(Math.round(n));
|
||||||
|
}
|
||||||
|
if (n < 1000000) {
|
||||||
|
return (Math.round(n / 100) / 10) + "K";
|
||||||
|
}
|
||||||
|
if (n < 1000000000) {
|
||||||
|
return (Math.round(n / 100000) / 10) + "M";
|
||||||
|
}
|
||||||
|
return (Math.round(n / 100000000) / 10) + "B";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoney(value) {
|
||||||
|
if (value === null || value === undefined || !isFinite(value)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return "$" + Number(value).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero-filled per-day cost series for the history chart, ending at todayIso.
|
||||||
|
function chartSeries(daily, days, todayIso) {
|
||||||
|
var byDate = {};
|
||||||
|
for (var i = 0; i < (daily || []).length; i++) {
|
||||||
|
byDate[daily[i].date] = daily[i];
|
||||||
|
}
|
||||||
|
var series = [];
|
||||||
|
var today = new Date(todayIso + "T00:00:00Z");
|
||||||
|
for (var d = days - 1; d >= 0; d--) {
|
||||||
|
var day = new Date(today.getTime() - d * 86400000);
|
||||||
|
var iso = day.toISOString().slice(0, 10);
|
||||||
|
var entry = byDate[iso];
|
||||||
|
series.push({
|
||||||
|
date: iso,
|
||||||
|
value: entry ? entry.totalCost : 0,
|
||||||
|
tokens: entry ? entry.totalTokens : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shellQuote(value) {
|
||||||
|
return "'" + String(value).replace(/'/g, "'\"'\"'") + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
function commandPrefix(proxyUrl) {
|
||||||
|
// ~/.local/bin is not always on plasmashell's PATH.
|
||||||
|
var prefix = 'env PATH="$HOME/.local/bin:$PATH"';
|
||||||
|
if (proxyUrl && proxyUrl.trim().length > 0) {
|
||||||
|
var quoted = shellQuote(proxyUrl.trim());
|
||||||
|
prefix += " http_proxy=" + quoted + " https_proxy=" + quoted
|
||||||
|
+ " HTTP_PROXY=" + quoted + " HTTPS_PROXY=" + quoted
|
||||||
|
+ " ALL_PROXY=" + quoted;
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One usage command per selected provider; a single unscoped command when the
|
||||||
|
// selection is empty (the CLI then reports the providers enabled in its own
|
||||||
|
// config file).
|
||||||
|
function buildCommands(codexbarPath, selected, proxyUrl) {
|
||||||
|
var base = commandPrefix(proxyUrl) + " " + codexbarPath + " usage --format json --no-color";
|
||||||
|
if (!selected || selected.length === 0) {
|
||||||
|
return [base];
|
||||||
|
}
|
||||||
|
return selected.map(function(id) {
|
||||||
|
return base + " --provider " + id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCostCommand(codexbarPath, proxyUrl) {
|
||||||
|
return commandPrefix(proxyUrl) + " " + codexbarPath + " cost --format json --no-color";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge freshly parsed provider models into the cached map. Successful
|
||||||
|
// fetches replace the cache entry; errors keep the last good data and only
|
||||||
|
// mark it stale, so panel gauges never blank out during background refreshes.
|
||||||
|
function applyUpdate(prevById, models, nowMs) {
|
||||||
|
var byId = {};
|
||||||
|
var key;
|
||||||
|
for (key in prevById) {
|
||||||
|
byId[key] = prevById[key];
|
||||||
|
}
|
||||||
|
for (var i = 0; i < models.length; i++) {
|
||||||
|
var model = models[i];
|
||||||
|
var cached = byId[model.id];
|
||||||
|
if (model.error && cached && cached.windows && cached.windows.length > 0) {
|
||||||
|
var stale = {};
|
||||||
|
for (key in cached) {
|
||||||
|
stale[key] = cached[key];
|
||||||
|
}
|
||||||
|
stale.staleError = model.error;
|
||||||
|
byId[model.id] = stale;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
model.fetchedAtMs = nowMs;
|
||||||
|
model.staleError = null;
|
||||||
|
byId[model.id] = model;
|
||||||
|
}
|
||||||
|
return { byId: byId };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAgo(thenMs, nowMs) {
|
||||||
|
if (thenMs === null || thenMs === undefined || thenMs < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var minutes = Math.floor((nowMs - thenMs) / 60000);
|
||||||
|
if (minutes < 1) {
|
||||||
|
return "just now";
|
||||||
|
}
|
||||||
|
if (minutes < 60) {
|
||||||
|
return minutes + "m ago";
|
||||||
|
}
|
||||||
|
var hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) {
|
||||||
|
return hours + "h " + (minutes % 60) + "m ago";
|
||||||
|
}
|
||||||
|
var days = Math.floor(hours / 24);
|
||||||
|
return days + "d " + (hours % 24) + "h ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectionList(csv) {
|
||||||
|
if (!csv) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var out = [];
|
||||||
|
var parts = csv.split(",");
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var id = parts[i].trim();
|
||||||
|
if (id.length > 0) {
|
||||||
|
out.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelection(csv, id, checked) {
|
||||||
|
var list = selectionList(csv);
|
||||||
|
var index = list.indexOf(id);
|
||||||
|
if (checked && index === -1) {
|
||||||
|
list.push(id);
|
||||||
|
} else if (!checked && index !== -1) {
|
||||||
|
list.splice(index, 1);
|
||||||
|
}
|
||||||
|
return list.join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node.js test hook; ignored by the QML JS engine.
|
||||||
|
if (typeof module !== "undefined" && module.exports) {
|
||||||
|
module.exports = {
|
||||||
|
PROVIDER_CATALOG: PROVIDER_CATALOG,
|
||||||
|
providerDisplayName: providerDisplayName,
|
||||||
|
shortName: shortName,
|
||||||
|
windowLabel: windowLabel,
|
||||||
|
usageStage: usageStage,
|
||||||
|
formatCountdown: formatCountdown,
|
||||||
|
parseUsageJson: parseUsageJson,
|
||||||
|
worstUsedPercent: worstUsedPercent,
|
||||||
|
selectionList: selectionList,
|
||||||
|
toggleSelection: toggleSelection,
|
||||||
|
gaugeRings: gaugeRings,
|
||||||
|
gaugeCenterPercent: gaugeCenterPercent,
|
||||||
|
sweepAngle: sweepAngle,
|
||||||
|
applyUpdate: applyUpdate,
|
||||||
|
formatAgo: formatAgo,
|
||||||
|
parseCostJson: parseCostJson,
|
||||||
|
humanTokens: humanTokens,
|
||||||
|
formatMoney: formatMoney,
|
||||||
|
chartSeries: chartSeries,
|
||||||
|
buildCommands: buildCommands,
|
||||||
|
buildCostCommand: buildCostCommand,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import QtQuick
|
||||||
|
import org.kde.plasma.configuration
|
||||||
|
|
||||||
|
ConfigModel {
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("General")
|
||||||
|
icon: "configure"
|
||||||
|
source: "ConfigGeneral.qml"
|
||||||
|
}
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("Providers")
|
||||||
|
icon: "applications-development"
|
||||||
|
source: "ConfigProviders.qml"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?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">
|
||||||
|
<entry name="codexbarPath" type="String">
|
||||||
|
<label>Path to the codexbar CLI binary.</label>
|
||||||
|
<default>codexbar</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="refreshMinutes" type="Int">
|
||||||
|
<label>Refresh interval in minutes.</label>
|
||||||
|
<default>5</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showPace" type="Bool">
|
||||||
|
<label>Show pace summary lines under usage bars.</label>
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showCost" type="Bool">
|
||||||
|
<label>Fetch and show local token cost usage (Codex and Claude).</label>
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showHistory" type="Bool">
|
||||||
|
<label>Show the daily spend history chart in provider cards.</label>
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="proxyUrl" type="String">
|
||||||
|
<label>Proxy URL exported as http(s)_proxy/ALL_PROXY for CLI requests, e.g. socks5://127.0.0.1:1080.</label>
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="selectedProviders" type="String">
|
||||||
|
<label>Comma-separated provider ids to display. Empty means the providers enabled in the CodexBar CLI config.</label>
|
||||||
|
<default>codex,claude</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="cacheJson" type="String">
|
||||||
|
<label>Serialized last-known usage data, shown instantly on startup while a refresh runs in the background.</label>
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
</kcfg>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0" stop-color="#23272e"/>
|
||||||
|
<stop offset="1" stop-color="#15181d"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="4" y="4" width="120" height="120" rx="27" fill="url(#bg)"/>
|
||||||
|
<g transform="rotate(-90 64 64)">
|
||||||
|
<circle cx="64" cy="64" r="44" fill="none" stroke="#ffffff" stroke-opacity="0.1" stroke-width="11"/>
|
||||||
|
<circle cx="64" cy="64" r="27" fill="none" stroke="#ffffff" stroke-opacity="0.1" stroke-width="11"/>
|
||||||
|
<circle cx="64" cy="64" r="44" fill="none" stroke="#3daee9" stroke-width="11" stroke-linecap="round" stroke-dasharray="196 281"/>
|
||||||
|
<circle cx="64" cy="64" r="27" fill="none" stroke="#f67400" stroke-width="11" stroke-linecap="round" stroke-dasharray="76 170"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 876 B |
+65
@@ -0,0 +1,65 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import "../code/parser.js" as Parser
|
||||||
|
|
||||||
|
// Panel view: one two-ring radial gauge per provider
|
||||||
|
// (outer ring = session left, inner ring = weekly left).
|
||||||
|
Item {
|
||||||
|
id: compact
|
||||||
|
|
||||||
|
readonly property bool vertical: Plasmoid.formFactor === PlasmaCore.Types.Vertical
|
||||||
|
readonly property int gaugeSize: compact.vertical
|
||||||
|
? Math.min(compact.width, Kirigami.Units.gridUnit * 2.4)
|
||||||
|
: Math.min(compact.height, Kirigami.Units.gridUnit * 2.4)
|
||||||
|
readonly property int count: Math.max(1, root.models.length)
|
||||||
|
|
||||||
|
Layout.minimumWidth: compact.vertical ? compact.gaugeSize : compact.count * (compact.gaugeSize + gaugeGrid.spacing)
|
||||||
|
Layout.minimumHeight: compact.vertical ? compact.count * (compact.gaugeSize + gaugeGrid.spacing) : compact.gaugeSize
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
id: gaugeGrid
|
||||||
|
anchors.centerIn: parent
|
||||||
|
columns: compact.vertical ? 1 : compact.count
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
visible: root.models.length === 0
|
||||||
|
source: Qt.resolvedUrl("../icons/codexbar.svg")
|
||||||
|
width: compact.gaugeSize
|
||||||
|
height: compact.gaugeSize
|
||||||
|
opacity: root.loading ? 0.5 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.models
|
||||||
|
|
||||||
|
delegate: RadialGauge {
|
||||||
|
id: meter
|
||||||
|
required property var modelData
|
||||||
|
readonly property var rings: Parser.gaugeRings(modelData)
|
||||||
|
readonly property int centerPercent: Parser.gaugeCenterPercent(modelData)
|
||||||
|
|
||||||
|
width: compact.gaugeSize
|
||||||
|
height: compact.gaugeSize
|
||||||
|
outerColor: root.sessionColor
|
||||||
|
innerColor: root.weeklyColor
|
||||||
|
outerPercent: rings.outerIdx >= 0
|
||||||
|
? modelData.windows[rings.outerIdx].usedPercent : -1
|
||||||
|
innerPercent: rings.innerIdx >= 0
|
||||||
|
? modelData.windows[rings.innerIdx].usedPercent : -1
|
||||||
|
centerText: modelData.error
|
||||||
|
? "!"
|
||||||
|
: (meter.centerPercent >= 0 ? String(meter.centerPercent) : "")
|
||||||
|
centerTextScale: 0.34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.expanded = !root.expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
id: page
|
||||||
|
|
||||||
|
property alias cfg_codexbarPath: pathField.text
|
||||||
|
property alias cfg_refreshMinutes: refreshSpin.value
|
||||||
|
property alias cfg_showPace: paceCheck.checked
|
||||||
|
property alias cfg_showCost: costCheck.checked
|
||||||
|
property alias cfg_showHistory: historyCheck.checked
|
||||||
|
property alias cfg_proxyUrl: proxyField.text
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: pathField
|
||||||
|
Kirigami.FormData.label: i18n("codexbar binary:")
|
||||||
|
placeholderText: "codexbar"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Plain name is resolved via PATH (including ~/.local/bin); an absolute path also works.")
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
opacity: 0.6
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: refreshSpin
|
||||||
|
Kirigami.FormData.label: i18n("Refresh every:")
|
||||||
|
from: 1
|
||||||
|
to: 120
|
||||||
|
textFromValue: function(value) { return i18np("%1 minute", "%1 minutes", value) }
|
||||||
|
valueFromText: function(text) { return parseInt(text) || 5 }
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: paceCheck
|
||||||
|
Kirigami.FormData.label: i18n("Details:")
|
||||||
|
text: i18n("Show pace summaries")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: costCheck
|
||||||
|
text: i18n("Show local token costs (Codex/Claude)")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: historyCheck
|
||||||
|
text: i18n("Show daily spend history chart")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: proxyField
|
||||||
|
Kirigami.FormData.label: i18n("Proxy:")
|
||||||
|
placeholderText: "socks5://127.0.0.1:1080"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Exported as http_proxy/https_proxy/ALL_PROXY for every CLI call. Useful when provider APIs are blocked in your region. Leave empty for a direct connection.")
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
opacity: 0.6
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import "../code/parser.js" as Parser
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: page
|
||||||
|
|
||||||
|
property string cfg_selectedProviders: ""
|
||||||
|
|
||||||
|
readonly property var selectedList: Parser.selectionList(page.cfg_selectedProviders)
|
||||||
|
|
||||||
|
function isSelected(id) {
|
||||||
|
return page.selectedList.indexOf(id) !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(id, checked) {
|
||||||
|
page.cfg_selectedProviders = Parser.toggleSelection(page.cfg_selectedProviders, id, checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: i18n("Pick the coding agents to display. With nothing selected, the widget shows the providers enabled in the CodexBar CLI config (~/.config/codexbar/config.json).")
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.SearchField {
|
||||||
|
id: searchField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: i18n("Filter providers…")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: providerListView
|
||||||
|
clip: true
|
||||||
|
model: {
|
||||||
|
var query = searchField.text.toLowerCase().trim()
|
||||||
|
if (query.length === 0) {
|
||||||
|
return Parser.PROVIDER_CATALOG
|
||||||
|
}
|
||||||
|
return Parser.PROVIDER_CATALOG.filter(function(p) {
|
||||||
|
return p.id.indexOf(query) !== -1
|
||||||
|
|| p.name.toLowerCase().indexOf(query) !== -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: QQC2.CheckDelegate {
|
||||||
|
required property var modelData
|
||||||
|
width: providerListView.width
|
||||||
|
text: modelData.name + " (" + modelData.id + ")"
|
||||||
|
checked: page.isSelected(modelData.id)
|
||||||
|
onToggled: page.toggle(modelData.id, checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: page.selectedList.length > 0
|
||||||
|
? i18np("%1 provider selected", "%1 providers selected", page.selectedList.length)
|
||||||
|
: i18n("Following the CLI config")
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.plasma.components as PlasmaComponents3
|
||||||
|
import org.kde.plasma.extras as PlasmaExtras
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import "../code/parser.js" as Parser
|
||||||
|
|
||||||
|
PlasmaExtras.Representation {
|
||||||
|
id: full
|
||||||
|
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 22
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 26
|
||||||
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 16
|
||||||
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 10
|
||||||
|
|
||||||
|
collapseMarginsHint: true
|
||||||
|
|
||||||
|
function stageColor(usedPercent) {
|
||||||
|
var stage = Parser.usageStage(usedPercent)
|
||||||
|
if (stage === "crit") {
|
||||||
|
return Kirigami.Theme.negativeTextColor
|
||||||
|
}
|
||||||
|
if (stage === "warn") {
|
||||||
|
return Kirigami.Theme.neutralTextColor
|
||||||
|
}
|
||||||
|
return Kirigami.Theme.positiveTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetText(w) {
|
||||||
|
var countdown = Parser.formatCountdown(w.resetsAt, root.nowMs)
|
||||||
|
if (countdown === "now") {
|
||||||
|
return i18n("resets soon")
|
||||||
|
}
|
||||||
|
if (countdown.length > 0) {
|
||||||
|
return i18n("resets in %1", countdown)
|
||||||
|
}
|
||||||
|
return w.resetDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
function costLine(cost, kind) {
|
||||||
|
var money = Parser.formatMoney(kind === "today" ? cost.todayCostUSD : cost.month30CostUSD)
|
||||||
|
var tokens = Parser.humanTokens(kind === "today" ? cost.todayTokens : cost.month30Tokens)
|
||||||
|
if (money.length === 0 && tokens.length === 0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return money + (tokens.length > 0 ? " · " + tokens + " tok" : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
header: PlasmaExtras.PlasmoidHeading {
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
level: 2
|
||||||
|
text: i18n("CodexBar")
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: root.lastUpdatedMs > 0
|
||||||
|
text: i18n("updated %1", Parser.formatAgo(root.lastUpdatedMs, root.nowMs))
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.ToolButton {
|
||||||
|
id: refreshButton
|
||||||
|
icon.name: "view-refresh"
|
||||||
|
enabled: !root.loading
|
||||||
|
onClicked: root.refresh()
|
||||||
|
PlasmaComponents3.ToolTip { text: i18n("Refresh") }
|
||||||
|
|
||||||
|
rotation: 0
|
||||||
|
RotationAnimation on rotation {
|
||||||
|
running: root.loading
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
duration: 1100
|
||||||
|
loops: Animation.Infinite
|
||||||
|
}
|
||||||
|
onEnabledChanged: if (enabled) { rotation = 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ListView {
|
||||||
|
id: providerList
|
||||||
|
clip: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
model: root.models
|
||||||
|
topMargin: Kirigami.Units.smallSpacing
|
||||||
|
bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PlasmaExtras.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Kirigami.Units.gridUnit * 2
|
||||||
|
visible: root.models.length === 0
|
||||||
|
iconName: root.globalError.length > 0 ? "data-warning" : "office-chart-bar"
|
||||||
|
text: {
|
||||||
|
if (root.globalError.length > 0) {
|
||||||
|
return root.globalError
|
||||||
|
}
|
||||||
|
if (root.loading) {
|
||||||
|
return i18n("Fetching usage…")
|
||||||
|
}
|
||||||
|
return i18n("No providers configured. Pick providers in the widget settings or enable them with “codexbar config enable --provider <id>”.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: card
|
||||||
|
required property var modelData
|
||||||
|
readonly property var rings: Parser.gaugeRings(modelData)
|
||||||
|
readonly property int centerPercent: Parser.gaugeCenterPercent(modelData)
|
||||||
|
readonly property var cost: root.showCost ? (root.costById[modelData.id] || null) : null
|
||||||
|
|
||||||
|
width: providerList.width - providerList.leftMargin - providerList.rightMargin
|
||||||
|
height: cardContent.implicitHeight + Kirigami.Units.largeSpacing * 2
|
||||||
|
|
||||||
|
function ringColor(index) {
|
||||||
|
if (index === card.rings.outerIdx) {
|
||||||
|
return root.sessionColor
|
||||||
|
}
|
||||||
|
if (index === card.rings.innerIdx) {
|
||||||
|
return root.weeklyColor
|
||||||
|
}
|
||||||
|
return Qt.alpha(Kirigami.Theme.textColor, 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Kirigami.Units.cornerRadius !== undefined ? Kirigami.Units.cornerRadius : 5
|
||||||
|
color: Qt.alpha(Kirigami.Theme.textColor, 0.06)
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: cardContent
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
RadialGauge {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 4.5
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 4.5
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
outerColor: root.sessionColor
|
||||||
|
innerColor: root.weeklyColor
|
||||||
|
outerPercent: card.rings.outerIdx >= 0
|
||||||
|
? card.modelData.windows[card.rings.outerIdx].usedPercent : -1
|
||||||
|
innerPercent: card.rings.innerIdx >= 0
|
||||||
|
? card.modelData.windows[card.rings.innerIdx].usedPercent : -1
|
||||||
|
centerText: card.modelData.error
|
||||||
|
? "!"
|
||||||
|
: (card.centerPercent >= 0 ? card.centerPercent + "%" : "")
|
||||||
|
centerTextScale: 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
level: 4
|
||||||
|
text: card.modelData.name
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: card.modelData.status !== null
|
||||||
|
width: Kirigami.Units.smallSpacing * 2
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: Kirigami.Theme.negativeTextColor
|
||||||
|
|
||||||
|
PlasmaComponents3.ToolTip {
|
||||||
|
text: card.modelData.status
|
||||||
|
? (card.modelData.status.indicator + ": " + card.modelData.status.description)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: !!card.modelData.plan
|
||||||
|
text: card.modelData.plan || ""
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: !!card.modelData.account
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: card.modelData.account || ""
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: !!card.modelData.error
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: card.modelData.error || ""
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: Kirigami.Theme.negativeTextColor
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: !!card.modelData.staleError
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Refresh failed, showing data from %1: %2",
|
||||||
|
Parser.formatAgo(card.modelData.fetchedAtMs !== undefined ? card.modelData.fetchedAtMs : -1, root.nowMs),
|
||||||
|
card.modelData.staleError || "")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: Kirigami.Theme.neutralTextColor
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: card.modelData.windows
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
id: windowRow
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Kirigami.Units.smallSpacing * 2
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: card.ringColor(windowRow.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: windowRow.modelData.label
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: windowRow.modelData.usageKnown === false
|
||||||
|
? i18n("usage n/a")
|
||||||
|
: i18n("%1% used", windowRow.modelData.usedPercent)
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
color: windowRow.modelData.usageKnown === false
|
||||||
|
? Kirigami.Theme.disabledTextColor
|
||||||
|
: full.stageColor(windowRow.modelData.usedPercent)
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: "· " + full.resetText(windowRow.modelData)
|
||||||
|
visible: full.resetText(windowRow.modelData).length > 0
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: root.showPace && windowRow.modelData.paceSummary.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.smallSpacing * 3
|
||||||
|
text: windowRow.modelData.paceSummary
|
||||||
|
elide: Text.ElideRight
|
||||||
|
opacity: 0.5
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
visible: card.modelData.credits !== null && card.modelData.credits > 0
|
||||||
|
text: i18n("Credits: %1", card.modelData.credits !== null
|
||||||
|
? Number(card.modelData.credits).toLocaleString(Qt.locale(), "f", 1)
|
||||||
|
: "")
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
opacity: 0.7
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local token cost scan (Codex/Claude): spend and tokens.
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
visible: card.cost !== null
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: i18n("Today")
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: card.cost ? full.costLine(card.cost, "today") : ""
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: i18n("Last 30 days")
|
||||||
|
opacity: 0.6
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
text: card.cost ? full.costLine(card.cost, "month30") : ""
|
||||||
|
font.bold: true
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryChart {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.2
|
||||||
|
visible: root.showHistory && card.cost !== null && card.cost.daily.length > 0
|
||||||
|
accentColor: root.sessionColor
|
||||||
|
series: card.cost !== null
|
||||||
|
? Parser.chartSeries(card.cost.daily, 14, new Date(root.nowMs).toISOString().slice(0, 10))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: text.length > 0
|
||||||
|
text: {
|
||||||
|
var parts = []
|
||||||
|
if (card.modelData.source) {
|
||||||
|
parts.push(i18n("Source: %1", card.modelData.source))
|
||||||
|
}
|
||||||
|
if (card.modelData.version) {
|
||||||
|
parts.push(i18n("Version: %1", card.modelData.version))
|
||||||
|
}
|
||||||
|
return parts.join(" · ")
|
||||||
|
}
|
||||||
|
opacity: 0.45
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
// Rounded per-day spend bars for the last N days (zero days render as dots).
|
||||||
|
Item {
|
||||||
|
id: chart
|
||||||
|
|
||||||
|
property var series: []
|
||||||
|
property color accentColor: Kirigami.Theme.highlightColor
|
||||||
|
|
||||||
|
readonly property real maxValue: {
|
||||||
|
var max = 0
|
||||||
|
for (var i = 0; i < chart.series.length; i++) {
|
||||||
|
if (chart.series[i].value > max) {
|
||||||
|
max = chart.series[i].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: chart.series
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: chart.maxValue > 0
|
||||||
|
? Math.max(3, parent.height * modelData.value / chart.maxValue)
|
||||||
|
: 3
|
||||||
|
radius: Math.min(width / 2, 4)
|
||||||
|
color: chart.accentColor
|
||||||
|
opacity: modelData.value > 0 ? 0.75 : 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import "../code/parser.js" as Parser
|
||||||
|
|
||||||
|
// Two-ring radial gauge: outer ring = session window, inner ring = weekly
|
||||||
|
// window. A percent of -1 leaves the ring as a bare track.
|
||||||
|
Item {
|
||||||
|
id: gauge
|
||||||
|
|
||||||
|
property real outerPercent: -1
|
||||||
|
property real innerPercent: -1
|
||||||
|
property color outerColor: Kirigami.Theme.highlightColor
|
||||||
|
property color innerColor: "#f67400"
|
||||||
|
property string centerText: ""
|
||||||
|
property real centerTextScale: 0.32
|
||||||
|
|
||||||
|
readonly property real ringWidth: Math.max(2, Math.min(width, height) / 15)
|
||||||
|
readonly property real ringGap: ringWidth * 1.2
|
||||||
|
readonly property real outerRadius: (Math.min(width, height) - ringWidth) / 2
|
||||||
|
readonly property real innerRadius: outerRadius - ringWidth - ringGap
|
||||||
|
readonly property color trackColor: Qt.alpha(Kirigami.Theme.textColor, 0.18)
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
|
// Outer track.
|
||||||
|
ShapePath {
|
||||||
|
strokeColor: gauge.trackColor
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeWidth: gauge.ringWidth
|
||||||
|
capStyle: ShapePath.FlatCap
|
||||||
|
PathAngleArc {
|
||||||
|
centerX: gauge.width / 2
|
||||||
|
centerY: gauge.height / 2
|
||||||
|
radiusX: gauge.outerRadius
|
||||||
|
radiusY: gauge.outerRadius
|
||||||
|
startAngle: 0
|
||||||
|
sweepAngle: 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inner track.
|
||||||
|
ShapePath {
|
||||||
|
strokeColor: gauge.trackColor
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeWidth: gauge.ringWidth
|
||||||
|
capStyle: ShapePath.FlatCap
|
||||||
|
PathAngleArc {
|
||||||
|
centerX: gauge.width / 2
|
||||||
|
centerY: gauge.height / 2
|
||||||
|
radiusX: gauge.innerRadius
|
||||||
|
radiusY: gauge.innerRadius
|
||||||
|
startAngle: 0
|
||||||
|
sweepAngle: 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outer progress (session).
|
||||||
|
ShapePath {
|
||||||
|
strokeColor: gauge.outerColor
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeWidth: gauge.ringWidth
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
PathAngleArc {
|
||||||
|
centerX: gauge.width / 2
|
||||||
|
centerY: gauge.height / 2
|
||||||
|
radiusX: gauge.outerRadius
|
||||||
|
radiusY: gauge.outerRadius
|
||||||
|
startAngle: -90
|
||||||
|
sweepAngle: gauge.outerPercent >= 0 ? Parser.sweepAngle(gauge.outerPercent) : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inner progress (weekly).
|
||||||
|
ShapePath {
|
||||||
|
strokeColor: gauge.innerColor
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeWidth: gauge.ringWidth
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
PathAngleArc {
|
||||||
|
centerX: gauge.width / 2
|
||||||
|
centerY: gauge.height / 2
|
||||||
|
radiusX: gauge.innerRadius
|
||||||
|
radiusY: gauge.innerRadius
|
||||||
|
startAngle: -90
|
||||||
|
sweepAngle: gauge.innerPercent >= 0 ? Parser.sweepAngle(gauge.innerPercent) : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: gauge.centerText.length > 0
|
||||||
|
text: gauge.centerText
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
font.pixelSize: Math.max(7, Math.min(gauge.width, gauge.height) * gauge.centerTextScale)
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
import QtQuick
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
|
import org.kde.plasma.plasma5support as Plasma5Support
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import "../code/parser.js" as Parser
|
||||||
|
|
||||||
|
PlasmoidItem {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Cached provider view models keyed by provider id. Entries are only
|
||||||
|
// replaced when fresh data arrives, so the panel never blanks out while
|
||||||
|
// a background refresh is in flight.
|
||||||
|
property var modelsById: ({})
|
||||||
|
// Display order of provider ids (selection order, or arrival order when
|
||||||
|
// following the CLI config).
|
||||||
|
property var modelOrder: []
|
||||||
|
// Ordered list consumed by the representations.
|
||||||
|
property var models: []
|
||||||
|
property string globalError: ""
|
||||||
|
property bool loading: false
|
||||||
|
property int pendingCount: 0
|
||||||
|
property double lastUpdatedMs: -1
|
||||||
|
// Ticks every 30s so countdown and "ago" labels stay fresh.
|
||||||
|
property double nowMs: Date.now()
|
||||||
|
|
||||||
|
// Ring palette, shared by compact and full representations. The weekly
|
||||||
|
// orange is darkened on light themes to keep contrast against the track.
|
||||||
|
readonly property bool darkTheme: Kirigami.Theme.textColor.hslLightness > 0.5
|
||||||
|
readonly property color sessionColor: root.darkTheme ? "#3daee9" : "#2980b9"
|
||||||
|
readonly property color weeklyColor: root.darkTheme ? "#f67400" : "#c85400"
|
||||||
|
|
||||||
|
// Local token-cost stats keyed by provider id (Codex/Claude scans).
|
||||||
|
property var costById: ({})
|
||||||
|
|
||||||
|
readonly property string codexbarPath: Plasmoid.configuration.codexbarPath || "codexbar"
|
||||||
|
readonly property bool showPace: Plasmoid.configuration.showPace
|
||||||
|
readonly property bool showCost: Plasmoid.configuration.showCost
|
||||||
|
readonly property bool showHistory: Plasmoid.configuration.showHistory
|
||||||
|
readonly property string proxyUrl: Plasmoid.configuration.proxyUrl || ""
|
||||||
|
readonly property var selectedProviders: Parser.selectionList(Plasmoid.configuration.selectedProviders || "")
|
||||||
|
|
||||||
|
readonly property var commands: Parser.buildCommands(root.codexbarPath, root.selectedProviders, root.proxyUrl)
|
||||||
|
readonly property string costCommand: Parser.buildCostCommand(root.codexbarPath, root.proxyUrl)
|
||||||
|
|
||||||
|
switchWidth: Kirigami.Units.gridUnit * 15
|
||||||
|
switchHeight: Kirigami.Units.gridUnit * 12
|
||||||
|
|
||||||
|
Plasmoid.icon: "office-chart-bar"
|
||||||
|
toolTipMainText: i18n("CodexBar")
|
||||||
|
toolTipSubText: {
|
||||||
|
var lines = []
|
||||||
|
for (var i = 0; i < root.models.length; i++) {
|
||||||
|
var m = root.models[i]
|
||||||
|
if (m.error) {
|
||||||
|
lines.push(m.name + ": " + m.error)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var parts = []
|
||||||
|
for (var j = 0; j < m.windows.length; j++) {
|
||||||
|
var w = m.windows[j]
|
||||||
|
if (w.usageKnown === false) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts.push(w.label + " " + w.usedPercent + "% used")
|
||||||
|
}
|
||||||
|
lines.push(m.name + ": " + (parts.length ? parts.join(", ") : i18n("no data")))
|
||||||
|
}
|
||||||
|
if (root.globalError.length > 0 && root.models.length === 0) {
|
||||||
|
lines.push(root.globalError)
|
||||||
|
}
|
||||||
|
var ago = Parser.formatAgo(root.lastUpdatedMs, root.nowMs)
|
||||||
|
if (root.loading) {
|
||||||
|
lines.push(ago.length > 0
|
||||||
|
? i18n("Updating… (last update %1)", ago)
|
||||||
|
: i18n("Updating…"))
|
||||||
|
} else if (ago.length > 0) {
|
||||||
|
lines.push(i18n("Updated %1", ago))
|
||||||
|
}
|
||||||
|
return lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
onCommandsChanged: root.refresh()
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
if (root.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root.loading = true
|
||||||
|
root.pendingCount = root.commands.length + (root.showCost ? 1 : 0)
|
||||||
|
// Old models stay on screen; the executable engine reruns each
|
||||||
|
// command and results merge in as they arrive.
|
||||||
|
executable.connectedSources = []
|
||||||
|
for (var i = 0; i < root.commands.length; i++) {
|
||||||
|
executable.connectSource(root.commands[i])
|
||||||
|
}
|
||||||
|
if (root.showCost) {
|
||||||
|
executable.connectSource(root.costCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCostResult(stdout) {
|
||||||
|
var parsed = Parser.parseCostJson(stdout)
|
||||||
|
if (parsed.ok) {
|
||||||
|
root.costById = parsed.byId
|
||||||
|
root.persistCache()
|
||||||
|
}
|
||||||
|
// A failed cost scan keeps the previous numbers; usage is unaffected.
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResult(stdout, stderr, exitCode) {
|
||||||
|
var parsed = Parser.parseUsageJson(stdout)
|
||||||
|
if (!parsed.ok) {
|
||||||
|
var message = stderr && stderr.trim().length > 0
|
||||||
|
? stderr.trim().split("\n").pop()
|
||||||
|
: parsed.error
|
||||||
|
if (exitCode === 127) {
|
||||||
|
message = i18n("codexbar binary not found — set its path in the widget settings")
|
||||||
|
}
|
||||||
|
// No parseable payload: keep whatever is cached, surface the error.
|
||||||
|
root.globalError = root.models.length === 0 ? message : ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root.globalError = ""
|
||||||
|
var now = Date.now()
|
||||||
|
var state = Parser.applyUpdate(root.modelsById, parsed.models, now)
|
||||||
|
root.modelsById = state.byId
|
||||||
|
var hadFreshData = parsed.models.some(function(m) { return !m.error })
|
||||||
|
if (hadFreshData) {
|
||||||
|
root.lastUpdatedMs = now
|
||||||
|
}
|
||||||
|
for (var i = 0; i < parsed.models.length; i++) {
|
||||||
|
var id = parsed.models[i].id
|
||||||
|
if (root.modelOrder.indexOf(id) === -1) {
|
||||||
|
root.modelOrder = root.modelOrder.concat([id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.rebuildModels()
|
||||||
|
root.persistCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildModels() {
|
||||||
|
var order = root.selectedProviders.length > 0
|
||||||
|
? root.selectedProviders
|
||||||
|
: root.modelOrder
|
||||||
|
var list = []
|
||||||
|
for (var i = 0; i < order.length; i++) {
|
||||||
|
var m = root.modelsById[order[i]]
|
||||||
|
if (m) {
|
||||||
|
list.push(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.models = list
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistCache() {
|
||||||
|
Plasmoid.configuration.cacheJson = JSON.stringify({
|
||||||
|
byId: root.modelsById,
|
||||||
|
order: root.modelOrder,
|
||||||
|
costById: root.costById,
|
||||||
|
updatedAtMs: root.lastUpdatedMs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCache() {
|
||||||
|
var raw = Plasmoid.configuration.cacheJson || ""
|
||||||
|
if (raw.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var cache = JSON.parse(raw)
|
||||||
|
root.modelsById = cache.byId || {}
|
||||||
|
root.modelOrder = cache.order || []
|
||||||
|
root.costById = cache.costById || {}
|
||||||
|
root.lastUpdatedMs = cache.updatedAtMs !== undefined ? cache.updatedAtMs : -1
|
||||||
|
root.rebuildModels()
|
||||||
|
} catch (e) {
|
||||||
|
// Corrupt cache is not fatal; the next refresh rewrites it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plasma5Support.DataSource {
|
||||||
|
id: executable
|
||||||
|
engine: "executable"
|
||||||
|
connectedSources: []
|
||||||
|
onNewData: function(source, data) {
|
||||||
|
executable.disconnectSource(source)
|
||||||
|
root.pendingCount = Math.max(0, root.pendingCount - 1)
|
||||||
|
if (root.pendingCount === 0) {
|
||||||
|
root.loading = false
|
||||||
|
}
|
||||||
|
if (source === root.costCommand) {
|
||||||
|
root.handleCostResult(data["stdout"] || "")
|
||||||
|
} else {
|
||||||
|
root.handleResult(
|
||||||
|
data["stdout"] || "",
|
||||||
|
data["stderr"] || "",
|
||||||
|
data["exit code"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: Math.max(1, Plasmoid.configuration.refreshMinutes) * 60 * 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: root.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 30 * 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: root.nowMs = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.loadCache()
|
||||||
|
root.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
compactRepresentation: CompactRepresentation {}
|
||||||
|
fullRepresentation: FullRepresentation {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "freelancerevil@gmail.com",
|
||||||
|
"Name": "Pavel Rykov"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Category": "System Information",
|
||||||
|
"Description": "AI coding-provider limits from the CodexBar CLI",
|
||||||
|
"Description[ru]": "Лимиты AI-агентов из CodexBar CLI",
|
||||||
|
"Icon": "office-chart-bar",
|
||||||
|
"Id": "org.rpa.codexbar",
|
||||||
|
"License": "MIT",
|
||||||
|
"Name": "CodexBar",
|
||||||
|
"Version": "0.5.0",
|
||||||
|
"Website": "https://github.com/EvilFreelancer/CodexBar-KDE"
|
||||||
|
},
|
||||||
|
"KPackageStructure": "Plasma/Applet",
|
||||||
|
"X-Plasma-API-Minimum-Version": "6.0",
|
||||||
|
"X-Plasma-NotificationArea": true,
|
||||||
|
"X-Plasma-NotificationAreaCategory": "SystemServices"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user