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
102 changes: 102 additions & 0 deletions nvibrant/BarWidget.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Widgets
import qs.Services.UI

Item {
id: root

property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0

readonly property var cfg: pluginApi?.pluginSettings ?? ({})
readonly property var defaults: pluginApi?.manifest?.metadata?.defaultSettings ?? ({})

readonly property bool vibrantEnabled: cfg.enabled ?? defaults.enabled ?? false
readonly property int vibranceValue: cfg.vibranceValue ?? defaults.vibranceValue ?? 512
readonly property int displayCount: cfg.displayCount ?? defaults.displayCount ?? 1

readonly property real contentWidth: Style.capsuleHeight
readonly property real contentHeight: Style.capsuleHeight

implicitWidth: contentWidth
implicitHeight: contentHeight

function toggle() {
if (pluginApi) {
pluginApi.pluginSettings.enabled = !vibrantEnabled
pluginApi.saveSettings()
}
}

Rectangle {
id: visualCapsule
x: Style.pixelAlignCenter(parent.width, width)
y: Style.pixelAlignCenter(parent.height, height)
width: root.contentWidth
height: root.contentHeight
radius: Style.radiusL
color: mouseArea.containsMouse ? Color.mHover : Style.capsuleColor
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth

NIcon {
anchors.centerIn: parent
icon: "contrast"
applyUiScale: false
color: root.vibrantEnabled
? Color.mPrimary
: (mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface)
}
}

NPopupContextMenu {
id: contextMenu
model: [
{
"label": root.vibrantEnabled
? pluginApi?.tr("panel.disable-vibrance")
: pluginApi?.tr("panel.enable-vibrance"),
"action": "toggle",
"icon": root.vibrantEnabled ? "eye-off" : "eye"
},
{
"label": pluginApi?.tr("panel.settings"),
"action": "widget-settings",
"icon": "settings"
}
]
onTriggered: action => {
contextMenu.close()
PanelService.closeContextMenu(screen)
if (action === "toggle") {
root.toggle()
} else if (action === "widget-settings") {
BarService.openPluginSettings(screen, pluginApi.manifest)
}
}
}

MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton

onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton) {
root.toggle()
} else if (mouse.button === Qt.RightButton) {
PanelService.showContextMenu(contextMenu, root, screen)
}
}
}
}
46 changes: 46 additions & 0 deletions nvibrant/Main.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons

Item {
id: root
property var pluginApi: null

readonly property var cfg: pluginApi?.pluginSettings ?? ({})
readonly property var defaults: pluginApi?.manifest?.metadata?.defaultSettings ?? ({})

readonly property bool vibrantEnabled: cfg.enabled ?? defaults.enabled ?? false
readonly property int vibranceValue: cfg.vibranceValue ?? defaults.vibranceValue ?? 512
readonly property int displayCount: cfg.displayCount ?? defaults.displayCount ?? 1

function buildCmd(value) {
var args = ["/usr/sbin/nvibrant"]
for (var i = 0; i < root.displayCount; i++)
args.push(value.toString())
return args
}

function applyVibrance(value) {
var cmd = buildCmd(value)
Logger.i("NVibrant", "Running: " + cmd.join(" "))
Quickshell.exec(cmd)
}

// Reactively apply vibrance when any relevant property changes
onVibrantEnabledChanged: applyVibrance(vibrantEnabled ? vibranceValue : 0)
onVibranceValueChanged: if (vibrantEnabled) applyVibrance(vibranceValue)
onDisplayCountChanged: applyVibrance(vibrantEnabled ? vibranceValue : 0)

function toggle() {
if (pluginApi) {
pluginApi.pluginSettings.enabled = !vibrantEnabled
pluginApi.saveSettings()
}
}

IpcHandler {
target: "plugin:nvibrant"
function toggle() { root.toggle() }
}
}
51 changes: 51 additions & 0 deletions nvibrant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# NVibrant

A Noctalia plugin to toggle **NVIDIA digital vibrance** (color saturation) directly from the bar — no terminal needed.

![Preview](preview.png)

## Features

- One-click toggle in the bar — icon highlights when vibrance is active
- Configurable vibrance level (0–1023)
- Multi-monitor support
- State persists across restarts
- Right-click context menu with quick enable/disable

## Requirements

- [nvibrant](https://github.com/Tremeschin/nvibrant) installed (`yay -S nvibrant-bin`)
- NVIDIA GPU with display connected directly to it (not via iGPU/hybrid graphics)

> **Hybrid graphics (Optimus) note:** On laptops with hybrid AMD+NVIDIA, the internal display (eDP) is typically routed through the AMD iGPU and is **not** controllable by nvibrant. Switch your BIOS to **Discrete GPU / NVIDIA-only** mode to make the internal display available to nvibrant.

## Installation

Install via the Noctalia plugin manager, or manually:

```bash
git clone https://github.com/noctalia-dev/noctalia-plugins ~/.config/noctalia/plugins/nvibrant
```

*Note: If cloning manually, ensure the `nvibrant` folder is located directly inside your plugins directory.*

Then enable the plugin and add the bar widget in Noctalia Settings.

## Usage

| Action | Result |
|---|---|
| Left click | Toggle vibrance on/off |
| Right click | Context menu (toggle + settings) |
| IPC | `qs -c noctalia-shell ipc call plugin:nvibrant toggle` |

## Settings

| Setting | Default | Description |
|---|---|---|
| Vibrance Level | 512 | Intensity: 0 = default, 1023 = maximum saturation (~200%) |
| Display Count | 1 | Number of displays/ports to apply vibrance to |

## License

MIT
63 changes: 63 additions & 0 deletions nvibrant/Settings.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets

ColumnLayout {
id: root
property var pluginApi: null

readonly property var cfg: pluginApi?.pluginSettings ?? ({})
readonly property var defaults: pluginApi?.manifest?.metadata?.defaultSettings ?? ({})

spacing: Style.marginM

ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS

NLabel {
label: pluginApi?.tr("settings.vibrance-value")
description: pluginApi?.tr("settings.vibrance-value-desc")
}

NSpinBox {
id: vibranceSpinBox
from: 0
to: 1023
stepSize: 64
value: root.cfg.vibranceValue ?? root.defaults.vibranceValue ?? 512
}
}

NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginM
Layout.bottomMargin: Style.marginM
}

ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS

NLabel {
label: pluginApi?.tr("settings.display-count")
description: pluginApi?.tr("settings.display-count-desc")
}

NSpinBox {
id: displayCountSpinBox
from: 1
to: 8
stepSize: 1
value: root.cfg.displayCount ?? root.defaults.displayCount ?? 1
}
}

function saveSettings() {
if (!pluginApi) return
pluginApi.pluginSettings.vibranceValue = vibranceSpinBox.value
pluginApi.pluginSettings.displayCount = displayCountSpinBox.value
pluginApi.saveSettings()
}
}
13 changes: 13 additions & 0 deletions nvibrant/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"settings": {
"vibrance-value": "Vibrance Level",
"vibrance-value-desc": "Digital vibrance intensity (0 = default, 1023 = maximum saturation)",
"display-count": "Display Count",
"display-count-desc": "Number of displays/ports to apply vibrance to (check nvibrant output)"
},
"panel": {
"enable-vibrance": "Enable Vibrance",
"disable-vibrance": "Disable Vibrance",
"settings": "Settings"
}
}
26 changes: 26 additions & 0 deletions nvibrant/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"id": "nvibrant",
"name": "NVibrant",
"version": "1.0.0",
"minNoctaliaVersion": "4.0.0",
"author": "NoFilterA1",
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Toggle NVIDIA digital vibrance (color saturation) via nvibrant.",
"tags": ["Bar", "System", "Gaming"],
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"settings": "Settings.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"vibranceValue": 512,
"displayCount": 1,
"enabled": false
}
}
}
Binary file added nvibrant/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.