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

NIconButton {
id: root

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

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

property string iconColorKey: cfg.iconColor ?? defaults.iconColor ?? "none"
readonly property color iconColor: Color.resolveColorKey(iconColorKey)
readonly property bool hasCustomColor: iconColorKey !== "none"

icon: "device-desktop"
tooltipText: pluginApi?.tr("widget.tooltip")
tooltipDirection: BarService.getTooltipDirection(screen?.name)
baseSize: Style.getCapsuleHeightForScreen(screen?.name)
customRadius: Style.radiusL
colorBg: Style.capsuleColor
colorFg: hasCustomColor ? iconColor : Color.mOnSurface
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth

onClicked: {
if (pluginApi) pluginApi.openPanel(root.screen, this)
}

NPopupContextMenu {
id: contextMenu
model: [
{ "label": I18n.tr("actions.widget-settings"), "action": "settings", "icon": "settings" }
]
onTriggered: function(action) {
contextMenu.close()
PanelService.closeContextMenu(screen)
if (action === "settings") {
BarService.openPluginSettings(root.screen, pluginApi.manifest)
}
}
}

onRightClicked: {
PanelService.showContextMenu(contextMenu, root, screen)
}
}
66 changes: 66 additions & 0 deletions display-settings/Main.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services.Compositor

Item {
id: root
property var pluginApi: null

readonly property var liveOutputs: isNiri ? niriProvider.outputs : wlrProvider.outputs
readonly property var niriOutputs: configParser.outputs
readonly property bool isNiri: CompositorService.isNiri
readonly property bool loading: niriProvider.loading || wlrProvider.loading || configParser.loading
readonly property bool modifying: niriProvider.modifying

Component.onCompleted: { if (pluginApi) refresh() }
onPluginApiChanged: { if (pluginApi) refresh() }

function refresh() {
refreshLive()
if (isNiri) refreshConfig()
}

function refreshLive() {
if (isNiri) niriProvider.refresh()
else wlrProvider.refresh()
}

function refreshConfig() {
configParser.configPath = pluginApi?.pluginSettings?.niriConfigPath || "~/.config/niri/config.kdl"
configParser.start()
}

function setMode(name, mode) { niriProvider.setMode(name, mode) }
function setScale(name, scale) { niriProvider.setScale(name, scale) }
function setTransform(name, t) { niriProvider.setTransform(name, t) }
function setVrr(name, on) { niriProvider.setVrr(name, on) }
function setPower(name, on) { niriProvider.setPower(name, on) }

function startMonitor() { udev.start() }
function stopMonitor() { udev.stop() }

NiriOutputProvider {
id: niriProvider
onModifySucceeded: root.refreshLive()
}

WlrOutputProvider { id: wlrProvider }

NiriConfigParser { id: configParser }

UdevMonitor {
id: udev
onTriggered: root.refreshLive()
}

IpcHandler {
target: "plugin:display-settings"

function toggle() {
if (root.pluginApi) {
root.pluginApi.withCurrentScreen(screen => root.pluginApi.togglePanel(screen))
}
}
}
}
136 changes: 136 additions & 0 deletions display-settings/ModeDropdown.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets

ColumnLayout {
id: root

property var output
property var pluginApi
property bool expanded: false

signal modeSelected(string modeLabel)

spacing: Style.marginXS

readonly property string currentModeText: {
var idx = output.currentModeIndex
var modes = output.modes || []
if (idx >= 0 && idx < modes.length) {
var m = modes[idx]
return m.width + "x" + m.height + " @ " + parseFloat(m.refreshRate).toFixed(1) + " Hz"
}
return "-"
}

Rectangle {
id: header
property bool hovered: false
Layout.fillWidth: true
height: 34 * Style.uiScaleRatio
radius: Style.radiusS
color: hovered ? Color.mSurface : "transparent"
border.color: Color.mOutline
border.width: Style.borderS

RowLayout {
anchors { fill: parent; leftMargin: Style.marginS; rightMargin: Style.marginS }
spacing: Style.marginS

NText {
text: root.pluginApi?.tr("panel.mode")
pointSize: Style.fontSizeS; font.weight: Font.DemiBold; color: Color.mOnSurfaceVariant
}
NText {
text: root.currentModeText
pointSize: Style.fontSizeS; color: Color.mOnSurface
Layout.fillWidth: true
elide: Text.ElideRight
}
NIcon {
icon: root.expanded ? "chevron-up" : "chevron-down"
pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant
}
}

MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: header.hovered = true
onExited: header.hovered = false
onClicked: root.expanded = !root.expanded
}
}

ColumnLayout {
visible: root.expanded
Layout.fillWidth: true
spacing: 0

Repeater {
model: root.output.modes || []

delegate: Rectangle {
id: row
property var mode: modelData
property bool isCurrent: index === root.output.currentModeIndex
property bool hovered: false

Layout.fillWidth: true
height: 34 * Style.uiScaleRatio
radius: Style.radiusS
color: isCurrent
? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.18)
: hovered ? Color.mSurface : "transparent"
Behavior on color { ColorAnimation { duration: 100 } }

RowLayout {
anchors { fill: parent; leftMargin: Style.marginM; rightMargin: Style.marginS }
spacing: Style.marginS

NText {
text: row.mode.width + "x" + row.mode.height
pointSize: Style.fontSizeS
color: row.isCurrent ? Color.mPrimary : Color.mOnSurface
font.weight: row.isCurrent ? 600 : 400
}
NText {
text: root.pluginApi?.tr("panel.refreshRateSuffix").arg(parseFloat(row.mode.refreshRate).toFixed(1))
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
}
NText {
visible: row.mode.isPreferred
text: root.pluginApi?.tr("panel.preferred")
pointSize: Style.fontSizeXS
color: Color.mPrimary
}
Item { Layout.fillWidth: true }
NIcon {
icon: "check"
color: Color.mPrimary
pointSize: Style.fontSizeS
visible: row.isCurrent
}
}

MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: root.enabled
onEntered: row.hovered = true
onExited: row.hovered = false
onClicked: {
if (!row.isCurrent) {
root.modeSelected(row.mode.label)
root.expanded = false
}
}
}
}
}
}
}
38 changes: 38 additions & 0 deletions display-settings/NiriConfigCard.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import QtQuick
import QtQuick.Layouts

OutputCard {
id: root

property var output
property var pluginApi

headerIcon: "file-code"
outputName: output.name
isDisabled: !output.enabled
disabledText: pluginApi?.tr("panel.statusOff")

Repeater {
model: root.buildProperties()
delegate: OutputProperty {
Layout.fillWidth: true
label: modelData.label
value: modelData.value
}
}

function buildProperties() {
var props = []
if (output.mode) push(props, "panel.mode", output.mode)
if (output.scale) push(props, "panel.scale", output.scale)
if (output.transform) push(props, "panel.transform", output.transform)
if (output.position) push(props, "panel.position", output.position)
if (!output.enabled) push(props, "panel.enabled", pluginApi?.tr("panel.off"))
if (output.vrr) push(props, "panel.vrr", output.vrr)
return props
}

function push(props, key, value) {
props.push({ label: pluginApi?.tr(key), value: value })
}
}
Loading