mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-04 19:42:08 -04:00
Compare commits
3 Commits
672754b0b5
...
displaycon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9673078a75 | ||
|
|
9e8c93bfd7 | ||
|
|
43d6f4b1d3 |
71
.github/workflows/run-obs.yml
vendored
71
.github/workflows/run-obs.yml
vendored
@@ -7,14 +7,13 @@ on:
|
||||
description: "Package to update (dms, dms-git, or all)"
|
||||
required: false
|
||||
default: "all"
|
||||
tag_version:
|
||||
description: "Specific tag version for dms stable (e.g., v1.0.2). Leave empty to auto-detect latest release."
|
||||
required: false
|
||||
default: ""
|
||||
rebuild_release:
|
||||
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
|
||||
required: false
|
||||
default: ""
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
schedule:
|
||||
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
||||
|
||||
@@ -98,7 +97,7 @@ jobs:
|
||||
# Rebuild requested - always proceed
|
||||
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
|
||||
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
|
||||
|
||||
elif [[ "$PKG" == "all" ]]; then
|
||||
# Check each package and build list of those needing updates
|
||||
@@ -162,51 +161,16 @@ jobs:
|
||||
id: packages
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
# Tag push event - use the pushed tag
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by tag: $VERSION"
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
# Scheduled run - dms-git only
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by schedule: updating git package"
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
# Manual workflow dispatch
|
||||
|
||||
# Determine version for dms stable
|
||||
if [[ "${{ github.event.inputs.package }}" == "dms" ]]; then
|
||||
# For explicit dms selection, require tag_version
|
||||
if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
|
||||
VERSION="${{ github.event.inputs.tag_version }}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Using specified tag: $VERSION"
|
||||
else
|
||||
echo "ERROR: tag_version is required when package=dms"
|
||||
echo "Please specify a tag version (e.g., v1.0.2) or use package=all for auto-detection"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "${{ github.event.inputs.package }}" == "all" ]]; then
|
||||
# For "all", auto-detect if tag_version not specified
|
||||
if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
|
||||
VERSION="${{ github.event.inputs.tag_version }}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Using specified tag: $VERSION"
|
||||
else
|
||||
# Auto-detect latest release for "all"
|
||||
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
|
||||
if [[ -n "$LATEST_TAG" ]]; then
|
||||
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
echo "Auto-detected latest release: $LATEST_TAG"
|
||||
else
|
||||
echo "ERROR: Could not auto-detect latest release"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Use filtered packages from check-updates when package="all" and no rebuild/tag specified
|
||||
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ -z "${{ github.event.inputs.tag_version }}" ]]; then
|
||||
# Use filtered packages from check-updates when package="all" and no rebuild requested
|
||||
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
|
||||
else
|
||||
@@ -222,7 +186,7 @@ jobs:
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
||||
|
||||
@@ -243,14 +207,14 @@ jobs:
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
||||
|
||||
# Single changelog entry (git snapshots don't need history)
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
{
|
||||
echo "dms-git (${NEW_VERSION}db1) nightly; urgency=medium"
|
||||
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
|
||||
echo ""
|
||||
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||
echo ""
|
||||
@@ -262,15 +226,10 @@ jobs:
|
||||
run: |
|
||||
VERSION="${{ steps.packages.outputs.version }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
echo "==> Updating packaging files to version: $VERSION_NO_V"
|
||||
echo "Updating packaging to version $VERSION_NO_V"
|
||||
|
||||
# Update spec file
|
||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
||||
|
||||
# Verify the update
|
||||
UPDATED_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1)
|
||||
echo "✓ Spec file now shows Version: $UPDATED_VERSION"
|
||||
|
||||
# Single changelog entry (full history on OBS website)
|
||||
DATE_STR=$(date "+%a %b %d %Y")
|
||||
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
||||
@@ -297,13 +256,13 @@ jobs:
|
||||
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
{
|
||||
echo "dms (${VERSION_NO_V}db1) stable; urgency=medium"
|
||||
echo "dms ($VERSION_NO_V) stable; urgency=medium"
|
||||
echo ""
|
||||
echo " * Update to $VERSION stable release"
|
||||
echo ""
|
||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||
} > "distro/debian/dms/debian/changelog"
|
||||
echo "✓ Updated Debian changelog to ${VERSION_NO_V}db1"
|
||||
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
||||
fi
|
||||
|
||||
- name: Install Go
|
||||
@@ -330,7 +289,6 @@ jobs:
|
||||
- name: Upload to OBS
|
||||
env:
|
||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||
TAG_VERSION: ${{ steps.packages.outputs.version }}
|
||||
run: |
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
|
||||
@@ -342,7 +300,6 @@ jobs:
|
||||
MESSAGE="Automated update from GitHub Actions"
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
||||
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
|
||||
fi
|
||||
|
||||
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
||||
@@ -352,7 +309,7 @@ jobs:
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Uploading $PKG to OBS..."
|
||||
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||
echo "🔄 Using rebuild release number: db$REBUILD_RELEASE"
|
||||
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
@@ -393,7 +350,7 @@ jobs:
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||
echo "**Rebuild Number:** db${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -96,7 +96,7 @@ go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env*
|
||||
.env
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
This file is more of a quick reference so I know what to account for before next releases.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
- Added clipboard and clipboard history integration
|
||||
- Added swipe to dismiss notification popups and from center
|
||||
- Added paste from clipboard history view - requires wtype
|
||||
- Optimize surface damage of OSD & Toast
|
||||
- Add monitor configurator (niri, Hyprland, MangoWC)
|
||||
- **BREAKING** ghostty theme changed to ~/.config/ghostty/themes/danktheme
|
||||
- requires intervention and doc update
|
||||
|
||||
@@ -9,7 +9,7 @@ Type=dbus
|
||||
BusName=org.freedesktop.Notifications
|
||||
ExecStart=/usr/bin/dms run --session
|
||||
ExecReload=/usr/bin/pkill -USR1 -x dms
|
||||
Restart=on-failure
|
||||
Restart=always
|
||||
RestartSec=1.23
|
||||
TimeoutStopSec=10
|
||||
|
||||
|
||||
@@ -14,63 +14,34 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
||||
|
||||
## System Integration
|
||||
|
||||
### Wayland Protocols (Client)
|
||||
**Wayland Protocols**
|
||||
- `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
|
||||
|
||||
All Wayland protocols are consumed as a client - connecting to the compositor.
|
||||
**DBus Interfaces**
|
||||
- NetworkManager/iwd - Network management
|
||||
- logind - Session control and inhibit locks
|
||||
- accountsservice - User account information
|
||||
- CUPS - Printer management
|
||||
- Custom IPC via unix socket (JSON API)
|
||||
|
||||
| Protocol | Purpose |
|
||||
| ----------------------------------------- | ----------------------------------------------------------- |
|
||||
| `wlr-gamma-control-unstable-v1` | Night mode color temperature control |
|
||||
| `wlr-screencopy-unstable-v1` | Screen capture for color picker/screenshot |
|
||||
| `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
|
||||
**Hardware Control**
|
||||
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
||||
- Backlight control - Internal display brightness via `login1` or sysfs
|
||||
- LED control - Keyboard/device LED management
|
||||
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
|
||||
|
||||
**Plugin System**
|
||||
- Plugin registry integration
|
||||
- Plugin lifecycle management
|
||||
- Settings persistence
|
||||
|
||||
## CLI Commands
|
||||
|
||||
- `dms run [-d]` - Start shell (optionally as daemon)
|
||||
- `dms restart` / `dms kill` - Manage running processes
|
||||
- `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.)
|
||||
@@ -99,7 +70,6 @@ The on-screen preview displays the selected format. JSON output includes hex, RG
|
||||
Requires Go 1.24+
|
||||
|
||||
**Development build:**
|
||||
|
||||
```bash
|
||||
make # Build dms CLI
|
||||
make dankinstall # Build installer
|
||||
@@ -107,7 +77,6 @@ make test # Run tests
|
||||
```
|
||||
|
||||
**Distribution build:**
|
||||
|
||||
```bash
|
||||
make dist # Build without update/greeter features
|
||||
```
|
||||
@@ -115,7 +84,6 @@ make dist # Build without update/greeter features
|
||||
Produces `bin/dms-linux-amd64` and `bin/dms-linux-arm64`
|
||||
|
||||
**Installation:**
|
||||
|
||||
```bash
|
||||
sudo make install # Install to /usr/local/bin/dms
|
||||
```
|
||||
@@ -123,7 +91,6 @@ sudo make install # Install to /usr/local/bin/dms
|
||||
## Development
|
||||
|
||||
**Setup pre-commit hooks:**
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
@@ -131,7 +98,6 @@ git config core.hooksPath .githooks
|
||||
This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
|
||||
|
||||
**Regenerating Wayland Protocol Bindings:**
|
||||
|
||||
```bash
|
||||
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 \
|
||||
@@ -139,7 +105,6 @@ go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
|
||||
```
|
||||
|
||||
**Module Structure:**
|
||||
|
||||
- `cmd/` - Binary entrypoints (dms, dankinstall)
|
||||
- `internal/distros/` - Distribution-specific installation logic
|
||||
- `internal/proto/` - Wayland protocol bindings
|
||||
|
||||
@@ -220,7 +220,7 @@ func getBaseVersion() string {
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return "1.0.2"
|
||||
return "0.6.2"
|
||||
}
|
||||
|
||||
func startDebugServer() error {
|
||||
|
||||
@@ -18,25 +18,6 @@ import (
|
||||
|
||||
type ipcTargets map[string]map[string][]string
|
||||
|
||||
// getProcessExitCode returns the exit code from a ProcessState.
|
||||
// For normal exits, returns the exit code directly.
|
||||
// For signal termination, returns 128 + signal number (Unix convention).
|
||||
func getProcessExitCode(state *os.ProcessState) int {
|
||||
if state == nil {
|
||||
return 1
|
||||
}
|
||||
if code := state.ExitCode(); code != -1 {
|
||||
return code
|
||||
}
|
||||
// Process was killed by signal - extract signal number
|
||||
if status, ok := state.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() {
|
||||
return 128 + int(status.Signal())
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var isSessionManaged bool
|
||||
|
||||
func execDetachedRestart(targetPID int) {
|
||||
@@ -233,28 +214,14 @@ func runShellInteractive(session bool) {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Handle SIGUSR1 restart for non-session managed processes
|
||||
if sig == syscall.SIGUSR1 && !isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
// Exit immediately to avoid race conditions with detached restart
|
||||
return
|
||||
}
|
||||
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
log.Infof("\nReceived signal %v, shutting down...", sig)
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
@@ -268,7 +235,7 @@ func runShellInteractive(session bool) {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,28 +440,15 @@ func runShellDaemon(session bool) {
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Handle SIGUSR1 restart for non-session managed processes
|
||||
if sig == syscall.SIGUSR1 && !isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
// Exit immediately to avoid race conditions with detached restart
|
||||
return
|
||||
}
|
||||
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
// All other signals: clean shutdown
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
@@ -506,7 +460,7 @@ func runShellDaemon(session bool) {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,13 +265,7 @@ func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
|
||||
|
||||
colorResult := DeploymentResult{
|
||||
ConfigType: "Ghostty Colors",
|
||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "themes", "dankcolors"),
|
||||
}
|
||||
|
||||
themesDir := filepath.Dir(colorResult.Path)
|
||||
if err := os.MkdirAll(themesDir, 0755); err != nil {
|
||||
mainResult.Error = fmt.Errorf("failed to create themes directory: %w", err)
|
||||
return []DeploymentResult{mainResult}, mainResult.Error
|
||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config-dankcolors"),
|
||||
}
|
||||
|
||||
if err := os.WriteFile(colorResult.Path, []byte(GhosttyColorConfig), 0644); err != nil {
|
||||
|
||||
@@ -468,7 +468,7 @@ func TestHyprlandConfigStructure(t *testing.T) {
|
||||
func TestGhosttyConfigStructure(t *testing.T) {
|
||||
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
||||
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
||||
assert.Contains(t, GhosttyConfig, "theme = dankcolors")
|
||||
assert.Contains(t, GhosttyConfig, "config-file = ./config-dankcolors")
|
||||
}
|
||||
|
||||
func TestGhosttyColorConfigStructure(t *testing.T) {
|
||||
|
||||
@@ -48,4 +48,4 @@ keybind = shift+enter=text:\n
|
||||
gtk-single-instance = true
|
||||
|
||||
# Dank color generation
|
||||
theme = dankcolors
|
||||
config-file = ./config-dankcolors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dms-git (1.0.2+git2528.d336866fdb1) nightly; urgency=medium
|
||||
dms-git (1.0.2+git2528.d336866f) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2528: d336866f)
|
||||
|
||||
@@ -16,6 +16,23 @@ dms-git (1.0.2+git2518.a783d650) nightly; urgency=medium
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 15:11:40 +0000
|
||||
|
||||
dms-git (1.0.2+git2510.0f89886c) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2510: 0f89886c)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:46:43 +0000
|
||||
|
||||
dms-git (1.0.2+git2507.b2ac9c6c) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2507: b2ac9c6c)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:18:05 +0000
|
||||
|
||||
dms-git (1.0.2+git2505.82f881af) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2505: 82f881af)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 05:55:03 +0000
|
||||
|
||||
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium
|
||||
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.3.tar.gz</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.2.tar.gz</param>
|
||||
<param name="filename">dms-source.tar.gz</param>
|
||||
</service>
|
||||
<!-- Download amd64 binary -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-amd64.gz</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-amd64.gz</param>
|
||||
</service>
|
||||
<!-- Download arm64 binary -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-arm64.gz</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param>
|
||||
</service>
|
||||
</services>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
dms (1.0.3db1) unstable; urgency=medium
|
||||
dms (1.0.2ppa6) unstable; urgency=medium
|
||||
|
||||
* Update to v1.0.3 stable release
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Mon, 16 Dec 2025 10:00:00 +0000
|
||||
|
||||
dms (1.0.2db1) unstable; urgency=medium
|
||||
|
||||
* Update to v1.0.2 stable release
|
||||
* Rebuild to fix repository metadata issues
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:47:39 +0000
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: dms
|
||||
Version: 1.0.3
|
||||
Release: 1%{?dist}
|
||||
Version: 1.0.2
|
||||
Release: 7%{?dist}
|
||||
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
License: MIT
|
||||
@@ -105,9 +105,6 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
|
||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
%changelog
|
||||
* Mon Dec 16 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.3-1
|
||||
- Update to stable v1.0.3 release
|
||||
|
||||
* Fri Dec 12 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.2-1
|
||||
- Update to stable v1.0.2 release
|
||||
- Bug fixes and improvements
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
|
||||
# ./distro/scripts/obs-upload.sh debian dms
|
||||
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
||||
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with db2 suffix
|
||||
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with db2 suffix (flag syntax)
|
||||
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with ppa2 suffix
|
||||
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with ppa2 suffix (flag syntax)
|
||||
|
||||
set -e
|
||||
|
||||
@@ -126,8 +126,8 @@ check_obs_version_exists() {
|
||||
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
|
||||
# Commit hash check for -git packages
|
||||
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then
|
||||
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(db[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(db[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
NEW_COMMIT=$(echo "$VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
||||
|
||||
if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
|
||||
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)"
|
||||
@@ -279,8 +279,7 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||
|
||||
# Apply rebuild suffix if specified (must happen before API check)
|
||||
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/db[0-9]*$//')
|
||||
CHANGELOG_VERSION="${BASE_VERSION}db${REBUILD_RELEASE}"
|
||||
CHANGELOG_VERSION="${CHANGELOG_VERSION}ppa${REBUILD_RELEASE}"
|
||||
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
||||
fi
|
||||
|
||||
@@ -308,16 +307,12 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||
else
|
||||
# Rebuild number specified - check if this exact version already exists (exact mode)
|
||||
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
|
||||
echo "==> Version $CHANGELOG_VERSION already exists in OBS"
|
||||
echo " This exact version (including db${REBUILD_RELEASE}) is already uploaded."
|
||||
echo " Skipping upload - nothing to do."
|
||||
echo ""
|
||||
echo " 💡 To rebuild with a different release number, try incrementing:"
|
||||
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS"
|
||||
echo " This exact version (including ppa${REBUILD_RELEASE}) is already uploaded."
|
||||
echo " To rebuild with a different release number, try incrementing:"
|
||||
NEXT_NUM=$((REBUILD_RELEASE + 1))
|
||||
echo " REBUILD_RELEASE=$NEXT_NUM"
|
||||
echo ""
|
||||
echo "✓ Exiting gracefully (no changes needed)"
|
||||
exit 0
|
||||
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -516,7 +511,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
|
||||
if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
|
||||
SOURCE_URL="${URL_PROTOCOL}://${URL_HOST}${URL_PATH}"
|
||||
echo "==> Downloading source from: $SOURCE_URL"
|
||||
echo " Downloading source from: $SOURCE_URL"
|
||||
|
||||
if wget -q -O "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null ||
|
||||
curl -L -f -s -o "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null; then
|
||||
@@ -539,17 +534,9 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
fi
|
||||
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||
echo "ERROR: Failed to return to REPO_ROOT. Expected: $REPO_ROOT, Got: $(pwd)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: Failed to download source from $SOURCE_URL"
|
||||
echo "Attempted both wget and curl"
|
||||
echo "Please check:"
|
||||
echo " 1. URL is accessible: $SOURCE_URL"
|
||||
echo " 2. _service file has correct version"
|
||||
echo " 3. GitHub releases are available"
|
||||
echo "Error: Failed to download source from $SOURCE_URL"
|
||||
echo "Tried both wget and curl. Please check the URL and network connectivity."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -566,7 +553,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Found source directory: $SOURCE_DIR"
|
||||
echo " Found source directory: $SOURCE_DIR"
|
||||
|
||||
# Vendor Go dependencies for dms-git
|
||||
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then
|
||||
@@ -725,10 +712,6 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
TARBALL_BASE=$(basename "$SOURCE_DIR")
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||
echo "ERROR: Failed to return to REPO_ROOT after tarball creation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGE" == "dms" ]]; then
|
||||
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1)
|
||||
@@ -740,10 +723,6 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
||||
rm -f "$WORK_DIR/$COMBINED_TARBALL"
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||
echo "ERROR: Failed to return to REPO_ROOT after tarball recreation"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -817,29 +796,23 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> Ensuring we're in the OSC working directory"
|
||||
cd "$WORK_DIR" || {
|
||||
echo "ERROR: Cannot cd to WORK_DIR: $WORK_DIR"
|
||||
echo "DEBUG: Current directory: $(pwd)"
|
||||
echo "DEBUG: WORK_DIR exists: $(test -d "$WORK_DIR" && echo "yes" || echo "no")"
|
||||
exit 1
|
||||
}
|
||||
echo "DEBUG: Successfully entered WORK_DIR: $(pwd)"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
# Server-side cleanup via API
|
||||
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
|
||||
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
|
||||
if [[ -n "$OBS_FILES" ]]; then
|
||||
DELETED_COUNT=0
|
||||
KEEP_CURRENT=""
|
||||
KEEP_PATTERN=""
|
||||
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||
KEEP_CURRENT="${PACKAGE}_${CHANGELOG_VERSION}.tar.gz"
|
||||
echo " Keeping only current version: ${KEEP_CURRENT}"
|
||||
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
||||
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}"
|
||||
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
|
||||
fi
|
||||
|
||||
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
|
||||
if [[ "$old_file" == "$KEEP_CURRENT" ]]; then
|
||||
echo " - Keeping: $old_file"
|
||||
if [[ -n "$KEEP_PATTERN" ]] && [[ "$old_file" == ${KEEP_PATTERN}* ]]; then
|
||||
echo " - Keeping current version: $old_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -862,11 +835,14 @@ else
|
||||
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
|
||||
fi
|
||||
|
||||
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
|
||||
# Fallback update with --server-side-source-service-files flag only syncs metadata (spec, dsc, _service)
|
||||
echo "==> Updating working copy"
|
||||
if ! osc up 2>/dev/null; then
|
||||
echo "Error: Failed to update working copy"
|
||||
exit 1
|
||||
if ! osc up --server-side-source-service-files 2>/dev/null; then
|
||||
echo " Note: Using regular update (--server-side-source-service-files not supported)"
|
||||
if ! osc up; then
|
||||
echo "Error: Failed to update working copy"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure we're in WORK_DIR and it exists
|
||||
@@ -906,15 +882,6 @@ elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [[ "$(pwd)" != "$WORK_DIR" ]]; then
|
||||
echo "ERROR: Lost directory context. Expected: $WORK_DIR, Got: $(pwd)"
|
||||
cd "$WORK_DIR" || {
|
||||
echo "FATAL: Cannot recover - unable to cd to WORK_DIR"
|
||||
exit 1
|
||||
}
|
||||
echo "WARNING: Recovered directory context"
|
||||
fi
|
||||
|
||||
osc addremove 2>&1 | grep -v "Git SCM package" || true
|
||||
|
||||
SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
|
||||
@@ -941,7 +908,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
|
||||
else
|
||||
echo "==> Committing to OBS"
|
||||
set +e
|
||||
osc commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
osc commit -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
COMMIT_EXIT=${PIPESTATUS[0]}
|
||||
set -e
|
||||
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
@@ -360,49 +360,47 @@ Singleton {
|
||||
property string displayNameMode: "system"
|
||||
property var screenPreferences: ({})
|
||||
property var showOnLastDisplay: ({})
|
||||
property var niriOutputSettings: ({})
|
||||
property var hyprlandOutputSettings: ({})
|
||||
|
||||
property var barConfigs: [
|
||||
{
|
||||
"id": "default",
|
||||
"name": "Main Bar",
|
||||
"enabled": true,
|
||||
"position": 0,
|
||||
"screenPreferences": ["all"],
|
||||
"showOnLastDisplay": true,
|
||||
"leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
|
||||
"centerWidgets": ["music", "clock", "weather"],
|
||||
"rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
|
||||
"spacing": 4,
|
||||
"innerPadding": 4,
|
||||
"bottomGap": 0,
|
||||
"transparency": 1.0,
|
||||
"widgetTransparency": 1.0,
|
||||
"squareCorners": false,
|
||||
"noBackground": false,
|
||||
"gothCornersEnabled": false,
|
||||
"gothCornerRadiusOverride": false,
|
||||
"gothCornerRadiusValue": 12,
|
||||
"borderEnabled": false,
|
||||
"borderColor": "surfaceText",
|
||||
"borderOpacity": 1.0,
|
||||
"borderThickness": 1,
|
||||
"widgetOutlineEnabled": false,
|
||||
"widgetOutlineColor": "primary",
|
||||
"widgetOutlineOpacity": 1.0,
|
||||
"widgetOutlineThickness": 1,
|
||||
"fontScale": 1.0,
|
||||
"autoHide": false,
|
||||
"autoHideDelay": 250,
|
||||
"openOnOverview": false,
|
||||
"visible": true,
|
||||
"popupGapsAuto": true,
|
||||
"popupGapsManual": 4,
|
||||
"maximizeDetection": true,
|
||||
"scrollEnabled": true,
|
||||
"scrollXBehavior": "column",
|
||||
"scrollYBehavior": "workspace"
|
||||
id: "default",
|
||||
name: "Main Bar",
|
||||
enabled: true,
|
||||
position: 0,
|
||||
screenPreferences: ["all"],
|
||||
showOnLastDisplay: true,
|
||||
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
|
||||
centerWidgets: ["music", "clock", "weather"],
|
||||
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
|
||||
spacing: 4,
|
||||
innerPadding: 4,
|
||||
bottomGap: 0,
|
||||
transparency: 1.0,
|
||||
widgetTransparency: 1.0,
|
||||
squareCorners: false,
|
||||
noBackground: false,
|
||||
gothCornersEnabled: false,
|
||||
gothCornerRadiusOverride: false,
|
||||
gothCornerRadiusValue: 12,
|
||||
borderEnabled: false,
|
||||
borderColor: "surfaceText",
|
||||
borderOpacity: 1.0,
|
||||
borderThickness: 1,
|
||||
widgetOutlineEnabled: false,
|
||||
widgetOutlineColor: "primary",
|
||||
widgetOutlineOpacity: 1.0,
|
||||
widgetOutlineThickness: 1,
|
||||
fontScale: 1.0,
|
||||
autoHide: false,
|
||||
autoHideDelay: 250,
|
||||
openOnOverview: false,
|
||||
visible: true,
|
||||
popupGapsAuto: true,
|
||||
popupGapsManual: 4,
|
||||
maximizeDetection: true,
|
||||
scrollEnabled: true,
|
||||
scrollXBehavior: "column",
|
||||
scrollYBehavior: "workspace"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -460,25 +458,25 @@ Singleton {
|
||||
|
||||
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
|
||||
settings_file="$config_dir/settings.ini"
|
||||
if [ -f "$settings_file" ]; then
|
||||
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
|
||||
settings_file="$config_dir/settings.ini"
|
||||
if [ -f "$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
|
||||
if grep -q "\\[Settings\\]" "$settings_file"; then
|
||||
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
|
||||
else
|
||||
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
|
||||
if grep -q "\\[Settings\\]" "$settings_file"; then
|
||||
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
|
||||
else
|
||||
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
else
|
||||
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
|
||||
pkill -HUP -f 'gtk' 2>/dev/null || true`;
|
||||
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
|
||||
pkill -HUP -f 'gtk' 2>/dev/null || true`;
|
||||
|
||||
Quickshell.execDetached(["sh", "-lc", configScript]);
|
||||
}
|
||||
@@ -491,36 +489,36 @@ Singleton {
|
||||
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
|
||||
|
||||
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
|
||||
update_qt_icon_theme() {
|
||||
local config_file="$1"
|
||||
local theme_name="$2"
|
||||
if [ -f "$config_file" ]; then
|
||||
if grep -q "^\\[Appearance\\]" "$config_file"; then
|
||||
if grep -q "^icon_theme=" "$config_file"; then
|
||||
update_qt_icon_theme() {
|
||||
local config_file="$1"
|
||||
local theme_name="$2"
|
||||
if [ -f "$config_file" ]; then
|
||||
if grep -q "^\\[Appearance\\]" "$config_file"; then
|
||||
if grep -q "^icon_theme=" "$config_file"; then
|
||||
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
|
||||
else
|
||||
else
|
||||
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
|
||||
fi
|
||||
else
|
||||
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
|
||||
fi
|
||||
else
|
||||
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
|
||||
fi
|
||||
}
|
||||
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
|
||||
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
|
||||
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
|
||||
fi
|
||||
else
|
||||
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
|
||||
fi
|
||||
else
|
||||
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
|
||||
fi
|
||||
}
|
||||
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
|
||||
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
|
||||
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
|
||||
|
||||
Quickshell.execDetached(["sh", "-lc", script]);
|
||||
}
|
||||
|
||||
readonly property var _hooks: ({
|
||||
"applyStoredTheme": applyStoredTheme,
|
||||
"regenSystemThemes": regenSystemThemes,
|
||||
"updateNiriLayout": updateNiriLayout,
|
||||
"applyStoredIconTheme": applyStoredIconTheme,
|
||||
"updateBarConfigs": updateBarConfigs
|
||||
applyStoredTheme: applyStoredTheme,
|
||||
regenSystemThemes: regenSystemThemes,
|
||||
updateNiriLayout: updateNiriLayout,
|
||||
applyStoredIconTheme: applyStoredIconTheme,
|
||||
updateBarConfigs: updateBarConfigs
|
||||
})
|
||||
|
||||
function set(key, value) {
|
||||
@@ -725,7 +723,7 @@ Singleton {
|
||||
let leftBar = 0;
|
||||
let rightBar = 0;
|
||||
|
||||
for (var i = 0; i < enabledBars.length; i++) {
|
||||
for (let i = 0; i < enabledBars.length; i++) {
|
||||
const other = enabledBars[i];
|
||||
if (other.id === barConfig.id)
|
||||
continue;
|
||||
@@ -795,7 +793,7 @@ Singleton {
|
||||
|
||||
if (barConfig) {
|
||||
const enabledBars = getEnabledBarConfigs();
|
||||
for (var i = 0; i < enabledBars.length; i++) {
|
||||
for (let i = 0; i < enabledBars.length; i++) {
|
||||
const other = enabledBars[i];
|
||||
if (other.id === barConfig.id)
|
||||
continue;
|
||||
@@ -927,7 +925,7 @@ Singleton {
|
||||
const conflicts = [];
|
||||
const enabledBars = getEnabledBarConfigs();
|
||||
|
||||
for (var i = 0; i < enabledBars.length; i++) {
|
||||
for (let i = 0; i < enabledBars.length; i++) {
|
||||
const other = enabledBars[i];
|
||||
if (other.id === barId)
|
||||
continue;
|
||||
@@ -940,9 +938,9 @@ Singleton {
|
||||
const hasAll = barScreens.includes("all") || otherScreens.includes("all");
|
||||
if (hasAll) {
|
||||
conflicts.push({
|
||||
"barId": other.id,
|
||||
"barName": other.name,
|
||||
"reason": "Same position on all screens"
|
||||
barId: other.id,
|
||||
barName: other.name,
|
||||
reason: "Same position on all screens"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -950,9 +948,9 @@ Singleton {
|
||||
const overlapping = barScreens.some(screen => otherScreens.includes(screen));
|
||||
if (overlapping) {
|
||||
conflicts.push({
|
||||
"barId": other.id,
|
||||
"barName": other.name,
|
||||
"reason": "Same position on overlapping screens"
|
||||
barId: other.id,
|
||||
barName: other.name,
|
||||
reason: "Same position on overlapping screens"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -974,7 +972,7 @@ Singleton {
|
||||
|
||||
function getScreensSortedByPosition() {
|
||||
const screens = [];
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
screens.push(Quickshell.screens[i]);
|
||||
}
|
||||
screens.sort((a, b) => {
|
||||
@@ -991,7 +989,7 @@ Singleton {
|
||||
const sorted = getScreensSortedByPosition();
|
||||
let modelCount = 0;
|
||||
let screenIndex = -1;
|
||||
for (var i = 0; i < sorted.length; i++) {
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
if (sorted[i].model === screen.model) {
|
||||
if (sorted[i].name === screen.name) {
|
||||
screenIndex = modelCount;
|
||||
@@ -1189,7 +1187,7 @@ Singleton {
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"spacing": spacing
|
||||
spacing: spacing
|
||||
});
|
||||
}
|
||||
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
|
||||
@@ -1218,7 +1216,7 @@ Singleton {
|
||||
return;
|
||||
}
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"position": position
|
||||
position: position
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1226,7 +1224,7 @@ Singleton {
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"leftWidgets": order
|
||||
leftWidgets: order
|
||||
});
|
||||
updateListModel(leftWidgetsModel, order);
|
||||
}
|
||||
@@ -1236,7 +1234,7 @@ Singleton {
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"centerWidgets": order
|
||||
centerWidgets: order
|
||||
});
|
||||
updateListModel(centerWidgetsModel, order);
|
||||
}
|
||||
@@ -1246,7 +1244,7 @@ Singleton {
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"rightWidgets": order
|
||||
rightWidgets: order
|
||||
});
|
||||
updateListModel(rightWidgetsModel, order);
|
||||
}
|
||||
@@ -1259,9 +1257,9 @@ Singleton {
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"leftWidgets": defaultLeft,
|
||||
"centerWidgets": defaultCenter,
|
||||
"rightWidgets": defaultRight
|
||||
leftWidgets: defaultLeft,
|
||||
centerWidgets: defaultCenter,
|
||||
rightWidgets: defaultRight
|
||||
});
|
||||
}
|
||||
updateListModel(leftWidgetsModel, defaultLeft);
|
||||
@@ -1309,7 +1307,7 @@ Singleton {
|
||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||
if (defaultBar) {
|
||||
updateBarConfig(defaultBar.id, {
|
||||
"visible": !defaultBar.visible
|
||||
visible: !defaultBar.visible
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1347,87 +1345,6 @@ Singleton {
|
||||
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();
|
||||
}
|
||||
|
||||
function getHyprlandOutputSetting(outputId, key, defaultValue) {
|
||||
if (!hyprlandOutputSettings[outputId])
|
||||
return defaultValue;
|
||||
return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
|
||||
}
|
||||
|
||||
function setHyprlandOutputSetting(outputId, key, value) {
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
if (!updated[outputId])
|
||||
updated[outputId] = {};
|
||||
updated[outputId][key] = value;
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeHyprlandOutputSetting(outputId, key) {
|
||||
if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
delete updated[outputId][key];
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getHyprlandOutputSettings(outputId) {
|
||||
const settings = hyprlandOutputSettings[outputId];
|
||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
||||
}
|
||||
|
||||
function setHyprlandOutputSettings(outputId, settings) {
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
updated[outputId] = settings;
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeHyprlandOutputSettings(outputId) {
|
||||
if (!hyprlandOutputSettings[outputId])
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
||||
delete updated[outputId];
|
||||
hyprlandOutputSettings = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: leftWidgetsModel
|
||||
}
|
||||
|
||||
@@ -918,7 +918,6 @@ Singleton {
|
||||
|
||||
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
|
||||
const colors = {};
|
||||
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
|
||||
|
||||
function addColor(matugenKey, darkVal, lightVal) {
|
||||
if (!darkVal && !lightVal)
|
||||
@@ -931,7 +930,7 @@ Singleton {
|
||||
"color": String(lightVal || darkVal)
|
||||
},
|
||||
"default": {
|
||||
"color": String((isLight && lightVal) ? lightVal : darkVal)
|
||||
"color": String(darkVal || lightVal)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -258,8 +258,6 @@ var SPEC = {
|
||||
displayNameMode: { def: "system" },
|
||||
screenPreferences: { def: {} },
|
||||
showOnLastDisplay: { def: {} },
|
||||
niriOutputSettings: { def: {} },
|
||||
hyprlandOutputSettings: { def: {} },
|
||||
|
||||
barConfigs: { def: [{
|
||||
id: "default",
|
||||
|
||||
@@ -24,26 +24,26 @@ DankModal {
|
||||
repeat: true
|
||||
running: root.shouldBeVisible
|
||||
onTriggered: {
|
||||
root.countdown--;
|
||||
root.countdown--
|
||||
if (root.countdown <= 0) {
|
||||
root.reverted();
|
||||
root.close();
|
||||
root.reverted()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
countdown = 10;
|
||||
countdownTimer.start();
|
||||
countdown = 10
|
||||
countdownTimer.start()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
countdownTimer.stop();
|
||||
countdownTimer.stop()
|
||||
}
|
||||
|
||||
onBackgroundClicked: {
|
||||
root.reverted();
|
||||
root.close();
|
||||
root.reverted()
|
||||
root.close()
|
||||
}
|
||||
|
||||
content: Component {
|
||||
@@ -55,15 +55,15 @@ DankModal {
|
||||
implicitHeight: mainColumn.implicitHeight
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.reverted();
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
root.reverted()
|
||||
root.close()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: event => {
|
||||
root.confirmed();
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
root.confirmed()
|
||||
root.close()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -149,8 +149,8 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.reverted();
|
||||
root.close();
|
||||
root.reverted()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,8 +178,8 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.confirmed();
|
||||
root.close();
|
||||
root.confirmed()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,8 +203,8 @@ DankModal {
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
root.reverted();
|
||||
root.close();
|
||||
root.reverted()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,9 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +48,9 @@ FocusScope {
|
||||
sourceComponent: TimeWeatherTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +66,9 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +84,9 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +100,9 @@ FocusScope {
|
||||
sourceComponent: WorkspacesTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +118,9 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,8 +134,9 @@ FocusScope {
|
||||
sourceComponent: DisplayConfigTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +150,9 @@ FocusScope {
|
||||
sourceComponent: GammaControlTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +166,9 @@ FocusScope {
|
||||
sourceComponent: DisplayWidgetsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +182,9 @@ FocusScope {
|
||||
sourceComponent: NetworkTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,8 +198,9 @@ FocusScope {
|
||||
sourceComponent: PrinterTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,8 +214,9 @@ FocusScope {
|
||||
sourceComponent: LauncherTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +230,9 @@ FocusScope {
|
||||
sourceComponent: ThemeColorsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +246,9 @@ FocusScope {
|
||||
sourceComponent: LockScreenTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,8 +264,9 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,8 +280,9 @@ FocusScope {
|
||||
sourceComponent: AboutTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,8 +296,9 @@ FocusScope {
|
||||
sourceComponent: TypographyMotionTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +312,9 @@ FocusScope {
|
||||
sourceComponent: SoundsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,8 +328,9 @@ FocusScope {
|
||||
sourceComponent: MediaPlayerTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,8 +344,9 @@ FocusScope {
|
||||
sourceComponent: NotificationsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,8 +360,9 @@ FocusScope {
|
||||
sourceComponent: OSDTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,8 +376,9 @@ FocusScope {
|
||||
sourceComponent: RunningAppsTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,8 +392,9 @@ FocusScope {
|
||||
sourceComponent: SystemUpdaterTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,8 +408,9 @@ FocusScope {
|
||||
sourceComponent: PowerSleepTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,8 +426,9 @@ FocusScope {
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,8 +442,9 @@ FocusScope {
|
||||
sourceComponent: ClipboardTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
if (active && item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkspaceIndex(modelData, index) {
|
||||
function getWorkspaceIndex(modelData) {
|
||||
let isPlaceholder;
|
||||
if (root.useExtWorkspace) {
|
||||
isPlaceholder = modelData?.hidden === true;
|
||||
@@ -976,7 +976,7 @@ Item {
|
||||
StyledText {
|
||||
id: wsIndexText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.getWorkspaceIndex(modelData, index)
|
||||
text: root.getWorkspaceIndex(modelData)
|
||||
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||
@@ -1203,12 +1203,12 @@ Item {
|
||||
Loader {
|
||||
id: indexLoader
|
||||
anchors.fill: parent
|
||||
active: SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
|
||||
active: !isPlaceholder && SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
|
||||
sourceComponent: Item {
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
return root.getWorkspaceIndex(modelData, index);
|
||||
return root.getWorkspaceIndex(modelData);
|
||||
}
|
||||
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||
|
||||
@@ -94,10 +94,11 @@ Item {
|
||||
|
||||
Timer {
|
||||
id: hoverDelayTwo
|
||||
interval: 300
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
refreshButtonTooltipTwo.show(I18n.tr("Refresh Weather"), refreshButtonTwo, 0, 0, "left");
|
||||
const p = refreshButtonMouseAreaTwo.mapToItem(null, parent.width / 2, parent.height + Theme.spacingXS);
|
||||
refreshButtonTooltipTwo.show(I18n.tr("Refresh Weather"), p.x, p.y, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +118,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankTooltipV2 {
|
||||
DankTooltip {
|
||||
id: refreshButtonTooltipTwo
|
||||
}
|
||||
|
||||
@@ -819,10 +820,11 @@ Item {
|
||||
|
||||
Timer {
|
||||
id: hoverDelay
|
||||
interval: 300
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
refreshButtonTooltip.show(I18n.tr("Refresh Weather"), refreshButton, 0, 0, "left");
|
||||
const p = refreshButtonMouseArea.mapToItem(null, parent.width / 2, parent.height + Theme.spacingXS);
|
||||
refreshButtonTooltip.show(I18n.tr("Refresh Weather"), p.x, p.y, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -842,7 +844,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankTooltipV2 {
|
||||
DankTooltip {
|
||||
id: refreshButtonTooltip
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,9 @@ Item {
|
||||
property bool longPressing: false
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
property real dragAxisOffset: 0
|
||||
property point dragOffset: Qt.point(0, 0)
|
||||
property int targetIndex: -1
|
||||
property int originalIndex: -1
|
||||
property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||
property bool showWindowTitle: false
|
||||
property string windowTitle: ""
|
||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||
@@ -96,7 +95,7 @@ Item {
|
||||
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
|
||||
}
|
||||
onIsHoveredChanged: {
|
||||
if (mouseArea.pressed || dragging)
|
||||
if (mouseArea.pressed)
|
||||
return;
|
||||
if (isHovered) {
|
||||
exitAnimation.stop();
|
||||
@@ -129,8 +128,8 @@ Item {
|
||||
running: false
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "hoverAnimOffset"
|
||||
target: iconTransform
|
||||
property: animateX ? "x" : "y"
|
||||
to: animationDirection * animationDistance * 0.25
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
@@ -138,8 +137,8 @@ Item {
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "hoverAnimOffset"
|
||||
target: iconTransform
|
||||
property: animateX ? "x" : "y"
|
||||
to: animationDirection * animationDistance * 0.2
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
@@ -151,8 +150,8 @@ Item {
|
||||
id: exitAnimation
|
||||
|
||||
running: false
|
||||
target: root
|
||||
property: "hoverAnimOffset"
|
||||
target: iconTransform
|
||||
property: animateX ? "x" : "y"
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
@@ -188,79 +187,16 @@ Item {
|
||||
}
|
||||
onReleased: mouse => {
|
||||
longPressTimer.stop();
|
||||
|
||||
const wasDragging = dragging;
|
||||
const didReorder = wasDragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps;
|
||||
|
||||
if (didReorder)
|
||||
dockApps.movePinnedApp(originalIndex, targetIndex);
|
||||
|
||||
longPressing = false;
|
||||
dragging = false;
|
||||
dragAxisOffset = 0;
|
||||
targetIndex = -1;
|
||||
originalIndex = -1;
|
||||
|
||||
if (dockApps && !didReorder) {
|
||||
dockApps.draggedIndex = -1;
|
||||
dockApps.dropTargetIndex = -1;
|
||||
}
|
||||
|
||||
if (wasDragging || mouse.button !== Qt.LeftButton)
|
||||
return;
|
||||
|
||||
handleLeftClick();
|
||||
}
|
||||
|
||||
function handleLeftClick() {
|
||||
if (!appData)
|
||||
return;
|
||||
|
||||
switch (appData.type) {
|
||||
case "pinned":
|
||||
if (!appData.appId)
|
||||
return;
|
||||
const pinnedEntry = cachedDesktopEntry;
|
||||
if (pinnedEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
"id": appData.appId,
|
||||
"name": pinnedEntry.name || appData.appId,
|
||||
"icon": pinnedEntry.icon || "",
|
||||
"exec": pinnedEntry.exec || "",
|
||||
"comment": pinnedEntry.comment || ""
|
||||
});
|
||||
if (longPressing) {
|
||||
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
|
||||
dockApps.movePinnedApp(originalIndex, targetIndex);
|
||||
}
|
||||
SessionService.launchDesktopEntry(pinnedEntry);
|
||||
break;
|
||||
case "window":
|
||||
const windowToplevel = getToplevelObject();
|
||||
if (windowToplevel)
|
||||
windowToplevel.activate();
|
||||
break;
|
||||
case "grouped":
|
||||
if (appData.windowCount === 0) {
|
||||
if (!appData.appId)
|
||||
return;
|
||||
const groupedEntry = cachedDesktopEntry;
|
||||
if (groupedEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
"id": appData.appId,
|
||||
"name": groupedEntry.name || appData.appId,
|
||||
"icon": groupedEntry.icon || "",
|
||||
"exec": groupedEntry.exec || "",
|
||||
"comment": groupedEntry.comment || ""
|
||||
});
|
||||
}
|
||||
SessionService.launchDesktopEntry(groupedEntry);
|
||||
} else if (appData.windowCount === 1) {
|
||||
const groupedToplevel = getToplevelObject();
|
||||
if (groupedToplevel)
|
||||
groupedToplevel.activate();
|
||||
} else if (contextMenu) {
|
||||
const shouldHidePin = appData.appId === "org.quickshell";
|
||||
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||
}
|
||||
break;
|
||||
|
||||
longPressing = false;
|
||||
dragging = false;
|
||||
dragOffset = Qt.point(0, 0);
|
||||
targetIndex = -1;
|
||||
originalIndex = -1;
|
||||
}
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
@@ -270,47 +206,90 @@ Item {
|
||||
dragging = true;
|
||||
targetIndex = index;
|
||||
originalIndex = index;
|
||||
if (dockApps) {
|
||||
dockApps.draggedIndex = index;
|
||||
dockApps.dropTargetIndex = index;
|
||||
}
|
||||
}
|
||||
if (dragging) {
|
||||
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y);
|
||||
if (dockApps) {
|
||||
const threshold = actualIconSize;
|
||||
let newTargetIndex = targetIndex;
|
||||
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
|
||||
newTargetIndex = targetIndex + 1;
|
||||
} else if (dragOffset.x < -threshold && targetIndex > 0) {
|
||||
newTargetIndex = targetIndex - 1;
|
||||
}
|
||||
if (newTargetIndex !== targetIndex) {
|
||||
targetIndex = newTargetIndex;
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dragging || !dockApps)
|
||||
return;
|
||||
|
||||
const axisOffset = isVertical ? (mouse.y - dragStartPos.y) : (mouse.x - dragStartPos.x);
|
||||
dragAxisOffset = axisOffset;
|
||||
|
||||
const spacing = Math.min(8, Math.max(4, actualIconSize * 0.08));
|
||||
const itemSize = actualIconSize * 1.2 + spacing;
|
||||
const slotOffset = Math.round(axisOffset / itemSize);
|
||||
const newTargetIndex = Math.max(0, Math.min(dockApps.pinnedAppCount - 1, originalIndex + slotOffset));
|
||||
|
||||
if (newTargetIndex !== targetIndex) {
|
||||
targetIndex = newTargetIndex;
|
||||
dockApps.dropTargetIndex = newTargetIndex;
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (!appData)
|
||||
if (!appData || longPressing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.MiddleButton) {
|
||||
switch (appData.type) {
|
||||
case "window":
|
||||
appData.toplevel?.close();
|
||||
break;
|
||||
case "grouped":
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (appData.type === "pinned") {
|
||||
if (appData && appData.appId) {
|
||||
const desktopEntry = cachedDesktopEntry;
|
||||
if (desktopEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
"id": appData.appId,
|
||||
"name": desktopEntry.name || appData.appId,
|
||||
"icon": desktopEntry.icon || "",
|
||||
"exec": desktopEntry.exec || "",
|
||||
"comment": desktopEntry.comment || ""
|
||||
});
|
||||
}
|
||||
SessionService.launchDesktopEntry(desktopEntry);
|
||||
}
|
||||
} else if (appData.type === "window") {
|
||||
const toplevel = getToplevelObject();
|
||||
if (toplevel) {
|
||||
toplevel.activate();
|
||||
}
|
||||
} else if (appData.type === "grouped") {
|
||||
if (appData.windowCount === 0) {
|
||||
if (appData && appData.appId) {
|
||||
const desktopEntry = cachedDesktopEntry;
|
||||
if (desktopEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
"id": appData.appId,
|
||||
"name": desktopEntry.name || appData.appId,
|
||||
"icon": desktopEntry.icon || "",
|
||||
"exec": desktopEntry.exec || "",
|
||||
"comment": desktopEntry.comment || ""
|
||||
});
|
||||
}
|
||||
SessionService.launchDesktopEntry(desktopEntry);
|
||||
}
|
||||
} else if (appData.windowCount === 1) {
|
||||
// For single window, activate directly
|
||||
const toplevel = getToplevelObject();
|
||||
if (toplevel) {
|
||||
console.log("Activating grouped app window:", appData.windowTitle);
|
||||
toplevel.activate();
|
||||
} else {
|
||||
console.warn("No toplevel found for grouped app");
|
||||
}
|
||||
} else {
|
||||
if (contextMenu) {
|
||||
const shouldHidePin = appData.appId === "org.quickshell";
|
||||
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (appData?.type === "window") {
|
||||
appData?.toplevel?.close();
|
||||
} else if (appData?.type === "grouped") {
|
||||
if (contextMenu) {
|
||||
const shouldHidePin = appData.appId === "org.quickshell";
|
||||
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!appData.appId)
|
||||
return;
|
||||
} else if (appData && appData.appId) {
|
||||
const desktopEntry = cachedDesktopEntry;
|
||||
if (desktopEntry) {
|
||||
AppUsageHistoryData.addAppUsage({
|
||||
@@ -322,39 +301,26 @@ Item {
|
||||
});
|
||||
}
|
||||
SessionService.launchDesktopEntry(desktopEntry);
|
||||
break;
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (!contextMenu)
|
||||
return;
|
||||
const shouldHidePin = appData.appId === "org.quickshell";
|
||||
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||
if (contextMenu && appData) {
|
||||
const shouldHidePin = appData.appId === "org.quickshell";
|
||||
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||
} else {
|
||||
console.warn("No context menu or appData available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property real hoverAnimOffset: 0
|
||||
|
||||
Item {
|
||||
id: visualContent
|
||||
anchors.fill: parent
|
||||
|
||||
transform: Translate {
|
||||
id: iconTransform
|
||||
x: {
|
||||
if (dragging && !isVertical)
|
||||
return dragAxisOffset;
|
||||
if (!dragging && isVertical)
|
||||
return hoverAnimOffset;
|
||||
return 0;
|
||||
}
|
||||
y: {
|
||||
if (dragging && isVertical)
|
||||
return dragAxisOffset;
|
||||
if (!dragging && !isVertical)
|
||||
return hoverAnimOffset;
|
||||
return 0;
|
||||
}
|
||||
x: 0
|
||||
y: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -14,9 +17,6 @@ Item {
|
||||
property bool isVertical: false
|
||||
property var dockScreen: null
|
||||
property real iconSize: 40
|
||||
property int draggedIndex: -1
|
||||
property int dropTargetIndex: -1
|
||||
property bool suppressShiftAnimation: false
|
||||
|
||||
clip: false
|
||||
implicitWidth: isVertical ? appLayout.height : appLayout.width
|
||||
@@ -24,18 +24,18 @@ Item {
|
||||
|
||||
function movePinnedApp(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const currentPinned = [...(SessionData.pinnedApps || [])];
|
||||
const currentPinned = [...(SessionData.pinnedApps || [])]
|
||||
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0 || toIndex >= currentPinned.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const movedApp = currentPinned.splice(fromIndex, 1)[0];
|
||||
currentPinned.splice(toIndex, 0, movedApp);
|
||||
const movedApp = currentPinned.splice(fromIndex, 1)[0]
|
||||
currentPinned.splice(toIndex, 0, movedApp)
|
||||
|
||||
SessionData.setPinnedApps(currentPinned);
|
||||
SessionData.setPinnedApps(currentPinned)
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -53,250 +53,202 @@ Item {
|
||||
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||
spacing: Math.min(8, Math.max(4, root.iconSize * 0.08))
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
property var dockItems: []
|
||||
property var dockItems: []
|
||||
|
||||
model: ScriptModel {
|
||||
values: repeater.dockItems
|
||||
objectProp: "uniqueKey"
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: repeater.dockItems
|
||||
objectProp: "uniqueKey"
|
||||
}
|
||||
|
||||
Component.onCompleted: updateModel()
|
||||
Component.onCompleted: updateModel()
|
||||
|
||||
function updateModel() {
|
||||
const items = [];
|
||||
const pinnedApps = [...(SessionData.pinnedApps || [])];
|
||||
const sortedToplevels = CompositorService.sortedToplevels;
|
||||
function updateModel() {
|
||||
const items = []
|
||||
const pinnedApps = [...(SessionData.pinnedApps || [])]
|
||||
const sortedToplevels = CompositorService.sortedToplevels
|
||||
|
||||
if (root.groupByApp) {
|
||||
const appGroups = new Map();
|
||||
if (root.groupByApp) {
|
||||
const appGroups = new Map()
|
||||
|
||||
pinnedApps.forEach(rawAppId => {
|
||||
const appId = Paths.moddedAppId(rawAppId);
|
||||
pinnedApps.forEach(rawAppId => {
|
||||
const appId = Paths.moddedAppId(rawAppId)
|
||||
appGroups.set(appId, {
|
||||
appId: appId,
|
||||
isPinned: true,
|
||||
windows: []
|
||||
})
|
||||
})
|
||||
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
const rawAppId = toplevel.appId || "unknown"
|
||||
const appId = Paths.moddedAppId(rawAppId)
|
||||
if (!appGroups.has(appId)) {
|
||||
appGroups.set(appId, {
|
||||
appId: appId,
|
||||
isPinned: true,
|
||||
isPinned: false,
|
||||
windows: []
|
||||
});
|
||||
});
|
||||
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
const rawAppId = toplevel.appId || "unknown";
|
||||
const appId = Paths.moddedAppId(rawAppId);
|
||||
if (!appGroups.has(appId)) {
|
||||
appGroups.set(appId, {
|
||||
appId: appId,
|
||||
isPinned: false,
|
||||
windows: []
|
||||
});
|
||||
}
|
||||
|
||||
appGroups.get(appId).windows.push({
|
||||
toplevel: toplevel,
|
||||
index: index
|
||||
});
|
||||
});
|
||||
|
||||
const pinnedGroups = [];
|
||||
const unpinnedGroups = [];
|
||||
|
||||
Array.from(appGroups.entries()).forEach(([appId, group]) => {
|
||||
const firstWindow = group.windows.length > 0 ? group.windows[0] : null;
|
||||
|
||||
const item = {
|
||||
uniqueKey: "grouped_" + appId,
|
||||
type: "grouped",
|
||||
appId: appId,
|
||||
toplevel: firstWindow ? firstWindow.toplevel : null,
|
||||
isPinned: group.isPinned,
|
||||
isRunning: group.windows.length > 0,
|
||||
windowCount: group.windows.length,
|
||||
allWindows: group.windows
|
||||
};
|
||||
|
||||
if (group.isPinned) {
|
||||
pinnedGroups.push(item);
|
||||
} else {
|
||||
unpinnedGroups.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
pinnedGroups.forEach(item => items.push(item));
|
||||
|
||||
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
|
||||
items.push({
|
||||
uniqueKey: "separator_grouped",
|
||||
type: "separator",
|
||||
appId: "__SEPARATOR__",
|
||||
toplevel: null,
|
||||
isPinned: false,
|
||||
isRunning: false
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unpinnedGroups.forEach(item => items.push(item));
|
||||
root.pinnedAppCount = pinnedGroups.length;
|
||||
} else {
|
||||
pinnedApps.forEach(rawAppId => {
|
||||
const appId = Paths.moddedAppId(rawAppId);
|
||||
items.push({
|
||||
uniqueKey: "pinned_" + appId,
|
||||
type: "pinned",
|
||||
appId: appId,
|
||||
toplevel: null,
|
||||
isPinned: true,
|
||||
isRunning: false
|
||||
});
|
||||
});
|
||||
appGroups.get(appId).windows.push({
|
||||
toplevel: toplevel,
|
||||
index: index
|
||||
})
|
||||
})
|
||||
|
||||
root.pinnedAppCount = pinnedApps.length;
|
||||
const pinnedGroups = []
|
||||
const unpinnedGroups = []
|
||||
|
||||
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
|
||||
items.push({
|
||||
uniqueKey: "separator_ungrouped",
|
||||
type: "separator",
|
||||
appId: "__SEPARATOR__",
|
||||
toplevel: null,
|
||||
isPinned: false,
|
||||
isRunning: false
|
||||
});
|
||||
Array.from(appGroups.entries()).forEach(([appId, group]) => {
|
||||
const firstWindow = group.windows.length > 0 ? group.windows[0] : null
|
||||
|
||||
const item = {
|
||||
uniqueKey: "grouped_" + appId,
|
||||
type: "grouped",
|
||||
appId: appId,
|
||||
toplevel: firstWindow ? firstWindow.toplevel : null,
|
||||
isPinned: group.isPinned,
|
||||
isRunning: group.windows.length > 0,
|
||||
windowCount: group.windows.length,
|
||||
allWindows: group.windows
|
||||
}
|
||||
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
let uniqueKey = "window_" + index;
|
||||
if (CompositorService.isHyprland && Hyprland.toplevels) {
|
||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values);
|
||||
for (let i = 0; i < hyprlandToplevels.length; i++) {
|
||||
if (hyprlandToplevels[i].wayland === toplevel) {
|
||||
uniqueKey = "window_" + hyprlandToplevels[i].address;
|
||||
break;
|
||||
}
|
||||
if (group.isPinned) {
|
||||
pinnedGroups.push(item)
|
||||
} else {
|
||||
unpinnedGroups.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
pinnedGroups.forEach(item => items.push(item))
|
||||
|
||||
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
|
||||
items.push({
|
||||
uniqueKey: "separator_grouped",
|
||||
type: "separator",
|
||||
appId: "__SEPARATOR__",
|
||||
toplevel: null,
|
||||
isPinned: false,
|
||||
isRunning: false
|
||||
})
|
||||
}
|
||||
|
||||
unpinnedGroups.forEach(item => items.push(item))
|
||||
root.pinnedAppCount = pinnedGroups.length
|
||||
} else {
|
||||
pinnedApps.forEach(rawAppId => {
|
||||
const appId = Paths.moddedAppId(rawAppId)
|
||||
items.push({
|
||||
uniqueKey: "pinned_" + appId,
|
||||
type: "pinned",
|
||||
appId: appId,
|
||||
toplevel: null,
|
||||
isPinned: true,
|
||||
isRunning: false
|
||||
})
|
||||
})
|
||||
|
||||
root.pinnedAppCount = pinnedApps.length
|
||||
|
||||
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
|
||||
items.push({
|
||||
uniqueKey: "separator_ungrouped",
|
||||
type: "separator",
|
||||
appId: "__SEPARATOR__",
|
||||
toplevel: null,
|
||||
isPinned: false,
|
||||
isRunning: false
|
||||
})
|
||||
}
|
||||
|
||||
sortedToplevels.forEach((toplevel, index) => {
|
||||
let uniqueKey = "window_" + index
|
||||
if (CompositorService.isHyprland && Hyprland.toplevels) {
|
||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
||||
for (let i = 0; i < hyprlandToplevels.length; i++) {
|
||||
if (hyprlandToplevels[i].wayland === toplevel) {
|
||||
uniqueKey = "window_" + hyprlandToplevels[i].address
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
uniqueKey: uniqueKey,
|
||||
type: "window",
|
||||
appId: Paths.moddedAppId(toplevel.appId),
|
||||
toplevel: toplevel,
|
||||
isPinned: false,
|
||||
isRunning: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dockItems = items;
|
||||
items.push({
|
||||
uniqueKey: uniqueKey,
|
||||
type: "window",
|
||||
appId: Paths.moddedAppId(toplevel.appId),
|
||||
toplevel: toplevel,
|
||||
isPinned: false,
|
||||
isRunning: true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
property alias dockButton: button
|
||||
property var itemData: modelData
|
||||
clip: false
|
||||
z: button.dragging ? 100 : 0
|
||||
dockItems = items
|
||||
}
|
||||
|
||||
width: itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2)
|
||||
height: itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize)
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
property alias dockButton: button
|
||||
property var itemData: modelData
|
||||
clip: false
|
||||
|
||||
property real shiftOffset: {
|
||||
if (root.draggedIndex < 0 || !itemData.isPinned || itemData.type === "separator")
|
||||
return 0;
|
||||
if (model.index === root.draggedIndex)
|
||||
return 0;
|
||||
width: itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2)
|
||||
height: itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize)
|
||||
|
||||
const dragIdx = root.draggedIndex;
|
||||
const dropIdx = root.dropTargetIndex;
|
||||
const myIdx = model.index;
|
||||
const shiftAmount = root.iconSize * 1.2 + layoutFlow.spacing;
|
||||
Rectangle {
|
||||
visible: itemData.type === "separator"
|
||||
width: root.isVertical ? root.iconSize * 0.5 : 2
|
||||
height: root.isVertical ? 2 : root.iconSize * 0.5
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
radius: 1
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
if (dropIdx < 0)
|
||||
return 0;
|
||||
if (dragIdx < dropIdx && myIdx > dragIdx && myIdx <= dropIdx)
|
||||
return -shiftAmount;
|
||||
if (dragIdx > dropIdx && myIdx >= dropIdx && myIdx < dragIdx)
|
||||
return shiftAmount;
|
||||
return 0;
|
||||
}
|
||||
DockAppButton {
|
||||
id: button
|
||||
visible: itemData.type !== "separator"
|
||||
anchors.centerIn: parent
|
||||
|
||||
transform: Translate {
|
||||
x: root.isVertical ? 0 : delegateItem.shiftOffset
|
||||
y: root.isVertical ? delegateItem.shiftOffset : 0
|
||||
width: delegateItem.width
|
||||
height: delegateItem.height
|
||||
actualIconSize: root.iconSize
|
||||
|
||||
Behavior on x {
|
||||
enabled: !root.suppressShiftAnimation
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
appData: itemData
|
||||
contextMenu: root.contextMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
parentDockScreen: root.dockScreen
|
||||
|
||||
Behavior on y {
|
||||
enabled: !root.suppressShiftAnimation
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: itemData.type === "separator"
|
||||
width: root.isVertical ? root.iconSize * 0.5 : 2
|
||||
height: root.isVertical ? 2 : root.iconSize * 0.5
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
radius: 1
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
DockAppButton {
|
||||
id: button
|
||||
visible: itemData.type !== "separator"
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: delegateItem.width
|
||||
height: delegateItem.height
|
||||
actualIconSize: root.iconSize
|
||||
|
||||
appData: itemData
|
||||
contextMenu: root.contextMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
parentDockScreen: root.dockScreen
|
||||
|
||||
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
||||
windowTitle: {
|
||||
const title = itemData?.toplevel?.title || "(Unnamed)";
|
||||
return title.length > 50 ? title.substring(0, 47) + "..." : title;
|
||||
}
|
||||
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
||||
windowTitle: {
|
||||
const title = itemData?.toplevel?.title || "(Unnamed)"
|
||||
return title.length > 50 ? title.substring(0, 47) + "..." : title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onToplevelsChanged() {
|
||||
repeater.updateModel();
|
||||
repeater.updateModel()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onPinnedAppsChanged() {
|
||||
root.suppressShiftAnimation = true;
|
||||
root.draggedIndex = -1;
|
||||
root.dropTargetIndex = -1;
|
||||
repeater.updateModel();
|
||||
Qt.callLater(() => {
|
||||
root.suppressShiftAnimation = false;
|
||||
});
|
||||
repeater.updateModel()
|
||||
}
|
||||
}
|
||||
|
||||
onGroupByAppChanged: {
|
||||
repeater.updateModel();
|
||||
repeater.updateModel()
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,275 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property string outputName: ""
|
||||
property var outputData: null
|
||||
property bool expanded: false
|
||||
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: headerRow.implicitHeight + Theme.spacingS * 2
|
||||
color: headerMouse.containsMouse ? Theme.withAlpha(Theme.primary, 0.1) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: root.expanded ? "expand_more" : "chevron_right"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Compositor Settings")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: headerMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.expanded = !root.expanded
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: root.expanded
|
||||
topPadding: Theme.spacingS
|
||||
|
||||
property int currentBitdepth: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "bitdepth", 8);
|
||||
}
|
||||
property bool is10Bit: currentBitdepth === 10
|
||||
|
||||
property string currentCm: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||
}
|
||||
property bool isHdrMode: currentCm === "hdr" || currentCm === "hdredid"
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("10-bit Color")
|
||||
description: I18n.tr("Enable 10-bit color depth for wider color gamut and HDR support")
|
||||
checked: settingsColumn.is10Bit
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "bitdepth", 10);
|
||||
} else {
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "bitdepth", null);
|
||||
if (settingsColumn.isHdrMode)
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: settingsColumn.is10Bit
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: I18n.tr("Color Gamut")
|
||||
addHorizontalPadding: true
|
||||
currentValue: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||
return cmLabelMap[val] || I18n.tr("Auto (Wide)");
|
||||
}
|
||||
options: [I18n.tr("Auto (Wide)"), I18n.tr("Wide (BT2020)"), "DCI-P3", "Apple P3", "Adobe RGB", "EDID", "HDR", I18n.tr("HDR (EDID)")]
|
||||
|
||||
property var cmValueMap: ({
|
||||
[I18n.tr("Auto (Wide)")]: "auto",
|
||||
[I18n.tr("Wide (BT2020)")]: "wide",
|
||||
"DCI-P3": "dcip3",
|
||||
"Apple P3": "dp3",
|
||||
"Adobe RGB": "adobe",
|
||||
"EDID": "edid",
|
||||
"HDR": "hdr",
|
||||
[I18n.tr("HDR (EDID)")]: "hdredid"
|
||||
})
|
||||
|
||||
property var cmLabelMap: ({
|
||||
"auto": I18n.tr("Auto (Wide)"),
|
||||
"wide": I18n.tr("Wide (BT2020)"),
|
||||
"dcip3": "DCI-P3",
|
||||
"dp3": "Apple P3",
|
||||
"adobe": "Adobe RGB",
|
||||
"edid": "EDID",
|
||||
"hdr": "HDR",
|
||||
"hdredid": I18n.tr("HDR (EDID)")
|
||||
})
|
||||
|
||||
onValueChanged: value => {
|
||||
const cmValue = cmValueMap[value] || "auto";
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "colorManagement", cmValue);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: warningColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius / 2
|
||||
color: Theme.withAlpha(Theme.warning, 0.15)
|
||||
border.color: Theme.withAlpha(Theme.warning, 0.3)
|
||||
border.width: 1
|
||||
visible: settingsColumn.isHdrMode
|
||||
|
||||
Column {
|
||||
id: warningColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
StyledText {
|
||||
text: I18n.tr("Experimental Feature")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
text: I18n.tr("HDR mode is experimental. Verify your monitor supports HDR before enabling.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: settingsColumn.isHdrMode
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("HDR Tone Mapping")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
leftPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("SDR Brightness")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: "1.0 - 2.0"
|
||||
text: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null);
|
||||
return val !== null ? val.toString() : "";
|
||||
}
|
||||
onEditingFinished: {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null);
|
||||
return;
|
||||
}
|
||||
const val = parseFloat(trimmed);
|
||||
if (isNaN(val) || val < 0.1 || val > 5.0)
|
||||
return;
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", parseFloat(val.toFixed(2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("SDR Saturation")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: "0.5 - 1.5"
|
||||
text: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null);
|
||||
return val !== null ? val.toString() : "";
|
||||
}
|
||||
onEditingFinished: {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null);
|
||||
return;
|
||||
}
|
||||
const val = parseFloat(trimmed);
|
||||
if (isNaN(val) || val < 0.0 || val > 3.0)
|
||||
return;
|
||||
DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", parseFloat(val.toFixed(2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: warningContent.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
readonly property bool showError: DisplayConfigState.includeStatus.exists && !DisplayConfigState.includeStatus.included
|
||||
readonly property bool showSetup: !DisplayConfigState.includeStatus.exists && !DisplayConfigState.includeStatus.included
|
||||
|
||||
color: (showError || showSetup) ? Theme.withAlpha(Theme.primary, 0.15) : "transparent"
|
||||
border.color: (showError || showSetup) ? Theme.withAlpha(Theme.primary, 0.3) : "transparent"
|
||||
border.width: 1
|
||||
visible: (showError || showSetup) && DisplayConfigState.hasOutputBackend && !DisplayConfigState.checkingInclude
|
||||
|
||||
Column {
|
||||
id: warningContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "warning"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.showSetup)
|
||||
return I18n.tr("First Time Setup");
|
||||
if (root.showError)
|
||||
return I18n.tr("Outputs Include Missing");
|
||||
return "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.showSetup)
|
||||
return I18n.tr("Click 'Setup' to create the outputs config and add include to your compositor config.");
|
||||
if (root.showError)
|
||||
return I18n.tr("dms/outputs config exists but is not included in your compositor config. Display changes won't persist.");
|
||||
return "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: fixButton
|
||||
visible: root.showError || root.showSetup
|
||||
text: {
|
||||
if (DisplayConfigState.fixingInclude)
|
||||
return I18n.tr("Fixing...");
|
||||
if (root.showSetup)
|
||||
return I18n.tr("Setup");
|
||||
return I18n.tr("Fix Now");
|
||||
}
|
||||
backgroundColor: Theme.primary
|
||||
textColor: Theme.primaryText
|
||||
enabled: !DisplayConfigState.fixingInclude
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: DisplayConfigState.fixOutputsInclude()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: 280
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
|
||||
Item {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
|
||||
property var bounds: DisplayConfigState.getOutputBounds()
|
||||
property real scaleFactor: {
|
||||
if (bounds.width === 0 || bounds.height === 0)
|
||||
return 0.1;
|
||||
const padding = Theme.spacingL * 2;
|
||||
const scaleX = (width - padding) / bounds.width;
|
||||
const scaleY = (height - padding) / bounds.height;
|
||||
return Math.min(scaleX, scaleY);
|
||||
}
|
||||
property point offset: Qt.point((width - bounds.width * scaleFactor) / 2 - bounds.minX * scaleFactor, (height - bounds.height * scaleFactor) / 2 - bounds.minY * scaleFactor)
|
||||
|
||||
Connections {
|
||||
target: DisplayConfigState
|
||||
function onAllOutputsChanged() {
|
||||
canvas.bounds = DisplayConfigState.getOutputBounds();
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DisplayConfigState.allOutputs ? Object.keys(DisplayConfigState.allOutputs) : []
|
||||
|
||||
delegate: MonitorRect {
|
||||
required property string modelData
|
||||
outputName: modelData
|
||||
outputData: DisplayConfigState.allOutputs[modelData]
|
||||
canvasScaleFactor: canvas.scaleFactor
|
||||
canvasOffset: canvas.offset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property string outputName
|
||||
required property var outputData
|
||||
required property real canvasScaleFactor
|
||||
required property point canvasOffset
|
||||
|
||||
property bool isConnected: outputData?.connected ?? false
|
||||
property bool isDragging: false
|
||||
property point originalLogical: Qt.point(0, 0)
|
||||
property point snappedLogical: Qt.point(0, 0)
|
||||
property bool isValidPosition: true
|
||||
|
||||
property var physSize: DisplayConfigState.getPhysicalSize(outputData)
|
||||
property var logicalSize: DisplayConfigState.getLogicalSize(outputData)
|
||||
|
||||
x: isDragging ? x : (outputData?.logical?.x ?? 0) * canvasScaleFactor + canvasOffset.x
|
||||
y: isDragging ? y : (outputData?.logical?.y ?? 0) * canvasScaleFactor + canvasOffset.y
|
||||
width: logicalSize.w * canvasScaleFactor
|
||||
height: logicalSize.h * canvasScaleFactor
|
||||
radius: Theme.cornerRadius
|
||||
opacity: isConnected ? 1.0 : 0.5
|
||||
|
||||
color: {
|
||||
if (!isConnected)
|
||||
return Theme.surfaceContainerHighest;
|
||||
if (!isValidPosition)
|
||||
return Theme.withAlpha(Theme.error, 0.3);
|
||||
if (isDragging)
|
||||
return Theme.withAlpha(Theme.primary, 0.4);
|
||||
if (dragArea.containsMouse)
|
||||
return Theme.withAlpha(Theme.primary, 0.2);
|
||||
return Theme.surfaceContainerHigh;
|
||||
}
|
||||
|
||||
border.color: {
|
||||
if (!isConnected)
|
||||
return Theme.outline;
|
||||
if (!isValidPosition)
|
||||
return Theme.error;
|
||||
if (isDragging)
|
||||
return Theme.primary;
|
||||
if (CompositorService.getFocusedScreen()?.name === outputName)
|
||||
return Theme.primary;
|
||||
return Theme.outline;
|
||||
}
|
||||
border.width: isDragging ? 3 : 2
|
||||
z: isDragging ? 100 : (isConnected ? 1 : 0)
|
||||
|
||||
Rectangle {
|
||||
id: snapPreview
|
||||
visible: root.isDragging && root.isValidPosition
|
||||
x: root.snappedLogical.x * root.canvasScaleFactor + root.canvasOffset.x - root.x
|
||||
y: root.snappedLogical.y * root.canvasScaleFactor + root.canvasOffset.y - root.y
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: 2
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
name: root.isConnected ? "desktop_windows" : "desktop_access_disabled"
|
||||
size: Math.min(24, Math.min(root.width * 0.3, root.height * 0.25))
|
||||
color: root.isConnected ? (root.isValidPosition ? Theme.primary : Theme.error) : Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DisplayConfigState.getOutputDisplayName(root.outputData, root.outputName)
|
||||
font.pixelSize: Math.max(10, Math.min(14, root.width * 0.12))
|
||||
font.weight: Font.Medium
|
||||
color: root.isConnected ? Theme.surfaceText : Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
elide: Text.ElideMiddle
|
||||
width: Math.min(implicitWidth, root.width - 8)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.isConnected ? (root.physSize.w + "x" + root.physSize.h) : I18n.tr("Disconnected")
|
||||
font.pixelSize: Math.max(8, Math.min(11, root.width * 0.09))
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: root.isConnected
|
||||
cursorShape: !root.isConnected ? Qt.ArrowCursor : (root.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor)
|
||||
drag.target: root.isConnected ? root : null
|
||||
drag.axis: Drag.XAndYAxis
|
||||
drag.threshold: 0
|
||||
|
||||
onPressed: mouse => {
|
||||
if (!root.isConnected)
|
||||
return;
|
||||
root.isDragging = true;
|
||||
root.originalLogical = Qt.point(root.outputData?.logical?.x ?? 0, root.outputData?.logical?.y ?? 0);
|
||||
root.snappedLogical = root.originalLogical;
|
||||
root.isValidPosition = true;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (!root.isDragging || !root.isConnected)
|
||||
return;
|
||||
let posX = Math.round((root.x - root.canvasOffset.x) / root.canvasScaleFactor);
|
||||
let posY = Math.round((root.y - root.canvasOffset.y) / root.canvasScaleFactor);
|
||||
|
||||
const size = DisplayConfigState.getLogicalSize(root.outputData);
|
||||
|
||||
const snapped = DisplayConfigState.snapToEdges(root.outputName, posX, posY, size.w, size.h);
|
||||
root.snappedLogical = snapped;
|
||||
root.isValidPosition = !DisplayConfigState.checkOverlap(root.outputName, snapped.x, snapped.y, size.w, size.h);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (!root.isDragging || !root.isConnected)
|
||||
return;
|
||||
root.isDragging = false;
|
||||
|
||||
const size = DisplayConfigState.getLogicalSize(root.outputData);
|
||||
const finalX = root.snappedLogical.x;
|
||||
const finalY = root.snappedLogical.y;
|
||||
|
||||
if (DisplayConfigState.checkOverlap(root.outputName, finalX, finalY, size.w, size.h)) {
|
||||
root.isValidPosition = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalX === root.originalLogical.x && finalY === root.originalLogical.y)
|
||||
return;
|
||||
|
||||
DisplayConfigState.initOriginalOutputs();
|
||||
DisplayConfigState.backendUpdateOutputPosition(root.outputName, finalX, finalY);
|
||||
DisplayConfigState.setPendingChange(root.outputName, "position", {
|
||||
"x": finalX,
|
||||
"y": finalY
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Drag.active: dragArea.drag.active && root.isConnected
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property string outputName: ""
|
||||
property var outputData: null
|
||||
property bool expanded: false
|
||||
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: headerRow.implicitHeight + Theme.spacingS * 2
|
||||
color: headerMouse.containsMouse ? Theme.withAlpha(Theme.primary, 0.1) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: root.expanded ? "expand_more" : "chevron_right"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Compositor Settings")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: headerMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.expanded = !root.expanded
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: root.expanded
|
||||
topPadding: Theme.spacingS
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Disable Output")
|
||||
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false)
|
||||
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "disabled", checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Focus at Startup")
|
||||
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "focusAtStartup", false)
|
||||
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "focusAtStartup", checked)
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: I18n.tr("Hot Corners")
|
||||
addHorizontalPadding: true
|
||||
|
||||
property var hotCornersData: {
|
||||
void (DisplayConfigState.pendingNiriChanges);
|
||||
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "hotCorners", null);
|
||||
}
|
||||
|
||||
currentValue: {
|
||||
if (!hotCornersData)
|
||||
return I18n.tr("Inherit");
|
||||
if (hotCornersData.off)
|
||||
return I18n.tr("Off");
|
||||
const corners = hotCornersData.corners || [];
|
||||
if (corners.length === 0)
|
||||
return I18n.tr("Inherit");
|
||||
if (corners.length === 4)
|
||||
return I18n.tr("All");
|
||||
return I18n.tr("Select...");
|
||||
}
|
||||
options: [I18n.tr("Inherit"), I18n.tr("Off"), I18n.tr("All"), I18n.tr("Select...")]
|
||||
|
||||
onValueChanged: value => {
|
||||
switch (value) {
|
||||
case I18n.tr("Inherit"):
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", null);
|
||||
break;
|
||||
case I18n.tr("Off"):
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||
"off": true
|
||||
});
|
||||
break;
|
||||
case I18n.tr("All"):
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||
"corners": ["top-left", "top-right", "bottom-left", "bottom-right"]
|
||||
});
|
||||
break;
|
||||
case I18n.tr("Select..."):
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||
"corners": []
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: hotCornersGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
property var hotCornersData: {
|
||||
void (DisplayConfigState.pendingNiriChanges);
|
||||
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "hotCorners", null);
|
||||
}
|
||||
|
||||
visible: hotCornersData && !hotCornersData.off && hotCornersData.corners !== undefined
|
||||
|
||||
DankButtonGroup {
|
||||
id: hotCornersGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
selectionMode: "multi"
|
||||
checkEnabled: false
|
||||
buttonHeight: 32
|
||||
buttonPadding: parent.width < 400 ? Theme.spacingXS : Theme.spacingM
|
||||
minButtonWidth: parent.width < 400 ? 28 : 56
|
||||
textSize: parent.width < 400 ? 11 : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Top Left"), I18n.tr("Top Right"), I18n.tr("Bottom Left"), I18n.tr("Bottom Right")]
|
||||
|
||||
property var cornerKeys: ["top-left", "top-right", "bottom-left", "bottom-right"]
|
||||
|
||||
currentSelection: {
|
||||
const hcData = parent.hotCornersData;
|
||||
if (!hcData?.corners)
|
||||
return [];
|
||||
return hcData.corners.map(key => {
|
||||
const idx = cornerKeys.indexOf(key);
|
||||
return idx >= 0 ? model[idx] : null;
|
||||
}).filter(v => v !== null);
|
||||
}
|
||||
|
||||
onSelectionChanged: (index, selected) => {
|
||||
const corners = currentSelection.map(label => {
|
||||
const idx = model.indexOf(label);
|
||||
return idx >= 0 ? cornerKeys[idx] : null;
|
||||
}).filter(v => v !== null);
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||
"corners": corners
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: layoutColumn.implicitHeight
|
||||
|
||||
Column {
|
||||
id: layoutColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Layout Overrides")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Override global layout settings for this output")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.withAlpha(Theme.surfaceVariantText, 0.7)
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Window Gaps (px)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: I18n.tr("Inherit")
|
||||
text: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||
if (layout?.gaps === undefined)
|
||||
return "";
|
||||
return layout.gaps.toString();
|
||||
}
|
||||
onEditingFinished: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
delete layout.gaps;
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||
return;
|
||||
}
|
||||
const val = parseInt(trimmed);
|
||||
if (isNaN(val) || val < 0)
|
||||
return;
|
||||
layout.gaps = val;
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Default Width (%)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: I18n.tr("Inherit")
|
||||
text: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||
if (!layout?.defaultColumnWidth)
|
||||
return "";
|
||||
if (layout.defaultColumnWidth.type !== "proportion")
|
||||
return "";
|
||||
const percent = layout.defaultColumnWidth.value * 100;
|
||||
return parseFloat(percent.toFixed(4)).toString();
|
||||
}
|
||||
onEditingFinished: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||
const trimmed = text.trim().replace("%", "");
|
||||
if (!trimmed) {
|
||||
delete layout.defaultColumnWidth;
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||
return;
|
||||
}
|
||||
const val = parseFloat(trimmed);
|
||||
if (isNaN(val) || val <= 0 || val > 100)
|
||||
return;
|
||||
layout.defaultColumnWidth = {
|
||||
"type": "proportion",
|
||||
"value": parseFloat((val / 100).toFixed(6))
|
||||
};
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Preset Widths (%)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "e.g. 33.33, 50, 66.67"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.withAlpha(Theme.surfaceVariantText, 0.7)
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: I18n.tr("Inherit")
|
||||
text: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||
const presets = layout?.presetColumnWidths || [];
|
||||
if (presets.length === 0)
|
||||
return "";
|
||||
return presets.filter(p => p.type === "proportion").map(p => parseFloat((p.value * 100).toFixed(4))).join(", ");
|
||||
}
|
||||
onEditingFinished: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
delete layout.presetColumnWidths;
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||
return;
|
||||
}
|
||||
const parts = trimmed.split(/[,\s]+/).filter(s => s);
|
||||
const presets = [];
|
||||
for (const part of parts) {
|
||||
const val = parseFloat(part.replace("%", ""));
|
||||
if (!isNaN(val) && val > 0 && val <= 100)
|
||||
presets.push({
|
||||
"type": "proportion",
|
||||
"value": parseFloat((val / 100).toFixed(6))
|
||||
});
|
||||
}
|
||||
if (presets.length === 0) {
|
||||
delete layout.presetColumnWidths;
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||
return;
|
||||
}
|
||||
presets.sort((a, b) => a.value - b.value);
|
||||
layout.presetColumnWidths = presets;
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Center Single Column")
|
||||
property var layoutData: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null)
|
||||
checked: layoutData?.alwaysCenterSingleColumn ?? false
|
||||
onToggled: checked => {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||
if (checked) {
|
||||
layout.alwaysCenterSingleColumn = true;
|
||||
} else {
|
||||
delete layout.alwaysCenterSingleColumn;
|
||||
}
|
||||
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: messageContent.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: messageContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "monitor"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Monitor Configuration")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Display configuration is not available. WLR output management protocol not supported.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
required property string outputName
|
||||
required property var outputData
|
||||
property bool isConnected: outputData?.connected ?? false
|
||||
|
||||
width: parent.width
|
||||
height: settingsColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, isConnected ? 0.5 : 0.3)
|
||||
border.color: Theme.withAlpha(Theme.outline, 0.3)
|
||||
border.width: 1
|
||||
opacity: isConnected ? 1.0 : 0.7
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.isConnected ? "desktop_windows" : "desktop_access_disabled"
|
||||
size: Theme.iconSize - 4
|
||||
color: root.isConnected ? Theme.primary : Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM - (disconnectedBadge.visible ? disconnectedBadge.width + Theme.spacingS : 0)
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: DisplayConfigState.getOutputDisplayName(root.outputData, root.outputName)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: root.isConnected ? Theme.surfaceText : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (root.outputData?.model ?? "") + (root.outputData?.make ? " - " + root.outputData.make : "")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: disconnectedBadge
|
||||
visible: !root.isConnected
|
||||
width: disconnectedText.implicitWidth + Theme.spacingM
|
||||
height: disconnectedText.implicitHeight + Theme.spacingXS
|
||||
radius: height / 2
|
||||
color: Theme.withAlpha(Theme.outline, 0.3)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
id: disconnectedText
|
||||
text: I18n.tr("Disconnected")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: I18n.tr("Resolution & Refresh")
|
||||
visible: root.isConnected
|
||||
currentValue: {
|
||||
const pendingMode = DisplayConfigState.getPendingValue(root.outputName, "mode");
|
||||
if (pendingMode)
|
||||
return pendingMode;
|
||||
const data = DisplayConfigState.outputs[root.outputName];
|
||||
if (!data?.modes || data?.current_mode === undefined)
|
||||
return "Auto";
|
||||
const mode = data.modes[data.current_mode];
|
||||
return mode ? DisplayConfigState.formatMode(mode) : "Auto";
|
||||
}
|
||||
options: {
|
||||
const data = DisplayConfigState.outputs[root.outputName];
|
||||
if (!data?.modes)
|
||||
return ["Auto"];
|
||||
const opts = [];
|
||||
for (var i = 0; i < data.modes.length; i++) {
|
||||
opts.push(DisplayConfigState.formatMode(data.modes[i]));
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "mode", value)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: !root.isConnected
|
||||
text: I18n.tr("Configuration will be preserved when this display reconnects")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: root.isConnected
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Scale")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Item {
|
||||
id: scaleContainer
|
||||
width: parent.width
|
||||
height: scaleDropdown.visible ? scaleDropdown.height : scaleInput.height
|
||||
|
||||
property bool customMode: false
|
||||
property string currentScale: {
|
||||
const pendingScale = DisplayConfigState.getPendingValue(root.outputName, "scale");
|
||||
if (pendingScale !== undefined)
|
||||
return parseFloat(pendingScale.toFixed(2)).toString();
|
||||
const scale = DisplayConfigState.outputs[root.outputName]?.logical?.scale ?? 1.0;
|
||||
return parseFloat(scale.toFixed(2)).toString();
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: scaleDropdown
|
||||
width: parent.width
|
||||
dropdownWidth: parent.width
|
||||
visible: !scaleContainer.customMode
|
||||
currentValue: scaleContainer.currentScale
|
||||
options: {
|
||||
const standard = ["0.5", "0.75", "1", "1.25", "1.5", "1.75", "2", "2.5", "3", I18n.tr("Custom...")];
|
||||
const current = scaleContainer.currentScale;
|
||||
if (standard.slice(0, -1).includes(current))
|
||||
return standard;
|
||||
const opts = [...standard.slice(0, -1), current, standard[standard.length - 1]];
|
||||
return opts.sort((a, b) => {
|
||||
if (a === I18n.tr("Custom..."))
|
||||
return 1;
|
||||
if (b === I18n.tr("Custom..."))
|
||||
return -1;
|
||||
return parseFloat(a) - parseFloat(b);
|
||||
});
|
||||
}
|
||||
onValueChanged: value => {
|
||||
if (value === I18n.tr("Custom...")) {
|
||||
scaleContainer.customMode = true;
|
||||
scaleInput.text = scaleContainer.currentScale;
|
||||
scaleInput.forceActiveFocus();
|
||||
scaleInput.selectAll();
|
||||
return;
|
||||
}
|
||||
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(value));
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: scaleInput
|
||||
width: parent.width
|
||||
height: 40
|
||||
visible: scaleContainer.customMode
|
||||
placeholderText: "0.5 - 4.0"
|
||||
|
||||
function applyValue() {
|
||||
const val = parseFloat(text);
|
||||
if (isNaN(val) || val < 0.25 || val > 4) {
|
||||
text = scaleContainer.currentScale;
|
||||
scaleContainer.customMode = false;
|
||||
return;
|
||||
}
|
||||
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(2)));
|
||||
scaleContainer.customMode = false;
|
||||
}
|
||||
|
||||
onAccepted: applyValue()
|
||||
onEditingFinished: applyValue()
|
||||
Keys.onEscapePressed: {
|
||||
text = scaleContainer.currentScale;
|
||||
scaleContainer.customMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Transform")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
dropdownWidth: parent.width
|
||||
currentValue: {
|
||||
const pendingTransform = DisplayConfigState.getPendingValue(root.outputName, "transform");
|
||||
if (pendingTransform)
|
||||
return DisplayConfigState.getTransformLabel(pendingTransform);
|
||||
const data = DisplayConfigState.outputs[root.outputName];
|
||||
return DisplayConfigState.getTransformLabel(data?.logical?.transform ?? "Normal");
|
||||
}
|
||||
options: [I18n.tr("Normal"), I18n.tr("90°"), I18n.tr("180°"), I18n.tr("270°"), I18n.tr("Flipped"), I18n.tr("Flipped 90°"), I18n.tr("Flipped 180°"), I18n.tr("Flipped 270°")]
|
||||
onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "transform", DisplayConfigState.getTransformValue(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Variable Refresh Rate")
|
||||
visible: root.isConnected && !CompositorService.isDwl && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||
checked: {
|
||||
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
|
||||
if (pendingVrr !== undefined)
|
||||
return pendingVrr;
|
||||
return DisplayConfigState.outputs[root.outputName]?.vrr_enabled ?? false;
|
||||
}
|
||||
onToggled: checked => DisplayConfigState.setPendingChange(root.outputName, "vrr", checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("VRR On-Demand")
|
||||
description: I18n.tr("VRR activates only when applications request it")
|
||||
visible: root.isConnected && CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "vrrOnDemand", false)
|
||||
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "vrrOnDemand", checked)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.withAlpha(Theme.outline, 0.2)
|
||||
visible: compositorSettingsLoader.active
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: compositorSettingsLoader
|
||||
width: parent.width
|
||||
active: root.isConnected && compositorSettingsSource !== ""
|
||||
source: compositorSettingsSource
|
||||
|
||||
property string compositorSettingsSource: {
|
||||
switch (CompositorService.compositor) {
|
||||
case "niri":
|
||||
return "NiriOutputSettings.qml";
|
||||
case "hyprland":
|
||||
return "HyprlandOutputSettings.qml";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
item.outputName = root.outputName;
|
||||
item.outputData = root.outputData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,110 +9,103 @@ Item {
|
||||
id: root
|
||||
|
||||
function getBarComponentsFromSettings() {
|
||||
const bars = SettingsData.barConfigs || [];
|
||||
const bars = SettingsData.barConfigs || []
|
||||
return bars.map(bar => ({
|
||||
"id": "bar:" + bar.id,
|
||||
"name": bar.name || "Bar",
|
||||
"description": I18n.tr("Individual bar configuration"),
|
||||
"icon": "toolbar",
|
||||
"barId": bar.id
|
||||
}));
|
||||
"id": "bar:" + bar.id,
|
||||
"name": bar.name || "Bar",
|
||||
"description": I18n.tr("Individual bar configuration"),
|
||||
"icon": "toolbar",
|
||||
"barId": bar.id
|
||||
}))
|
||||
}
|
||||
|
||||
property var variantComponents: getVariantComponentsList()
|
||||
|
||||
function getVariantComponentsList() {
|
||||
return [...getBarComponentsFromSettings(),
|
||||
{
|
||||
"id": "dock",
|
||||
"name": I18n.tr("Application Dock"),
|
||||
"description": I18n.tr("Bottom dock for pinned and running applications"),
|
||||
"icon": "dock"
|
||||
},
|
||||
{
|
||||
"id": "notifications",
|
||||
"name": I18n.tr("Notification Popups"),
|
||||
"description": I18n.tr("Notification toast popups"),
|
||||
"icon": "notifications"
|
||||
},
|
||||
{
|
||||
"id": "wallpaper",
|
||||
"name": I18n.tr("Wallpaper"),
|
||||
"description": I18n.tr("Desktop background images"),
|
||||
"icon": "wallpaper"
|
||||
},
|
||||
{
|
||||
"id": "osd",
|
||||
"name": I18n.tr("On-Screen Displays"),
|
||||
"description": I18n.tr("Volume, brightness, and other system OSDs"),
|
||||
"icon": "picture_in_picture"
|
||||
},
|
||||
{
|
||||
"id": "toast",
|
||||
"name": I18n.tr("Toast Messages"),
|
||||
"description": I18n.tr("System toast notifications"),
|
||||
"icon": "campaign"
|
||||
},
|
||||
{
|
||||
"id": "notepad",
|
||||
"name": I18n.tr("Notepad Slideout"),
|
||||
"description": I18n.tr("Quick note-taking slideout panel"),
|
||||
"icon": "sticky_note_2"
|
||||
}
|
||||
];
|
||||
return [...getBarComponentsFromSettings(), {
|
||||
"id": "dock",
|
||||
"name": I18n.tr("Application Dock"),
|
||||
"description": I18n.tr("Bottom dock for pinned and running applications"),
|
||||
"icon": "dock"
|
||||
}, {
|
||||
"id": "notifications",
|
||||
"name": I18n.tr("Notification Popups"),
|
||||
"description": I18n.tr("Notification toast popups"),
|
||||
"icon": "notifications"
|
||||
}, {
|
||||
"id": "wallpaper",
|
||||
"name": I18n.tr("Wallpaper"),
|
||||
"description": I18n.tr("Desktop background images"),
|
||||
"icon": "wallpaper"
|
||||
}, {
|
||||
"id": "osd",
|
||||
"name": I18n.tr("On-Screen Displays"),
|
||||
"description": I18n.tr("Volume, brightness, and other system OSDs"),
|
||||
"icon": "picture_in_picture"
|
||||
}, {
|
||||
"id": "toast",
|
||||
"name": I18n.tr("Toast Messages"),
|
||||
"description": I18n.tr("System toast notifications"),
|
||||
"icon": "campaign"
|
||||
}, {
|
||||
"id": "notepad",
|
||||
"name": I18n.tr("Notepad Slideout"),
|
||||
"description": I18n.tr("Quick note-taking slideout panel"),
|
||||
"icon": "sticky_note_2"
|
||||
}]
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onBarConfigsChanged() {
|
||||
variantComponents = getVariantComponentsList();
|
||||
variantComponents = getVariantComponentsList()
|
||||
}
|
||||
}
|
||||
|
||||
function getScreenPreferences(componentId) {
|
||||
if (componentId.startsWith("bar:")) {
|
||||
const barId = componentId.substring(4);
|
||||
const barConfig = SettingsData.getBarConfig(barId);
|
||||
return barConfig?.screenPreferences || ["all"];
|
||||
const barId = componentId.substring(4)
|
||||
const barConfig = SettingsData.getBarConfig(barId)
|
||||
return barConfig?.screenPreferences || ["all"]
|
||||
}
|
||||
return SettingsData.screenPreferences && SettingsData.screenPreferences[componentId] || ["all"];
|
||||
return SettingsData.screenPreferences && SettingsData.screenPreferences[componentId] || ["all"]
|
||||
}
|
||||
|
||||
function setScreenPreferences(componentId, screenNames) {
|
||||
if (componentId.startsWith("bar:")) {
|
||||
const barId = componentId.substring(4);
|
||||
const barId = componentId.substring(4)
|
||||
SettingsData.updateBarConfig(barId, {
|
||||
"screenPreferences": screenNames
|
||||
});
|
||||
return;
|
||||
"screenPreferences": screenNames
|
||||
})
|
||||
return
|
||||
}
|
||||
var prefs = SettingsData.screenPreferences || {};
|
||||
var newPrefs = Object.assign({}, prefs);
|
||||
newPrefs[componentId] = screenNames;
|
||||
SettingsData.set("screenPreferences", newPrefs);
|
||||
var prefs = SettingsData.screenPreferences || {}
|
||||
var newPrefs = Object.assign({}, prefs)
|
||||
newPrefs[componentId] = screenNames
|
||||
SettingsData.set("screenPreferences", newPrefs)
|
||||
}
|
||||
|
||||
function getShowOnLastDisplay(componentId) {
|
||||
if (componentId.startsWith("bar:")) {
|
||||
const barId = componentId.substring(4);
|
||||
const barConfig = SettingsData.getBarConfig(barId);
|
||||
return barConfig?.showOnLastDisplay ?? true;
|
||||
const barId = componentId.substring(4)
|
||||
const barConfig = SettingsData.getBarConfig(barId)
|
||||
return barConfig?.showOnLastDisplay ?? true
|
||||
}
|
||||
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false;
|
||||
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false
|
||||
}
|
||||
|
||||
function setShowOnLastDisplay(componentId, enabled) {
|
||||
if (componentId.startsWith("bar:")) {
|
||||
const barId = componentId.substring(4);
|
||||
const barId = componentId.substring(4)
|
||||
SettingsData.updateBarConfig(barId, {
|
||||
"showOnLastDisplay": enabled
|
||||
});
|
||||
return;
|
||||
"showOnLastDisplay": enabled
|
||||
})
|
||||
return
|
||||
}
|
||||
var prefs = SettingsData.showOnLastDisplay || {};
|
||||
var newPrefs = Object.assign({}, prefs);
|
||||
newPrefs[componentId] = enabled;
|
||||
SettingsData.set("showOnLastDisplay", newPrefs);
|
||||
var prefs = SettingsData.showOnLastDisplay || {}
|
||||
var newPrefs = Object.assign({}, prefs)
|
||||
newPrefs[componentId] = enabled
|
||||
SettingsData.set("showOnLastDisplay", newPrefs)
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
@@ -217,16 +210,16 @@ Item {
|
||||
model: [I18n.tr("Name"), I18n.tr("Model")]
|
||||
currentIndex: SettingsData.displayNameMode === "model" ? 1 : 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.displayNameMode = index === 1 ? "model" : "system";
|
||||
SettingsData.saveSettings();
|
||||
}
|
||||
if (!selected)
|
||||
return
|
||||
SettingsData.displayNameMode = index === 1 ? "model" : "system"
|
||||
SettingsData.saveSettings()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDisplayNameModeChanged() {
|
||||
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0;
|
||||
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,9 +273,9 @@ Item {
|
||||
StyledText {
|
||||
text: {
|
||||
if (parent.currentMode) {
|
||||
return parent.currentMode.width + "×" + parent.currentMode.height + "@" + Math.round(parent.currentMode.refresh / 1000) + "Hz";
|
||||
return parent.currentMode.width + "×" + parent.currentMode.height + "@" + Math.round(parent.currentMode.refresh / 1000) + "Hz"
|
||||
}
|
||||
return modelData.width + "×" + modelData.height;
|
||||
return modelData.width + "×" + modelData.height
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -385,20 +378,20 @@ Item {
|
||||
text: I18n.tr("All displays")
|
||||
description: I18n.tr("Show on all connected displays")
|
||||
checked: {
|
||||
var prefs = root.getScreenPreferences(parent.componentId);
|
||||
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
||||
var prefs = root.getScreenPreferences(parent.componentId)
|
||||
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||
}
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
root.setScreenPreferences(parent.componentId, ["all"]);
|
||||
} else {
|
||||
root.setScreenPreferences(parent.componentId, []);
|
||||
const cid = parent.componentId;
|
||||
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(cid) || cid.startsWith("bar:")) {
|
||||
root.setShowOnLastDisplay(cid, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checked) {
|
||||
root.setScreenPreferences(parent.componentId, ["all"])
|
||||
} else {
|
||||
root.setScreenPreferences(parent.componentId, [])
|
||||
const cid = parent.componentId
|
||||
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(cid) || cid.startsWith("bar:")) {
|
||||
root.setShowOnLastDisplay(cid, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -407,15 +400,15 @@ Item {
|
||||
description: I18n.tr("Always show when there's only one connected display")
|
||||
checked: root.getShowOnLastDisplay(parent.componentId)
|
||||
visible: {
|
||||
const prefs = root.getScreenPreferences(parent.componentId);
|
||||
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
||||
const cid = parent.componentId;
|
||||
const isRelevantComponent = ["dankBar", "dock", "notifications", "osd", "toast", "notepad"].includes(cid) || cid.startsWith("bar:");
|
||||
return !isAll && isRelevantComponent;
|
||||
const prefs = root.getScreenPreferences(parent.componentId)
|
||||
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||
const cid = parent.componentId
|
||||
const isRelevantComponent = ["dankBar", "dock", "notifications", "osd", "toast", "notepad"].includes(cid) || cid.startsWith("bar:")
|
||||
return !isAll && isRelevantComponent
|
||||
}
|
||||
onToggled: checked => {
|
||||
root.setShowOnLastDisplay(parent.componentId, checked);
|
||||
}
|
||||
root.setShowOnLastDisplay(parent.componentId, checked)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -424,8 +417,8 @@ Item {
|
||||
color: Theme.outline
|
||||
opacity: 0.2
|
||||
visible: {
|
||||
var prefs = root.getScreenPreferences(parent.componentId);
|
||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
||||
var prefs = root.getScreenPreferences(parent.componentId)
|
||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,8 +426,8 @@ Item {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: {
|
||||
var prefs = root.getScreenPreferences(parent.componentId);
|
||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
||||
var prefs = root.getScreenPreferences(parent.componentId)
|
||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -448,41 +441,41 @@ Item {
|
||||
text: SettingsData.getScreenDisplayName(screenData)
|
||||
description: screenData.width + "×" + screenData.height + " • " + (SettingsData.displayNameMode === "system" ? (screenData.model || "Unknown Model") : screenData.name)
|
||||
checked: {
|
||||
var prefs = root.getScreenPreferences(componentId);
|
||||
var prefs = root.getScreenPreferences(componentId)
|
||||
if (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||
return false;
|
||||
return SettingsData.isScreenInPreferences(screenData, prefs);
|
||||
return false
|
||||
return SettingsData.isScreenInPreferences(screenData, prefs)
|
||||
}
|
||||
onToggled: checked => {
|
||||
var currentPrefs = root.getScreenPreferences(componentId);
|
||||
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
|
||||
currentPrefs = [];
|
||||
}
|
||||
var currentPrefs = root.getScreenPreferences(componentId)
|
||||
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
|
||||
currentPrefs = []
|
||||
}
|
||||
|
||||
const screenModelIndex = SettingsData.getScreenModelIndex(screenData);
|
||||
const screenModelIndex = SettingsData.getScreenModelIndex(screenData)
|
||||
|
||||
var newPrefs = currentPrefs.filter(pref => {
|
||||
if (typeof pref === "string")
|
||||
return false;
|
||||
if (pref.modelIndex !== undefined && screenModelIndex >= 0) {
|
||||
return !(pref.model === screenData.model && pref.modelIndex === screenModelIndex);
|
||||
}
|
||||
return pref.name !== screenData.name || pref.model !== screenData.model;
|
||||
});
|
||||
var newPrefs = currentPrefs.filter(pref => {
|
||||
if (typeof pref === "string")
|
||||
return false
|
||||
if (pref.modelIndex !== undefined && screenModelIndex >= 0) {
|
||||
return !(pref.model === screenData.model && pref.modelIndex === screenModelIndex)
|
||||
}
|
||||
return pref.name !== screenData.name || pref.model !== screenData.model
|
||||
})
|
||||
|
||||
if (checked) {
|
||||
const prefObj = {
|
||||
"name": screenData.name,
|
||||
"model": screenData.model || ""
|
||||
};
|
||||
if (screenModelIndex >= 0) {
|
||||
prefObj.modelIndex = screenModelIndex;
|
||||
}
|
||||
newPrefs.push(prefObj);
|
||||
}
|
||||
if (checked) {
|
||||
const prefObj = {
|
||||
"name": screenData.name,
|
||||
"model": screenData.model || ""
|
||||
}
|
||||
if (screenModelIndex >= 0) {
|
||||
prefObj.modelIndex = screenModelIndex
|
||||
}
|
||||
newPrefs.push(prefObj)
|
||||
}
|
||||
|
||||
root.setScreenPreferences(componentId, newPrefs);
|
||||
}
|
||||
root.setScreenPreferences(componentId, newPrefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -8,14 +10,14 @@ Item {
|
||||
|
||||
function formatGammaTime(isoString) {
|
||||
if (!isoString)
|
||||
return "";
|
||||
return ""
|
||||
try {
|
||||
const date = new Date(isoString);
|
||||
const date = new Date(isoString)
|
||||
if (isNaN(date.getTime()))
|
||||
return "";
|
||||
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
|
||||
return ""
|
||||
return date.toLocaleTimeString(Qt.locale(), "HH:mm")
|
||||
} catch (e) {
|
||||
return "";
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +74,17 @@ Item {
|
||||
|
||||
width: parent.width
|
||||
text: I18n.tr("Night Mode")
|
||||
description: DisplayService.gammaControlAvailable ? I18n.tr("Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.") : I18n.tr("Gamma control not available. Requires DMS API v6+.")
|
||||
description: DisplayService.gammaControlAvailable ? I18n.tr("Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.") : I18n.tr(
|
||||
"Gamma control not available. Requires DMS API v6+.")
|
||||
checked: DisplayService.nightModeEnabled
|
||||
enabled: DisplayService.gammaControlAvailable
|
||||
onToggled: checked => {
|
||||
DisplayService.toggleNightMode();
|
||||
}
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNightModeEnabledChanged() {
|
||||
nightModeToggle.checked = DisplayService.nightModeEnabled;
|
||||
nightModeToggle.checked = DisplayService.nightModeEnabled
|
||||
}
|
||||
|
||||
target: DisplayService
|
||||
@@ -101,19 +104,19 @@ Item {
|
||||
description: SessionData.nightModeAutoEnabled ? I18n.tr("Color temperature for night mode") : I18n.tr("Warm color temperature to apply")
|
||||
currentValue: SessionData.nightModeTemperature + "K"
|
||||
options: {
|
||||
var temps = [];
|
||||
var temps = []
|
||||
for (var i = 2500; i <= 6000; i += 500) {
|
||||
temps.push(i + "K");
|
||||
temps.push(i + "K")
|
||||
}
|
||||
return temps;
|
||||
return temps
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var temp = parseInt(value.replace("K", ""));
|
||||
SessionData.setNightModeTemperature(temp);
|
||||
if (SessionData.nightModeHighTemperature < temp) {
|
||||
SessionData.setNightModeHighTemperature(temp);
|
||||
}
|
||||
}
|
||||
var temp = parseInt(value.replace("K", ""))
|
||||
SessionData.setNightModeTemperature(temp)
|
||||
if (SessionData.nightModeHighTemperature < temp) {
|
||||
SessionData.setNightModeHighTemperature(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -123,19 +126,19 @@ Item {
|
||||
currentValue: SessionData.nightModeHighTemperature + "K"
|
||||
visible: SessionData.nightModeAutoEnabled
|
||||
options: {
|
||||
var temps = [];
|
||||
var minTemp = SessionData.nightModeTemperature;
|
||||
var temps = []
|
||||
var minTemp = SessionData.nightModeTemperature
|
||||
for (var i = Math.max(2500, minTemp); i <= 10000; i += 500) {
|
||||
temps.push(i + "K");
|
||||
temps.push(i + "K")
|
||||
}
|
||||
return temps;
|
||||
return temps
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var temp = parseInt(value.replace("K", ""));
|
||||
if (temp >= SessionData.nightModeTemperature) {
|
||||
SessionData.setNightModeHighTemperature(temp);
|
||||
}
|
||||
}
|
||||
var temp = parseInt(value.replace("K", ""))
|
||||
if (temp >= SessionData.nightModeTemperature) {
|
||||
SessionData.setNightModeHighTemperature(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,18 +150,18 @@ Item {
|
||||
checked: SessionData.nightModeAutoEnabled
|
||||
visible: DisplayService.gammaControlAvailable
|
||||
onToggled: checked => {
|
||||
if (checked && !DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode();
|
||||
} else if (!checked && DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode();
|
||||
}
|
||||
SessionData.setNightModeAutoEnabled(checked);
|
||||
}
|
||||
if (checked && !DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode()
|
||||
} else if (!checked && DisplayService.nightModeEnabled) {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
SessionData.setNightModeAutoEnabled(checked)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticToggle.checked = SessionData.nightModeAutoEnabled;
|
||||
automaticToggle.checked = SessionData.nightModeAutoEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +175,7 @@ Item {
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticSettings.visible = SessionData.nightModeAutoEnabled;
|
||||
automaticSettings.visible = SessionData.nightModeAutoEnabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,32 +188,29 @@ Item {
|
||||
width: 200
|
||||
height: 45
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: [
|
||||
{
|
||||
model: [{
|
||||
"text": "Time",
|
||||
"icon": "access_time"
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"text": "Location",
|
||||
"icon": "place"
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
Component.onCompleted: {
|
||||
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0;
|
||||
Qt.callLater(updateIndicator);
|
||||
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||
Qt.callLater(updateIndicator)
|
||||
}
|
||||
|
||||
onTabClicked: index => {
|
||||
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time");
|
||||
currentIndex = index;
|
||||
}
|
||||
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time")
|
||||
currentIndex = index
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoModeChanged() {
|
||||
modeTabBarNight.currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0;
|
||||
Qt.callLater(modeTabBarNight.updateIndicator);
|
||||
modeTabBarNight.currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0
|
||||
Qt.callLater(modeTabBarNight.updateIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,30 +267,30 @@ Item {
|
||||
dropdownWidth: 70
|
||||
currentValue: SessionData.nightModeStartHour.toString()
|
||||
options: {
|
||||
var hours = [];
|
||||
var hours = []
|
||||
for (var i = 0; i < 24; i++) {
|
||||
hours.push(i.toString());
|
||||
hours.push(i.toString())
|
||||
}
|
||||
return hours;
|
||||
return hours
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeStartHour(parseInt(value));
|
||||
}
|
||||
SessionData.setNightModeStartHour(parseInt(value))
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
dropdownWidth: 70
|
||||
currentValue: SessionData.nightModeStartMinute.toString().padStart(2, '0')
|
||||
options: {
|
||||
var minutes = [];
|
||||
var minutes = []
|
||||
for (var i = 0; i < 60; i += 5) {
|
||||
minutes.push(i.toString().padStart(2, '0'));
|
||||
minutes.push(i.toString().padStart(2, '0'))
|
||||
}
|
||||
return minutes;
|
||||
return minutes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeStartMinute(parseInt(value));
|
||||
}
|
||||
SessionData.setNightModeStartMinute(parseInt(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,30 +310,30 @@ Item {
|
||||
dropdownWidth: 70
|
||||
currentValue: SessionData.nightModeEndHour.toString()
|
||||
options: {
|
||||
var hours = [];
|
||||
var hours = []
|
||||
for (var i = 0; i < 24; i++) {
|
||||
hours.push(i.toString());
|
||||
hours.push(i.toString())
|
||||
}
|
||||
return hours;
|
||||
return hours
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeEndHour(parseInt(value));
|
||||
}
|
||||
SessionData.setNightModeEndHour(parseInt(value))
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
dropdownWidth: 70
|
||||
currentValue: SessionData.nightModeEndMinute.toString().padStart(2, '0')
|
||||
options: {
|
||||
var minutes = [];
|
||||
var minutes = []
|
||||
for (var i = 0; i < 60; i += 5) {
|
||||
minutes.push(i.toString().padStart(2, '0'));
|
||||
minutes.push(i.toString().padStart(2, '0'))
|
||||
}
|
||||
return minutes;
|
||||
return minutes
|
||||
}
|
||||
onValueChanged: value => {
|
||||
SessionData.setNightModeEndMinute(parseInt(value));
|
||||
}
|
||||
SessionData.setNightModeEndMinute(parseInt(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,13 +352,13 @@ Item {
|
||||
description: I18n.tr("Automatically detect location based on IP address")
|
||||
checked: SessionData.nightModeUseIPLocation || false
|
||||
onToggled: checked => {
|
||||
SessionData.setNightModeUseIPLocation(checked);
|
||||
}
|
||||
SessionData.setNightModeUseIPLocation(checked)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeUseIPLocationChanged() {
|
||||
ipLocationToggle.checked = SessionData.nightModeUseIPLocation;
|
||||
ipLocationToggle.checked = SessionData.nightModeUseIPLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,9 +393,9 @@ Item {
|
||||
text: SessionData.latitude.toString()
|
||||
placeholderText: "0.0"
|
||||
onEditingFinished: {
|
||||
const lat = parseFloat(text);
|
||||
const lat = parseFloat(text)
|
||||
if (!isNaN(lat) && lat >= -90 && lat <= 90 && lat !== SessionData.latitude) {
|
||||
SessionData.setLatitude(lat);
|
||||
SessionData.setLatitude(lat)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,9 +416,9 @@ Item {
|
||||
text: SessionData.longitude.toString()
|
||||
placeholderText: "0.0"
|
||||
onEditingFinished: {
|
||||
const lon = parseFloat(text);
|
||||
const lon = parseFloat(text)
|
||||
if (!isNaN(lon) && lon >= -180 && lon <= 180 && lon !== SessionData.longitude) {
|
||||
SessionData.setLongitude(lon);
|
||||
SessionData.setLongitude(lon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,17 +32,15 @@ StyledRect {
|
||||
|
||||
readonly property bool collapsed: collapsible && !expanded
|
||||
readonly property bool hasHeader: root.title !== "" || root.iconName !== ""
|
||||
property bool userToggledCollapse: false
|
||||
property bool animationsEnabled: false
|
||||
|
||||
Component.onCompleted: Qt.callLater(() => animationsEnabled = true)
|
||||
|
||||
Behavior on height {
|
||||
enabled: root.userToggledCollapse
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
onRunningChanged: {
|
||||
if (!running)
|
||||
root.userToggledCollapse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +98,6 @@ StyledRect {
|
||||
onClicked: {
|
||||
if (!root.collapsible)
|
||||
return;
|
||||
root.userToggledCollapse = true;
|
||||
root.expanded = !root.expanded;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +108,14 @@ StyledRect {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: !root.collapsed
|
||||
opacity: root.collapsed ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,82 +234,23 @@ PanelWindow {
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: detailsText.implicitHeight
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
StyledText {
|
||||
id: detailsText
|
||||
text: ToastService.currentDetails
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
|
||||
default:
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
visible: ToastService.currentDetails.length > 0
|
||||
|
||||
StyledText {
|
||||
id: detailsText
|
||||
text: ToastService.currentDetails
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
|
||||
default:
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: copyDetailsButton.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: copyDetailsButton
|
||||
iconName: "content_copy"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: {
|
||||
switch (ToastService.currentLevel) {
|
||||
case ToastService.levelError:
|
||||
case ToastService.levelWarn:
|
||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
|
||||
default:
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
buttonSize: Theme.iconSizeSmall + 8
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
property bool showTooltip: false
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["dms", "cl", "copy", ToastService.currentDetails]);
|
||||
showTooltip = true;
|
||||
detailsTooltipTimer.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: detailsTooltipTimer
|
||||
interval: 1500
|
||||
onTriggered: copyDetailsButton.showTooltip = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: copyDetailsButton.showTooltip
|
||||
width: detailsTooltipLabel.implicitWidth + 16
|
||||
height: detailsTooltipLabel.implicitHeight + 8
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
y: -height - 4
|
||||
x: -width / 2 + copyDetailsButton.width / 2
|
||||
|
||||
StyledText {
|
||||
id: detailsTooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: root.copiedText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -272,77 +272,82 @@ Singleton {
|
||||
|
||||
function generateOutputsConfig(outputsData) {
|
||||
if (!outputsData || Object.keys(outputsData).length === 0)
|
||||
return;
|
||||
let lines = ["# Auto-generated by DMS - do not edit manually", "# VRR is global: set adaptive_sync=1 in config.conf", ""];
|
||||
return
|
||||
|
||||
let lines = ["# Auto-generated by DMS - do not edit manually", "# VRR is global: set adaptive_sync=1 in config.conf", ""]
|
||||
|
||||
for (const outputName in outputsData) {
|
||||
const output = outputsData[outputName];
|
||||
const output = outputsData[outputName]
|
||||
if (!output)
|
||||
continue;
|
||||
let width = 1920;
|
||||
let height = 1080;
|
||||
let refreshRate = 60;
|
||||
continue
|
||||
|
||||
let width = 1920
|
||||
let height = 1080
|
||||
let refreshRate = 60
|
||||
if (output.modes && output.current_mode !== undefined) {
|
||||
const mode = output.modes[output.current_mode];
|
||||
const mode = output.modes[output.current_mode]
|
||||
if (mode) {
|
||||
width = mode.width || 1920;
|
||||
height = mode.height || 1080;
|
||||
refreshRate = Math.round((mode.refresh_rate || 60000) / 1000);
|
||||
width = mode.width || 1920
|
||||
height = mode.height || 1080
|
||||
refreshRate = Math.round((mode.refresh_rate || 60000) / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const x = output.logical?.x ?? 0;
|
||||
const y = output.logical?.y ?? 0;
|
||||
const scale = output.logical?.scale ?? 1.0;
|
||||
const transform = transformToMango(output.logical?.transform ?? "Normal");
|
||||
const x = output.logical?.x ?? 0
|
||||
const y = output.logical?.y ?? 0
|
||||
const scale = output.logical?.scale ?? 1.0
|
||||
const transform = transformToMango(output.logical?.transform ?? "Normal")
|
||||
|
||||
const rule = [outputName, "0.55", "1", "tile", transform, scale, x, y, width, height, refreshRate].join(",");
|
||||
const rule = [
|
||||
outputName,
|
||||
"0.55",
|
||||
"1",
|
||||
"tile",
|
||||
transform,
|
||||
scale,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
refreshRate
|
||||
].join(",")
|
||||
|
||||
lines.push("monitorrule=" + rule);
|
||||
lines.push("monitorrule=" + rule)
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
lines.push("")
|
||||
|
||||
const content = lines.join("\n");
|
||||
const content = lines.join("\n")
|
||||
|
||||
Proc.runCommand("mango-write-outputs", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("DwlService: Failed to write outputs config:", output);
|
||||
return;
|
||||
console.warn("DwlService: Failed to write outputs config:", output)
|
||||
return
|
||||
}
|
||||
console.info("DwlService: Generated outputs config at", outputsPath);
|
||||
console.info("DwlService: Generated outputs config at", outputsPath)
|
||||
if (CompositorService.isDwl)
|
||||
reloadConfig();
|
||||
});
|
||||
reloadConfig()
|
||||
})
|
||||
}
|
||||
|
||||
function reloadConfig() {
|
||||
Proc.runCommand("mango-reload", ["mmsg", "-d", "reload_config"], (output, exitCode) => {
|
||||
if (exitCode !== 0)
|
||||
console.warn("DwlService: mmsg reload_config failed:", output);
|
||||
});
|
||||
console.warn("DwlService: mmsg reload_config failed:", output)
|
||||
})
|
||||
}
|
||||
|
||||
function transformToMango(transform) {
|
||||
switch (transform) {
|
||||
case "Normal":
|
||||
return 0;
|
||||
case "90":
|
||||
return 1;
|
||||
case "180":
|
||||
return 2;
|
||||
case "270":
|
||||
return 3;
|
||||
case "Flipped":
|
||||
return 4;
|
||||
case "Flipped90":
|
||||
return 5;
|
||||
case "Flipped180":
|
||||
return 6;
|
||||
case "Flipped270":
|
||||
return 7;
|
||||
default:
|
||||
return 0;
|
||||
case "Normal": return 0
|
||||
case "90": return 1
|
||||
case "180": return 2
|
||||
case "270": return 3
|
||||
case "Flipped": return 4
|
||||
case "Flipped90": return 5
|
||||
case "Flipped180": return 6
|
||||
case "Flipped270": return 7
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
@@ -14,162 +15,96 @@ Singleton {
|
||||
readonly property string outputsPath: hyprDmsDir + "/outputs.conf"
|
||||
|
||||
function getOutputIdentifier(output, outputName) {
|
||||
if (SettingsData.displayNameMode === "model" && output.make && output.model)
|
||||
return "desc:" + output.make + " " + output.model;
|
||||
return outputName;
|
||||
if (SettingsData.displayNameMode === "model" && output.make && output.model) {
|
||||
return "desc:" + output.make + " " + output.model
|
||||
}
|
||||
return outputName
|
||||
}
|
||||
|
||||
function generateOutputsConfig(outputsData, hyprlandSettings) {
|
||||
function generateOutputsConfig(outputsData) {
|
||||
if (!outputsData || Object.keys(outputsData).length === 0)
|
||||
return;
|
||||
return
|
||||
|
||||
const settings = hyprlandSettings || SettingsData.hyprlandOutputSettings;
|
||||
let lines = ["# Auto-generated by DMS - do not edit manually", ""];
|
||||
let monitorv2Blocks = [];
|
||||
let lines = ["# Auto-generated by DMS - do not edit manually", ""]
|
||||
|
||||
for (const outputName in outputsData) {
|
||||
const output = outputsData[outputName];
|
||||
const output = outputsData[outputName]
|
||||
if (!output)
|
||||
continue;
|
||||
continue
|
||||
|
||||
const identifier = getOutputIdentifier(output, outputName);
|
||||
const outputSettings = settings[identifier] || {};
|
||||
|
||||
let resolution = "preferred";
|
||||
let resolution = "preferred"
|
||||
if (output.modes && output.current_mode !== undefined) {
|
||||
const mode = output.modes[output.current_mode];
|
||||
const mode = output.modes[output.current_mode]
|
||||
if (mode)
|
||||
resolution = mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3);
|
||||
resolution = mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3)
|
||||
}
|
||||
|
||||
const x = output.logical?.x ?? 0;
|
||||
const y = output.logical?.y ?? 0;
|
||||
const position = x + "x" + y;
|
||||
const scale = output.logical?.scale ?? 1.0;
|
||||
const x = output.logical?.x ?? 0
|
||||
const y = output.logical?.y ?? 0
|
||||
const position = x + "x" + y
|
||||
|
||||
let monitorLine = "monitor = " + identifier + ", " + resolution + ", " + position + ", " + scale;
|
||||
const scale = output.logical?.scale ?? 1.0
|
||||
|
||||
const transform = transformToHyprland(output.logical?.transform ?? "Normal");
|
||||
const identifier = getOutputIdentifier(output, outputName)
|
||||
let monitorLine = "monitor = " + identifier + ", " + resolution + ", " + position + ", " + scale
|
||||
|
||||
const transform = transformToHyprland(output.logical?.transform ?? "Normal")
|
||||
if (transform !== 0)
|
||||
monitorLine += ", transform, " + transform;
|
||||
monitorLine += ", transform, " + transform
|
||||
|
||||
if (output.vrr_supported && output.vrr_enabled)
|
||||
monitorLine += ", vrr, 1";
|
||||
monitorLine += ", vrr, 1"
|
||||
|
||||
if (outputSettings.bitdepth && outputSettings.bitdepth !== 8)
|
||||
monitorLine += ", bitdepth, " + outputSettings.bitdepth;
|
||||
|
||||
if (outputSettings.colorManagement && outputSettings.colorManagement !== "auto")
|
||||
monitorLine += ", cm, " + outputSettings.colorManagement;
|
||||
|
||||
if (outputSettings.sdrBrightness !== undefined && outputSettings.sdrBrightness !== 1.0)
|
||||
monitorLine += ", sdrbrightness, " + outputSettings.sdrBrightness;
|
||||
|
||||
if (outputSettings.sdrSaturation !== undefined && outputSettings.sdrSaturation !== 1.0)
|
||||
monitorLine += ", sdrsaturation, " + outputSettings.sdrSaturation;
|
||||
|
||||
lines.push(monitorLine);
|
||||
|
||||
const needsMonitorv2 = outputSettings.supportsHdr || outputSettings.supportsWideColor ||
|
||||
outputSettings.sdrMinLuminance !== undefined || outputSettings.sdrMaxLuminance !== undefined ||
|
||||
outputSettings.minLuminance !== undefined || outputSettings.maxLuminance !== undefined ||
|
||||
outputSettings.maxAvgLuminance !== undefined;
|
||||
|
||||
if (needsMonitorv2) {
|
||||
let block = "monitorv2 {\n";
|
||||
block += " output = " + identifier + "\n";
|
||||
|
||||
if (outputSettings.supportsWideColor)
|
||||
block += " supports_wide_color = true\n";
|
||||
if (outputSettings.supportsHdr)
|
||||
block += " supports_hdr = true\n";
|
||||
if (outputSettings.sdrMinLuminance !== undefined)
|
||||
block += " sdr_min_luminance = " + outputSettings.sdrMinLuminance + "\n";
|
||||
if (outputSettings.sdrMaxLuminance !== undefined)
|
||||
block += " sdr_max_luminance = " + outputSettings.sdrMaxLuminance + "\n";
|
||||
if (outputSettings.minLuminance !== undefined)
|
||||
block += " min_luminance = " + outputSettings.minLuminance + "\n";
|
||||
if (outputSettings.maxLuminance !== undefined)
|
||||
block += " max_luminance = " + outputSettings.maxLuminance + "\n";
|
||||
if (outputSettings.maxAvgLuminance !== undefined)
|
||||
block += " max_avg_luminance = " + outputSettings.maxAvgLuminance + "\n";
|
||||
|
||||
block += "}";
|
||||
monitorv2Blocks.push(block);
|
||||
}
|
||||
lines.push(monitorLine)
|
||||
}
|
||||
|
||||
if (monitorv2Blocks.length > 0) {
|
||||
lines.push("");
|
||||
for (const block of monitorv2Blocks)
|
||||
lines.push(block);
|
||||
}
|
||||
lines.push("")
|
||||
|
||||
lines.push("");
|
||||
|
||||
const content = lines.join("\n");
|
||||
const content = lines.join("\n")
|
||||
|
||||
Proc.runCommand("hypr-write-outputs", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("HyprlandService: Failed to write outputs config:", output);
|
||||
return;
|
||||
console.warn("HyprlandService: Failed to write outputs config:", output)
|
||||
return
|
||||
}
|
||||
console.info("HyprlandService: Generated outputs config at", outputsPath);
|
||||
console.info("HyprlandService: Generated outputs config at", outputsPath)
|
||||
if (CompositorService.isHyprland)
|
||||
reloadConfig();
|
||||
});
|
||||
reloadConfig()
|
||||
})
|
||||
}
|
||||
|
||||
function reloadConfig() {
|
||||
Proc.runCommand("hyprctl-reload", ["hyprctl", "reload"], (output, exitCode) => {
|
||||
if (exitCode !== 0)
|
||||
console.warn("HyprlandService: hyprctl reload failed:", output);
|
||||
});
|
||||
console.warn("HyprlandService: hyprctl reload failed:", output)
|
||||
})
|
||||
}
|
||||
|
||||
function transformToHyprland(transform) {
|
||||
switch (transform) {
|
||||
case "Normal":
|
||||
return 0;
|
||||
case "90":
|
||||
return 1;
|
||||
case "180":
|
||||
return 2;
|
||||
case "270":
|
||||
return 3;
|
||||
case "Flipped":
|
||||
return 4;
|
||||
case "Flipped90":
|
||||
return 5;
|
||||
case "Flipped180":
|
||||
return 6;
|
||||
case "Flipped270":
|
||||
return 7;
|
||||
default:
|
||||
return 0;
|
||||
case "Normal": return 0
|
||||
case "90": return 1
|
||||
case "180": return 2
|
||||
case "270": return 3
|
||||
case "Flipped": return 4
|
||||
case "Flipped90": return 5
|
||||
case "Flipped180": return 6
|
||||
case "Flipped270": return 7
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
|
||||
function hyprlandToTransform(value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return "Normal";
|
||||
case 1:
|
||||
return "90";
|
||||
case 2:
|
||||
return "180";
|
||||
case 3:
|
||||
return "270";
|
||||
case 4:
|
||||
return "Flipped";
|
||||
case 5:
|
||||
return "Flipped90";
|
||||
case 6:
|
||||
return "Flipped180";
|
||||
case 7:
|
||||
return "Flipped270";
|
||||
default:
|
||||
return "Normal";
|
||||
case 0: return "Normal"
|
||||
case 1: return "90"
|
||||
case 2: return "180"
|
||||
case 3: return "270"
|
||||
case 4: return "Flipped"
|
||||
case 5: return "Flipped90"
|
||||
case 6: return "Flipped180"
|
||||
case 7: return "Flipped270"
|
||||
default: return "Normal"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,6 +580,7 @@ Singleton {
|
||||
const windowIndex = windows.findIndex(w => w.id === data.id);
|
||||
if (windowIndex < 0)
|
||||
return;
|
||||
|
||||
const updatedWindows = [...windows];
|
||||
const updatedWindow = {};
|
||||
for (let prop in updatedWindows[windowIndex]) {
|
||||
@@ -1139,15 +1140,8 @@ Singleton {
|
||||
for (const outputName in data) {
|
||||
const output = data[outputName];
|
||||
const identifier = getOutputIdentifier(output, outputName);
|
||||
const niriSettings = SettingsData.getNiriOutputSettings(identifier);
|
||||
|
||||
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]) {
|
||||
const mode = output.modes[output.current_mode];
|
||||
kdlContent += ` mode "${mode.width}x${mode.height}@${(mode.refresh_rate / 1000).toFixed(3)}"\n`;
|
||||
@@ -1178,21 +1172,9 @@ Singleton {
|
||||
}
|
||||
|
||||
if (output.vrr_enabled) {
|
||||
const vrrOnDemand = niriSettings.vrrOnDemand ?? false;
|
||||
kdlContent += vrrOnDemand ? ` variable-refresh-rate on-demand=true\n` : ` variable-refresh-rate\n`;
|
||||
kdlContent += ` 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`;
|
||||
}
|
||||
|
||||
@@ -1209,55 +1191,6 @@ 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") {
|
||||
const val = layout.defaultColumnWidth.value;
|
||||
const formatted = Number.isInteger(val) ? val.toFixed(1) : val.toString();
|
||||
block += ` default-column-width { proportion ${formatted}; }\n`;
|
||||
}
|
||||
if (layout.presetColumnWidths && layout.presetColumnWidths.length > 0) {
|
||||
block += ` preset-column-widths {\n`;
|
||||
for (const preset of layout.presetColumnWidths) {
|
||||
if (preset.type === "proportion") {
|
||||
const val = preset.value;
|
||||
const formatted = Number.isInteger(val) ? val.toFixed(1) : val.toString();
|
||||
block += ` proportion ${formatted}\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 {
|
||||
function screenshot(): string {
|
||||
if (!CompositorService.isNiri) {
|
||||
|
||||
@@ -16,7 +16,7 @@ Rectangle {
|
||||
property int buttonHeight: 40
|
||||
property int horizontalPadding: Theme.spacingL
|
||||
|
||||
signal clicked
|
||||
signal clicked()
|
||||
|
||||
width: Math.max(contentRow.implicitWidth + horizontalPadding * 2, 64)
|
||||
height: buttonHeight
|
||||
@@ -29,11 +29,9 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: {
|
||||
if (pressed)
|
||||
return Theme.primaryPressed;
|
||||
if (hovered)
|
||||
return Theme.primaryHover;
|
||||
return "transparent";
|
||||
if (pressed) return Theme.primaryPressed
|
||||
if (hovered) return Theme.primaryHover
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
|
||||
@@ -18,7 +18,6 @@ Flow {
|
||||
property int buttonPadding: Theme.spacingL
|
||||
property int checkIconSize: Theme.iconSizeSmall
|
||||
property int textSize: Theme.fontSizeMedium
|
||||
property bool userInteracted: false
|
||||
|
||||
signal selectionChanged(int index, bool selected)
|
||||
signal animationCompleted()
|
||||
@@ -28,10 +27,7 @@ Flow {
|
||||
Timer {
|
||||
id: animationTimer
|
||||
interval: Theme.shortDuration
|
||||
onTriggered: {
|
||||
root.userInteracted = false;
|
||||
root.animationCompleted();
|
||||
}
|
||||
onTriggered: root.animationCompleted()
|
||||
}
|
||||
|
||||
function isSelected(index) {
|
||||
@@ -42,7 +38,6 @@ Flow {
|
||||
}
|
||||
|
||||
function selectItem(index) {
|
||||
userInteracted = true;
|
||||
if (multiSelect) {
|
||||
const modelValue = model[index]
|
||||
let newSelection = [...currentSelection]
|
||||
@@ -98,7 +93,6 @@ Flow {
|
||||
bottomRightRadius: (isLast || selected) ? Theme.cornerRadius : 4
|
||||
|
||||
Behavior on width {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -106,7 +100,6 @@ Flow {
|
||||
}
|
||||
|
||||
Behavior on topLeftRadius {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -114,7 +107,6 @@ Flow {
|
||||
}
|
||||
|
||||
Behavior on topRightRadius {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -122,7 +114,6 @@ Flow {
|
||||
}
|
||||
|
||||
Behavior on bottomLeftRadius {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -130,7 +121,6 @@ Flow {
|
||||
}
|
||||
|
||||
Behavior on bottomRightRadius {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -138,7 +128,6 @@ Flow {
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
enabled: root.userInteracted
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -187,7 +176,6 @@ Flow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
@@ -195,7 +183,6 @@ Flow {
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
enabled: root.userInteracted
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[templates.dmsghostty]
|
||||
input_path = 'SHELL_DIR/matugen/templates/ghostty.conf'
|
||||
output_path = '~/.config/ghostty/themes/dankcolors'
|
||||
output_path = '~/.config/ghostty/config-dankcolors'
|
||||
|
||||
Reference in New Issue
Block a user