1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00

displays: add niri-specific layout options to configurator

This commit is contained in:
bbedward
2025-12-16 12:23:34 -05:00
parent f6b09751e9
commit ff506548d3
9 changed files with 1028 additions and 303 deletions

View File

@@ -14,34 +14,63 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
## System Integration ## System Integration
**Wayland Protocols** ### Wayland Protocols (Client)
- `wlr-gamma-control-unstable-v1` - Night mode and gamma control
- `wlr-screencopy-unstable-v1` - Screen capture for color picker
- `wlr-layer-shell-unstable-v1` - Overlay surfaces for color picker
- `wp-viewporter` - Fractional scaling support
- `dwl-ipc-unstable-v2` - dwl/MangoWC workspace integration
- `ext-workspace-v1` - Workspace protocol support
- `wlr-output-management-unstable-v1` - Display configuration
**DBus Interfaces** All Wayland protocols are consumed as a client - connecting to the compositor.
- NetworkManager/iwd - Network management
- logind - Session control and inhibit locks
- accountsservice - User account information
- CUPS - Printer management
- Custom IPC via unix socket (JSON API)
**Hardware Control** | Protocol | Purpose |
- DDC/CI protocol - External monitor brightness control (like `ddcutil`) | ----------------------------------------- | ----------------------------------------------------------- |
- Backlight control - Internal display brightness via `login1` or sysfs | `wlr-gamma-control-unstable-v1` | Night mode color temperature control |
- LED control - Keyboard/device LED management | `wlr-screencopy-unstable-v1` | Screen capture for color picker/screenshot |
- evdev input monitoring - Keyboard state tracking (caps lock, etc.) | `wlr-layer-shell-unstable-v1` | Overlay surfaces for color picker UI/screenshot |
| `wlr-output-management-unstable-v1` | Display configuration |
| `wlr-output-power-management-unstable-v1` | DPMS on/off CLI |
| `wp-viewporter` | Fractional scaling support (color picker/screenshot UIs) |
| `keyboard-shortcuts-inhibit-unstable-v1` | Inhibit compositor shortcuts during color picker/screenshot |
| `ext-data-control-v1` | Clipboard history and persistence |
| `ext-workspace-v1` | Workspace integration |
| `dwl-ipc-unstable-v2` | dwl/MangoWC IPC for tags, outputs, etc. |
### DBus Interfaces
**Client (consuming external services):**
| Interface | Purpose |
| -------------------------------- | --------------------------------------------- |
| `org.bluez` | Bluetooth management with pairing agent |
| `org.freedesktop.NetworkManager` | Network management |
| `net.connman.iwd` | iwd Wi-Fi backend |
| `org.freedesktop.network1` | systemd-networkd integration |
| `org.freedesktop.login1` | Session control, sleep inhibitors, brightness |
| `org.freedesktop.Accounts` | User account information |
| `org.freedesktop.portal.Desktop` | Desktop appearance settings (color scheme) |
| CUPS via IPP + D-Bus | Printer management with job notifications |
**Server (implementing interfaces):**
| Interface | Purpose |
| ----------------------------- | -------------------------------------- |
| `org.freedesktop.ScreenSaver` | Screensaver inhibit for video playback |
Custom IPC via unix socket (JSON API) for shell communication.
### Hardware Control
| Subsystem | Method | Purpose |
| --------- | ------------------- | ---------------------------------- |
| DDC/CI | I2C direct | External monitor brightness |
| Backlight | logind or sysfs | Internal display brightness |
| evdev | `/dev/input/event*` | Keyboard state (caps lock LED) |
| udev | netlink monitor | Backlight device updates (for OSD) |
### Plugin System
**Plugin System**
- Plugin registry integration - Plugin registry integration
- Plugin lifecycle management - Plugin lifecycle management
- Settings persistence - Settings persistence
## CLI Commands ## CLI Commands
- `dms run [-d]` - Start shell (optionally as daemon) - `dms run [-d]` - Start shell (optionally as daemon)
- `dms restart` / `dms kill` - Manage running processes - `dms restart` / `dms kill` - Manage running processes
- `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.) - `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.)
@@ -70,6 +99,7 @@ The on-screen preview displays the selected format. JSON output includes hex, RG
Requires Go 1.24+ Requires Go 1.24+
**Development build:** **Development build:**
```bash ```bash
make # Build dms CLI make # Build dms CLI
make dankinstall # Build installer make dankinstall # Build installer
@@ -77,6 +107,7 @@ make test # Run tests
``` ```
**Distribution build:** **Distribution build:**
```bash ```bash
make dist # Build without update/greeter features make dist # Build without update/greeter features
``` ```
@@ -84,6 +115,7 @@ make dist # Build without update/greeter features
Produces `bin/dms-linux-amd64` and `bin/dms-linux-arm64` Produces `bin/dms-linux-amd64` and `bin/dms-linux-arm64`
**Installation:** **Installation:**
```bash ```bash
sudo make install # Install to /usr/local/bin/dms sudo make install # Install to /usr/local/bin/dms
``` ```
@@ -91,6 +123,7 @@ sudo make install # Install to /usr/local/bin/dms
## Development ## Development
**Setup pre-commit hooks:** **Setup pre-commit hooks:**
```bash ```bash
git config core.hooksPath .githooks git config core.hooksPath .githooks
``` ```
@@ -98,6 +131,7 @@ git config core.hooksPath .githooks
This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged. This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
**Regenerating Wayland Protocol Bindings:** **Regenerating Wayland Protocol Bindings:**
```bash ```bash
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \ go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
@@ -105,6 +139,7 @@ go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
``` ```
**Module Structure:** **Module Structure:**
- `cmd/` - Binary entrypoints (dms, dankinstall) - `cmd/` - Binary entrypoints (dms, dankinstall)
- `internal/distros/` - Distribution-specific installation logic - `internal/distros/` - Distribution-specific installation logic
- `internal/proto/` - Wayland protocol bindings - `internal/proto/` - Wayland protocol bindings

View File

@@ -1,5 +1,5 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior
import QtCore import QtCore
import QtQuick import QtQuick
@@ -360,47 +360,48 @@ Singleton {
property string displayNameMode: "system" property string displayNameMode: "system"
property var screenPreferences: ({}) property var screenPreferences: ({})
property var showOnLastDisplay: ({}) property var showOnLastDisplay: ({})
property var niriOutputSettings: ({})
property var barConfigs: [ property var barConfigs: [
{ {
id: "default", "id": "default",
name: "Main Bar", "name": "Main Bar",
enabled: true, "enabled": true,
position: 0, "position": 0,
screenPreferences: ["all"], "screenPreferences": ["all"],
showOnLastDisplay: true, "showOnLastDisplay": true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"], "leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"], "centerWidgets": ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"], "rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4, "spacing": 4,
innerPadding: 4, "innerPadding": 4,
bottomGap: 0, "bottomGap": 0,
transparency: 1.0, "transparency": 1.0,
widgetTransparency: 1.0, "widgetTransparency": 1.0,
squareCorners: false, "squareCorners": false,
noBackground: false, "noBackground": false,
gothCornersEnabled: false, "gothCornersEnabled": false,
gothCornerRadiusOverride: false, "gothCornerRadiusOverride": false,
gothCornerRadiusValue: 12, "gothCornerRadiusValue": 12,
borderEnabled: false, "borderEnabled": false,
borderColor: "surfaceText", "borderColor": "surfaceText",
borderOpacity: 1.0, "borderOpacity": 1.0,
borderThickness: 1, "borderThickness": 1,
widgetOutlineEnabled: false, "widgetOutlineEnabled": false,
widgetOutlineColor: "primary", "widgetOutlineColor": "primary",
widgetOutlineOpacity: 1.0, "widgetOutlineOpacity": 1.0,
widgetOutlineThickness: 1, "widgetOutlineThickness": 1,
fontScale: 1.0, "fontScale": 1.0,
autoHide: false, "autoHide": false,
autoHideDelay: 250, "autoHideDelay": 250,
openOnOverview: false, "openOnOverview": false,
visible: true, "visible": true,
popupGapsAuto: true, "popupGapsAuto": true,
popupGapsManual: 4, "popupGapsManual": 4,
maximizeDetection: true, "maximizeDetection": true,
scrollEnabled: true, "scrollEnabled": true,
scrollXBehavior: "column", "scrollXBehavior": "column",
scrollYBehavior: "workspace" "scrollYBehavior": "workspace"
} }
] ]
@@ -458,25 +459,25 @@ Singleton {
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0 const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini" settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then if [ -f "$settings_file" ]; then
if grep -q "^gtk-icon-theme-name=" "$settings_file"; then if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file" sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
else else
if grep -q "\\[Settings\\]" "$settings_file"; then if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file" sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file" echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
fi fi
else fi
else
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file" echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
fi fi
done done
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`; pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]); Quickshell.execDetached(["sh", "-lc", configScript]);
} }
@@ -489,36 +490,36 @@ pkill -HUP -f 'gtk' 2>/dev/null || true`;
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''"); const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
update_qt_icon_theme() { update_qt_icon_theme() {
local config_file="$1" local config_file="$1"
local theme_name="$2" local theme_name="$2"
if [ -f "$config_file" ]; then if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then if grep -q "^icon_theme=" "$config_file"; then
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file" sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
else else
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file" sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
fi fi
else else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file" printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi fi
else else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file" printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi fi
} }
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}' update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}' update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`; rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]); Quickshell.execDetached(["sh", "-lc", script]);
} }
readonly property var _hooks: ({ readonly property var _hooks: ({
applyStoredTheme: applyStoredTheme, "applyStoredTheme": applyStoredTheme,
regenSystemThemes: regenSystemThemes, "regenSystemThemes": regenSystemThemes,
updateNiriLayout: updateNiriLayout, "updateNiriLayout": updateNiriLayout,
applyStoredIconTheme: applyStoredIconTheme, "applyStoredIconTheme": applyStoredIconTheme,
updateBarConfigs: updateBarConfigs "updateBarConfigs": updateBarConfigs
}) })
function set(key, value) { function set(key, value) {
@@ -723,7 +724,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
let leftBar = 0; let leftBar = 0;
let rightBar = 0; let rightBar = 0;
for (let i = 0; i < enabledBars.length; i++) { for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i]; const other = enabledBars[i];
if (other.id === barConfig.id) if (other.id === barConfig.id)
continue; continue;
@@ -793,7 +794,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
if (barConfig) { if (barConfig) {
const enabledBars = getEnabledBarConfigs(); const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) { for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i]; const other = enabledBars[i];
if (other.id === barConfig.id) if (other.id === barConfig.id)
continue; continue;
@@ -925,7 +926,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const conflicts = []; const conflicts = [];
const enabledBars = getEnabledBarConfigs(); const enabledBars = getEnabledBarConfigs();
for (let i = 0; i < enabledBars.length; i++) { for (var i = 0; i < enabledBars.length; i++) {
const other = enabledBars[i]; const other = enabledBars[i];
if (other.id === barId) if (other.id === barId)
continue; continue;
@@ -938,9 +939,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const hasAll = barScreens.includes("all") || otherScreens.includes("all"); const hasAll = barScreens.includes("all") || otherScreens.includes("all");
if (hasAll) { if (hasAll) {
conflicts.push({ conflicts.push({
barId: other.id, "barId": other.id,
barName: other.name, "barName": other.name,
reason: "Same position on all screens" "reason": "Same position on all screens"
}); });
continue; continue;
} }
@@ -948,9 +949,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const overlapping = barScreens.some(screen => otherScreens.includes(screen)); const overlapping = barScreens.some(screen => otherScreens.includes(screen));
if (overlapping) { if (overlapping) {
conflicts.push({ conflicts.push({
barId: other.id, "barId": other.id,
barName: other.name, "barName": other.name,
reason: "Same position on overlapping screens" "reason": "Same position on overlapping screens"
}); });
} }
} }
@@ -972,7 +973,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
function getScreensSortedByPosition() { function getScreensSortedByPosition() {
const screens = []; const screens = [];
for (let i = 0; i < Quickshell.screens.length; i++) { for (var i = 0; i < Quickshell.screens.length; i++) {
screens.push(Quickshell.screens[i]); screens.push(Quickshell.screens[i]);
} }
screens.sort((a, b) => { screens.sort((a, b) => {
@@ -989,7 +990,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const sorted = getScreensSortedByPosition(); const sorted = getScreensSortedByPosition();
let modelCount = 0; let modelCount = 0;
let screenIndex = -1; let screenIndex = -1;
for (let i = 0; i < sorted.length; i++) { for (var i = 0; i < sorted.length; i++) {
if (sorted[i].model === screen.model) { if (sorted[i].model === screen.model) {
if (sorted[i].name === screen.name) { if (sorted[i].name === screen.name) {
screenIndex = modelCount; screenIndex = modelCount;
@@ -1187,7 +1188,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
spacing: spacing "spacing": spacing
}); });
} }
if (typeof NiriService !== "undefined" && CompositorService.isNiri) { if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
@@ -1216,7 +1217,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return; return;
} }
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
position: position "position": position
}); });
} }
@@ -1224,7 +1225,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
leftWidgets: order "leftWidgets": order
}); });
updateListModel(leftWidgetsModel, order); updateListModel(leftWidgetsModel, order);
} }
@@ -1234,7 +1235,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
centerWidgets: order "centerWidgets": order
}); });
updateListModel(centerWidgetsModel, order); updateListModel(centerWidgetsModel, order);
} }
@@ -1244,7 +1245,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
rightWidgets: order "rightWidgets": order
}); });
updateListModel(rightWidgetsModel, order); updateListModel(rightWidgetsModel, order);
} }
@@ -1257,9 +1258,9 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
leftWidgets: defaultLeft, "leftWidgets": defaultLeft,
centerWidgets: defaultCenter, "centerWidgets": defaultCenter,
rightWidgets: defaultRight "rightWidgets": defaultRight
}); });
} }
updateListModel(leftWidgetsModel, defaultLeft); updateListModel(leftWidgetsModel, defaultLeft);
@@ -1307,7 +1308,7 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
const defaultBar = barConfigs[0] || getBarConfig("default"); const defaultBar = barConfigs[0] || getBarConfig("default");
if (defaultBar) { if (defaultBar) {
updateBarConfig(defaultBar.id, { updateBarConfig(defaultBar.id, {
visible: !defaultBar.visible "visible": !defaultBar.visible
}); });
} }
} }
@@ -1345,6 +1346,42 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
return settings ? JSON.parse(JSON.stringify(settings)) : {}; return settings ? JSON.parse(JSON.stringify(settings)) : {};
} }
function getNiriOutputSetting(outputId, key, defaultValue) {
if (!niriOutputSettings[outputId])
return defaultValue;
return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
}
function setNiriOutputSetting(outputId, key, value) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
if (!updated[outputId])
updated[outputId] = {};
updated[outputId][key] = value;
niriOutputSettings = updated;
saveSettings();
}
function getNiriOutputSettings(outputId) {
const settings = niriOutputSettings[outputId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function setNiriOutputSettings(outputId, settings) {
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
updated[outputId] = settings;
niriOutputSettings = updated;
saveSettings();
}
function removeNiriOutputSettings(outputId) {
if (!niriOutputSettings[outputId])
return;
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
delete updated[outputId];
niriOutputSettings = updated;
saveSettings();
}
ListModel { ListModel {
id: leftWidgetsModel id: leftWidgetsModel
} }

View File

@@ -258,6 +258,7 @@ var SPEC = {
displayNameMode: { def: "system" }, displayNameMode: { def: "system" },
screenPreferences: { def: {} }, screenPreferences: { def: {} },
showOnLastDisplay: { def: {} }, showOnLastDisplay: { def: {} },
niriOutputSettings: { def: {} },
barConfigs: { def: [{ barConfigs: { def: [{
id: "default", id: "default",

View File

@@ -32,9 +32,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -48,9 +47,8 @@ FocusScope {
sourceComponent: TimeWeatherTab {} sourceComponent: TimeWeatherTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -66,9 +64,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -84,9 +81,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -100,9 +96,8 @@ FocusScope {
sourceComponent: WorkspacesTab {} sourceComponent: WorkspacesTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -118,9 +113,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -134,9 +128,8 @@ FocusScope {
sourceComponent: DisplayConfigTab {} sourceComponent: DisplayConfigTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -150,9 +143,8 @@ FocusScope {
sourceComponent: GammaControlTab {} sourceComponent: GammaControlTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -166,9 +158,8 @@ FocusScope {
sourceComponent: DisplayWidgetsTab {} sourceComponent: DisplayWidgetsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -182,9 +173,8 @@ FocusScope {
sourceComponent: NetworkTab {} sourceComponent: NetworkTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -198,9 +188,8 @@ FocusScope {
sourceComponent: PrinterTab {} sourceComponent: PrinterTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -214,9 +203,8 @@ FocusScope {
sourceComponent: LauncherTab {} sourceComponent: LauncherTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -230,9 +218,8 @@ FocusScope {
sourceComponent: ThemeColorsTab {} sourceComponent: ThemeColorsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -246,9 +233,8 @@ FocusScope {
sourceComponent: LockScreenTab {} sourceComponent: LockScreenTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -264,9 +250,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -280,9 +265,8 @@ FocusScope {
sourceComponent: AboutTab {} sourceComponent: AboutTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -296,9 +280,8 @@ FocusScope {
sourceComponent: TypographyMotionTab {} sourceComponent: TypographyMotionTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -312,9 +295,8 @@ FocusScope {
sourceComponent: SoundsTab {} sourceComponent: SoundsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -328,9 +310,8 @@ FocusScope {
sourceComponent: MediaPlayerTab {} sourceComponent: MediaPlayerTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -344,9 +325,8 @@ FocusScope {
sourceComponent: NotificationsTab {} sourceComponent: NotificationsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -360,9 +340,8 @@ FocusScope {
sourceComponent: OSDTab {} sourceComponent: OSDTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -376,9 +355,8 @@ FocusScope {
sourceComponent: RunningAppsTab {} sourceComponent: RunningAppsTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -392,9 +370,8 @@ FocusScope {
sourceComponent: SystemUpdaterTab {} sourceComponent: SystemUpdaterTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -408,9 +385,8 @@ FocusScope {
sourceComponent: PowerSleepTab {} sourceComponent: PowerSleepTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -426,9 +402,8 @@ FocusScope {
} }
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
@@ -442,9 +417,8 @@ FocusScope {
sourceComponent: ClipboardTab {} sourceComponent: ClipboardTab {}
onActiveChanged: { onActiveChanged: {
if (active && item) { if (active && item)
Qt.callLater(() => item.forceActiveFocus()); Qt.callLater(() => item.forceActiveFocus());
}
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -32,15 +32,17 @@ StyledRect {
readonly property bool collapsed: collapsible && !expanded readonly property bool collapsed: collapsible && !expanded
readonly property bool hasHeader: root.title !== "" || root.iconName !== "" readonly property bool hasHeader: root.title !== "" || root.iconName !== ""
property bool animationsEnabled: false property bool userToggledCollapse: false
Component.onCompleted: Qt.callLater(() => animationsEnabled = true)
Behavior on height { Behavior on height {
enabled: root.animationsEnabled enabled: root.userToggledCollapse
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
onRunningChanged: {
if (!running)
root.userToggledCollapse = false;
}
} }
} }
@@ -98,6 +100,7 @@ StyledRect {
onClicked: { onClicked: {
if (!root.collapsible) if (!root.collapsible)
return; return;
root.userToggledCollapse = true;
root.expanded = !root.expanded; root.expanded = !root.expanded;
} }
} }
@@ -108,14 +111,6 @@ StyledRect {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: !root.collapsed visible: !root.collapsed
opacity: root.collapsed ? 0 : 1
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
} }
} }

View File

@@ -580,7 +580,6 @@ Singleton {
const windowIndex = windows.findIndex(w => w.id === data.id); const windowIndex = windows.findIndex(w => w.id === data.id);
if (windowIndex < 0) if (windowIndex < 0)
return; return;
const updatedWindows = [...windows]; const updatedWindows = [...windows];
const updatedWindow = {}; const updatedWindow = {};
for (let prop in updatedWindows[windowIndex]) { for (let prop in updatedWindows[windowIndex]) {
@@ -1140,8 +1139,15 @@ Singleton {
for (const outputName in data) { for (const outputName in data) {
const output = data[outputName]; const output = data[outputName];
const identifier = getOutputIdentifier(output, outputName); const identifier = getOutputIdentifier(output, outputName);
const niriSettings = SettingsData.getNiriOutputSettings(identifier);
kdlContent += `output "${identifier}" {\n`; kdlContent += `output "${identifier}" {\n`;
if (niriSettings.disabled) {
kdlContent += ` off\n}\n\n`;
continue;
}
if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) { if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) {
const mode = output.modes[output.current_mode]; const mode = output.modes[output.current_mode];
kdlContent += ` mode "${mode.width}x${mode.height}@${(mode.refresh_rate / 1000).toFixed(3)}"\n`; kdlContent += ` mode "${mode.width}x${mode.height}@${(mode.refresh_rate / 1000).toFixed(3)}"\n`;
@@ -1172,9 +1178,21 @@ Singleton {
} }
if (output.vrr_enabled) { if (output.vrr_enabled) {
kdlContent += ` variable-refresh-rate\n`; const vrrOnDemand = niriSettings.vrrOnDemand ?? false;
kdlContent += vrrOnDemand ? ` variable-refresh-rate on-demand=true\n` : ` variable-refresh-rate\n`;
} }
if (niriSettings.focusAtStartup) {
kdlContent += ` focus-at-startup\n`;
}
if (niriSettings.backdropColor) {
kdlContent += ` backdrop-color "${niriSettings.backdropColor}"\n`;
}
kdlContent += generateHotCornersBlock(niriSettings);
kdlContent += generateLayoutBlock(niriSettings);
kdlContent += `}\n\n`; kdlContent += `}\n\n`;
} }
@@ -1191,6 +1209,49 @@ Singleton {
}); });
} }
function generateHotCornersBlock(niriSettings) {
if (!niriSettings.hotCorners)
return "";
const hc = niriSettings.hotCorners;
if (hc.off)
return ` hot-corners {\n off\n }\n`;
const corners = hc.corners || [];
if (corners.length === 0)
return "";
let block = ` hot-corners {\n`;
for (const corner of corners) {
block += ` ${corner}\n`;
}
block += ` }\n`;
return block;
}
function generateLayoutBlock(niriSettings) {
if (!niriSettings.layout)
return "";
const layout = niriSettings.layout;
const hasSettings = layout.gaps !== undefined || layout.defaultColumnWidth || layout.presetColumnWidths || layout.alwaysCenterSingleColumn !== undefined;
if (!hasSettings)
return "";
let block = ` layout {\n`;
if (layout.gaps !== undefined)
block += ` gaps ${layout.gaps}\n`;
if (layout.defaultColumnWidth?.type === "proportion")
block += ` default-column-width { proportion ${layout.defaultColumnWidth.value}; }\n`;
if (layout.presetColumnWidths && layout.presetColumnWidths.length > 0) {
block += ` preset-column-widths {\n`;
for (const preset of layout.presetColumnWidths) {
if (preset.type === "proportion")
block += ` proportion ${preset.value}\n`;
}
block += ` }\n`;
}
if (layout.alwaysCenterSingleColumn !== undefined)
block += layout.alwaysCenterSingleColumn ? ` always-center-single-column\n` : ` always-center-single-column false\n`;
block += ` }\n`;
return block;
}
IpcHandler { IpcHandler {
function screenshot(): string { function screenshot(): string {
if (!CompositorService.isNiri) { if (!CompositorService.isNiri) {

View File

@@ -16,7 +16,7 @@ Rectangle {
property int buttonHeight: 40 property int buttonHeight: 40
property int horizontalPadding: Theme.spacingL property int horizontalPadding: Theme.spacingL
signal clicked() signal clicked
width: Math.max(contentRow.implicitWidth + horizontalPadding * 2, 64) width: Math.max(contentRow.implicitWidth + horizontalPadding * 2, 64)
height: buttonHeight height: buttonHeight
@@ -29,9 +29,11 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
radius: parent.radius radius: parent.radius
color: { color: {
if (pressed) return Theme.primaryPressed if (pressed)
if (hovered) return Theme.primaryHover return Theme.primaryPressed;
return "transparent" if (hovered)
return Theme.primaryHover;
return "transparent";
} }
Behavior on color { Behavior on color {

View File

@@ -18,6 +18,7 @@ Flow {
property int buttonPadding: Theme.spacingL property int buttonPadding: Theme.spacingL
property int checkIconSize: Theme.iconSizeSmall property int checkIconSize: Theme.iconSizeSmall
property int textSize: Theme.fontSizeMedium property int textSize: Theme.fontSizeMedium
property bool userInteracted: false
signal selectionChanged(int index, bool selected) signal selectionChanged(int index, bool selected)
signal animationCompleted() signal animationCompleted()
@@ -27,7 +28,10 @@ Flow {
Timer { Timer {
id: animationTimer id: animationTimer
interval: Theme.shortDuration interval: Theme.shortDuration
onTriggered: root.animationCompleted() onTriggered: {
root.userInteracted = false;
root.animationCompleted();
}
} }
function isSelected(index) { function isSelected(index) {
@@ -38,6 +42,7 @@ Flow {
} }
function selectItem(index) { function selectItem(index) {
userInteracted = true;
if (multiSelect) { if (multiSelect) {
const modelValue = model[index] const modelValue = model[index]
let newSelection = [...currentSelection] let newSelection = [...currentSelection]
@@ -93,6 +98,7 @@ Flow {
bottomRightRadius: (isLast || selected) ? Theme.cornerRadius : 4 bottomRightRadius: (isLast || selected) ? Theme.cornerRadius : 4
Behavior on width { Behavior on width {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -100,6 +106,7 @@ Flow {
} }
Behavior on topLeftRadius { Behavior on topLeftRadius {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -107,6 +114,7 @@ Flow {
} }
Behavior on topRightRadius { Behavior on topRightRadius {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -114,6 +122,7 @@ Flow {
} }
Behavior on bottomLeftRadius { Behavior on bottomLeftRadius {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -121,6 +130,7 @@ Flow {
} }
Behavior on bottomRightRadius { Behavior on bottomRightRadius {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -128,6 +138,7 @@ Flow {
} }
Behavior on color { Behavior on color {
enabled: root.userInteracted
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -176,6 +187,7 @@ Flow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Behavior on opacity { Behavior on opacity {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -183,6 +195,7 @@ Flow {
} }
Behavior on scale { Behavior on scale {
enabled: root.userInteracted
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing