Compare commits

..
26 Commits
Author SHA1 Message Date
Guillem CANAL b8865e1311 dankbar: make widget outline radius concentric with the background (#3720) 2026-10-08 11:03:35 -04:00
bbedward 686eb1bfd6 cups: fix 24.20 compatibility
fixes #3721

(cherry picked from commit 2bf7f7c556)
2026-10-08 09:35:00 -04:00
dms-ci[bot] 4a87e8227d bump VERSION to v1.6.3 2026-10-07 17:47:03 +00:00
bbedward c0b26caf2b dank-qml-common: classify scroll input by phase instead of pixelDelta
stable-1.6 branch of the submodule carries dank-qml-common 1ae7222 (port of d630451), so DankFlickable, DankListView and DankGridView stop treating Qt 6.12 mouse wheels as touchpads. The flake input, flake.lock and .gitmodules follow that branch.
2026-10-07 13:15:39 -04:00
Vladimir Kostelei c6c40dbced fix: fingerprint lid open (#3448)
* lock: keep the fingerprint reader usable across suspend

The reader stopped responding after the lid was closed and opened, after a long
idle period, and after suspend.

The shell held a PAM session for as long as the screen was locked, so a suspend
always landed on a running verify. fprintd then suspends the device under the
verify, pam_fprintd's ReleaseDevice fails with "device is still busy", and the
claim is never released, so every later Claim fails until fprintd restarts. The
reader is now released when the monitors blank and armed again when they come
back, so a suspend has nothing left to wedge.

The PAM profile drops to max-tries=1 and sets an explicit timeout. pam_fprintd
only counts a wrong finger, so one finished session is now one wrong scan and
maxFprintTries means the number of scans it names rather than five times that.
A timeout expiry arrives as PamResult.Error, exactly like a device fault, so a
session that ran at least as long as the configured timeout is treated as a
planned renewal instead of spending the error budget and painting an error.

A suspend that still lands while the screen is on wedges as before. Recovery now
waits out fprintd's idle timeout instead of climbing to it, which brings the
reader back about 37 seconds after resume instead of about 98.

SessionService gains lidOpened from UPower, because logind can ignore the lid on
AC and resume alone is not enough. The lock PAM state machine is fixed alongside:
PamResult.Failed was dropped, a pending retry outlived the contexts that owned
it, and an availability check reset the wrong-scan counter.

* lock: time the fingerprint session from the verify, not the claim

pam_fprintd's timeout runs from the verify, but the session age was measured
from start(), which folds the claim into it. A slow claim plus a verify that
failed before its own timeout could add up past the timeout and be taken for a
planned renewal, skipping the backoff. A session that never reached a verify had
the same problem.

The age now starts at the message pam_fprintd sends when the verify begins, so a
session that never verified can never qualify as an expiry, and a slow claim no
longer ages a fault. The message is translated and quickshell clears it before
completed, so only its timing is used, never its text.

That message arrives after pam_fprintd has already armed its deadline, because
VerifyStart is asynchronous, so the age read here runs short of the real timeout
and a slack makes it up. The bias is deliberate. A fault misread as a timeout
costs one extra retry, while a timeout misread as a fault parks the reader behind
a backoff that grows to a minute, which on an idle lock screen would leave it
unusable most of the time.

Also corrects the comment that claimed a timeout is reported without a message.

---------

(cherry picked from commit d7074a53eb)
2026-10-07 13:05:09 -04:00
Satoshoe 02ddd010a0 settings: skip unchanged saves so external edits are not swallowed (#3504)
saveSettings() set _selfWrite before setText(). When the serialized
settings were identical to the file, nothing was written, no
fileChanged followed, and the flag stayed set. The next external edit
of settings.json was then taken for our own write and never reloaded.

(cherry picked from commit 84594e6449)
2026-10-07 13:03:42 -04:00
bbedward 6eb9df87cf network: set routing preference only from settings
fixes #3068

stable-1.6: core part only, the settings and network popout changes are master-only

(cherry picked from commit 089dc98166)
2026-10-07 13:03:41 -04:00
Artem Timofeev 0adbdfdde1 fix(power): reconcile refresh rate after resume (#3459)
(cherry picked from commit 8fedfae1dc)
2026-10-07 13:01:32 -04:00
LemonMon fb002d1977 fix: stabilize tray item key to prevent position jumps on property changes (#3386)
* fix: keep tray order stable when tooltipTitle changes dynamically

Revert the earlier simplistic key approach: keep getTrayItemKey()
unchanged (id::tooltipTitle) so duplicate-id disambiguation (#1542)
is preserved and persisted id::tooltipTitle entries keep matching.

Instead make persisted-key lookups tolerant of dynamic tooltipTitle:
resolveOrderIndex() in sortByPreferredOrder() does exact match first,
falling back to prefix matching an id::* entry only when the id has a
single instance. findSavedTrayKey()/isTrayIdHidden() replace direct
SessionData.isHiddenTrayId() calls and showTrayId() now receives the
actual saved key.

Prefix fallback is gated by trayItemCountForId() to single-instance
ids only, so multiple items sharing an id always match exactly and
hiding/ordering one never affects the others.

* fix: bail on ambiguous saved tray keys in fallback

* tray: use occurrence index as stable key, add one-time session migration

* tray: pin stable key per item instance so it never flips when siblings come and go

* tray: assign unique per-id sequential keys to avoid collision after sibling exit

* tray: reuse free key slots so a restarted singleton keeps its plain id

(cherry picked from commit 60342b7416)
2026-10-07 13:01:06 -04:00
Marlon Kranz 8b7c9085ca fix(notifications): edit rules from the menu instead of deleting them (#3670)
* fix(notifications): edit rules from the menu instead of deleting them

A notification rule can set an action and Bypass DND together, but
Unmute and Block in Do Not Disturb spliced the whole rule, dropping the
other setting. They now reset only their own field, and a rule is
removed once it no longer does anything (action and urgency default,
no bypass). Mute and Allow in Do Not Disturb set their field on the
app's existing rule instead of appending one that first-match policy
would shadow.

The menu now only reads and edits enabled, exact rules on the app name
or desktop entry. It used to match any rule whose pattern equaled the
app name, including summary, body, contains and regex rules, and could
delete disabled ones.

Also adds a Copy item to the notification menu on popups and center
cards, copying the summary and body as plain text.

* fix(notifications): let mute apply alongside other rule actions

Rule actions are first-match, so a mute rule behind an app's existing
rule (e.g. No history) never applied while the menu showed Unmute.
Mute is now evaluated like Bypass DND: any matching enabled mute rule
disables the popup, and the other actions stay first-match, so No
history and Mute both apply.

* fix(notifications): skip the sound for dropped and muted notifications

The notification sound played before the check that dismisses a
notification with neither a popup nor a center entry, so dropped
notifications (transient while muted, Popup only plus Mute) still made
a sound. Play it only for notifications that are kept, and not when a
mute rule applies, like Do Not Disturb.

stable-1.6: ported the rule edits and the sound/mute policy only, the Copy menu item depends on NotificationActions which stable does not have

(cherry picked from commit 51f115f11e)
2026-10-07 13:00:11 -04:00
Marcus Ramberg 8b34bbf71a fix: scope bluez dbus match to bluezService (#3573)
Ensure the watcher only seees bluez messages

(cherry picked from commit 05377e690a)
2026-10-07 12:58:50 -04:00
bbedward fd468a6b78 brightness: ddcutil-parity ddc probing, bounded rereads
fixes #3616

(cherry picked from commit 0b9d931ea3)
2026-10-07 12:58:37 -04:00
Pedro Jose Soto Rivera 7c04b61bcc network(networkd): treat bridge, bond, team and vlan links as wired (#3706)
* network(networkd): treat bridge, bond, team and vlan links as wired

When the IP configuration lives on a bridge or link aggregate, the
enslaved member NIC reports opState=enslaved and the aggregate itself
was dropped by isWired() because only Type=ether was accepted, so the
widget showed Disconnected. Accept the same link types NetworkManager
already handles (#1581).

Also treat lxdbr*/incusbr* as virtual so container bridges, now of a
wired type, are not picked as the uplink.

Fixes #3463

* network(networkd): only treat a bridge as wired if it has a physical member

Name prefixes can't cover every container/VM bridge (lxcbr0, waydroid0,
...). Check the bridge's members in /sys/class/net/<br>/brif instead and
only accept it as the uplink when at least one member is not virtual
(veth*, tap*, ...). This replaces the lxdbr*/incusbr* prefixes.

(cherry picked from commit f886789835)
2026-10-07 12:58:37 -04:00
Pedro Jose Soto Rivera bc4916a132 network(iwd): follow the Wi-Fi device when iwd recreates it (#3707)
When iwd restarts it recreates wlan0 under a new D-Bus path. Only the
Station interface was tracked on InterfacesAdded, so devicePath kept
pointing at the dead object: the PropertiesChanged match for the new
device was never registered, updateState() read Powered from the old
path, and SetWiFiEnabled() failed with "No matching method found".
WiFiEnabled stayed false after handleStationRemoved() until DMS was
restarted.

Handle the Device interface on InterfacesAdded/InterfacesRemoved: move
devicePath and its signal match to the new object and take Name and
Powered from the signal. Process every interface in an
InterfacesRemoved signal instead of stopping at the first match.

Refs #3495

(cherry picked from commit ff50b492b1)
2026-10-07 12:58:37 -04:00
Stephanos Kamprogiannis 58a356bb05 idle: wake monitors after automatic lock (#3633)
Locking rearms the normal idle monitors and can lose their wake transition.
Enable the existing locked-monitor wake listener for idle power-off too.

(cherry picked from commit 22fa08f0ff)
2026-10-07 12:58:37 -04:00
arfan 3061e1301d fix(theme-vscode): update editorSuggestWidget colors for improved visibility (#3599)
* fix(theme-vscode): update editorSuggestWidget colors for improved visibility in dark, default, and light themes

* fix(theme-vscode): update button.hoverBackground to use primary_fixed colors for consistency

(cherry picked from commit c1818dee2c)
2026-10-07 12:58:37 -04:00
arfan e333da2013 fix(theme): update focus highlight colors for list and quick input components (#3579)
(cherry picked from commit 814620a934)
2026-10-07 12:58:37 -04:00
bbedward 307d05d76c matugen: fix kitty cursor color
fixes #3520

(cherry picked from commit f3af870d93)
2026-10-07 12:58:37 -04:00
bbedward 10dbe73335 matugen: fix kde color scheme templates ignoring the accent
(cherry picked from commit f102a83d9f)
2026-10-07 12:58:37 -04:00
Huỳnh Thiện Lộc b6fee3a98f fix(island): avoid bottom clipping in media expanded sheet (#3593)
(cherry picked from commit 0717908d53)
2026-10-07 12:58:37 -04:00
Thomas Kroll 6166d51c7c fix(workspace): ignore duplicate submit in rename modal (#3549)
Pressing Enter in the name field fires both the text field's accepted
signal and the dialog's own Return handler, so submitAndClose() runs
twice. The first call renames and hides the window, which clears the
field; the second call then renames to an empty string and unsets the
workspace name. The OK button only fires once, so it was unaffected.

Ignore submits once the window is hidden.

Signed-off-by: Thomas Kroll <[email protected]>
(cherry picked from commit eb0cb80884)
2026-10-07 12:58:37 -04:00
Huỳnh Thiện Lộc d06fcccff9 fix(notepad): hide placeholder during IME composition (#3646)
(cherry picked from commit 340f2d179c)
2026-10-07 12:58:37 -04:00
Marlon Kranz d9cfeded47 fix(lock): show the lock screen on every display when none is selected (#3666)
With two or more screens connected and none of them in the lock screen
display list (a laptop docked at a different desk), every surface was
inactive and no password field rendered, so the only way out was a
TTY. getFilteredScreens only falls back to all screens with
showOnLastDisplay and exactly one screen, which the lock screen picker
never sets. Treat an empty result as all screens, the same as the
default "all" preference.

(cherry picked from commit 6a320399c9)
2026-10-07 12:58:37 -04:00
bbedward c4ced3ac9d disable fanout 2026-10-07 12:36:06 -04:00
bbedward ce2ec80d80 bar: detect touchpads by scroll phase instead of pixelDelta 2026-10-07 12:34:11 -04:00
bbedward a340eba493 config: drop Before=graphical-session.target from the generated hyprland target
(cherry picked from commit 507b818a55)
2026-10-06 11:05:47 -04:00
52 changed files with 1525 additions and 623 deletions
+32 -32
View File
@@ -520,37 +520,37 @@ jobs:
# Pin the satellite repos to this release's version. Each dispatch target
# decides for itself whether to tag: X.Y.0 always tags, X.Y.Z skips when the
# repo has no commits of its own since its last tag.
fanout:
name: Pin ${{ matrix.repo }}
runs-on: ubuntu-latest
needs: [release]
if: ${{ !contains(inputs.tag, '-') }}
strategy:
fail-fast: false
matrix:
repo:
- dgop
- dankgo
# fanout:
# name: Pin ${{ matrix.repo }}
# runs-on: ubuntu-latest
# needs: [release]
# if: ${{ !contains(inputs.tag, '-') }}
# strategy:
# fail-fast: false
# matrix:
# repo:
# - dgop
# - dankgo
# - dank-qml-common
# - dankcalendar
- danksearch
- dank-greeter
steps:
- name: Create GitHub App token
id: app_token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ matrix.repo }}
- name: Dispatch pin-release
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
REPO: ${{ github.repository_owner }}/${{ matrix.repo }}
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
gh workflow run pin-release.yml --repo "$REPO" -f version="$TAG"
echo "dispatched pin-release ${TAG} to ${REPO}" >> "$GITHUB_STEP_SUMMARY"
# - danksearch
# - dank-greeter
# steps:
# - name: Create GitHub App token
# id: app_token
# uses: actions/create-github-app-token@v3
# with:
# app-id: ${{ secrets.APP_ID }}
# private-key: ${{ secrets.APP_PRIVATE_KEY }}
# owner: ${{ github.repository_owner }}
# repositories: ${{ matrix.repo }}
#
# - name: Dispatch pin-release
# env:
# GH_TOKEN: ${{ steps.app_token.outputs.token }}
# REPO: ${{ github.repository_owner }}/${{ matrix.repo }}
# TAG: ${{ inputs.tag }}
# run: |
# set -euo pipefail
# gh workflow run pin-release.yml --repo "$REPO" -f version="$TAG"
# echo "dispatched pin-release ${TAG} to ${REPO}" >> "$GITHUB_STEP_SUMMARY"
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "dank-qml-common"]
path = dank-qml-common
url = https://github.com/AvengeMedia/dank-qml-common.git
branch = master
branch = stable-1.6
+1 -1
View File
@@ -557,7 +557,7 @@ func TestKittyConfigStructure(t *testing.T) {
func TestKittyThemeConfigStructure(t *testing.T) {
assert.Contains(t, KittyThemeConfig, "foreground #e0e2e8")
assert.Contains(t, KittyThemeConfig, "background #101418")
assert.Contains(t, KittyThemeConfig, "cursor #e0e2e8")
assert.Contains(t, KittyThemeConfig, "cursor #9dcbfb")
assert.Contains(t, KittyThemeConfig, "color0 #101418")
assert.Contains(t, KittyThemeConfig, "color15 #ffffff")
}
@@ -1,5 +1,5 @@
cursor #e0e2e8
cursor_text_color #c2c7cf
cursor #9dcbfb
cursor_text_color #003354
foreground #e0e2e8
background #101418
+19 -11
View File
@@ -3,21 +3,32 @@ package config
import (
"os"
"path/filepath"
"slices"
)
// Mirrors Hyprland's shipped target. Before=graphical-session.target would cycle with the add-wants implicit After=dms.service.
const hyprlandSessionTargetUnit = `[Unit]
Description=Hyprland Session Target
BindsTo=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target
PropagatesStopTo=graphical-session.target
`
var staleHyprlandSessionTargetUnits = []string{
`[Unit]
Description=Hyprland Session Target
Requires=graphical-session.target
After=graphical-session.target
`,
`[Unit]
Description=Hyprland Session Target
BindsTo=graphical-session.target
Before=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target
`
const legacyHyprlandSessionTargetUnit = `[Unit]
Description=Hyprland Session Target
Requires=graphical-session.target
After=graphical-session.target
`
`,
}
// Only a missing or dankinstall-written unit is replaced; hand-written units are left alone.
func EnsureHyprlandSessionTarget() (string, error) {
@@ -30,10 +41,7 @@ func EnsureHyprlandSessionTarget() (string, error) {
existing, err := os.ReadFile(targetPath)
if err == nil {
content := string(existing)
if content == hyprlandSessionTargetUnit {
return targetPath, nil
}
if content != legacyHyprlandSessionTargetUnit {
if content == hyprlandSessionTargetUnit || !slices.Contains(staleHyprlandSessionTargetUnits, content) {
return targetPath, nil
}
}
@@ -31,23 +31,25 @@ func TestEnsureHyprlandSessionTargetWritesMissing(t *testing.T) {
}
}
func TestEnsureHyprlandSessionTargetUpgradesLegacy(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
path := targetPathFor(t, home)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(legacyHyprlandSessionTargetUnit), 0o644); err != nil {
t.Fatal(err)
}
func TestEnsureHyprlandSessionTargetUpgradesStale(t *testing.T) {
for _, stale := range staleHyprlandSessionTargetUnits {
home := t.TempDir()
t.Setenv("HOME", home)
path := targetPathFor(t, home)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(stale), 0o644); err != nil {
t.Fatal(err)
}
if _, err := EnsureHyprlandSessionTarget(); err != nil {
t.Fatal(err)
}
data, _ := os.ReadFile(path)
if string(data) != hyprlandSessionTargetUnit {
t.Fatalf("legacy unit not upgraded:\n%s", data)
if _, err := EnsureHyprlandSessionTarget(); err != nil {
t.Fatal(err)
}
data, _ := os.ReadFile(path)
if string(data) != hyprlandSessionTargetUnit {
t.Fatalf("stale unit not upgraded:\n%s", data)
}
}
}
@@ -3,6 +3,7 @@ package qmlchecks
import (
"os"
"regexp"
"strconv"
"strings"
"testing"
)
@@ -55,3 +56,51 @@ func TestLockScreenPamSupportsManagedAndSystemPolicies(t *testing.T) {
t.Fatalf("DMS-managed policy must generate the sanitized user PAM stack on Nix-store installs")
}
}
func TestLockScreenFprintProfileMatchesPamQml(t *testing.T) {
profile, err := os.ReadFile("../../../quickshell/assets/pam/fprint")
if err != nil {
t.Fatalf("read fprint PAM profile: %v", err)
}
qml, err := os.ReadFile("../../../quickshell/Modules/Lock/Pam.qml")
if err != nil {
t.Fatalf("read lock screen PAM QML: %v", err)
}
// Pam.qml tells a pam_fprintd timeout expiry apart from a device fault by
// how long the session ran, so a profile change that is not mirrored in
// sessionTimeoutMs makes real faults look like planned renewals.
profileTimeout := regexp.MustCompile(`pam_fprintd\.so[^\n]*\btimeout=(\d+)`).FindSubmatch(profile)
if profileTimeout == nil {
t.Fatalf("fprint profile must set an explicit pam_fprintd timeout")
}
qmlTimeout := regexp.MustCompile(`sessionTimeoutMs:\s*(\d+)`).FindSubmatch(qml)
if qmlTimeout == nil {
t.Fatalf("Pam.qml must declare sessionTimeoutMs")
}
seconds, err := strconv.Atoi(string(profileTimeout[1]))
if err != nil {
t.Fatalf("parse profile timeout: %v", err)
}
millis, err := strconv.Atoi(string(qmlTimeout[1]))
if err != nil {
t.Fatalf("parse sessionTimeoutMs: %v", err)
}
if seconds*1000 != millis {
t.Fatalf("sessionTimeoutMs is %dms but the fprint profile sets timeout=%d", millis, seconds)
}
// fprintd 1.94.0 and older ignore this option when it is longer than two
// digits and silently fall back to 30 seconds.
if len(profileTimeout[1]) > 2 {
t.Fatalf("pam_fprintd timeout=%s is too long for fprintd <= 1.94.0, which would ignore it", profileTimeout[1])
}
// One finished session must equal one wrong finger, otherwise
// SettingsData.maxFprintTries silently means a multiple of the scans it names.
if !regexp.MustCompile(`pam_fprintd\.so[^\n]*\bmax-tries=1\b`).Match(profile) {
t.Fatalf("fprint profile must use max-tries=1 so the shell counts wrong scans one for one")
}
}
+5 -3
View File
@@ -18,10 +18,12 @@ const (
propertiesIface = "org.freedesktop.DBus.Properties"
)
// Sender-scoped: the system bus is shared process-wide, so unscoped rules wake
// every other subsystem's signal channel too.
var bluezMatchRules = [][]dbus.MatchOption{
{dbus.WithMatchInterface(propertiesIface), dbus.WithMatchMember("PropertiesChanged")},
{dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesAdded")},
{dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesRemoved")},
{dbus.WithMatchSender(bluezService), dbus.WithMatchInterface(propertiesIface), dbus.WithMatchMember("PropertiesChanged")},
{dbus.WithMatchSender(bluezService), dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesAdded")},
{dbus.WithMatchSender(bluezService), dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesRemoved")},
}
func NewManager() (*Manager, error) {
+1 -1
View File
@@ -19,7 +19,7 @@ func WaitForAdapter() error {
conn.Signal(signals)
defer conn.RemoveSignal(signals)
adapterAdded := []dbus.MatchOption{dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesAdded")}
adapterAdded := []dbus.MatchOption{dbus.WithMatchSender(bluezService), dbus.WithMatchInterface(objectMgrIface), dbus.WithMatchMember("InterfacesAdded")}
if err := conn.AddMatchSignal(adapterAdded...); err != nil {
return err
}
+266 -73
View File
@@ -2,6 +2,7 @@ package brightness
import (
"encoding/binary"
"errors"
"fmt"
"math"
"os"
@@ -20,13 +21,40 @@ const (
DDCCI_VCP_SET = 0x03
VCP_BRIGHTNESS = 0x10
DDC_SOURCE_ADDR = 0x51
EDID_ADDR = 0x50
)
// ddcutil's DEFAULT_FLOCK_POLL_MILLISEC and DEFAULT_FLOCK_MAX_WAIT_MILLISEC (src/base/parms.h).
const (
ddcBusLockPoll = 100 * time.Millisecond
ddcBusLockWait = 3 * time.Second
)
var (
errDDCDisabled = errors.New("disabled via DMS_NO_DDC")
errInvalidEDID = errors.New("invalid edid")
errDisplayAsleep = errors.New("display asleep")
errNoMonitor = errors.New("no monitor on bus")
errBusReused = errors.New("bus belongs to another adapter")
)
var ddcRereadDelays = []time.Duration{5 * time.Second, 10 * time.Second, 20 * time.Second}
func NewDDCBackend() (*DDCBackend, error) {
return newDDCBackend(nil)
}
func newDDCBackend(onReread func()) (*DDCBackend, error) {
if os.Getenv("DMS_NO_DDC") != "" {
return nil, errDDCDisabled
}
b := &DDCBackend{
scanInterval: 30 * time.Second,
debounceTimers: make(map[string]*time.Timer),
debouncePending: make(map[string]ddcPendingSet),
onReread: onReread,
stop: make(chan struct{}),
}
if err := b.scanI2CDevices(); err != nil {
@@ -53,6 +81,7 @@ func (b *DDCBackend) scanI2CDevicesInternal(force bool) error {
}
activeBuses := make(map[int]bool)
connectors := drmConnectorsByBus()
for i := range 32 {
busPath := fmt.Sprintf("/dev/i2c-%d", i)
@@ -70,18 +99,36 @@ func (b *DDCBackend) scanI2CDevicesInternal(force bool) error {
// Don't re-probe identified monitors: DDC traffic during a wake
// sequence can disturb some monitors' own brightness handling.
if _, ok := b.devices.Load(id); ok {
if known, ok := b.devices.Load(id); ok {
if known.adapter == getI2CDeviceSysfsName(i) {
continue
}
b.devices.Delete(id)
log.Debugf("removed DDC device %s (bus number reused by another adapter)", id)
}
verdict := ddcBusVerdictFor(i, connectors)
if verdict == ddcBusSkip {
log.Debugf("i2c-%d: no monitor per DRM sysfs, not probing", i)
continue
}
if ddcDisplayAsleep(i, connectors) {
log.Debugf("i2c-%d: display asleep, not probing", i)
continue
}
dev, err := b.probeDDCDevice(i)
dev, err := b.probeDDCDevice(i, verdict == ddcBusNeedsEDIDRead)
if err != nil || dev == nil {
continue
}
dev.id = id
unread := dev.max == 0
b.devices.Store(id, dev)
log.Debugf("found DDC device on i2c-%d", i)
if unread && b.onReread != nil {
go b.rereadUntilKnown(dev)
}
}
b.devices.Range(func(id string, dev *ddcDevice) bool {
@@ -97,53 +144,119 @@ func (b *DDCBackend) scanI2CDevicesInternal(force bool) error {
return nil
}
func (b *DDCBackend) probeDDCDevice(bus int) (*ddcDevice, error) {
busPath := fmt.Sprintf("/dev/i2c-%d", bus)
fd, err := syscall.Open(busPath, syscall.O_RDWR, 0)
func (b *DDCBackend) probeDDCDevice(bus int, readEDID bool) (*ddcDevice, error) {
fd, err := openDDCBus(bus)
if err != nil {
return nil, err
}
defer syscall.Close(fd)
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(DDCCI_ADDR)); errno != 0 {
return nil, errno
}
dummy := make([]byte, 32)
syscall.Read(fd, dummy) //nolint:errcheck
writebuf := []byte{DDC_SOURCE_ADDR, 0x80}
writebuf = append(writebuf, ddcciChecksum(writebuf))
n, err := syscall.Write(fd, writebuf)
if err == nil && n == len(writebuf) {
name := b.getDDCName(bus)
dev := &ddcDevice{
bus: bus,
addr: DDCCI_ADDR,
name: name,
if readEDID {
edid, err := readBusEDID(fd)
if err != nil {
return nil, err
}
if isLaptopEDID(edid) {
return nil, errors.New("laptop panel")
}
b.readInitialBrightness(fd, dev)
return dev, nil
}
readbuf := make([]byte, 4)
n, err = syscall.Read(fd, readbuf)
if err != nil || n == 0 {
if err := setI2CAddr(fd, DDCCI_ADDR); err != nil {
return nil, err
}
if !detectX37(fd) {
return nil, fmt.Errorf("x37 unresponsive")
}
name := b.getDDCName(bus)
dev := &ddcDevice{
bus: bus,
addr: DDCCI_ADDR,
name: name,
bus: bus,
addr: DDCCI_ADDR,
name: b.getDDCName(bus),
adapter: getI2CDeviceSysfsName(bus),
}
b.readInitialBrightness(fd, dev)
return dev, nil
}
// Based on ddcutil's i2c_open_bus() cross-instance lock (src/base/flock.c): ddcutil, other dms
// processes and this one take turns on a bus.
func openDDCBus(bus int) (int, error) {
// Without O_CLOEXEC a process spawned mid-probe inherits the fd and the flock with it.
fd, err := syscall.Open(fmt.Sprintf("/dev/i2c-%d", bus), syscall.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil {
return -1, err
}
deadline := time.Now().Add(ddcBusLockWait)
for {
err = unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB)
if err == nil {
return fd, nil
}
if !errors.Is(err, unix.EWOULDBLOCK) || time.Now().After(deadline) {
syscall.Close(fd)
return -1, fmt.Errorf("lock i2c-%d: %w", bus, err)
}
time.Sleep(ddcBusLockPoll)
}
}
func setI2CAddr(fd, addr int) error {
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(addr)); errno != 0 {
return errno
}
return nil
}
// Based on ddcutil's i2c_detect_x37() (src/i2c/i2c_x37.c).
func detectX37(fd int) bool {
if n, err := syscall.Read(fd, make([]byte, 1)); err == nil && n == 1 {
return true
}
_, err := syscall.Write(fd, nil)
return err == nil
}
// Based on ddcutil's i2c_get_raw_edid_by_fd() with the fileio reader (src/i2c/i2c_edid.c).
func readBusEDID(fd int) ([]byte, error) {
if err := setI2CAddr(fd, EDID_ADDR); err != nil {
return nil, err
}
var err error
consecutiveEIO := 0
for _, size := range []int{128, 128, 256, 256} {
edid := make([]byte, size)
if err = readEDIDOnce(fd, edid); err == nil {
return edid[:128], nil
}
switch {
case errors.Is(err, syscall.ENXIO), errors.Is(err, syscall.EOPNOTSUPP), errors.Is(err, syscall.ETIMEDOUT):
return nil, err
case errors.Is(err, syscall.EIO):
consecutiveEIO++
if consecutiveEIO >= 2 {
return nil, err
}
default:
consecutiveEIO = 0
}
}
return nil, err
}
func readEDIDOnce(fd int, edid []byte) error {
if _, err := syscall.Write(fd, []byte{0x00}); err != nil {
return err
}
n, err := syscall.Read(fd, edid)
if err != nil {
return err
}
if n < 128 || !isValidEDID(edid[:128]) {
return errInvalidEDID
}
return nil
}
func (b *DDCBackend) getDDCName(bus int) string {
sysfsPath := fmt.Sprintf("/sys/class/i2c-adapter/i2c-%d/name", bus)
data, err := os.ReadFile(sysfsPath)
@@ -176,6 +289,63 @@ func (b *DDCBackend) readInitialBrightness(fd int, dev *ddcDevice) {
}
}
// Bounded and spaced: extra DDC traffic can upset some monitors (#2049).
func (b *DDCBackend) rereadUntilKnown(dev *ddcDevice) {
for _, delay := range ddcRereadDelays {
select {
case <-b.stop:
return
case <-time.After(delay):
}
read, retry := b.rereadBrightness(dev)
if read {
b.onReread()
return
}
if !retry {
return
}
}
log.Debugf("giving up on reading brightness for %s", dev.id)
}
func (b *DDCBackend) rereadBrightness(dev *ddcDevice) (read, retry bool) {
b.ioMutex.Lock()
defer b.ioMutex.Unlock()
if current, ok := b.devices.Load(dev.id); !ok || current != dev || dev.max > 0 {
return false, false
}
switch err := b.checkBusAlive(dev); {
case errors.Is(err, errBusReused):
return false, false
case err != nil:
return false, true
}
fd, err := openDDCBus(dev.bus)
if err != nil {
log.Debugf("brightness reread skipped for %s: %v", dev.id, err)
return false, true
}
defer syscall.Close(fd)
if err := setI2CAddr(fd, dev.addr); err != nil {
return false, false
}
cap, err := b.getVCPFeature(fd, VCP_BRIGHTNESS)
if err != nil {
log.Debugf("brightness reread failed for %s: %v", dev.id, err)
return false, true
}
dev.max = cap.max
dev.lastBrightness = cap.current
log.Debugf("read %s brightness %d/%d", dev.id, cap.current, cap.max)
return true, false
}
func (b *DDCBackend) GetDevices() ([]Device, error) {
if err := b.scanI2CDevices(); err != nil {
log.Debugf("DDC scan error: %v", err)
@@ -183,6 +353,8 @@ func (b *DDCBackend) GetDevices() ([]Device, error) {
devices := make([]Device, 0)
b.ioMutex.Lock()
defer b.ioMutex.Unlock()
b.devices.Range(func(id string, dev *ddcDevice) bool {
devices = append(devices, Device{
Class: ClassDDC,
@@ -190,7 +362,7 @@ func (b *DDCBackend) GetDevices() ([]Device, error) {
Name: dev.name,
Current: dev.lastBrightness,
Max: dev.max,
CurrentPercent: dev.lastBrightness,
CurrentPercent: ddcPercentFromRaw(dev.lastBrightness, dev.max),
Backend: "ddc",
})
return true
@@ -274,6 +446,9 @@ func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) er
return fmt.Errorf("device not found: %s", id)
}
b.ioMutex.Lock()
defer b.ioMutex.Unlock()
busPath := fmt.Sprintf("/dev/i2c-%d", dev.bus)
if _, err := os.Stat(busPath); os.IsNotExist(err) {
@@ -282,52 +457,85 @@ func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) er
return fmt.Errorf("device disconnected: %s", id)
}
fd, err := syscall.Open(busPath, syscall.O_RDWR, 0)
if err != nil {
switch err := b.checkBusAlive(dev); {
case errors.Is(err, errBusReused):
b.devices.Delete(id)
log.Debugf("removed DDC device %s (bus number reused by another adapter)", id)
return err
case err != nil:
return err
}
fd, err := openDDCBus(dev.bus)
switch {
case errors.Is(err, unix.EWOULDBLOCK):
return err
case err != nil:
b.devices.Delete(id)
log.Debugf("removed DDC device %s (open failed: %v)", id, err)
return fmt.Errorf("open i2c device: %w", err)
}
defer syscall.Close(fd)
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(dev.addr)); errno != 0 {
return fmt.Errorf("set i2c slave addr: %w", errno)
if err := setI2CAddr(fd, dev.addr); err != nil {
return fmt.Errorf("set i2c slave addr: %w", err)
}
max := dev.max
if max == 0 {
maxValue := dev.max
if maxValue == 0 {
cap, err := b.getVCPFeature(fd, VCP_BRIGHTNESS)
if err != nil {
return fmt.Errorf("get current capability: %w", err)
}
max = cap.max
dev.max = max
b.devices.Store(id, dev)
maxValue = cap.max
}
if maxValue <= 0 {
return fmt.Errorf("%s reported max brightness %d", id, maxValue)
}
if err := b.setVCPFeature(fd, VCP_BRIGHTNESS, value); err != nil {
raw := ddcRawFromPercent(value, maxValue)
if err := b.setVCPFeature(fd, VCP_BRIGHTNESS, raw); err != nil {
return fmt.Errorf("set vcp feature: %w", err)
}
log.Debugf("set %s to %d/%d", id, value, max)
log.Debugf("set %s to %d/%d", id, raw, maxValue)
dev.max = max
dev.lastBrightness = value
dev.max = maxValue
dev.lastBrightness = raw
b.devices.Store(id, dev)
return nil
}
func (b *DDCBackend) getVCPFeature(fd int, vcp byte) (*ddcCapability, error) {
for range 3 {
dummy := make([]byte, 32)
n, _ := syscall.Read(fd, dummy)
if n == 0 {
break
}
time.Sleep(20 * time.Millisecond)
// Based on ddcutil's i2c_check_open_bus_alive() (src/i2c/i2c_bus_core.c), sysfs side only. Known
// devices are keyed by bus number, which the kernel hands to the next adapter once one goes away.
func (b *DDCBackend) checkBusAlive(dev *ddcDevice) error {
if isIgnorableI2CBus(dev.bus) || getI2CDeviceSysfsName(dev.bus) != dev.adapter {
return errBusReused
}
connectors := drmConnectorsByBus()
if ddcBusVerdictFor(dev.bus, connectors) == ddcBusSkip {
return errNoMonitor
}
if ddcDisplayAsleep(dev.bus, connectors) {
return errDisplayAsleep
}
return nil
}
func ddcRawFromPercent(percent, maxValue int) int {
percent = min(max(percent, 0), 100)
return int(math.Round(float64(percent*maxValue) / 100))
}
func ddcPercentFromRaw(raw, maxValue int) int {
if maxValue <= 0 {
return 0
}
return min(int(math.Round(float64(raw*100)/float64(maxValue))), 100)
}
func (b *DDCBackend) getVCPFeature(fd int, vcp byte) (*ddcCapability, error) {
data := []byte{
DDCCI_VCP_GET,
vcp,
@@ -347,25 +555,6 @@ func (b *DDCBackend) getVCPFeature(fd int, vcp byte) (*ddcCapability, error) {
time.Sleep(50 * time.Millisecond)
pollFds := []unix.PollFd{
{
Fd: int32(fd),
Events: unix.POLLIN,
},
}
pollTimeout := 200
pollResult, err := unix.Poll(pollFds, pollTimeout)
if err != nil {
return nil, fmt.Errorf("poll i2c: %w", err)
}
if pollResult == 0 {
return nil, fmt.Errorf("poll timeout after %dms", pollTimeout)
}
if pollFds[0].Revents&unix.POLLIN == 0 {
return nil, fmt.Errorf("poll returned but POLLIN not set")
}
response := make([]byte, 12)
n, err = syscall.Read(fd, response)
if err != nil || n < 8 {
@@ -520,4 +709,8 @@ func (b *DDCBackend) WaitPending() {
}
func (b *DDCBackend) Close() {
if b.stop == nil {
return
}
b.closeOnce.Do(func() { close(b.stop) })
}
+192 -55
View File
@@ -1,9 +1,12 @@
package brightness
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
@@ -11,9 +14,12 @@ import (
)
// isIgnorableI2CBus checks if an I2C bus should be skipped during DDC probing.
// Based on ddcutil's sysfs_is_ignorable_i2c_device() (sysfs_base.c:1441)
// Based on ddcutil's sysfs_is_ignorable_i2c_device() (src/sysfs/sysfs_simple.c)
func isIgnorableI2CBus(busno int) bool {
name := getI2CDeviceSysfsName(busno)
if name == "DPMST" {
return false
}
driver := getI2CSysfsDriver(busno)
if name != "" && isIgnorableI2CDeviceName(name, driver) {
@@ -23,19 +29,20 @@ func isIgnorableI2CBus(busno int) bool {
// Only probe display adapters (0x03xxxx) and docking stations (0x0axxxx)
class := getI2CDeviceSysfsClass(busno)
if class != 0 {
classHigh := class & 0xFFFF0000
ignorable := (classHigh != 0x030000 && classHigh != 0x0A0000)
if ignorable {
log.Debugf("i2c-%d: ignoring class 0x%08x", busno, class)
}
return ignorable
if class == 0 {
// No PCI class says nothing about a platform adapter, but a real adapter always has a name.
return name == ""
}
return false
classHigh := class & 0xFFFF0000
ignorable := (classHigh != 0x030000 && classHigh != 0x0A0000)
if ignorable {
log.Debugf("i2c-%d: ignoring class 0x%08x", busno, class)
}
return ignorable
}
// Based on ddcutil's ignorable_i2c_device_sysfs_name() (sysfs_base.c:1408)
// Based on ddcutil's ignorable_i2c_device_sysfs_name() (src/sysfs/sysfs_simple.c)
func isIgnorableI2CDeviceName(name, driver string) bool {
ignorablePrefixes := []string{
"SMBus",
@@ -44,7 +51,8 @@ func isIgnorableI2CDeviceName(name, driver string) bool {
"smu",
"mac-io",
"u4",
"AMDGPU SMU", // AMD Navi2+ - probing hangs GPU
"AMDGPU SMU", // AMD Navi2+ - probing hangs GPU
"AMDGPU DM i2c OEM bus", // RGB controllers, not displays
}
for _, prefix := range ignorablePrefixes {
@@ -71,65 +79,194 @@ func getI2CDeviceSysfsName(busno int) string {
return strings.TrimSpace(string(data))
}
// Based on ddcutil's get_i2c_device_sysfs_class() (sysfs_base.c:1380)
// Based on ddcutil's get_i2c_device_sysfs_class() (src/sysfs/sysfs_simple.c)
func getI2CDeviceSysfsClass(busno int) uint32 {
classPath := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device/class", busno)
data, err := os.ReadFile(classPath)
if err != nil {
classPath = fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device/device/device/class", busno)
data, err = os.ReadFile(classPath)
paths := []string{
fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno),
fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device", busno),
fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/i2c-dev/i2c-%d/device", busno, busno),
}
for _, path := range paths {
adapter := findI2CAdapter(path)
if adapter == "" {
continue
}
data, err := os.ReadFile(filepath.Join(adapter, "class"))
if err != nil {
return 0
continue
}
class, err := strconv.ParseUint(strings.TrimPrefix(strings.TrimSpace(string(data)), "0x"), 16, 32)
if err == nil && class != 0 {
return uint32(class)
}
}
classStr := strings.TrimSpace(string(data))
classStr = strings.TrimPrefix(classStr, "0x")
class, err := strconv.ParseUint(classStr, 16, 32)
if err != nil {
return 0
}
return uint32(class)
return 0
}
// Based on ddcutil's get_i2c_sysfs_driver_by_busno() (sysfs_base.c:1284)
// Based on ddcutil's get_driver_for_busno() (src/sysfs/sysfs_simple.c)
func getI2CSysfsDriver(busno int) string {
devicePath := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno)
adapterPath, err := findI2CAdapter(devicePath)
adapter := findI2CAdapter(fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno))
if adapter == "" {
return ""
}
module, err := filepath.EvalSymlinks(filepath.Join(adapter, "driver", "module"))
if err != nil {
return ""
}
return filepath.Base(module)
}
driverLink := filepath.Join(adapterPath, "driver")
target, err := os.Readlink(driverLink)
// Based on ddcutil's sysfs_find_adapter() (src/sysfs/sysfs_simple.c): the nearest
// ancestor under /sys/devices carrying a class attribute.
func findI2CAdapter(path string) string {
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
return ""
}
return filepath.Base(target)
}
func findI2CAdapter(devicePath string) (string, error) {
currentPath := devicePath
for range 10 {
if _, err := os.Stat(filepath.Join(currentPath, "name")); err == nil {
return currentPath, nil
for strings.HasPrefix(resolved, "/sys/devices") {
if info, err := os.Stat(filepath.Join(resolved, "class")); err == nil && info.Mode().IsRegular() {
return resolved
}
deviceLink := filepath.Join(currentPath, "device")
target, err := os.Readlink(deviceLink)
if err != nil {
break
}
if !filepath.IsAbs(target) {
target = filepath.Join(filepath.Dir(currentPath), target)
}
currentPath = filepath.Clean(target)
resolved = filepath.Dir(resolved)
}
return ""
}
return "", fmt.Errorf("could not find adapter for %s", devicePath)
// ddcutil's known_reliable_drivers (src/sysfs/sysfs_base.c): drivers that keep the DRM connector
// edid, status and dpms attributes current.
var sysfsReliableDrivers = []string{"i915", "xe", "amdgpu", "radeon", "nouveau"}
var (
drmConnectorPattern = regexp.MustCompile(`^card[0-9]+-`)
i2cBusPattern = regexp.MustCompile(`^i2c-([0-9]+)$`)
)
type ddcBusVerdict int
const (
ddcBusSkip ddcBusVerdict = iota
ddcBusHasEDID
ddcBusNeedsEDIDRead
)
// Based on ddcutil's i2c_edid_exists() and its laptop panel exclusion (src/i2c/i2c_bus_core.c).
func ddcBusVerdictFor(busno int, connectors map[int]string) ddcBusVerdict {
name := getI2CDeviceSysfsName(busno)
displayLink := name == "DisplayLink I2C Adapter"
reliable := slices.Contains(sysfsReliableDrivers, getI2CSysfsDriver(busno))
connector, mapped := connectors[busno]
if !mapped {
if reliable && !displayLink && name != "DPMST" && len(connectors) > 0 {
return ddcBusSkip
}
return ddcBusNeedsEDIDRead
}
if strings.Contains(connector, "-eDP-") || strings.Contains(connector, "-LVDS-") {
return ddcBusSkip
}
if reliable || displayLink {
if len(drmConnectorAttr(connector, "edid")) >= 128 {
return ddcBusHasEDID
}
return ddcBusSkip
}
if strings.TrimSpace(drmConnectorAttr(connector, "status")) == "disconnected" {
return ddcBusSkip
}
return ddcBusNeedsEDIDRead
}
// Based on ddcutil's dpms_check_drm_asleep_by_businfo() (src/sysfs/sysfs_dpms.c).
func ddcDisplayAsleep(busno int, connectors map[int]string) bool {
connector, mapped := connectors[busno]
if !mapped || !slices.Contains(sysfsReliableDrivers, getI2CSysfsDriver(busno)) {
return false
}
return strings.TrimSpace(drmConnectorAttr(connector, "dpms")) != "On"
}
// Based on ddcutil's get_connector_bus_numbers() (src/sysfs/sysfs_simple.c): a DP connector's own
// i2c-N directory, any other connector's ddc/i2c-dev/i2c-N.
func drmConnectorsByBus() map[int]string {
connectors := map[int]string{}
entries, err := os.ReadDir("/sys/class/drm")
if err != nil {
return connectors
}
for _, entry := range entries {
name := entry.Name()
if !drmConnectorPattern.MatchString(name) {
continue
}
dir := filepath.Join("/sys/class/drm", name)
if !strings.Contains(name, "-DP-") {
dir = filepath.Join(dir, "ddc", "i2c-dev")
}
if busno, ok := i2cSubdirBus(dir); ok {
connectors[busno] = name
}
}
return connectors
}
func i2cSubdirBus(dir string) (int, bool) {
entries, err := os.ReadDir(dir)
if err != nil {
return 0, false
}
for _, entry := range entries {
match := i2cBusPattern.FindStringSubmatch(entry.Name())
if match == nil {
continue
}
if busno, err := strconv.Atoi(match[1]); err == nil {
return busno, true
}
}
return 0, false
}
func drmConnectorAttr(connector, attr string) string {
data, err := os.ReadFile(filepath.Join("/sys/class/drm", connector, attr))
if err != nil {
return ""
}
return string(data)
}
var edidHeader = []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}
// Based on ddcutil's is_valid_raw_edid() (src/util/edid.c).
func isValidEDID(edid []byte) bool {
if len(edid) < 128 || !bytes.Equal(edid[:8], edidHeader) {
return false
}
var sum byte
for _, b := range edid[:128] {
sum += b
}
return sum == 0
}
// Based on ddcutil's is_laptop_parsed_edid() (src/util/edid.c).
func isLaptopEDID(edid []byte) bool {
return edidDescriptorText(edid, 0xfc) == "" && edidDescriptorText(edid, 0xff) == ""
}
// Based on ddcutil's get_edid_descriptor_strings() (src/util/edid.c).
func edidDescriptorText(edid []byte, tag byte) string {
text := ""
for i := range 4 {
descriptor := edid[54+i*18 : 72+i*18]
if descriptor[0] != 0 || descriptor[1] != 0 || descriptor[2] != 0 || descriptor[4] != 0 || descriptor[3] != tag {
continue
}
raw := descriptor[5:18]
if end := bytes.IndexByte(raw, 0x0a); end >= 0 {
raw = raw[:end]
}
text = strings.TrimRight(string(raw), " \t\n\v\f\r")
}
return text
}
+5 -5
View File
@@ -1,8 +1,8 @@
package brightness
import (
"errors"
"fmt"
"os"
"sort"
"strings"
"time"
@@ -42,12 +42,12 @@ func (m *Manager) initLogind() {
}
func (m *Manager) initDDC() {
if os.Getenv("DMS_NO_DDC") != "" {
ddc, err := newDDCBackend(m.updateState)
switch {
case errors.Is(err, errDDCDisabled):
log.Info("DDC backend disabled via DMS_NO_DDC")
return
}
ddc, err := NewDDCBackend()
if err != nil {
case err != nil:
log.Debugf("Failed to initialize DDC backend: %v", err)
return
}
+6
View File
@@ -96,6 +96,11 @@ type DDCBackend struct {
debounceTimers map[string]*time.Timer
debouncePending map[string]ddcPendingSet
debounceWg sync.WaitGroup
ioMutex sync.Mutex
onReread func()
stop chan struct{}
closeOnce sync.Once
}
type ddcPendingSet struct {
@@ -108,6 +113,7 @@ type ddcDevice struct {
addr int
id string
name string
adapter string
max int
lastBrightness int
}
+42 -17
View File
@@ -154,23 +154,7 @@ func (b *IWDBackend) applyManagedObjects(objects map[dbus.ObjectPath]map[string]
}
if _, hasDevice := interfaces[iwdDeviceInterface]; hasDevice {
b.devicePath = path
if devProps, ok := interfaces[iwdDeviceInterface]; ok {
if nameVar, ok := devProps["Name"]; ok {
if name, ok := nameVar.Value().(string); ok {
b.stateMutex.Lock()
b.state.WiFiDevice = name
b.stateMutex.Unlock()
}
}
if poweredVar, ok := devProps["Powered"]; ok {
if powered, ok := poweredVar.Value().(bool); ok {
b.stateMutex.Lock()
b.state.WiFiEnabled = powered
b.stateMutex.Unlock()
}
}
}
b.applyDeviceProps(interfaces[iwdDeviceInterface])
}
if _, hasAdapter := interfaces[iwdAdapterInterface]; hasAdapter {
b.adapterPath = path
@@ -193,6 +177,47 @@ func (b *IWDBackend) applyManagedObjects(objects map[dbus.ObjectPath]map[string]
return nil
}
func (b *IWDBackend) applyDeviceProps(props map[string]dbus.Variant) {
if nameVar, ok := props["Name"]; ok {
if name, ok := nameVar.Value().(string); ok {
b.stateMutex.Lock()
b.state.WiFiDevice = name
b.stateMutex.Unlock()
}
}
if poweredVar, ok := props["Powered"]; ok {
if powered, ok := poweredVar.Value().(bool); ok {
b.stateMutex.Lock()
b.state.WiFiEnabled = powered
b.stateMutex.Unlock()
}
}
}
// setDevice records a device that appeared at a new path, which is what
// happens when iwd restarts and recreates wlan0. It returns the path it
// replaced so the caller can move the signal match over.
func (b *IWDBackend) setDevice(path dbus.ObjectPath, props map[string]dbus.Variant) (prev dbus.ObjectPath, changed bool) {
if path == "" || path == b.devicePath {
return "", false
}
prev = b.devicePath
b.devicePath = path
b.applyDeviceProps(props)
return prev, true
}
func (b *IWDBackend) clearDevice(path dbus.ObjectPath) bool {
if path == "" || path != b.devicePath {
return false
}
b.devicePath = ""
b.stateMutex.Lock()
b.state.WiFiEnabled = false
b.stateMutex.Unlock()
return true
}
func (b *IWDBackend) GetCurrentState() (*BackendState, error) {
b.stateMutex.RLock()
defer b.stateMutex.RUnlock()
@@ -26,12 +26,7 @@ func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
b.conn.Signal(sigChan)
if b.devicePath != "" {
err := b.conn.AddMatchSignal(
dbus.WithMatchObjectPath(b.devicePath),
dbus.WithMatchInterface(dbusPropertiesInterface),
dbus.WithMatchMember("PropertiesChanged"),
)
if err != nil {
if err := b.addDeviceSignalMatch(b.devicePath); err != nil {
return fmt.Errorf("failed to add device signal match: %w", err)
}
}
@@ -86,6 +81,48 @@ func (b *IWDBackend) addStationSignalMatch(path dbus.ObjectPath) error {
)
}
func (b *IWDBackend) addDeviceSignalMatch(path dbus.ObjectPath) error {
return b.conn.AddMatchSignal(
dbus.WithMatchObjectPath(path),
dbus.WithMatchInterface(dbusPropertiesInterface),
dbus.WithMatchMember("PropertiesChanged"),
)
}
func (b *IWDBackend) removeDeviceSignalMatch(path dbus.ObjectPath) {
err := b.conn.RemoveMatchSignal(
dbus.WithMatchObjectPath(path),
dbus.WithMatchInterface(dbusPropertiesInterface),
dbus.WithMatchMember("PropertiesChanged"),
)
if err != nil {
log.Debugf("Failed to remove iwd device signal match for %s: %v", path, err)
}
}
func (b *IWDBackend) handleDeviceAdded(path dbus.ObjectPath, props map[string]dbus.Variant) bool {
prev, changed := b.setDevice(path, props)
if !changed {
return false
}
if prev != "" {
b.removeDeviceSignalMatch(prev)
}
if err := b.addDeviceSignalMatch(path); err != nil {
log.Warnf("Failed to add iwd device signal match for %s: %v", path, err)
}
return true
}
func (b *IWDBackend) handleDeviceRemoved(path dbus.ObjectPath) bool {
if !b.clearDevice(path) {
return false
}
b.removeDeviceSignalMatch(path)
return true
}
func (b *IWDBackend) handleStationAdded(path dbus.ObjectPath) bool {
if path == "" || path == b.stationPath {
return false
@@ -139,6 +176,12 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
if len(sig.Body) >= 2 {
path, _ := sig.Body[0].(dbus.ObjectPath)
if interfaces, ok := sig.Body[1].(map[string]map[string]dbus.Variant); ok {
// Device before Station: handleStationAdded reads Powered from devicePath.
if props, ok := interfaces[iwdDeviceInterface]; ok {
if b.handleDeviceAdded(path, props) && b.onStateChange != nil {
b.onStateChange()
}
}
if _, ok := interfaces[iwdStationInterface]; ok {
if b.handleStationAdded(path) && b.onStateChange != nil {
b.onStateChange()
@@ -159,17 +202,19 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
path, _ := sig.Body[0].(dbus.ObjectPath)
if interfaces, ok := sig.Body[1].([]string); ok {
for _, iface := range interfaces {
if iface == iwdStationInterface {
switch iface {
case iwdDeviceInterface:
if b.handleDeviceRemoved(path) && b.onStateChange != nil {
b.onStateChange()
}
case iwdStationInterface:
if b.handleStationRemoved(path) && b.onStateChange != nil {
b.onStateChange()
}
break
}
if iface == iwdKnownNetworkInterface {
case iwdKnownNetworkInterface:
if b.refreshWiFiNetworkState() && b.onStateChange != nil {
b.onStateChange()
}
break
}
}
}
@@ -460,3 +460,71 @@ func TestConnectAttempt_DoubleFinalization(t *testing.T) {
assert.Equal(t, "bad-credentials", backend.state.LastError)
backend.stateMutex.RUnlock()
}
func TestIWDBackend_SetDevice_FollowsRecreatedDevice(t *testing.T) {
backend, _ := NewIWDBackend()
err := backend.applyManagedObjects(map[dbus.ObjectPath]map[string]map[string]dbus.Variant{
"/net/connman/iwd/0/3": {
iwdDeviceInterface: {"Name": dbus.MakeVariant("wlan0"), "Powered": dbus.MakeVariant(true)},
iwdStationInterface: {},
},
})
assert.NoError(t, err)
// iwd restarts: the old objects go away and wlan0 comes back with a new ifindex.
assert.True(t, backend.handleStationRemoved("/net/connman/iwd/0/3"))
assert.True(t, backend.clearDevice("/net/connman/iwd/0/3"))
prev, changed := backend.setDevice("/net/connman/iwd/0/7", map[string]dbus.Variant{
"Name": dbus.MakeVariant("wlan0"),
"Powered": dbus.MakeVariant(true),
})
assert.True(t, changed)
assert.Empty(t, prev)
assert.Equal(t, dbus.ObjectPath("/net/connman/iwd/0/7"), backend.devicePath)
enabled, _ := backend.GetWiFiEnabled()
assert.True(t, enabled, "Wi-Fi must be enabled again once the recreated device reports Powered")
}
func TestIWDBackend_SetDevice_ReplacesPathWithoutRemovalSignal(t *testing.T) {
backend, _ := NewIWDBackend()
backend.devicePath = "/net/connman/iwd/0/3"
prev, changed := backend.setDevice("/net/connman/iwd/0/7", map[string]dbus.Variant{
"Powered": dbus.MakeVariant(true),
})
assert.True(t, changed)
assert.Equal(t, dbus.ObjectPath("/net/connman/iwd/0/3"), prev)
assert.Equal(t, dbus.ObjectPath("/net/connman/iwd/0/7"), backend.devicePath)
}
func TestIWDBackend_SetDevice_SamePathIsNoop(t *testing.T) {
backend, _ := NewIWDBackend()
backend.devicePath = "/net/connman/iwd/0/3"
_, changed := backend.setDevice("/net/connman/iwd/0/3", map[string]dbus.Variant{
"Powered": dbus.MakeVariant(false),
})
assert.False(t, changed)
_, changed = backend.setDevice("", nil)
assert.False(t, changed)
}
func TestIWDBackend_ClearDevice(t *testing.T) {
backend, _ := NewIWDBackend()
backend.devicePath = "/net/connman/iwd/0/3"
assert.False(t, backend.clearDevice("/net/connman/iwd/0/9"), "unrelated path must be ignored")
assert.Equal(t, dbus.ObjectPath("/net/connman/iwd/0/3"), backend.devicePath)
assert.True(t, backend.clearDevice("/net/connman/iwd/0/3"))
assert.Empty(t, backend.devicePath)
enabled, _ := backend.GetWiFiEnabled()
assert.False(t, enabled)
assert.ErrorContains(t, backend.SetWiFiEnabled(true), "no WiFi device available")
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
"sync"
@@ -30,12 +31,54 @@ func (l *linkInfo) isWired() bool {
if looksVirtual(l.name) {
return false
}
if l.linkType == "bridge" {
return hasPhysicalMember(l.name)
}
if l.linkType != "" {
return l.linkType == "ether"
return isWiredLinkType(l.linkType)
}
return !strings.HasPrefix(l.name, "wlan") && !strings.HasPrefix(l.name, "wlp")
}
// isWiredLinkType reports whether a networkd link Type can carry the wired
// uplink. Link aggregates and bridges hold the IP configuration while the
// enslaved member NIC stays in the "enslaved" state, same as #1581 for
// NetworkManager. Bridges are further checked in isWired.
func isWiredLinkType(t string) bool {
switch t {
case "ether", "bridge", "bond", "team", "vlan":
return true
}
return false
}
// bridgeMembers lists the interfaces enslaved to a bridge. It is a variable so
// tests can stub the sysfs lookup.
var bridgeMembers = func(bridge string) []string {
entries, err := os.ReadDir("/sys/class/net/" + bridge + "/brif")
if err != nil {
return nil
}
names := make([]string, 0, len(entries))
for _, e := range entries {
names = append(names, e.Name())
}
return names
}
// hasPhysicalMember reports whether a bridge has at least one member that is
// not virtual (veth, tap, ...). This tells a host bridge carrying the uplink
// apart from container/VM bridges (lxc, waydroid, ...) that only hold virtual
// members.
func hasPhysicalMember(bridge string) bool {
for _, m := range bridgeMembers(bridge) {
if !looksVirtual(m) {
return true
}
}
return false
}
func (l *linkInfo) isWireless() bool {
if looksVirtual(l.name) {
return false
@@ -166,6 +166,15 @@ func TestLinkInfo_Classify(t *testing.T) {
{"veth ether excluded", "veth1234", "ether", false, false},
{"podman bridge ether excluded", "podman3", "ether", false, false},
{"docker bridge ether excluded", "docker0", "ether", false, false},
// The IP lives on the aggregate while the member NIC is "enslaved" (#3463).
{"bridge type", "br0", "bridge", true, false},
{"bond type", "bond0", "bond", true, false},
{"team type", "team0", "team", true, false},
{"vlan type", "vlan10", "vlan", true, false},
{"docker user bridge excluded", "br-1a2b3c", "bridge", false, false},
{"libvirt bridge excluded", "virbr0", "bridge", false, false},
{"bridge with only virtual members excluded", "lxcbr0", "bridge", false, false},
{"bridge without members excluded", "waydroid0", "bridge", false, false},
// Fallback path: linkType unavailable, name-prefix heuristic applies.
{"fallback enp wired", "enp141s0", "", true, false},
{"fallback wlan wireless", "wlan0", "", false, true},
@@ -174,6 +183,15 @@ func TestLinkInfo_Classify(t *testing.T) {
{"fallback docker skipped", "docker0", "", false, false},
{"fallback tun skipped", "tun0", "", false, false},
}
members := map[string][]string{
"br0": {"enp42s0", "vnet0"},
"lxcbr0": {"vethAbCd12"},
"waydroid0": nil,
}
orig := bridgeMembers
bridgeMembers = func(b string) []string { return members[b] }
t.Cleanup(func() { bridgeMembers = orig })
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
l := &linkInfo{name: tc.ifname, linkType: tc.linkType}
@@ -254,7 +272,7 @@ func TestLooksVirtual(t *testing.T) {
for _, n := range virtual {
assert.True(t, looksVirtual(n), "%s should look virtual", n)
}
real := []string{"enp141s0", "eno1", "wlan0", "wlp3s0", "wifi", "dock", "nebula.homelab", "wg0"}
real := []string{"enp141s0", "eno1", "wlan0", "wlp3s0", "wifi", "dock", "nebula.homelab", "wg0", "br0", "bond0", "team0", "vlan10"}
for _, n := range real {
assert.False(t, looksVirtual(n), "%s should not look virtual", n)
}
+14 -9
View File
@@ -16,7 +16,8 @@ const (
metricPreferred = int64(100)
metricNonPreferred = int64(300)
metricDefault = int64(100)
// -1 lets NetworkManager pick the metric by device type (nm-settings ipv4.route-metric)
metricDefault = int64(-1)
)
func (m *Manager) SetConnectionPreference(pref ConnectionPreference) error {
@@ -46,7 +47,7 @@ func (m *Manager) SetConnectionPreference(pref ConnectionPreference) error {
case PreferenceCellular:
return m.prioritizeCellular()
case PreferenceAuto:
return m.balancePriorities()
return m.restoreDefaultPriorities()
}
return nil
@@ -112,7 +113,7 @@ func (m *Manager) prioritizeCellular() error {
return nil
}
func (m *Manager) balancePriorities() error {
func (m *Manager) restoreDefaultPriorities() error {
if err := m.setConnectionPriority("802-3-ethernet", priorityDefault, metricDefault); err != nil {
log.Warnf("Failed to reset Ethernet priority: %v", err)
}
@@ -242,16 +243,20 @@ func (m *Manager) setConnectionPriority(connType string, autoconnectPriority int
}
func priorityMatches(variant dbus.Variant, expected int64) bool {
value, ok := variantInt64(variant)
return ok && value == expected
return settingInt64(variant, int64(priorityDefault)) == expected
}
func routeMetricMatches(section map[string]dbus.Variant, expected int64) bool {
if section == nil {
return false
return settingInt64(section["route-metric"], metricDefault) == expected
}
// GetSettings omits properties at their default, so a missing key is the default value
func settingInt64(variant dbus.Variant, def int64) int64 {
value, ok := variantInt64(variant)
if !ok {
return def
}
value, ok := variantInt64(section["route-metric"])
return ok && value == expected
return value
}
func variantInt64(variant dbus.Variant) (int64, bool) {
@@ -3,6 +3,7 @@ package network
import (
"testing"
"github.com/godbus/dbus/v5"
"github.com/stretchr/testify/assert"
)
@@ -45,7 +46,10 @@ func TestManager_GetConnectionPreference(t *testing.T) {
}
}
// Note: Full testing of priority operations would require mocking NetworkManager
// D-Bus interfaces. The tests above cover the basic logic and error handling.
// Integration tests would be needed for complete coverage of network connection
// priority updates and reactivation.
func TestProfileAtDefaultsMatchesAutoTargets(t *testing.T) {
assert.True(t, priorityMatches(dbus.Variant{}, int64(priorityDefault)))
assert.True(t, routeMetricMatches(nil, metricDefault))
assert.True(t, routeMetricMatches(map[string]dbus.Variant{"method": dbus.MakeVariant("auto")}, metricDefault))
assert.False(t, routeMetricMatches(map[string]dbus.Variant{"route-metric": dbus.MakeVariant(int64(100))}, metricDefault))
assert.False(t, priorityMatches(dbus.MakeVariant(int32(100)), int64(priorityDefault)))
}
+2 -1
View File
@@ -277,7 +277,8 @@ const (
// ipp defaults
const (
CharsetLanguage = "en-US"
// cups 2.4.20+ validates this case-sensitively (lowercase only), and libcups itself sends lowercase
CharsetLanguage = "en-us"
Charset = "utf-8"
ProtocolVersionMajor = int8(2)
ProtocolVersionMinor = int8(0)
Generated
+4 -3
View File
@@ -3,15 +3,16 @@
"dank-qml-common": {
"flake": false,
"locked": {
"lastModified": 1788376015,
"narHash": "sha256-/tT8rTznLADndTwLBzdgzzgfJH1ZhYQibYo5v/F8U+U=",
"lastModified": 1791392812,
"narHash": "sha256-id6Y5XNO5K3KfXSOsEYVUwiq1ygYGNX5LeKSgHM3GR8=",
"owner": "AvengeMedia",
"repo": "dank-qml-common",
"rev": "26396ce432d6c71c3f5367438f96f4a8d667e160",
"rev": "1ae722232da7110d26cd2c355b6615e078e94197",
"type": "github"
},
"original": {
"owner": "AvengeMedia",
"ref": "stable-1.6",
"repo": "dank-qml-common",
"type": "github"
}
+1 -1
View File
@@ -8,7 +8,7 @@
flake = false;
};
dank-qml-common = {
url = "github:AvengeMedia/dank-qml-common";
url = "github:AvengeMedia/dank-qml-common/stable-1.6";
flake = false;
};
};
+1 -1
View File
@@ -14,7 +14,7 @@ Singleton {
id: root
readonly property var log: Log.scoped("SessionData")
readonly property int sessionConfigVersion: 4
readonly property int sessionConfigVersion: 5
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
+37 -15
View File
@@ -1988,8 +1988,11 @@ Singleton {
function saveSettings() {
if (_loading || _parseError || !_hasLoaded)
return;
const json = JSON.stringify(Store.toJson(root), null, 2);
if (json === settingsFile.text())
return;
_selfWrite = true;
settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2));
settingsFile.setText(json);
if (_isReadOnly)
_checkSettingsWritable();
}
@@ -3225,10 +3228,11 @@ Singleton {
if (!app && !desktop)
return -1;
return rules.findIndex(rule => {
if (!predicate(rule))
if (!predicate(rule) || (rule.matchType || "contains").toString().toLowerCase() !== "exact")
return false;
const pattern = (rule.pattern || "").toString().toLowerCase();
return pattern !== "" && (pattern === app || pattern === desktop);
const value = !rule.field || rule.field === "appName" ? app : rule.field === "desktopEntry" ? desktop : "";
return pattern !== "" && pattern === value;
});
}
@@ -3246,20 +3250,31 @@ Singleton {
saveSettings();
}
function _removeAppRule(appName, desktopEntry, predicate) {
function _hasNoAction(rule) {
return (rule.action || "default").toString().toLowerCase() === "default";
}
// Edits the first enabled matching rule and drops it once it no longer does anything.
function _updateAppRule(appName, desktopEntry, predicate, changes) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
const index = _appRuleIndex(rules, appName, desktopEntry, predicate);
const index = _appRuleIndex(rules, appName, desktopEntry, rule => rule.enabled !== false && predicate(rule));
if (index === -1)
return;
rules.splice(index, 1);
return false;
const rule = Object.assign(rules[index], changes);
if (_hasNoAction(rule) && (rule.urgency || "default").toString().toLowerCase() === "default" && !_isDndBypassRule(rule))
rules.splice(index, 1);
notificationRules = rules;
saveSettings();
return true;
}
function addMuteRuleForApp(appName, desktopEntry) {
_addAppRule(appName, desktopEntry, {
action: "mute"
});
if (!_updateAppRule(appName, desktopEntry, _hasNoAction, {
action: "mute"
}))
_addAppRule(appName, desktopEntry, {
action: "mute"
});
}
function isAppMuted(appName, desktopEntry) {
@@ -3267,7 +3282,9 @@ Singleton {
}
function removeMuteRuleForApp(appName, desktopEntry) {
_removeAppRule(appName, desktopEntry, _isMuteRule);
_updateAppRule(appName, desktopEntry, _isMuteRule, {
action: "default"
});
}
function isAppDndBypassed(appName, desktopEntry) {
@@ -3276,14 +3293,19 @@ Singleton {
function setAppDndBypass(appName, desktopEntry, enabled) {
if (!enabled) {
_removeAppRule(appName, desktopEntry, _isDndBypassRule);
_updateAppRule(appName, desktopEntry, _isDndBypassRule, {
bypassDnd: false
});
return;
}
if (isAppDndBypassed(appName, desktopEntry))
return;
_addAppRule(appName, desktopEntry, {
bypassDnd: true
});
if (!_updateAppRule(appName, desktopEntry, () => true, {
bypassDnd: true
}))
_addAppRule(appName, desktopEntry, {
bypassDnd: true
});
}
function updateNotificationRule(index, ruleData) {
+29
View File
@@ -0,0 +1,29 @@
.pragma library
// Qt Wayland sends scroll phases only for the finger axis source; pixelDelta also comes with wheels from Qt 6.12 (qtbase d19bd66b4)
const Kind = Object.freeze({
none: "none",
touchpad: "touchpad",
wheel: "wheel",
highResWheel: "highResWheel"
});
function isTouchpad(wheel) {
if (wheel.phase === Qt.NoScrollPhase)
return false;
return wheel.pixelDelta.x !== 0 || wheel.pixelDelta.y !== 0;
}
function verticalKind(wheel) {
if (isTouchpad(wheel))
return Kind.touchpad;
const angle = wheel.angleDelta.y;
if (angle === 0)
return Kind.none;
return angle % 120 === 0 ? Kind.wheel : Kind.highResWheel;
}
function anyAxisDelta(wheel) {
const point = isTouchpad(wheel) ? wheel.pixelDelta : wheel.angleDelta;
return point.x || point.y;
}
@@ -83,5 +83,29 @@ function migrateToVersion(obj, targetVersion, settingsData) {
session.configVersion = 4;
}
if (currentVersion < 5) {
console.info("SessionData: Migrating session to version 5");
console.info("SessionData: Tray keys now use a stable id (or id::instance) instead of id::tooltipTitle");
function stripTrayKeys(list) {
if (!list || list.constructor !== Array) return list;
var out = [];
for (var i = 0; i < list.length; i++) {
var raw = list[i];
if (typeof raw !== "string") continue;
var key = raw.includes("::") ? raw.split("::")[0] : raw;
var hasDup = false;
for (var j = 0; j < out.length; j++) {
if (out[j] === key) { hasDup = true; break; }
}
if (key && !hasDup) out.push(key);
}
return out;
}
if (session.hiddenTrayIds !== undefined) session.hiddenTrayIds = stripTrayKeys(session.hiddenTrayIds);
if (session.trayItemOrder !== undefined) session.trayItemOrder = stripTrayKeys(session.trayItemOrder);
session.configVersion = 5;
}
return session;
}
@@ -46,6 +46,8 @@ DankFloatingWindow {
}
function submitAndClose() {
if (!root.visible)
return;
if (aqueousWorkspace || CompositorService.isAqueous) {
if (!aqueousWorkspace || renaming)
return;
+4 -4
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import "../../Common/WheelInput.js" as WheelInput
MouseArea {
id: root
@@ -93,16 +94,15 @@ MouseArea {
const deltaY = wheel.angleDelta.y;
const deltaX = wheel.angleDelta.x;
const isTouchpadY = wheel.pixelDelta && wheel.pixelDelta.y !== 0;
const isTouchpadX = wheel.pixelDelta && wheel.pixelDelta.x !== 0;
const isTouchpad = WheelInput.isTouchpad(wheel);
if (CompositorService.isNiri && xBehavior !== "none" && Math.abs(deltaX) > Math.abs(deltaY)) {
accumulateX(isTouchpadX, deltaX, xBehavior);
accumulateX(isTouchpad, deltaX, xBehavior);
return;
}
if (yBehavior === "none")
return;
accumulateY(isTouchpadY, deltaY, yBehavior);
accumulateY(isTouchpad, deltaY, yBehavior);
}
onWheel: wheel => processWheel(wheel)
@@ -40,13 +40,33 @@ BasePill {
return !hiddenTrayIds.includes(itemId.toLowerCase());
});
}
property var _trayKeyByItem: new Map()
function getTrayItemKey(item) {
const id = item?.id || "";
const tooltipTitle = item?.tooltipTitle || "";
if (!tooltipTitle || tooltipTitle === id) {
return id;
if (!id)
return "";
if (root._trayKeyByItem.has(item))
return root._trayKeyByItem.get(item);
const taken = new Set();
for (const live of root.allTrayItems) {
if (live !== item && (live?.id || "") === id && root._trayKeyByItem.has(live))
taken.add(root._trayKeyByItem.get(live));
}
return `${id}::${tooltipTitle}`;
let key = id;
for (let n = 1; taken.has(key); n++)
key = `${id}::${n}`;
root._trayKeyByItem.set(item, key);
return key;
}
function resolveOrderIndex(key, orderMap) {
if (orderMap.has(key))
return orderMap.get(key);
return -1;
}
function isTrayIdHidden(key) {
return SessionData.isHiddenTrayId(key);
}
function trayIconSourceFor(trayItem) {
@@ -150,6 +170,13 @@ BasePill {
}
}
Connections {
target: SystemTray
function onItemUnregistered(item) {
root._trayKeyByItem.delete(item);
}
}
function sortByPreferredOrder(items, trigger) {
void trigger;
const savedOrder = SessionData.trayItemOrder || [];
@@ -159,15 +186,17 @@ BasePill {
return [...items].sort((a, b) => {
const keyA = getTrayItemKey(a);
const keyB = getTrayItemKey(b);
const orderA = orderMap.has(keyA) ? orderMap.get(keyA) : 10000 + items.indexOf(a);
const orderB = orderMap.has(keyB) ? orderMap.get(keyB) : 10000 + items.indexOf(b);
return orderA - orderB;
const orderA = resolveOrderIndex(keyA, orderMap);
const orderB = resolveOrderIndex(keyB, orderMap);
const posA = orderA >= 0 ? orderA : 10000 + items.indexOf(a);
const posB = orderB >= 0 ? orderB : 10000 + items.indexOf(b);
return posA - posB;
});
}
readonly property var allSortedTrayItems: sortByPreferredOrder(allTrayItems, _trayOrderTrigger)
readonly property var allSortedTrayItemKeys: allSortedTrayItems.map(item => getTrayItemKey(item))
readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !root.isTrayIdHidden(root.getTrayItemKey(item)))
readonly property int automaticVisibleItemLimit: {
if (!root.useAutomaticOverflow)
return root.visibleSortedTrayItems.length;
@@ -190,7 +219,7 @@ BasePill {
item: item
}))
readonly property var autoOverflowBarItems: visibleSortedTrayItems.slice(automaticVisibleItemLimit)
readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => root.isTrayIdHidden(root.getTrayItemKey(item)))
readonly property var hiddenBarItemKeys: manualHiddenBarItems.concat(autoOverflowBarItems).map(item => root.getTrayItemKey(item))
readonly property var hiddenBarItems: allSortedTrayItems.filter(item => hiddenBarItemKeys.indexOf(root.getTrayItemKey(item)) !== -1)
readonly property string trayIconTintMode: {
@@ -298,7 +327,7 @@ BasePill {
}
function isManualHiddenTrayItem(item) {
return SessionData.isHiddenTrayId(getTrayItemKey(item));
return root.isTrayIdHidden(root.getTrayItemKey(item));
}
function isAutoOverflowTrayItem(item) {
@@ -8,6 +8,7 @@ import Quickshell.WindowManager
import qs.Common
import qs.Services
import qs.Widgets
import "../../../Common/WheelInput.js" as WheelInput
Item {
id: root
@@ -897,7 +898,7 @@ Item {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius + border.width
color: "transparent"
border.width: {
if (barConfig?.widgetOutlineEnabled ?? false) {
@@ -992,7 +993,7 @@ Item {
return;
const delta = wheel.angleDelta.y;
const isTouchpad = wheel.pixelDelta && wheel.pixelDelta.y !== 0;
const isTouchpad = WheelInput.isTouchpad(wheel);
const reverse = SettingsData.reverseScrolling ? -1 : 1;
if (isTouchpad) {
@@ -194,7 +194,6 @@ Item {
id: mediaExpandedLoader
activity: "media"
height: Math.max(target.height, root.height)
active: root.mediaSurfaceActive && (root.expanded || root.expandedFade > 0)
asynchronous: false
sourceComponent: root.mediaExpandedComponent
+2 -1
View File
@@ -272,7 +272,8 @@ Scope {
property bool isActiveScreen: {
if (Quickshell.screens.length <= 1)
return true;
return SettingsData.getFilteredScreens("lockScreen").includes(screen);
const screens = SettingsData.getFilteredScreens("lockScreen");
return screens.length === 0 || screens.includes(screen);
}
color: isActiveScreen ? "transparent" : SettingsData.lockScreenInactiveColor
+11 -2
View File
@@ -82,6 +82,8 @@ Item {
return I18n.tr("Too many attempts - locked out");
if (root.pamState === "fail")
return I18n.tr("Incorrect password - try again");
if (pam.fprint.status === "disabled")
return "";
if (pam.fprintState === "error")
return I18n.tr("Fingerprint error");
if (pam.fprintState === "max")
@@ -861,10 +863,15 @@ Item {
name: {
if (pam.u2fPending)
return "passkey";
if (pam.fprint.tries >= SettingsData.maxFprintTries)
switch (pam.fprint.status) {
case "max":
case "stopped":
return "fingerprint_off";
if (pam.fprint.active || pam.fprint.retrying)
case "active":
return "fingerprint";
case "retrying":
return "hourglass_empty";
}
if (pam.u2f.active)
return "passkey";
return "lock";
@@ -1005,6 +1012,8 @@ Item {
return;
}
root.pam.retryFprintOnActivity();
if (root.unlocking) {
event.accepted = true;
return;
+13
View File
@@ -16,7 +16,20 @@ FocusScope {
signal passwordChanged(string newPassword)
signal unlockRequested
HoverHandler {
enabled: root.pam.awaitingActivityRetry
onPointChanged: root.pam.retryFprintOnActivity()
}
PointHandler {
onActiveChanged: {
if (active)
root.pam.retryFprintOnActivity();
}
}
Keys.onPressed: event => {
root.pam.retryFprintOnActivity();
if (videoScreensaver.active && videoScreensaver.inputEnabled) {
videoScreensaver.dismiss();
event.accepted = true;
+154 -52
View File
@@ -32,9 +32,8 @@ Scope {
function resetAuthFlows(): void {
passwd.abort();
fprint.abort();
fprint.stop();
u2f.abort();
errorRetry.running = false;
u2fErrorRetry.running = false;
u2fPendingTimeout.running = false;
passwdActiveTimeout.running = false;
@@ -58,9 +57,8 @@ Scope {
if (!root.unlockInProgress) {
root.unlockInProgress = true;
passwd.abort();
fprint.abort();
fprint.stop();
u2f.abort();
errorRetry.running = false;
u2fErrorRetry.running = false;
u2fPendingTimeout.running = false;
root.u2fPending = false;
@@ -217,7 +215,7 @@ Scope {
if (res === PamResult.Success) {
if (!root.unlockInProgress) {
fprint.abort();
fprint.stop();
root.proceedAfterPrimaryAuth();
}
return;
@@ -260,69 +258,147 @@ Scope {
PamContext {
id: fprint
property bool available: SettingsData.lockFingerprintReady
property int tries
property int errorTries
property bool retrying: false
readonly property bool available: SettingsData.lockFingerprintReady
property int tries: 0
property int errorTries: 0
property double attemptStartedAt: 0
property bool completedDuringStart: false
readonly property int maxErrorTries: 200
readonly property int daemonIdleExitMs: 30000
// sessionTimeoutMs mirrors `timeout=` in assets/pam/fprint
readonly property int sessionTimeoutMs: 90000
readonly property int verifyStartSlackMs: 10000
readonly property bool retrying: errorRetry.running
readonly property int retryInterval: errorRetry.interval
readonly property bool allowed: available && SettingsData.enableFprint && root.lockSecured && !root.fprintSuppressedByPrimaryPam && !root.unlockInProgress && !root.u2fPending && !SessionService.preparingForSleep && !IdleService.monitorsOff && tries < SettingsData.maxFprintTries
readonly property string status: {
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam)
return "disabled";
if (tries >= SettingsData.maxFprintTries)
return "max";
if (!allowed)
return "paused";
if (active)
return "active";
if (retrying)
return "retrying";
return errorTries >= maxErrorTries ? "stopped" : "idle";
}
function stop(): void {
errorRetry.stop();
attemptStartedAt = 0;
abort();
}
// pam_fprintd reports an expired timeout as PAM_AUTHINFO_UNAVAIL, the same
// result a device fault gives, and its "Verification timed out" message is
// translated, so age is the signal we can rely on. Its timeout runs from the
// verify rather than from the claim, which is why the age is measured from
// the message announcing the verify: a slow claim then cannot make a fault
// look old enough, and a session that never reached a verify never qualifies.
// That message lands after pam_fprintd has already armed its deadline, so the
// age read here runs short of the real timeout and the slack makes it up. The
// bias is deliberate: a timeout misread as a fault parks the reader behind the
// backoff, while a fault misread as a timeout costs one extra retry.
function attemptSettled(): bool {
return attemptStartedAt > 0 && Date.now() - attemptStartedAt >= sessionTimeoutMs - verifyStartSlackMs;
}
function checkAvail(): void {
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam) {
retrying = false;
abort();
if (!allowed) {
stop();
return;
}
if (!active && !retrying)
startAttempt();
}
function startAttempt(): void {
if (!allowed || errorTries >= maxErrorTries) {
stop();
return;
}
if (active)
return;
errorRetry.stop();
if (root.fprintState === "error")
root.fprintState = "";
attemptStartedAt = 0;
completedDuringStart = false;
if (start())
return;
if (!completedDuringStart)
scheduleErrorRetry();
}
tries = 0;
function scheduleErrorRetry(): void {
stop();
if (!allowed)
return;
errorTries++;
root.fprintState = "error";
if (errorTries === 1)
root.flashMsg();
fprintStateReset.restart();
if (errorTries < maxErrorTries)
errorRetry.restart();
}
function recover(): void {
if (!allowed)
return;
errorTries = 0;
retrying = false;
start();
if (!active)
startAttempt();
}
onAllowedChanged: {
if (!allowed)
stop();
else
Qt.callLater(checkAvail);
}
config: "fprint"
configDirectory: Quickshell.shellDir + "/assets/pam"
onPamMessage: {
if (attemptStartedAt === 0)
attemptStartedAt = Date.now();
}
onCompleted: res => {
if (!available)
completedDuringStart = true;
if (!allowed)
return;
switch (res) {
case PamResult.Success:
retrying = false;
if (!root.unlockInProgress) {
passwd.abort();
root.proceedAfterPrimaryAuth();
}
stop();
passwd.abort();
root.proceedAfterPrimaryAuth();
return;
case PamResult.Error:
errorTries++;
if (errorTries < 200) {
retrying = true;
abort();
errorRetry.restart();
if (attemptSettled()) {
stop();
errorTries = 0;
startAttempt();
return;
}
retrying = false;
abort();
scheduleErrorRetry();
return;
case PamResult.Failed:
case PamResult.MaxTries:
retrying = false;
stop();
tries++;
if (tries < SettingsData.maxFprintTries) {
root.fprintState = "fail";
start();
} else {
root.fprintState = "max";
abort();
}
break;
default:
errorTries = 0;
root.fprintState = tries < SettingsData.maxFprintTries ? "fail" : "max";
root.flashMsg();
fprintStateReset.restart();
startAttempt();
return;
}
root.flashMsg();
fprintStateReset.start();
}
}
@@ -426,20 +502,35 @@ Scope {
Timer {
id: errorRetry
interval: Math.min(1500 * Math.pow(2, Math.max(0, fprint.errorTries - 1)), 30000)
onTriggered: fprint.start()
// A device wedged by a suspend mid-verify only comes back when fprintd
// restarts, and fprintd only exits after its idle timeout with no client
// attached, so the second retry outlasts that instead of climbing to it.
interval: {
if (fprint.errorTries <= 1)
return 1500;
if (fprint.errorTries === 2)
return fprint.daemonIdleExitMs + 5000;
return 60000;
}
onTriggered: fprint.startAttempt()
}
readonly property bool awaitingActivityRetry: fprint.errorTries > 0 && !fprint.retrying && !fprint.active
function retryFprintOnActivity(): void {
if (root.awaitingActivityRetry)
fprint.recover();
}
Connections {
target: SessionService
// timers run on monotonic time, so suspend leaves the backoff remainder to burn after wake (#3171)
function onSessionResumed() {
if (!fprint.available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam)
return;
fprint.errorTries = 0;
if (errorRetry.running)
errorRetry.restart();
fprint.recover();
}
function onLidOpened() {
fprint.recover();
}
}
@@ -491,7 +582,10 @@ Scope {
id: fprintStateReset
interval: 4000
onTriggered: root.fprintState = ""
onTriggered: {
if (root.fprintState !== "max")
root.fprintState = "";
}
}
onLockSecuredChanged: {
@@ -508,6 +602,8 @@ Scope {
root.attemptInfoMessages = [];
root.lockoutAnnouncedThisAttempt = false;
root.resetAuthFlows();
fprint.tries = 0;
fprint.errorTries = 0;
if (!SettingsData.lockPamExternallyManaged && !dankshellConfigWatcher.loaded && !userPamWatcher.loaded)
ensureUserPamConfig();
// FileView cannot watch a path that does not exist yet; re-read so a
@@ -521,11 +617,17 @@ Scope {
target: SettingsData
function onEnableFprintChanged(): void {
fprint.checkAvail();
if (SettingsData.enableFprint)
fprint.recover();
else
fprint.checkAvail();
}
function onLockFingerprintReadyChanged(): void {
fprint.checkAvail();
if (SettingsData.lockFingerprintReady)
fprint.recover();
else
fprint.checkAvail();
}
function onEnableU2fChanged(): void {
@@ -698,7 +698,7 @@ Column {
color: Theme.surfaceTextSecondary
font.family: textArea.font.family
font.pixelSize: textArea.font.pixelSize
visible: textArea.text.length === 0
visible: textArea.text.length === 0 && !textArea.inputMethodComposing
anchors.left: textArea.left
anchors.top: textArea.top
anchors.leftMargin: textArea.leftPadding
+1 -1
View File
@@ -70,7 +70,7 @@ Item {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius + border.width
color: "transparent"
border.width: {
if (barConfig?.widgetOutlineEnabled ?? false) {
+23
View File
@@ -63,6 +63,26 @@ Singleton {
onTriggered: root.requestSync("startup")
}
property int powerSyncRecoveryAttempt: 0
readonly property var powerSyncRecoveryIntervals: [1000, 5000, 15000, 30000]
Timer {
id: powerSyncRecoveryTimer
interval: root.powerSyncRecoveryIntervals[0]
repeat: false
onTriggered: {
root.requestSync("resume-reconcile");
root.powerSyncRecoveryAttempt++;
if (root.powerSyncRecoveryAttempt < root.powerSyncRecoveryIntervals.length) {
interval = root.powerSyncRecoveryIntervals[root.powerSyncRecoveryAttempt];
restart();
return;
}
root.powerSyncRecoveryAttempt = 0;
interval = root.powerSyncRecoveryIntervals[0];
}
}
signal brightnessChanged(bool showOsd)
signal deviceSwitched
@@ -1541,6 +1561,9 @@ Singleton {
resumeRecoveryAttempt = 0;
resumeRecoveryTimer.interval = 400;
resumeRecoveryTimer.restart();
powerSyncRecoveryAttempt = 0;
powerSyncRecoveryTimer.interval = powerSyncRecoveryIntervals[0];
powerSyncRecoveryTimer.restart();
}
}
+1 -2
View File
@@ -148,14 +148,13 @@ Singleton {
}
}
// Wakes monitors powered off by the "power off monitors on lock" path.
// Lock.qml's own wake handlers sit outside the session-lock surface and
// never receive input, so wake on input via seat-level idle-notify instead.
IdleMonitor {
id: lockWakeMonitor
timeout: 1
respectInhibitors: false
enabled: root.enabled && root.isShellLocked && root.monitorsOff && (SettingsData.lockScreenPowerOffMonitorsOnLock || root.lockPowerOffRequested)
enabled: root.enabled && root.isShellLocked && root.monitorsOff
onIsIdleChanged: {
if (!enabled)
return;
+17 -19
View File
@@ -504,6 +504,7 @@ Singleton {
};
policy.bypassDnd = rules.some(rule => rule.bypassDnd === true && _matchesNotificationRule(rule, info));
policy.disablePopup = rules.some(rule => (rule.action || "default").toString().toLowerCase() === "mute" && _matchesNotificationRule(rule, info));
for (const rule of rules) {
if (!_matchesNotificationRule(rule, info))
@@ -514,9 +515,6 @@ Singleton {
case "ignore":
policy.drop = true;
break;
case "mute":
policy.disablePopup = true;
break;
case "popup_only":
policy.hideFromCenter = true;
policy.disableHistory = true;
@@ -699,23 +697,7 @@ Singleton {
}
}
// Honor the freedesktop "suppress-sound" hint: the sender
// plays its own audio for this notification and asks the
// server not to double up. "sound-name" is the opposite — an
// explicit request for audio — so it plays even when the
// global new-notification sound is off.
const soundHints = notif.hints || {};
const suppressSound = !!soundHints["suppress-sound"];
const requestsSound = !!soundHints["sound-name"];
const dndBlocked = SessionData.doNotDisturb && !_allowedInDnd(policy.urgency, policy.bypassDnd);
if (!dndBlocked && SettingsData.soundsEnabled && (SettingsData.soundNewNotification || requestsSound) && !suppressSound) {
if (policy.urgency === NotificationUrgency.Critical) {
AudioService.playCriticalNotificationSound();
} else {
AudioService.playNormalNotificationSound();
}
}
const shouldShowPopup = !root.popupsDisabled && !dndBlocked && !policy.disablePopup;
const isTransient = notif.transient;
const shouldKeepInCenter = !isTransient && !policy.hideFromCenter;
@@ -727,6 +709,22 @@ Singleton {
return;
}
// Honor the freedesktop "suppress-sound" hint: the sender
// plays its own audio for this notification and asks the
// server not to double up. "sound-name" is the opposite — an
// explicit request for audio — so it plays even when the
// global new-notification sound is off.
const soundHints = notif.hints || {};
const suppressSound = !!soundHints["suppress-sound"];
const requestsSound = !!soundHints["sound-name"];
if (!dndBlocked && !policy.disablePopup && SettingsData.soundsEnabled && (SettingsData.soundNewNotification || requestsSound) && !suppressSound) {
if (policy.urgency === NotificationUrgency.Critical) {
AudioService.playCriticalNotificationSound();
} else {
AudioService.playNormalNotificationSound();
}
}
const wrapper = notifComponent.createObject(root, {
"popup": shouldShowPopup,
"notification": notif,
+37 -6
View File
@@ -42,11 +42,14 @@ Singleton {
signal sessionLocked
signal sessionUnlocked
signal sessionResumed
signal lidOpened
signal loginctlStateChanged
property bool stateInitialized: false
property string prepareForSleepSubscriptionId: ""
property bool prepareForSleepSubscriptionPending: false
property string lidSubscriptionId: ""
property bool lidSubscriptionPending: false
property double lastResumeSignalTimestamp: 0
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -696,7 +699,7 @@ Singleton {
if (DMSService.isConnected) {
checkDMSCapabilities();
} else {
clearPrepareForSleepSubscriptionState();
clearPowerSubscriptionState();
}
}
@@ -714,10 +717,11 @@ Singleton {
}
function onDbusSignalReceived(subscriptionId, data) {
if (subscriptionId !== prepareForSleepSubscriptionId) {
return;
if (subscriptionId === prepareForSleepSubscriptionId) {
handlePrepareForSleepSignal(data);
} else if (subscriptionId === lidSubscriptionId) {
handleLidPropertiesChanged(data);
}
handlePrepareForSleepSignal(data);
}
}
@@ -779,14 +783,41 @@ Singleton {
if (DMSService.capabilities.includes("dbus")) {
ensurePrepareForSleepSubscription();
ensureLidSubscription();
} else {
clearPrepareForSleepSubscriptionState();
clearPowerSubscriptionState();
}
}
function clearPrepareForSleepSubscriptionState() {
function clearPowerSubscriptionState() {
prepareForSleepSubscriptionId = "";
prepareForSleepSubscriptionPending = false;
lidSubscriptionId = "";
lidSubscriptionPending = false;
}
function ensureLidSubscription() {
if (!DMSService.isConnected || !DMSService.capabilities.includes("dbus"))
return;
if (lidSubscriptionId || lidSubscriptionPending)
return;
lidSubscriptionPending = true;
DMSService.dbusSubscribe("system", "org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.DBus.Properties", "PropertiesChanged", response => {
lidSubscriptionPending = false;
if (response.error) {
log.warn("Failed to subscribe to lid changes:", response.error);
return;
}
lidSubscriptionId = response.result?.subscriptionId || "";
});
}
function handleLidPropertiesChanged(data) {
if (data?.path !== "/org/freedesktop/UPower" || data.body?.[0] !== "org.freedesktop.UPower")
return;
if (data.body?.[1]?.LidIsClosed === false)
lidOpened();
}
function ensurePrepareForSleepSubscription() {
+1 -1
View File
@@ -1 +1 @@
v1.6.2
v1.6.3
+4 -1
View File
@@ -1,6 +1,9 @@
#%PAM-1.0
auth required pam_fprintd.so max-tries=5
# timeout bounds how long a stuck fprintd session can hold the reader.
# Modules/Lock/Pam.qml mirrors the value in sessionTimeoutMs to tell an expiry
# apart from a device fault; change both together.
auth required pam_fprintd.so max-tries=1 timeout=90
account required pam_permit.so
password required pam_deny.so
session required pam_permit.so
@@ -2,11 +2,11 @@
contrast=4
[General]
ColorScheme=DankMatugen
Name=Dank Shell (matugen)
ColorScheme=DankMatugenDark
Name=Dank Shell (matugen) Dark
[ColorEffects:Disabled]
Color={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
Color={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
@@ -16,7 +16,7 @@ IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=true
Color={{colors.outline.default.red}},{{colors.outline.default.green}},{{colors.outline.default.blue}}
Color={{colors.outline.dark.red}},{{colors.outline.dark.green}},{{colors.outline.dark.blue}}
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
@@ -26,121 +26,121 @@ IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
BackgroundNormal={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Complementary]
BackgroundAlternate={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface_container.dark.red}},{{colors.surface_container.dark.green}},{{colors.surface_container.dark.blue}}
BackgroundNormal={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Header]
BackgroundAlternate={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
BackgroundNormal={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Header][Inactive]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
BackgroundNormal={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Selection]
BackgroundAlternate={{colors.primary_container.default.red}},{{colors.primary_container.default.green}},{{colors.primary_container.default.blue}}
BackgroundNormal={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.on_primary.default.red}},{{colors.on_primary.default.green}},{{colors.on_primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_primary.default.red}},{{colors.on_primary.default.green}},{{colors.on_primary.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.primary_container.dark.red}},{{colors.primary_container.dark.green}},{{colors.primary_container.dark.blue}}
BackgroundNormal={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.on_primary.dark.red}},{{colors.on_primary.dark.green}},{{colors.on_primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_primary.dark.red}},{{colors.on_primary.dark.green}},{{colors.on_primary.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Tooltip]
BackgroundAlternate={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
BackgroundNormal={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:View]
BackgroundAlternate={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface_container_low.dark.red}},{{colors.surface_container_low.dark.green}},{{colors.surface_container_low.dark.blue}}
BackgroundNormal={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[Colors:Window]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
BackgroundNormal={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
DecorationFocus={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
DecorationHover={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundActive={{colors.primary.dark.red}},{{colors.primary.dark.green}},{{colors.primary.dark.blue}}
ForegroundInactive={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
ForegroundLink={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundNegative={{colors.error.dark.red}},{{colors.error.dark.green}},{{colors.error.dark.blue}}
ForegroundNeutral={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
ForegroundNormal={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
ForegroundPositive={{colors.tertiary.dark.red}},{{colors.tertiary.dark.green}},{{colors.tertiary.dark.blue}}
ForegroundVisited={{colors.secondary.dark.red}},{{colors.secondary.dark.green}},{{colors.secondary.dark.blue}}
[WM]
activeBackground={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
activeBlend={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
activeForeground={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
inactiveBackground={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
inactiveBlend={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
inactiveForeground={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
activeBackground={{colors.surface.dark.red}},{{colors.surface.dark.green}},{{colors.surface.dark.blue}}
activeBlend={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
activeForeground={{colors.on_surface.dark.red}},{{colors.on_surface.dark.green}},{{colors.on_surface.dark.blue}}
inactiveBackground={{colors.background.dark.red}},{{colors.background.dark.green}},{{colors.background.dark.blue}}
inactiveBlend={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
inactiveForeground={{colors.on_surface_variant.dark.red}},{{colors.on_surface_variant.dark.green}},{{colors.on_surface_variant.dark.blue}}
@@ -28,9 +28,9 @@ IntensityEffect=0
[Colors:Button]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
@@ -42,9 +42,9 @@ ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.gr
[Colors:Complementary]
BackgroundAlternate={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
@@ -56,9 +56,9 @@ ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.gr
[Colors:Header]
BackgroundAlternate={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
@@ -70,9 +70,9 @@ ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.gr
[Colors:Header][Inactive]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
@@ -82,25 +82,25 @@ ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.gre
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
[Colors:Selection]
BackgroundAlternate={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
BackgroundNormal={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
BackgroundAlternate={{colors.primary_container.default.red}},{{colors.primary_container.default.green}},{{colors.primary_container.default.blue}}
BackgroundNormal={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.on_primary.default.red}},{{colors.on_primary.default.green}},{{colors.on_primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundNormal={{colors.on_primary.default.red}},{{colors.on_primary.default.green}},{{colors.on_primary.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
[Colors:Tooltip]
BackgroundAlternate={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
@@ -112,9 +112,9 @@ ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.gr
[Colors:View]
BackgroundAlternate={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
@@ -126,9 +126,9 @@ ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.gr
[Colors:Window]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
DecorationHover={{colors.inverse_primary.default.red}},{{colors.inverse_primary.default.green}},{{colors.inverse_primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
+2 -2
View File
@@ -1,5 +1,5 @@
cursor {{colors.on_surface.default.hex}}
cursor_text_color {{colors.on_surface_variant.default.hex}}
cursor {{colors.primary.default.hex}}
cursor_text_color {{colors.on_primary.default.hex}}
foreground {{colors.on_surface.default.hex}}
background {{colors.background.default.hex}}
@@ -2,11 +2,11 @@
contrast=4
[General]
ColorScheme=DankMatugen
Name=Dank Shell (matugen)
ColorScheme=DankMatugenLight
Name=Dank Shell (matugen) Light
[ColorEffects:Disabled]
Color={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
Color={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
@@ -16,7 +16,7 @@ IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=true
Color={{colors.outline.default.red}},{{colors.outline.default.green}},{{colors.outline.default.blue}}
Color={{colors.outline.light.red}},{{colors.outline.light.green}},{{colors.outline.light.blue}}
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
@@ -26,121 +26,121 @@ IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
BackgroundNormal={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Complementary]
BackgroundAlternate={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface_container.light.red}},{{colors.surface_container.light.green}},{{colors.surface_container.light.blue}}
BackgroundNormal={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Header]
BackgroundAlternate={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
BackgroundNormal={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Header][Inactive]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
BackgroundNormal={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Selection]
BackgroundAlternate={{colors.primary_container.default.red}},{{colors.primary_container.default.green}},{{colors.primary_container.default.blue}}
BackgroundNormal={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.on_primary.default.red}},{{colors.on_primary.default.green}},{{colors.on_primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_primary.default.red}},{{colors.on_primary.default.green}},{{colors.on_primary.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.primary_container.light.red}},{{colors.primary_container.light.green}},{{colors.primary_container.light.blue}}
BackgroundNormal={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.on_primary.light.red}},{{colors.on_primary.light.green}},{{colors.on_primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_primary.light.red}},{{colors.on_primary.light.green}},{{colors.on_primary.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Tooltip]
BackgroundAlternate={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
BackgroundNormal={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:View]
BackgroundAlternate={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface_container_low.light.red}},{{colors.surface_container_low.light.green}},{{colors.surface_container_low.light.blue}}
BackgroundNormal={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[Colors:Window]
BackgroundAlternate={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
BackgroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
BackgroundAlternate={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
BackgroundNormal={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
DecorationFocus={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
DecorationHover={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundActive={{colors.primary.light.red}},{{colors.primary.light.green}},{{colors.primary.light.blue}}
ForegroundInactive={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
ForegroundLink={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundNegative={{colors.error.light.red}},{{colors.error.light.green}},{{colors.error.light.blue}}
ForegroundNeutral={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
ForegroundNormal={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
ForegroundPositive={{colors.tertiary.light.red}},{{colors.tertiary.light.green}},{{colors.tertiary.light.blue}}
ForegroundVisited={{colors.secondary.light.red}},{{colors.secondary.light.green}},{{colors.secondary.light.blue}}
[WM]
activeBackground={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
activeBlend={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
activeForeground={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
inactiveBackground={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
inactiveBlend={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
inactiveForeground={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
activeBackground={{colors.surface.light.red}},{{colors.surface.light.green}},{{colors.surface.light.blue}}
activeBlend={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
activeForeground={{colors.on_surface.light.red}},{{colors.on_surface.light.green}},{{colors.on_surface.light.blue}}
inactiveBackground={{colors.background.light.red}},{{colors.background.light.green}},{{colors.background.light.blue}}
inactiveBlend={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
inactiveForeground={{colors.on_surface_variant.light.red}},{{colors.on_surface_variant.light.green}},{{colors.on_surface_variant.light.blue}}
@@ -67,7 +67,7 @@
"list.focusOutline": "{{colors.primary.dark.hex}}",
"list.focusBackground": "{{colors.surface_container_high.dark.hex}}",
"list.highlightForeground": "{{colors.primary.dark.hex}}",
"list.focusHighlightForeground": "{{colors.primary.dark.hex}}",
"list.focusHighlightForeground": "{{colors.on_primary.dark.hex}}",
"list.errorForeground": "{{colors.error.dark.hex}}",
"list.warningForeground": "{{colors.secondary.dark.hex}}",
//
@@ -82,13 +82,16 @@
"dropdown.border": "{{colors.outline_variant.dark.hex}}",
"quickInput.background": "{{colors.surface_container_low.dark.hex}}",
"quickInput.foreground": "{{colors.on_surface.dark.hex}}",
"quickInputList.focusBackground": "{{colors.primary.dark.hex}}",
"quickInputList.focusForeground": "{{colors.on_primary.dark.hex}}",
"quickInputList.focusIconForeground": "{{colors.on_primary.dark.hex}}",
"widget.shadow": "{{colors.background.dark.hex}}80",
//
// Buttons
//
"button.background": "{{colors.primary.dark.hex}}",
"button.foreground": "{{colors.on_primary.dark.hex}}",
"button.hoverBackground": "{{colors.primary_container.dark.hex}}",
"button.hoverBackground": "{{colors.primary_fixed.dark.hex}}",
//
// Status bar
//
@@ -158,9 +161,10 @@
"editorHoverWidget.border": "{{colors.outline.dark.hex}}",
"editorSuggestWidget.background": "{{colors.surface_container.dark.hex}}",
"editorSuggestWidget.foreground": "{{colors.on_surface.dark.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.dark.hex}}",
"editorSuggestWidget.selectedForeground": "{{colors.on_surface.dark.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.primary.dark.hex}}",
"editorSuggestWidget.selectedForeground": "{{colors.on_primary.dark.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.dark.hex}}",
"editorSuggestWidget.focusHighlightForeground": "{{colors.on_primary.dark.hex}}",
//
// Scrollbar
//
@@ -49,7 +49,7 @@
"list.focusOutline": "{{colors.primary.default.hex}}",
"list.focusBackground": "{{colors.surface_container_high.default.hex}}",
"list.highlightForeground": "{{colors.primary.default.hex}}",
"list.focusHighlightForeground": "{{colors.primary.default.hex}}",
"list.focusHighlightForeground": "{{colors.on_primary.default.hex}}",
"list.errorForeground": "{{colors.error.default.hex}}",
"list.warningForeground": "{{colors.secondary.default.hex}}",
"input.background": "{{colors.surface_container_low.default.hex}}",
@@ -61,10 +61,13 @@
"dropdown.border": "{{colors.outline_variant.default.hex}}",
"quickInput.background": "{{colors.surface_container_low.default.hex}}",
"quickInput.foreground": "{{colors.on_surface.default.hex}}",
"quickInputList.focusBackground": "{{colors.primary.default.hex}}",
"quickInputList.focusForeground": "{{colors.on_primary.default.hex}}",
"quickInputList.focusIconForeground": "{{colors.on_primary.default.hex}}",
"widget.shadow": "{{colors.background.default.hex}}80",
"button.background": "{{colors.primary.default.hex}}",
"button.foreground": "{{colors.on_primary.default.hex}}",
"button.hoverBackground": "{{colors.primary_container.default.hex}}",
"button.hoverBackground": "{{colors.primary_fixed.default.hex}}",
"statusBar.background": "{{colors.background.default.hex}}",
"statusBar.foreground": "{{colors.on_surface.default.hex}}",
"statusBar.border": "{{colors.outline_variant.default.hex}}",
@@ -113,9 +116,10 @@
"editorHoverWidget.border": "{{colors.outline.default.hex}}",
"editorSuggestWidget.background": "{{colors.surface_container.default.hex}}",
"editorSuggestWidget.foreground": "{{colors.on_surface.default.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.default.hex}}",
"editorSuggestWidget.selectedForeground": "{{colors.on_surface.default.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.primary.default.hex}}",
"editorSuggestWidget.selectedForeground": "{{colors.on_primary.default.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.default.hex}}",
"editorSuggestWidget.focusHighlightForeground": "{{colors.on_primary.default.hex}}",
"scrollbarSlider.background": "{{colors.on_surface_variant.default.hex}}50",
"scrollbarSlider.hoverBackground": "{{colors.on_surface_variant.default.hex}}80",
"scrollbarSlider.activeBackground": "{{colors.on_surface_variant.default.hex}}AA",
@@ -43,9 +43,10 @@
"editorSuggestWidget.background": "{{colors.surface_container.light.hex}}",
"editorSuggestWidget.foreground": "{{colors.on_surface.light.hex}}",
"editorSuggestWidget.border": "{{colors.outline_variant.light.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.light.hex}}",
"editorSuggestWidget.selectedForeground": "{{colors.on_surface.light.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.primary.light.hex}}",
"editorSuggestWidget.selectedForeground": "{{colors.on_primary.light.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.light.hex}}",
"editorSuggestWidget.focusHighlightForeground": "{{colors.on_primary.light.hex}}",
"editorGroup.border": "{{colors.outline_variant.light.hex}}",
"editorGroup.dropBackground": "{{colors.primary_container.light.hex}}33",
"editorGroupHeader.tabsBackground": "{{colors.background.light.hex}}",
@@ -79,7 +80,7 @@
"list.focusBackground": "{{colors.surface_container_high.light.hex}}",
"list.focusForeground": "{{colors.on_surface.light.hex}}",
"list.highlightForeground": "{{colors.primary.light.hex}}",
"list.focusHighlightForeground": "{{colors.primary.light.hex}}",
"list.focusHighlightForeground": "{{colors.on_primary.light.hex}}",
"list.errorForeground": "{{colors.error.light.hex}}",
"list.warningForeground": "{{colors.tertiary.light.hex}}",
"statusBar.background": "{{colors.surface_container.light.hex}}",
@@ -106,11 +107,12 @@
"dropdown.border": "{{colors.outline_variant.light.hex}}",
"quickInput.background": "{{colors.surface_container.light.hex}}",
"quickInput.foreground": "{{colors.on_surface.light.hex}}",
"quickInputList.focusBackground": "{{colors.surface_container_high.light.hex}}",
"quickInputList.focusForeground": "{{colors.on_surface.light.hex}}",
"quickInputList.focusBackground": "{{colors.primary.light.hex}}",
"quickInputList.focusForeground": "{{colors.on_primary.light.hex}}",
"quickInputList.focusIconForeground": "{{colors.on_primary.light.hex}}",
"button.background": "{{colors.primary.light.hex}}",
"button.foreground": "{{colors.on_primary.light.hex}}",
"button.hoverBackground": "{{colors.primary_container.light.hex}}",
"button.hoverBackground": "{{colors.primary_fixed.light.hex}}",
"badge.background": "{{colors.secondary.light.hex}}",
"badge.foreground": "{{colors.on_secondary.light.hex}}",
"panel.background": "{{colors.surface_container.light.hex}}",