Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ Current Stable Release: v0.3.5
<img src="assets/vrgb-demo.png" width="400">
</p>

<p align="center">
<img src="assets/plasma-widget-demo.png" width="400">
</p>

<p align="center">
<img src="assets/plasma-widget-colour-pick.png" width="400">
</p>


## Features

Expand All @@ -77,6 +85,7 @@ Current Stable Release: v0.3.5
- Installer and uninstaller included
- Non-root daily usage via udev permissions
- Optional KDE autostart restore
- Optional KDE widget



Expand Down Expand Up @@ -162,6 +171,11 @@ Clone the repository and run the installer.

After installation log out and log back in so group permissions apply.

If want to install optional KDE widget

cd plasmoid
chmod +x install.sh
./install.sh

**Note:**
Keyboard color persists on reboot, but may reset to firmware default after a full power cycle.
Expand Down
Binary file added assets/plasma-widget-colour-pick.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/plasma-widget-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions plasmoid/build-translations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
# Compile po/*.po into the applet package.
#
# Plasma looks up an applet's catalogue under the domain
# plasma_applet_<plugin-id>, and KPackage adds the installed package's own
# contents/locale directory to the search path, so the .mo files ship with
# the widget instead of needing root access to /usr/share/locale.
set -e

DOMAIN="plasma_applet_org.vrgb.keyboard"
BASE="$(cd "$(dirname "$0")" && pwd)"

found=0
for po in "$BASE"/po/*.po; do
[ -e "$po" ] || continue
found=1
lang="$(basename "$po" .po)"
dest="$BASE/package/contents/locale/$lang/LC_MESSAGES"
mkdir -p "$dest"
msgfmt --check -o "$dest/$DOMAIN.mo" "$po"
printf '%s -> %s\n' "$lang" "${dest#"$BASE"/}/$DOMAIN.mo"
done

if [ "$found" -eq 0 ]; then
echo "No po/*.po files found. Run ./extract-messages.sh first." >&2
exit 1
fi

echo
echo "Now reinstall the applet so the catalogues are picked up:"
echo " ./install.sh"
36 changes: 36 additions & 0 deletions plasmoid/extract-messages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/sh
# Re-extract translatable strings from the QML sources into po/*.pot,
# then merge the changes into the existing translations.
#
# Run this whenever you add, remove, or reword an i18n() string.
set -e

DOMAIN="plasma_applet_org.vrgb.keyboard"
BASE="$(cd "$(dirname "$0")" && pwd)"
POT="$BASE/po/$DOMAIN.pot"

mkdir -p "$BASE/po"

# Run from the package root so the "#:" source references in the .pot stay
# repo-relative instead of baking in whoever's home directory built it.
cd "$BASE"

# -C -kde is the KDE extraction mode; it understands the i18n* call family and
# parses QML well enough since the call syntax matches C++.
xgettext --from-code=UTF-8 -C -kde \
-ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 \
--package-name="VRGB Keyboard Lighting" \
--msgid-bugs-address="https://github.com/vrgb-dev/vrgb/issues" \
-o "$POT" \
package/contents/ui/*.qml

echo "Wrote ${POT#"$BASE"/}"

for po in "$BASE"/po/*.po; do
[ -e "$po" ] || continue
msgmerge --quiet --update --backup=none "$po" "$POT"
echo "Merged into ${po#"$BASE"/}"
done

echo
echo "Next: translate any new strings in po/*.po, then run ./build-translations.sh"
28 changes: 28 additions & 0 deletions plasmoid/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
# Install or upgrade the VRGB Plasma 6 applet for the current user.
set -e

APPLET_ID="org.vrgb.keyboard"
PACKAGE_DIR="$(cd "$(dirname "$0")" && pwd)/package"

if ! command -v kpackagetool6 >/dev/null 2>&1; then
echo "kpackagetool6 not found - this applet requires Plasma 6." >&2
exit 1
fi

if ! command -v vrgb >/dev/null 2>&1 && [ ! -x /usr/local/bin/vrgb ]; then
echo "Warning: vrgb was not found. Install it first with ../install.sh" >&2
fi

if kpackagetool6 --type Plasma/Applet --list 2>/dev/null | grep -qx "$APPLET_ID"; then
echo "Upgrading $APPLET_ID..."
kpackagetool6 --type Plasma/Applet --upgrade "$PACKAGE_DIR"
else
echo "Installing $APPLET_ID..."
kpackagetool6 --type Plasma/Applet --install "$PACKAGE_DIR"
fi

echo
echo "Done. Add it with: right-click the panel -> Add Widgets -> \"VRGB Keyboard Lighting\""
echo "If you upgraded an already-running applet, restart plasmashell to reload it:"
echo " systemctl --user restart plasma-plasmashell"
13 changes: 13 additions & 0 deletions plasmoid/package/contents/config/main.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.freedesktop.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.freedesktop.org/standards/kcfg/1.0
http://www.freedesktop.org/standards/kcfg/1.0/kcfg.xsd">
<kcfgfile name=""/>
<group name="General">
<entry name="seededDefaults" type="Bool">
<label>Whether the built-in palette has already been turned into vrgb profiles.</label>
<default>false</default>
</entry>
</group>
</kcfg>
Binary file not shown.
143 changes: 143 additions & 0 deletions plasmoid/package/contents/ui/ColorPicker.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* VRGB - inline HSV colour picker (saturation/value square + hue strip).
*
* Emits picked() continuously while dragging; the caller is expected to
* throttle hardware writes rather than firing one per pixel.
*
* SPDX-License-Identifier: MIT
*/

import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami

ColumnLayout {
id: picker

signal picked(color c)

// Hue is kept separately from the emitted colour: greys report hsvHue == -1,
// so round-tripping through a colour would make the hue marker jump to red.
property real hue: 0
property real sat: 1
property real val: 1

readonly property color currentColor: Qt.hsva(hue, sat, val, 1)

// Seed the picker from outside without emitting picked().
function setColor(c) {
if (Qt.colorEqual(c, picker.currentColor)) {
return;
}
hue = (c.hsvHue < 0) ? hue : c.hsvHue;
sat = c.hsvSaturation;
val = c.hsvValue;
}

spacing: Kirigami.Units.smallSpacing

Rectangle {
id: svSquare

Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 7

radius: 3
border.width: 1
border.color: Qt.rgba(0, 0, 0, 0.25)

gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#ffffff" }
GradientStop { position: 1.0; color: Qt.hsva(picker.hue, 1, 1, 1) }
}

Rectangle {
anchors.fill: parent
radius: parent.radius
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: "#000000" }
}
}

Rectangle {
width: Math.round(Kirigami.Units.gridUnit * 0.8)
height: width
radius: width / 2
color: "transparent"
border.width: 2
// Dark ring on pale areas, light ring everywhere else.
border.color: (picker.val > 0.55 && picker.sat < 0.55) ? "#000000" : "#ffffff"
x: picker.sat * svSquare.width - width / 2
y: (1 - picker.val) * svSquare.height - height / 2
}

MouseArea {
anchors.fill: parent

function pick(mx, my) {
picker.sat = Math.max(0, Math.min(1, mx / width));
picker.val = 1 - Math.max(0, Math.min(1, my / height));
picker.picked(picker.currentColor);
}

onPressed: mouse => pick(mouse.x, mouse.y)
onPositionChanged: mouse => {
if (pressed) {
pick(mouse.x, mouse.y);
}
}
}
}

Rectangle {
id: hueStrip

Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit

radius: 3
border.width: 1
border.color: Qt.rgba(0, 0, 0, 0.25)

gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.000; color: "#ff0000" }
GradientStop { position: 0.167; color: "#ffff00" }
GradientStop { position: 0.333; color: "#00ff00" }
GradientStop { position: 0.500; color: "#00ffff" }
GradientStop { position: 0.667; color: "#0000ff" }
GradientStop { position: 0.833; color: "#ff00ff" }
GradientStop { position: 1.000; color: "#ff0000" }
}

Rectangle {
width: 3
height: parent.height + 4
y: -2
x: picker.hue * hueStrip.width - width / 2
color: "#ffffff"
border.width: 1
border.color: "#000000"
}

MouseArea {
anchors.fill: parent

function pick(mx) {
// Clamp below 1.0: a hue of exactly 1.0 wraps back to 0.
picker.hue = Math.max(0, Math.min(0.9999, mx / width));
picker.picked(picker.currentColor);
}

onPressed: mouse => pick(mouse.x)
onPositionChanged: mouse => {
if (pressed) {
pick(mouse.x);
}
}
}
}
}
Loading