Third review round.
- `Config` is copied by value all over the manager, but `ICCProfiles` and
`OutputTemps` are maps, so every copy aliased the ones the manager writes under
`configMutex`. `updateStateFromSchedule` stored such a copy in `m.state` and it
was marshalled on the connection writer goroutine while `ApplyICC` stored a
profile from the IPC goroutine (`fatal error: concurrent map iteration and map
write`, which no `recover()` catches); `SaveConfig` marshalled its copy after
the unlock, and `loadConfiguredICC`/`attachConfiguredICC` indexed the maps after
releasing the lock. Every copy that outlives the lock is cloned now
(`cloneConfig`/`configSnapshot`), so a value copy is a snapshot again.
- `SetOutputTemp` never created the gamma controls. On a default install (night
light off, gamma 1.0, contrast 1.0, no profile, no override) they do not exist,
so the `Range` over `m.outputs` matched nothing: the value was written to
`wayland.json`, no ramp was applied, and the settings slider snapped back to
"Default" until a daemon restart. It now persists first, creates the controls
through `ensureOutputControls` (shared with `ApplyICC`), and pushes the state
from the actor, and the published temperatures report the configured value even
before an output is known.
- A TRC is not a video card gamma ramp: `rTRC`/`gTRC`/`bTRC` describe the
display's own transfer function, so a gamma-2.2 profile was written as
`t^(1/2.2)` — mid gray 0.5 became 47909/65535 — washing the display out instead
of correcting it. `ApplyICC` refuses a profile without `vcgt`, the attach path
skips one with a warning, and `GenerateGammaRamp` returns an error instead of
falling back to the TRC or to an identity ramp. TRC stays profile metadata
(`dms icc info`, the settings dialog).
- `icc: applied ICC ramp` was logged at Info on every 25 K animation step; it is
Debug now, which is where a per-step message belongs.
- `I18n.tr("No information available")` in the profile info modal passes its
translator context, like every other string this PR adds.
Covered by TestManager_ConfigSnapshotOwnsItsMaps,
TestManager_ConfigSnapshotConcurrentMarshal (the previous commit dies with
`fatal error: concurrent map iteration and map write`),
TestManager_SetOutputTempWithoutControlsPublishesValue (the previous commit
reports 0 for the value it just stored), TestGenerateGammaRampRequiresVCGT, and
the updated TestProfileRampWithTemp, whose fixture now carries an identity vcgt
table instead of relying on an empty profile.
DMS Quickshell Interface
QML-based desktop shell interface for DankMaterialShell providing panels, widgets, and overlays.
See root README for project overview and installation.
Architecture
Modular QML Structure
Modules/- UI components (panels, widgets, overlays)Services/- System integration singletons (audio, network, bluetooth)Widgets/- Reusable UI controlsCommon/- Shared resources and themes
Technology Stack
- Quickshell - QML-based shell framework
- Qt/QtQuick - UI rendering and controls
- Material Design 3 - Design system and theming
Development
Run the shell:
quickshell -p quickshell/
Code formatting:
qmlfmt -t 4 -i 4 -b 250 -w path/to/file.qml
make lint-qml # Run from repo root; requires quickshell/.qmlls.ini (generated by `qs -p quickshell/`)
# Uses Qt 6 qmllint. Override path with QMLLINT=/path/to/qmllint if needed.
# Auto-detects `qmllint6`, Fedora's `qmllint-qt6`, `/usr/lib/qt6/bin/qmllint`, then `qmllint`.
Components
Panels & Bars
Modules/TopBar/- Multi-monitor status bars with workspace switchingModules/DankBar/- Customizable widget bar with plugin supportModules/Dock/- Application dock with window management
System Controls
Modules/ControlCenter/- WiFi, Bluetooth, audio, display settingsModules/Notifications/- Notification center with popups
Overlays
Modules/Spotlight/- Application and file launcherModules/Overview/- Workspace overviewModules/Lock/- Screen lock system
Utilities
Modules/ProcessList/- System monitoring and process managementModules/Calendar/- Calendar widget with event syncModules/Weather/- Weather display
Services
Singletons providing system integration:
Media & Audio
AudioService- PipeWire/PulseAudio volume and device controlMprisController- Media player integration
Network
NetworkService- NetworkManager WiFi controlBluetoothService- BlueZ Bluetooth management
Display
DisplayService- Brightness control and night modeWallpaperService- Wallpaper management and effects
System
BatteryService- Battery status and power profilesIdleService- Idle detection and inhibit locksClipboardService- Clipboard history with imagesDgopService- System metrics (CPU, RAM, GPU)
Integration
NiriService- Niri workspace integrationHyprlandService- Hyprland workspace integrationPluginService- Plugin discovery and lifecycle
Widgets
Reusable Material Design 3 components in Widgets/:
DankIcon- Icon component with Material fontDankSlider- Enhanced slider with animationsDankToggle- Toggle switch componentDankTabBar- Tab bar implementationDankGridView- Grid layout with adaptive columnsDankListView- Scrollable list viewDankTextField- Text input with validationDankDropdown- Dropdown selectionDankPopout- Base for overlay componentsStateLayer- Material interaction states
Theming
Dynamic Color Schemes
Wallpaper-based theming using matugen:
import qs.Common
Rectangle {
color: Theme.container
border.color: Theme.outline
}
Theme singleton provides Material Design 3 color system, spacing, fonts, and elevation.
Application Themes
Templates in scripts/templates/ generate themes for:
- GTK 3/4
- Qt5/Qt6
- Alacritty, Kitty, Ghostty, Foot, Wezterm terminals
- Neovim
- VSCode/VSCodium
- Firefox
Multi-Monitor Support
Per-monitor panel instances using Quickshell Variants:
Variants {
model: Quickshell.screens
PanelWindow {
screen: modelData
// Per-screen configuration
}
}
Workspace switchers adapt to compositor (Niri/Hyprland).
Plugin System
External plugins in ~/.config/DankMaterialShell/plugins/:
Widget plugins - UI components in DankBar Daemon plugins - Background processes without UI
Plugin manifest (plugin.json):
{
"id": "pluginId",
"name": "Plugin Name",
"version": "1.0.0",
"type": "widget",
"component": "./Widget.qml",
"settings": "./Settings.qml",
"permissions": ["settings_read", "settings_write"]
}
Plugins access pluginService for persistent data:
pluginService.savePluginData("pluginId", "key", value)
pluginService.loadPluginData("pluginId", "key", defaultValue)
IPC Integration
Backend IPC socket communication:
import Quickshell.Io
Process {
command: ["dms", "ipc", "call", "spotlight", "toggle"]
running: true
}
Common IPC commands exposed through services for reactive property bindings.
Code Conventions
Component Structure:
import QtQuick
import Quickshell
import qs.Common
import qs.Services
Item {
id: root
property type name: value
signal customSignal(type param)
Component { /* children */ }
}
Services (Singletons):
import QtQuick
import Quickshell
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
property bool featureAvailable: false
property type currentValue: defaultValue
function performAction(param) { /* implementation */ }
}
Guidelines:
- Use
Theme.propertyNamefor consistent styling - Bind directly to service properties for reactivity
- Use
DankIconfor all icons - Implement feature detection and graceful degradation
- 4-space indentation, no unnecessary comments
Translation
Internationalization using POEditor:
export POEDITOR_API_TOKEN="token"
export POEDITOR_PROJECT_ID="id"
python3 scripts/i18nsync.py sync
Pre-commit hook checks translation sync status.
License
MIT License - See LICENSE for details.