mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-12 23:32:50 -04:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2947ff4131 | |||
| b8fca10896 | |||
| 33e45794d2 | |||
| 42cc88ca65 | |||
| 0b7f2416ca | |||
| 5d5c745ee5 | |||
| e0429e4c60 | |||
| 0bece5287e | |||
| 60b5e47836 | |||
| aa75b44790 | |||
| 769f58caa9 | |||
| e7facf740d | |||
| 04921eef62 | |||
| 8863c42879 | |||
| 2745116ac5 |
@@ -7,13 +7,14 @@ on:
|
|||||||
description: "Package to update (dms, dms-git, or all)"
|
description: "Package to update (dms, dms-git, or all)"
|
||||||
required: false
|
required: false
|
||||||
default: "all"
|
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:
|
rebuild_release:
|
||||||
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
|
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
- cron: "0 */3 * * *" # Every 3 hours for dms-git builds
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ jobs:
|
|||||||
# Rebuild requested - always proceed
|
# Rebuild requested - always proceed
|
||||||
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
echo "🔄 Manual rebuild requested: $PKG (ppa$REBUILD)"
|
echo "🔄 Manual rebuild requested: $PKG (db$REBUILD)"
|
||||||
|
|
||||||
elif [[ "$PKG" == "all" ]]; then
|
elif [[ "$PKG" == "all" ]]; then
|
||||||
# Check each package and build list of those needing updates
|
# Check each package and build list of those needing updates
|
||||||
@@ -161,16 +162,51 @@ jobs:
|
|||||||
id: packages
|
id: packages
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||||
|
# Tag push event - use the pushed tag
|
||||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
VERSION="${GITHUB_REF#refs/tags/}"
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "Triggered by tag: $VERSION"
|
echo "Triggered by tag: $VERSION"
|
||||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||||
|
# Scheduled run - dms-git only
|
||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
echo "Triggered by schedule: updating git package"
|
echo "Triggered by schedule: updating git package"
|
||||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||||
# Use filtered packages from check-updates when package="all" and no rebuild requested
|
# Manual workflow dispatch
|
||||||
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]]; then
|
|
||||||
|
# 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
|
||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
|
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
|
||||||
else
|
else
|
||||||
@@ -186,7 +222,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
|
||||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||||
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
||||||
|
|
||||||
@@ -207,14 +243,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "1.0.2")
|
||||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||||
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
||||||
|
|
||||||
# Single changelog entry (git snapshots don't need history)
|
# Single changelog entry (git snapshots don't need history)
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
{
|
{
|
||||||
echo "dms-git ($NEW_VERSION) nightly; urgency=medium"
|
echo "dms-git (${NEW_VERSION}db1) nightly; urgency=medium"
|
||||||
echo ""
|
echo ""
|
||||||
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
echo " * Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -226,10 +262,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.packages.outputs.version }}"
|
VERSION="${{ steps.packages.outputs.version }}"
|
||||||
VERSION_NO_V="${VERSION#v}"
|
VERSION_NO_V="${VERSION#v}"
|
||||||
echo "Updating packaging to version $VERSION_NO_V"
|
echo "==> Updating packaging files to version: $VERSION_NO_V"
|
||||||
|
|
||||||
|
# Update spec file
|
||||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
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)
|
# Single changelog entry (full history on OBS website)
|
||||||
DATE_STR=$(date "+%a %b %d %Y")
|
DATE_STR=$(date "+%a %b %d %Y")
|
||||||
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
||||||
@@ -256,13 +297,13 @@ jobs:
|
|||||||
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
{
|
{
|
||||||
echo "dms ($VERSION_NO_V) stable; urgency=medium"
|
echo "dms (${VERSION_NO_V}db1) stable; urgency=medium"
|
||||||
echo ""
|
echo ""
|
||||||
echo " * Update to $VERSION stable release"
|
echo " * Update to $VERSION stable release"
|
||||||
echo ""
|
echo ""
|
||||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||||
} > "distro/debian/dms/debian/changelog"
|
} > "distro/debian/dms/debian/changelog"
|
||||||
echo "✓ Updated Debian changelog to $VERSION_NO_V"
|
echo "✓ Updated Debian changelog to ${VERSION_NO_V}db1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
@@ -289,6 +330,7 @@ jobs:
|
|||||||
- name: Upload to OBS
|
- name: Upload to OBS
|
||||||
env:
|
env:
|
||||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||||
|
TAG_VERSION: ${{ steps.packages.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
|
|
||||||
@@ -300,6 +342,7 @@ jobs:
|
|||||||
MESSAGE="Automated update from GitHub Actions"
|
MESSAGE="Automated update from GitHub Actions"
|
||||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||||
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
||||||
|
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
||||||
@@ -309,7 +352,7 @@ jobs:
|
|||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "Uploading $PKG to OBS..."
|
echo "Uploading $PKG to OBS..."
|
||||||
if [[ -n "$REBUILD_RELEASE" ]]; then
|
if [[ -n "$REBUILD_RELEASE" ]]; then
|
||||||
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
echo "🔄 Using rebuild release number: db$REBUILD_RELEASE"
|
||||||
fi
|
fi
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
@@ -350,7 +393,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||||
echo "**Rebuild Number:** ppa${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
echo "**Rebuild Number:** db${{ github.event.inputs.rebuild_release }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ go.work
|
|||||||
go.work.sum
|
go.work.sum
|
||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env*
|
||||||
|
|
||||||
# Editor/IDE
|
# Editor/IDE
|
||||||
# .idea/
|
# .idea/
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
This file is more of a quick reference so I know what to account for before next releases.
|
||||||
|
|
||||||
# 1.2.0
|
# 1.2.0
|
||||||
|
|
||||||
- Added clipboard and clipboard history integration
|
- Added clipboard and clipboard history integration
|
||||||
- Added swipe to dismiss notification popups and from center
|
- Added swipe to dismiss notification popups and from center
|
||||||
- Added paste from clipboard history view - requires wtype
|
- Added paste from clipboard history view - requires wtype
|
||||||
- Optimize surface damage of OSD & Toast
|
- 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
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ func getBaseVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback
|
// Fallback
|
||||||
return "0.6.2"
|
return "1.0.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDebugServer() error {
|
func startDebugServer() error {
|
||||||
|
|||||||
@@ -265,7 +265,13 @@ func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
|
|||||||
|
|
||||||
colorResult := DeploymentResult{
|
colorResult := DeploymentResult{
|
||||||
ConfigType: "Ghostty Colors",
|
ConfigType: "Ghostty Colors",
|
||||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config-dankcolors"),
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(colorResult.Path, []byte(GhosttyColorConfig), 0644); err != nil {
|
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) {
|
func TestGhosttyConfigStructure(t *testing.T) {
|
||||||
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
assert.Contains(t, GhosttyConfig, "window-decoration = false")
|
||||||
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
|
||||||
assert.Contains(t, GhosttyConfig, "config-file = ./config-dankcolors")
|
assert.Contains(t, GhosttyConfig, "theme = dankcolors")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGhosttyColorConfigStructure(t *testing.T) {
|
func TestGhosttyColorConfigStructure(t *testing.T) {
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ keybind = shift+enter=text:\n
|
|||||||
gtk-single-instance = true
|
gtk-single-instance = true
|
||||||
|
|
||||||
# Dank color generation
|
# Dank color generation
|
||||||
config-file = ./config-dankcolors
|
theme = dankcolors
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy == nil {
|
if proxy == nil || proxy.IsZombie() {
|
||||||
head := &ZwlrOutputHeadV1{}
|
head := &ZwlrOutputHeadV1{}
|
||||||
head.SetContext(i.Context())
|
head.SetContext(i.Context())
|
||||||
head.SetID(objectID)
|
head.SetID(objectID)
|
||||||
@@ -723,7 +723,7 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy == nil {
|
if proxy == nil || proxy.IsZombie() {
|
||||||
mode := &ZwlrOutputModeV1{}
|
mode := &ZwlrOutputModeV1{}
|
||||||
mode.SetContext(i.Context())
|
mode.SetContext(i.Context())
|
||||||
mode.SetID(objectID)
|
mode.SetID(objectID)
|
||||||
@@ -761,8 +761,8 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy == nil {
|
if proxy == nil || proxy.IsZombie() {
|
||||||
// Mode not yet registered, create it
|
// Mode not yet registered or zombie, create fresh
|
||||||
mode := &ZwlrOutputModeV1{}
|
mode := &ZwlrOutputModeV1{}
|
||||||
mode.SetContext(i.Context())
|
mode.SetContext(i.Context())
|
||||||
mode.SetID(objectID)
|
mode.SetID(objectID)
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
|||||||
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
||||||
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
|
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
|
||||||
head.name = e.Name
|
head.name = e.Name
|
||||||
|
head.ready = true
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
@@ -251,11 +252,11 @@ func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEven
|
|||||||
|
|
||||||
m.heads.Delete(headID)
|
m.heads.Delete(headID)
|
||||||
|
|
||||||
m.post(func() {
|
m.wlMutex.Lock()
|
||||||
m.wlMutex.Lock()
|
handle.Release()
|
||||||
handle.Release()
|
m.wlMutex.Unlock()
|
||||||
m.wlMutex.Unlock()
|
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -310,11 +311,11 @@ func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHe
|
|||||||
|
|
||||||
m.modes.Delete(modeID)
|
m.modes.Delete(modeID)
|
||||||
|
|
||||||
m.post(func() {
|
m.wlMutex.Lock()
|
||||||
m.wlMutex.Lock()
|
handle.Release()
|
||||||
handle.Release()
|
m.wlMutex.Unlock()
|
||||||
m.wlMutex.Unlock()
|
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -328,6 +329,10 @@ func (m *Manager) updateState() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !head.ready {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
modes := make([]OutputMode, 0)
|
modes := make([]OutputMode, 0)
|
||||||
var currentMode *OutputMode
|
var currentMode *OutputMode
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ type headState struct {
|
|||||||
modeIDs []uint32
|
modeIDs []uint32
|
||||||
adaptiveSync uint32
|
adaptiveSync uint32
|
||||||
finished bool
|
finished bool
|
||||||
|
ready bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type modeState struct {
|
type modeState struct {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
dms-git (1.0.2+git2528.d336866f) nightly; urgency=medium
|
dms-git (1.0.2+git2528.d336866fdb1) nightly; urgency=medium
|
||||||
|
|
||||||
* Git snapshot (commit 2528: d336866f)
|
* Git snapshot (commit 2528: d336866f)
|
||||||
|
|
||||||
@@ -16,23 +16,6 @@ dms-git (1.0.2+git2518.a783d650) nightly; urgency=medium
|
|||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 15:11:40 +0000
|
-- 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
|
dms-git (1.0.0+git2419.993f14a3) nightly; urgency=medium
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
<service name="download_url">
|
<service name="download_url">
|
||||||
<param name="protocol">https</param>
|
<param name="protocol">https</param>
|
||||||
<param name="host">github.com</param>
|
<param name="host">github.com</param>
|
||||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.2.tar.gz</param>
|
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v1.0.3.tar.gz</param>
|
||||||
<param name="filename">dms-source.tar.gz</param>
|
<param name="filename">dms-source.tar.gz</param>
|
||||||
</service>
|
</service>
|
||||||
<!-- Download amd64 binary -->
|
<!-- Download amd64 binary -->
|
||||||
<service name="download_url">
|
<service name="download_url">
|
||||||
<param name="protocol">https</param>
|
<param name="protocol">https</param>
|
||||||
<param name="host">github.com</param>
|
<param name="host">github.com</param>
|
||||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-amd64.gz</param>
|
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-amd64.gz</param>
|
||||||
</service>
|
</service>
|
||||||
<!-- Download arm64 binary -->
|
<!-- Download arm64 binary -->
|
||||||
<service name="download_url">
|
<service name="download_url">
|
||||||
<param name="protocol">https</param>
|
<param name="protocol">https</param>
|
||||||
<param name="host">github.com</param>
|
<param name="host">github.com</param>
|
||||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.2/dms-distropkg-arm64.gz</param>
|
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.0.3/dms-distropkg-arm64.gz</param>
|
||||||
</service>
|
</service>
|
||||||
</services>
|
</services>
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
dms (1.0.2ppa6) unstable; urgency=medium
|
dms (1.0.3db1) unstable; urgency=medium
|
||||||
|
|
||||||
* Rebuild to fix repository metadata issues
|
* 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
|
||||||
|
|
||||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:47:39 +0000
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 13 Dec 2025 06:47:39 +0000
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
%global debug_package %{nil}
|
%global debug_package %{nil}
|
||||||
|
|
||||||
Name: dms
|
Name: dms
|
||||||
Version: 1.0.2
|
Version: 1.0.3
|
||||||
Release: 7%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
@@ -105,6 +105,9 @@ pkill -USR1 -x dms >/dev/null 2>&1 || :
|
|||||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
%changelog
|
%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
|
* Fri Dec 12 2025 AvengeMedia <maintainer@avengemedia.com> - 1.0.2-1
|
||||||
- Update to stable v1.0.2 release
|
- Update to stable v1.0.2 release
|
||||||
- Bug fixes and improvements
|
- Bug fixes and improvements
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
|
# ./distro/scripts/obs-upload.sh dms "Update to v1.0.2"
|
||||||
# ./distro/scripts/obs-upload.sh debian dms
|
# ./distro/scripts/obs-upload.sh debian dms
|
||||||
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
||||||
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with ppa2 suffix
|
# ./distro/scripts/obs-upload.sh debian dms-git 2 # Rebuild with db2 suffix
|
||||||
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with ppa2 suffix (flag syntax)
|
# ./distro/scripts/obs-upload.sh dms-git --rebuild=2 # Rebuild with db2 suffix (flag syntax)
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -126,8 +126,8 @@ check_obs_version_exists() {
|
|||||||
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
|
OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs)
|
||||||
# Commit hash check for -git packages
|
# Commit hash check for -git packages
|
||||||
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then
|
if [[ "$CHECK_MODE" == "commit" ]] && [[ "$PACKAGE" == *"-git" ]]; then
|
||||||
OBS_COMMIT=$(echo "$OBS_VERSION" | grep -oP '\.([a-f0-9]{8})(ppa[0-9]+)?$' | grep -oP '[a-f0-9]{8}' || echo "")
|
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})(ppa[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 "")
|
||||||
|
|
||||||
if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
|
if [[ -n "$OBS_COMMIT" && -n "$NEW_COMMIT" && "$OBS_COMMIT" == "$NEW_COMMIT" ]]; then
|
||||||
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)"
|
echo "⚠️ Commit $NEW_COMMIT already exists in OBS (current version: $OBS_VERSION)"
|
||||||
@@ -279,7 +279,8 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
|||||||
|
|
||||||
# Apply rebuild suffix if specified (must happen before API check)
|
# Apply rebuild suffix if specified (must happen before API check)
|
||||||
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
if [[ -n "$REBUILD_RELEASE" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
CHANGELOG_VERSION="${CHANGELOG_VERSION}ppa${REBUILD_RELEASE}"
|
BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/db[0-9]*$//')
|
||||||
|
CHANGELOG_VERSION="${BASE_VERSION}db${REBUILD_RELEASE}"
|
||||||
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -307,12 +308,16 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
|||||||
else
|
else
|
||||||
# Rebuild number specified - check if this exact version already exists (exact mode)
|
# Rebuild number specified - check if this exact version already exists (exact mode)
|
||||||
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
|
if check_obs_version_exists "$OBS_PROJECT" "$PACKAGE" "$CHANGELOG_VERSION" "exact"; then
|
||||||
echo "==> Error: Version $CHANGELOG_VERSION already exists in OBS"
|
echo "==> Version $CHANGELOG_VERSION already exists in OBS"
|
||||||
echo " This exact version (including ppa${REBUILD_RELEASE}) is already uploaded."
|
echo " This exact version (including db${REBUILD_RELEASE}) is already uploaded."
|
||||||
echo " To rebuild with a different release number, try incrementing:"
|
echo " Skipping upload - nothing to do."
|
||||||
|
echo ""
|
||||||
|
echo " 💡 To rebuild with a different release number, try incrementing:"
|
||||||
NEXT_NUM=$((REBUILD_RELEASE + 1))
|
NEXT_NUM=$((REBUILD_RELEASE + 1))
|
||||||
echo " ./distro/scripts/obs-upload.sh $PACKAGE $NEXT_NUM"
|
echo " REBUILD_RELEASE=$NEXT_NUM"
|
||||||
exit 1
|
echo ""
|
||||||
|
echo "✓ Exiting gracefully (no changes needed)"
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -511,7 +516,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
|
|
||||||
if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
|
if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
|
||||||
SOURCE_URL="${URL_PROTOCOL}://${URL_HOST}${URL_PATH}"
|
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 ||
|
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
|
curl -L -f -s -o "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null; then
|
||||||
@@ -534,9 +539,17 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
fi
|
fi
|
||||||
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
|
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
|
||||||
cd "$REPO_ROOT"
|
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
|
else
|
||||||
echo "Error: Failed to download source from $SOURCE_URL"
|
echo "ERROR: Failed to download source from $SOURCE_URL"
|
||||||
echo "Tried both wget and curl. Please check the URL and network connectivity."
|
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"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -553,7 +566,7 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " Found source directory: $SOURCE_DIR"
|
echo "==> Found source directory: $SOURCE_DIR"
|
||||||
|
|
||||||
# Vendor Go dependencies for dms-git
|
# Vendor Go dependencies for dms-git
|
||||||
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then
|
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then
|
||||||
@@ -712,6 +725,10 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
TARBALL_BASE=$(basename "$SOURCE_DIR")
|
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"
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||||
cd "$REPO_ROOT"
|
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
|
if [[ "$PACKAGE" == "dms" ]]; then
|
||||||
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1)
|
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1)
|
||||||
@@ -723,6 +740,10 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
rm -f "$WORK_DIR/$COMBINED_TARBALL"
|
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"
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
if [[ "$(pwd)" != "$REPO_ROOT" ]]; then
|
||||||
|
echo "ERROR: Failed to return to REPO_ROOT after tarball recreation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -796,23 +817,29 @@ EOF
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$WORK_DIR"
|
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)"
|
||||||
|
|
||||||
# Server-side cleanup via API
|
# Server-side cleanup via API
|
||||||
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
|
echo "==> Cleaning old tarballs from OBS server (prevents downloading 100+ old versions)"
|
||||||
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
|
OBS_FILES=$(osc api "/source/$OBS_PROJECT/$PACKAGE" 2>/dev/null || echo "")
|
||||||
if [[ -n "$OBS_FILES" ]]; then
|
if [[ -n "$OBS_FILES" ]]; then
|
||||||
DELETED_COUNT=0
|
DELETED_COUNT=0
|
||||||
KEEP_PATTERN=""
|
KEEP_CURRENT=""
|
||||||
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
BASE_KEEP_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/ppa[0-9]*$//')
|
KEEP_CURRENT="${PACKAGE}_${CHANGELOG_VERSION}.tar.gz"
|
||||||
KEEP_PATTERN="${PACKAGE}_${BASE_KEEP_VERSION}"
|
echo " Keeping only current version: ${KEEP_CURRENT}"
|
||||||
echo " Keeping tarballs matching: ${KEEP_PATTERN}*"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
|
for old_file in $(echo "$OBS_FILES" | grep -oP '(?<=name=")[^"]*\.(tar\.gz|tar\.xz|tar\.bz2)(?=")' || true); do
|
||||||
if [[ -n "$KEEP_PATTERN" ]] && [[ "$old_file" == ${KEEP_PATTERN}* ]]; then
|
if [[ "$old_file" == "$KEEP_CURRENT" ]]; then
|
||||||
echo " - Keeping current version: $old_file"
|
echo " - Keeping: $old_file"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -835,14 +862,11 @@ else
|
|||||||
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
|
echo " ⚠️ Could not fetch file list from server, skipping cleanup"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback update with --server-side-source-service-files flag only syncs metadata (spec, dsc, _service)
|
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
|
||||||
echo "==> Updating working copy"
|
echo "==> Updating working copy"
|
||||||
if ! osc up --server-side-source-service-files 2>/dev/null; then
|
if ! osc up 2>/dev/null; then
|
||||||
echo " Note: Using regular update (--server-side-source-service-files not supported)"
|
echo "Error: Failed to update working copy"
|
||||||
if ! osc up; then
|
exit 1
|
||||||
echo "Error: Failed to update working copy"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure we're in WORK_DIR and it exists
|
# Ensure we're in WORK_DIR and it exists
|
||||||
@@ -882,6 +906,15 @@ elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
|||||||
fi
|
fi
|
||||||
echo ""
|
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
|
osc addremove 2>&1 | grep -v "Git SCM package" || true
|
||||||
|
|
||||||
SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
|
SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
|
||||||
@@ -908,7 +941,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
|
|||||||
else
|
else
|
||||||
echo "==> Committing to OBS"
|
echo "==> Committing to OBS"
|
||||||
set +e
|
set +e
|
||||||
osc commit -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
osc commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||||
COMMIT_EXIT=${PIPESTATUS[0]}
|
COMMIT_EXIT=${PIPESTATUS[0]}
|
||||||
set -e
|
set -e
|
||||||
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
||||||
|
|||||||
@@ -918,6 +918,7 @@ Singleton {
|
|||||||
|
|
||||||
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
|
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
|
||||||
const colors = {};
|
const colors = {};
|
||||||
|
const isLight = SessionData !== "undefined" && SessionData.isLightMode;
|
||||||
|
|
||||||
function addColor(matugenKey, darkVal, lightVal) {
|
function addColor(matugenKey, darkVal, lightVal) {
|
||||||
if (!darkVal && !lightVal)
|
if (!darkVal && !lightVal)
|
||||||
@@ -930,7 +931,7 @@ Singleton {
|
|||||||
"color": String(lightVal || darkVal)
|
"color": String(lightVal || darkVal)
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
"color": String(darkVal || lightVal)
|
"color": String((isLight && lightVal) ? lightVal : darkVal)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
right: root.useSingleWindow ? true : undefined
|
right: root.useSingleWindow
|
||||||
bottom: root.useSingleWindow ? true : undefined
|
bottom: root.useSingleWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
@@ -284,8 +284,8 @@ Item {
|
|||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useSingleWindow ? undefined : root.alignedWidth + (shadowBuffer * 2)
|
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
||||||
implicitHeight: root.useSingleWindow ? undefined : root.alignedHeight + (shadowBuffer * 2)
|
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ DankModal {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string outputName: ""
|
property string outputName: ""
|
||||||
property var position: undefined
|
property var changes: []
|
||||||
property var mode: undefined
|
property int countdown: 10
|
||||||
property var vrr: undefined
|
|
||||||
property int countdown: 15
|
signal confirmed
|
||||||
|
signal reverted
|
||||||
|
|
||||||
shouldBeVisible: false
|
shouldBeVisible: false
|
||||||
allowStacking: true
|
allowStacking: true
|
||||||
@@ -23,23 +24,27 @@ DankModal {
|
|||||||
repeat: true
|
repeat: true
|
||||||
running: root.shouldBeVisible
|
running: root.shouldBeVisible
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
countdown--;
|
root.countdown--;
|
||||||
if (countdown <= 0) {
|
if (root.countdown <= 0) {
|
||||||
revert();
|
root.reverted();
|
||||||
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
countdown = 15;
|
countdown = 10;
|
||||||
countdownTimer.start();
|
countdownTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosed: {
|
onDialogClosed: {
|
||||||
countdownTimer.stop();
|
countdownTimer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackgroundClicked: revert
|
onBackgroundClicked: {
|
||||||
|
root.reverted();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
FocusScope {
|
FocusScope {
|
||||||
@@ -50,12 +55,14 @@ DankModal {
|
|||||||
implicitHeight: mainColumn.implicitHeight
|
implicitHeight: mainColumn.implicitHeight
|
||||||
|
|
||||||
Keys.onEscapePressed: event => {
|
Keys.onEscapePressed: event => {
|
||||||
revert();
|
root.reverted();
|
||||||
|
root.close();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onReturnPressed: event => {
|
Keys.onReturnPressed: event => {
|
||||||
confirm();
|
root.confirmed();
|
||||||
|
root.close();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,81 +76,42 @@ DankModal {
|
|||||||
anchors.topMargin: Theme.spacingM
|
anchors.topMargin: Theme.spacingM
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
width: parent.width
|
text: I18n.tr("Confirm Display Changes")
|
||||||
spacing: Theme.spacingXS
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
StyledText {
|
font.weight: Font.Medium
|
||||||
text: I18n.tr("Confirm Display Changes")
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Display settings for ") + outputName
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 80
|
height: 70
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainerHighest
|
color: Theme.surfaceContainerHighest
|
||||||
|
|
||||||
Column {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 4
|
text: root.countdown + "s"
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge * 1.5
|
||||||
StyledText {
|
color: Theme.primary
|
||||||
text: I18n.tr("Reverting in:")
|
font.weight: Font.Bold
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: countdown + "s"
|
|
||||||
font.pixelSize: Theme.fontSizeXLarge * 1.5
|
|
||||||
color: Theme.primary
|
|
||||||
font.weight: Font.Bold
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.changes.length > 0
|
||||||
|
|
||||||
StyledText {
|
Repeater {
|
||||||
text: I18n.tr("Changes:")
|
model: root.changes
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
visible: position !== undefined && position !== null
|
required property var modelData
|
||||||
text: I18n.tr("Position: ") + (position ? position.x + ", " + position.y : "")
|
text: modelData
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceVariantText
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
|
||||||
visible: mode !== undefined && mode !== null && mode !== ""
|
|
||||||
text: I18n.tr("Mode: ") + (mode || "")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
visible: vrr !== undefined && vrr !== null
|
|
||||||
text: I18n.tr("VRR: ") + (vrr ? I18n.tr("Enabled") : I18n.tr("Disabled"))
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +148,10 @@ DankModal {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: revert
|
onClicked: {
|
||||||
|
root.reverted();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +177,10 @@ DankModal {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: confirm
|
onClicked: {
|
||||||
|
root.confirmed();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
@@ -228,18 +202,11 @@ DankModal {
|
|||||||
iconName: "close"
|
iconName: "close"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
onClicked: revert
|
onClicked: {
|
||||||
|
root.reverted();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirm() {
|
|
||||||
displaysTab.confirmChanges();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function revert() {
|
|
||||||
displaysTab.revertChanges();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,13 +125,45 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: displaysLoader
|
id: displayConfigLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 6
|
active: root.currentIndex === 24
|
||||||
visible: active
|
visible: active
|
||||||
focus: active
|
focus: active
|
||||||
|
|
||||||
sourceComponent: DisplaysTab {}
|
sourceComponent: DisplayConfigTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item) {
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: gammaControlLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 25
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: GammaControlTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item) {
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: displayWidgetsLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 26
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: DisplayWidgetsTab {}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active && item) {
|
if (active && item) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ FloatingWindow {
|
|||||||
objectName: "settingsModal"
|
objectName: "settingsModal"
|
||||||
title: I18n.tr("Settings", "settings window title")
|
title: I18n.tr("Settings", "settings window title")
|
||||||
minimumSize: Qt.size(500, 400)
|
minimumSize: Qt.size(500, 400)
|
||||||
implicitWidth: 800
|
implicitWidth: 900
|
||||||
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|||||||
@@ -144,6 +144,32 @@ Rectangle {
|
|||||||
"tabIndex": 2,
|
"tabIndex": 2,
|
||||||
"shortcutsOnly": true
|
"shortcutsOnly": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "displays",
|
||||||
|
"text": I18n.tr("Displays"),
|
||||||
|
"icon": "monitor",
|
||||||
|
"collapsedByDefault": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "display_config",
|
||||||
|
"text": I18n.tr("Configuration") + " (Beta)",
|
||||||
|
"icon": "display_settings",
|
||||||
|
"tabIndex": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "display_gamma",
|
||||||
|
"text": I18n.tr("Gamma Control"),
|
||||||
|
"icon": "brightness_6",
|
||||||
|
"tabIndex": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "display_widgets",
|
||||||
|
"text": I18n.tr("Widgets", "settings_displays"),
|
||||||
|
"icon": "widgets",
|
||||||
|
"tabIndex": 26
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "network",
|
"id": "network",
|
||||||
"text": I18n.tr("Network"),
|
"text": I18n.tr("Network"),
|
||||||
@@ -157,12 +183,6 @@ Rectangle {
|
|||||||
"icon": "computer",
|
"icon": "computer",
|
||||||
"collapsedByDefault": true,
|
"collapsedByDefault": true,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
|
||||||
"id": "displays",
|
|
||||||
"text": I18n.tr("Displays"),
|
|
||||||
"icon": "monitor",
|
|
||||||
"tabIndex": 6
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "printers",
|
"id": "printers",
|
||||||
"text": I18n.tr("Printers"),
|
"text": I18n.tr("Printers"),
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ Item {
|
|||||||
property bool longPressing: false
|
property bool longPressing: false
|
||||||
property bool dragging: false
|
property bool dragging: false
|
||||||
property point dragStartPos: Qt.point(0, 0)
|
property point dragStartPos: Qt.point(0, 0)
|
||||||
property point dragOffset: Qt.point(0, 0)
|
property real dragAxisOffset: 0
|
||||||
property int targetIndex: -1
|
property int targetIndex: -1
|
||||||
property int originalIndex: -1
|
property int originalIndex: -1
|
||||||
|
property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||||
property bool showWindowTitle: false
|
property bool showWindowTitle: false
|
||||||
property string windowTitle: ""
|
property string windowTitle: ""
|
||||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||||
@@ -95,7 +96,7 @@ Item {
|
|||||||
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
|
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
|
||||||
}
|
}
|
||||||
onIsHoveredChanged: {
|
onIsHoveredChanged: {
|
||||||
if (mouseArea.pressed)
|
if (mouseArea.pressed || dragging)
|
||||||
return;
|
return;
|
||||||
if (isHovered) {
|
if (isHovered) {
|
||||||
exitAnimation.stop();
|
exitAnimation.stop();
|
||||||
@@ -128,8 +129,8 @@ Item {
|
|||||||
running: false
|
running: false
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: iconTransform
|
target: root
|
||||||
property: animateX ? "x" : "y"
|
property: "hoverAnimOffset"
|
||||||
to: animationDirection * animationDistance * 0.25
|
to: animationDirection * animationDistance * 0.25
|
||||||
duration: Anims.durShort
|
duration: Anims.durShort
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
@@ -137,8 +138,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: iconTransform
|
target: root
|
||||||
property: animateX ? "x" : "y"
|
property: "hoverAnimOffset"
|
||||||
to: animationDirection * animationDistance * 0.2
|
to: animationDirection * animationDistance * 0.2
|
||||||
duration: Anims.durShort
|
duration: Anims.durShort
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
@@ -150,8 +151,8 @@ Item {
|
|||||||
id: exitAnimation
|
id: exitAnimation
|
||||||
|
|
||||||
running: false
|
running: false
|
||||||
target: iconTransform
|
target: root
|
||||||
property: animateX ? "x" : "y"
|
property: "hoverAnimOffset"
|
||||||
to: 0
|
to: 0
|
||||||
duration: Anims.durShort
|
duration: Anims.durShort
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
@@ -187,16 +188,79 @@ Item {
|
|||||||
}
|
}
|
||||||
onReleased: mouse => {
|
onReleased: mouse => {
|
||||||
longPressTimer.stop();
|
longPressTimer.stop();
|
||||||
if (longPressing) {
|
|
||||||
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
|
|
||||||
dockApps.movePinnedApp(originalIndex, targetIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
longPressing = false;
|
const wasDragging = dragging;
|
||||||
dragging = false;
|
const didReorder = wasDragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps;
|
||||||
dragOffset = Qt.point(0, 0);
|
|
||||||
targetIndex = -1;
|
if (didReorder)
|
||||||
originalIndex = -1;
|
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 || ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onPositionChanged: mouse => {
|
onPositionChanged: mouse => {
|
||||||
@@ -206,90 +270,47 @@ Item {
|
|||||||
dragging = true;
|
dragging = true;
|
||||||
targetIndex = index;
|
targetIndex = index;
|
||||||
originalIndex = 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 (!dragging || !dockApps)
|
||||||
if (dockApps) {
|
return;
|
||||||
const threshold = actualIconSize;
|
|
||||||
let newTargetIndex = targetIndex;
|
const axisOffset = isVertical ? (mouse.y - dragStartPos.y) : (mouse.x - dragStartPos.x);
|
||||||
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
|
dragAxisOffset = axisOffset;
|
||||||
newTargetIndex = targetIndex + 1;
|
|
||||||
} else if (dragOffset.x < -threshold && targetIndex > 0) {
|
const spacing = Math.min(8, Math.max(4, actualIconSize * 0.08));
|
||||||
newTargetIndex = targetIndex - 1;
|
const itemSize = actualIconSize * 1.2 + spacing;
|
||||||
}
|
const slotOffset = Math.round(axisOffset / itemSize);
|
||||||
if (newTargetIndex !== targetIndex) {
|
const newTargetIndex = Math.max(0, Math.min(dockApps.pinnedAppCount - 1, originalIndex + slotOffset));
|
||||||
targetIndex = newTargetIndex;
|
|
||||||
dragStartPos = Qt.point(mouse.x, mouse.y);
|
if (newTargetIndex !== targetIndex) {
|
||||||
}
|
targetIndex = newTargetIndex;
|
||||||
}
|
dockApps.dropTargetIndex = newTargetIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (!appData || longPressing) {
|
if (!appData)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.MiddleButton) {
|
||||||
if (appData.type === "pinned") {
|
switch (appData.type) {
|
||||||
if (appData && appData.appId) {
|
case "window":
|
||||||
const desktopEntry = cachedDesktopEntry;
|
appData.toplevel?.close();
|
||||||
if (desktopEntry) {
|
break;
|
||||||
AppUsageHistoryData.addAppUsage({
|
case "grouped":
|
||||||
"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) {
|
if (contextMenu) {
|
||||||
const shouldHidePin = appData.appId === "org.quickshell";
|
const shouldHidePin = appData.appId === "org.quickshell";
|
||||||
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||||
}
|
}
|
||||||
} else if (appData && appData.appId) {
|
break;
|
||||||
|
default:
|
||||||
|
if (!appData.appId)
|
||||||
|
return;
|
||||||
const desktopEntry = cachedDesktopEntry;
|
const desktopEntry = cachedDesktopEntry;
|
||||||
if (desktopEntry) {
|
if (desktopEntry) {
|
||||||
AppUsageHistoryData.addAppUsage({
|
AppUsageHistoryData.addAppUsage({
|
||||||
@@ -301,26 +322,39 @@ Item {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
SessionService.launchDesktopEntry(desktopEntry);
|
SessionService.launchDesktopEntry(desktopEntry);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
if (contextMenu && appData) {
|
if (!contextMenu)
|
||||||
const shouldHidePin = appData.appId === "org.quickshell";
|
return;
|
||||||
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
const shouldHidePin = appData.appId === "org.quickshell";
|
||||||
} else {
|
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
|
||||||
console.warn("No context menu or appData available");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property real hoverAnimOffset: 0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: visualContent
|
id: visualContent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
transform: Translate {
|
transform: Translate {
|
||||||
id: iconTransform
|
id: iconTransform
|
||||||
x: 0
|
x: {
|
||||||
y: 0
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -17,6 +14,9 @@ Item {
|
|||||||
property bool isVertical: false
|
property bool isVertical: false
|
||||||
property var dockScreen: null
|
property var dockScreen: null
|
||||||
property real iconSize: 40
|
property real iconSize: 40
|
||||||
|
property int draggedIndex: -1
|
||||||
|
property int dropTargetIndex: -1
|
||||||
|
property bool suppressShiftAnimation: false
|
||||||
|
|
||||||
clip: false
|
clip: false
|
||||||
implicitWidth: isVertical ? appLayout.height : appLayout.width
|
implicitWidth: isVertical ? appLayout.height : appLayout.width
|
||||||
@@ -24,18 +24,18 @@ Item {
|
|||||||
|
|
||||||
function movePinnedApp(fromIndex, toIndex) {
|
function movePinnedApp(fromIndex, toIndex) {
|
||||||
if (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) {
|
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0 || toIndex >= currentPinned.length) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const movedApp = currentPinned.splice(fromIndex, 1)[0]
|
const movedApp = currentPinned.splice(fromIndex, 1)[0];
|
||||||
currentPinned.splice(toIndex, 0, movedApp)
|
currentPinned.splice(toIndex, 0, movedApp);
|
||||||
|
|
||||||
SessionData.setPinnedApps(currentPinned)
|
SessionData.setPinnedApps(currentPinned);
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -53,202 +53,250 @@ Item {
|
|||||||
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||||
spacing: Math.min(8, Math.max(4, root.iconSize * 0.08))
|
spacing: Math.min(8, Math.max(4, root.iconSize * 0.08))
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: repeater
|
id: repeater
|
||||||
|
|
||||||
property var dockItems: []
|
property var dockItems: []
|
||||||
|
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: repeater.dockItems
|
values: repeater.dockItems
|
||||||
objectProp: "uniqueKey"
|
objectProp: "uniqueKey"
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: updateModel()
|
Component.onCompleted: updateModel()
|
||||||
|
|
||||||
function updateModel() {
|
function updateModel() {
|
||||||
const items = []
|
const items = [];
|
||||||
const pinnedApps = [...(SessionData.pinnedApps || [])]
|
const pinnedApps = [...(SessionData.pinnedApps || [])];
|
||||||
const sortedToplevels = CompositorService.sortedToplevels
|
const sortedToplevels = CompositorService.sortedToplevels;
|
||||||
|
|
||||||
if (root.groupByApp) {
|
if (root.groupByApp) {
|
||||||
const appGroups = new Map()
|
const appGroups = new Map();
|
||||||
|
|
||||||
pinnedApps.forEach(rawAppId => {
|
pinnedApps.forEach(rawAppId => {
|
||||||
const appId = Paths.moddedAppId(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, {
|
appGroups.set(appId, {
|
||||||
appId: appId,
|
appId: appId,
|
||||||
isPinned: false,
|
isPinned: true,
|
||||||
windows: []
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
appGroups.get(appId).windows.push({
|
unpinnedGroups.forEach(item => items.push(item));
|
||||||
toplevel: toplevel,
|
root.pinnedAppCount = pinnedGroups.length;
|
||||||
index: index
|
} else {
|
||||||
})
|
pinnedApps.forEach(rawAppId => {
|
||||||
})
|
const appId = Paths.moddedAppId(rawAppId);
|
||||||
|
items.push({
|
||||||
|
uniqueKey: "pinned_" + appId,
|
||||||
|
type: "pinned",
|
||||||
|
appId: appId,
|
||||||
|
toplevel: null,
|
||||||
|
isPinned: true,
|
||||||
|
isRunning: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const pinnedGroups = []
|
root.pinnedAppCount = pinnedApps.length;
|
||||||
const unpinnedGroups = []
|
|
||||||
|
|
||||||
Array.from(appGroups.entries()).forEach(([appId, group]) => {
|
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
|
||||||
const firstWindow = group.windows.length > 0 ? group.windows[0] : null
|
items.push({
|
||||||
|
uniqueKey: "separator_ungrouped",
|
||||||
const item = {
|
type: "separator",
|
||||||
uniqueKey: "grouped_" + appId,
|
appId: "__SEPARATOR__",
|
||||||
type: "grouped",
|
toplevel: null,
|
||||||
appId: appId,
|
isPinned: false,
|
||||||
toplevel: firstWindow ? firstWindow.toplevel : null,
|
isRunning: false
|
||||||
isPinned: group.isPinned,
|
});
|
||||||
isRunning: group.windows.length > 0,
|
|
||||||
windowCount: group.windows.length,
|
|
||||||
allWindows: group.windows
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group.isPinned) {
|
sortedToplevels.forEach((toplevel, index) => {
|
||||||
pinnedGroups.push(item)
|
let uniqueKey = "window_" + index;
|
||||||
} else {
|
if (CompositorService.isHyprland && Hyprland.toplevels) {
|
||||||
unpinnedGroups.push(item)
|
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;
|
||||||
pinnedGroups.forEach(item => items.push(item))
|
break;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: delegateItem
|
||||||
|
property alias dockButton: button
|
||||||
|
property var itemData: modelData
|
||||||
|
clip: false
|
||||||
|
z: button.dragging ? 100 : 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)
|
||||||
|
|
||||||
|
property real shiftOffset: {
|
||||||
|
if (root.draggedIndex < 0 || !itemData.isPinned || itemData.type === "separator")
|
||||||
|
return 0;
|
||||||
|
if (model.index === root.draggedIndex)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const dragIdx = root.draggedIndex;
|
||||||
|
const dropIdx = root.dropTargetIndex;
|
||||||
|
const myIdx = model.index;
|
||||||
|
const shiftAmount = root.iconSize * 1.2 + layoutFlow.spacing;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
x: root.isVertical ? 0 : delegateItem.shiftOffset
|
||||||
|
y: root.isVertical ? delegateItem.shiftOffset : 0
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
enabled: !root.suppressShiftAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
Behavior on y {
|
||||||
uniqueKey: uniqueKey,
|
enabled: !root.suppressShiftAnimation
|
||||||
type: "window",
|
NumberAnimation {
|
||||||
appId: Paths.moddedAppId(toplevel.appId),
|
duration: 150
|
||||||
toplevel: toplevel,
|
easing.type: Easing.OutCubic
|
||||||
isPinned: false,
|
}
|
||||||
isRunning: true
|
}
|
||||||
})
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
dockItems = items
|
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
|
||||||
|
}
|
||||||
|
|
||||||
delegate: Item {
|
DockAppButton {
|
||||||
id: delegateItem
|
id: button
|
||||||
property alias dockButton: button
|
visible: itemData.type !== "separator"
|
||||||
property var itemData: modelData
|
anchors.centerIn: parent
|
||||||
clip: false
|
|
||||||
|
|
||||||
width: itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2)
|
width: delegateItem.width
|
||||||
height: itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize)
|
height: delegateItem.height
|
||||||
|
actualIconSize: root.iconSize
|
||||||
|
|
||||||
Rectangle {
|
appData: itemData
|
||||||
visible: itemData.type === "separator"
|
contextMenu: root.contextMenu
|
||||||
width: root.isVertical ? root.iconSize * 0.5 : 2
|
dockApps: root
|
||||||
height: root.isVertical ? 2 : root.iconSize * 0.5
|
index: model.index
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
parentDockScreen: root.dockScreen
|
||||||
radius: 1
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
DockAppButton {
|
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
||||||
id: button
|
windowTitle: {
|
||||||
visible: itemData.type !== "separator"
|
const title = itemData?.toplevel?.title || "(Unnamed)";
|
||||||
anchors.centerIn: parent
|
return title.length > 50 ? title.substring(0, 47) + "..." : title;
|
||||||
|
}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: CompositorService
|
target: CompositorService
|
||||||
function onToplevelsChanged() {
|
function onToplevelsChanged() {
|
||||||
repeater.updateModel()
|
repeater.updateModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onPinnedAppsChanged() {
|
function onPinnedAppsChanged() {
|
||||||
repeater.updateModel()
|
root.suppressShiftAnimation = true;
|
||||||
|
root.draggedIndex = -1;
|
||||||
|
root.dropTargetIndex = -1;
|
||||||
|
repeater.updateModel();
|
||||||
|
Qt.callLater(() => {
|
||||||
|
root.suppressShiftAnimation = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onGroupByAppChanged: {
|
onGroupByAppChanged: {
|
||||||
repeater.updateModel()
|
repeater.updateModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,497 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function getBarComponentsFromSettings() {
|
||||||
|
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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
variantComponents = getVariantComponentsList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScreenPreferences(componentId) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4);
|
||||||
|
const barConfig = SettingsData.getBarConfig(barId);
|
||||||
|
return barConfig?.screenPreferences || ["all"];
|
||||||
|
}
|
||||||
|
return SettingsData.screenPreferences && SettingsData.screenPreferences[componentId] || ["all"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScreenPreferences(componentId, screenNames) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4);
|
||||||
|
SettingsData.updateBarConfig(barId, {
|
||||||
|
"screenPreferences": screenNames
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setShowOnLastDisplay(componentId, enabled) {
|
||||||
|
if (componentId.startsWith("bar:")) {
|
||||||
|
const barId = componentId.substring(4);
|
||||||
|
SettingsData.updateBarConfig(barId, {
|
||||||
|
"showOnLastDisplay": enabled
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var prefs = SettingsData.showOnLastDisplay || {};
|
||||||
|
var newPrefs = Object.assign({}, prefs);
|
||||||
|
newPrefs[componentId] = enabled;
|
||||||
|
SettingsData.set("showOnLastDisplay", newPrefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height + Theme.spacingXL
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
|
||||||
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: screensInfoSection.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: screensInfoSection
|
||||||
|
|
||||||
|
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.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Connected Displays")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Configure which displays show shell components")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Available Screens (") + Quickshell.screens.length + ")"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Display Name Format")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: displayModeGroup
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onDisplayNameModeChanged() {
|
||||||
|
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: screenRow.implicitHeight + Theme.spacingS * 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.3)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: screenRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "desktop_windows"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM * 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS / 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SettingsData.getScreenDisplayName(modelData)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
property var wlrOutput: WlrOutputService.wlrOutputAvailable ? WlrOutputService.getOutput(modelData.name) : null
|
||||||
|
property var currentMode: wlrOutput?.currentMode
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (parent.currentMode) {
|
||||||
|
return parent.currentMode.width + "×" + parent.currentMode.height + "@" + Math.round(parent.currentMode.refresh / 1000) + "Hz";
|
||||||
|
}
|
||||||
|
return modelData.width + "×" + modelData.height;
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "•"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SettingsData.displayNameMode === "system" ? (modelData.model || "Unknown Model") : modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.variantComponents
|
||||||
|
|
||||||
|
delegate: StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: componentSection.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: componentSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: modelData.icon
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.description
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Show on screens:")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
property string componentId: modelData.id
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Show on Last Display")
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
onToggled: checked => {
|
||||||
|
root.setShowOnLastDisplay(parent.componentId, checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.2
|
||||||
|
visible: {
|
||||||
|
var prefs = root.getScreenPreferences(parent.componentId);
|
||||||
|
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: {
|
||||||
|
var prefs = root.getScreenPreferences(parent.componentId);
|
||||||
|
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
delegate: DankToggle {
|
||||||
|
property var screenData: modelData
|
||||||
|
property string componentId: parent.parent.componentId
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
text: SettingsData.getScreenDisplayName(screenData)
|
||||||
|
description: screenData.width + "×" + screenData.height + " • " + (SettingsData.displayNameMode === "system" ? (screenData.model || "Unknown Model") : screenData.name)
|
||||||
|
checked: {
|
||||||
|
var prefs = root.getScreenPreferences(componentId);
|
||||||
|
if (typeof prefs[0] === "string" && prefs[0] === "all")
|
||||||
|
return false;
|
||||||
|
return SettingsData.isScreenInPreferences(screenData, prefs);
|
||||||
|
}
|
||||||
|
onToggled: checked => {
|
||||||
|
var currentPrefs = root.getScreenPreferences(componentId);
|
||||||
|
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
|
||||||
|
currentPrefs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
const prefObj = {
|
||||||
|
"name": screenData.name,
|
||||||
|
"model": screenData.model || ""
|
||||||
|
};
|
||||||
|
if (screenModelIndex >= 0) {
|
||||||
|
prefObj.modelIndex = screenModelIndex;
|
||||||
|
}
|
||||||
|
newPrefs.push(prefObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setScreenPreferences(componentId, newPrefs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-478
@@ -1,12 +1,10 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: displaysTab
|
id: root
|
||||||
|
|
||||||
function formatGammaTime(isoString) {
|
function formatGammaTime(isoString) {
|
||||||
if (!isoString)
|
if (!isoString)
|
||||||
@@ -21,113 +19,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBarComponentsFromSettings() {
|
|
||||||
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
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onBarConfigsChanged() {
|
|
||||||
variantComponents = getVariantComponentsList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScreenPreferences(componentId) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
const barConfig = SettingsData.getBarConfig(barId);
|
|
||||||
return barConfig?.screenPreferences || ["all"];
|
|
||||||
}
|
|
||||||
return SettingsData.screenPreferences && SettingsData.screenPreferences[componentId] || ["all"];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setScreenPreferences(componentId, screenNames) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
SettingsData.updateBarConfig(barId, {
|
|
||||||
screenPreferences: screenNames
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return SettingsData.showOnLastDisplay && SettingsData.showOnLastDisplay[componentId] || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setShowOnLastDisplay(componentId, enabled) {
|
|
||||||
if (componentId.startsWith("bar:")) {
|
|
||||||
const barId = componentId.substring(4);
|
|
||||||
SettingsData.updateBarConfig(barId, {
|
|
||||||
showOnLastDisplay: enabled
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var prefs = SettingsData.showOnLastDisplay || {};
|
|
||||||
var newPrefs = Object.assign({}, prefs);
|
|
||||||
newPrefs[componentId] = enabled;
|
|
||||||
SettingsData.set("showOnLastDisplay", newPrefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
@@ -678,7 +569,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: displaysTab.formatGammaTime(DisplayService.gammaSunriseTime)
|
text: root.formatGammaTime(DisplayService.gammaSunriseTime)
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -714,7 +605,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: displaysTab.formatGammaTime(DisplayService.gammaSunsetTime)
|
text: root.formatGammaTime(DisplayService.gammaSunsetTime)
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -761,7 +652,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: displaysTab.formatGammaTime(DisplayService.gammaNextTransition)
|
text: root.formatGammaTime(DisplayService.gammaNextTransition)
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -773,371 +664,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledRect {
|
|
||||||
width: parent.width
|
|
||||||
height: screensInfoSection.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: screensInfoSection
|
|
||||||
|
|
||||||
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.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Connected Displays")
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Configure which displays show shell components")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Available Screens (") + Quickshell.screens.length + ")"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 1
|
|
||||||
height: 1
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Display Name Format")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
DankButtonGroup {
|
|
||||||
id: displayModeGroup
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onDisplayNameModeChanged() {
|
|
||||||
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: screenRow.implicitHeight + Theme.spacingS * 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.3)
|
|
||||||
border.width: 0
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: screenRow
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "desktop_windows"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM * 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingXS / 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SettingsData.getScreenDisplayName(modelData)
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
property var wlrOutput: WlrOutputService.wlrOutputAvailable ? WlrOutputService.getOutput(modelData.name) : null
|
|
||||||
property var currentMode: wlrOutput?.currentMode
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: {
|
|
||||||
if (parent.currentMode) {
|
|
||||||
return parent.currentMode.width + "×" + parent.currentMode.height + "@" + Math.round(parent.currentMode.refresh / 1000) + "Hz";
|
|
||||||
}
|
|
||||||
return modelData.width + "×" + modelData.height;
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "•"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: SettingsData.displayNameMode === "system" ? (modelData.model || "Unknown Model") : modelData.name
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: displaysTab.variantComponents
|
|
||||||
|
|
||||||
delegate: StyledRect {
|
|
||||||
width: parent.width
|
|
||||||
height: componentSection.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: componentSection
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: modelData.icon
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: Theme.primary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.description
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Show on screens:")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
property string componentId: modelData.id
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: I18n.tr("All displays")
|
|
||||||
description: I18n.tr("Show on all connected displays")
|
|
||||||
checked: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
}
|
|
||||||
onToggled: checked => {
|
|
||||||
if (checked) {
|
|
||||||
displaysTab.setScreenPreferences(parent.componentId, ["all"]);
|
|
||||||
} else {
|
|
||||||
displaysTab.setScreenPreferences(parent.componentId, []);
|
|
||||||
const cid = parent.componentId;
|
|
||||||
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(cid) || cid.startsWith("bar:")) {
|
|
||||||
displaysTab.setShowOnLastDisplay(cid, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: I18n.tr("Show on Last Display")
|
|
||||||
description: I18n.tr("Always show when there's only one connected display")
|
|
||||||
checked: displaysTab.getShowOnLastDisplay(parent.componentId)
|
|
||||||
visible: {
|
|
||||||
const prefs = displaysTab.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 => {
|
|
||||||
displaysTab.setShowOnLastDisplay(parent.componentId, checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Theme.outline
|
|
||||||
opacity: 0.2
|
|
||||||
visible: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
visible: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(parent.componentId);
|
|
||||||
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all");
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: DankToggle {
|
|
||||||
property var screenData: modelData
|
|
||||||
property string componentId: parent.parent.componentId
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
text: SettingsData.getScreenDisplayName(screenData)
|
|
||||||
description: screenData.width + "×" + screenData.height + " • " + (SettingsData.displayNameMode === "system" ? (screenData.model || "Unknown Model") : screenData.name)
|
|
||||||
checked: {
|
|
||||||
var prefs = displaysTab.getScreenPreferences(componentId);
|
|
||||||
if (typeof prefs[0] === "string" && prefs[0] === "all")
|
|
||||||
return false;
|
|
||||||
return SettingsData.isScreenInPreferences(screenData, prefs);
|
|
||||||
}
|
|
||||||
onToggled: checked => {
|
|
||||||
var currentPrefs = displaysTab.getScreenPreferences(componentId);
|
|
||||||
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
|
|
||||||
currentPrefs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checked) {
|
|
||||||
const prefObj = {
|
|
||||||
name: screenData.name,
|
|
||||||
model: screenData.model || ""
|
|
||||||
};
|
|
||||||
if (screenModelIndex >= 0) {
|
|
||||||
prefObj.modelIndex = screenModelIndex;
|
|
||||||
}
|
|
||||||
newPrefs.push(prefObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
displaysTab.setScreenPreferences(componentId, newPrefs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,36 +328,21 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
DankButton {
|
||||||
id: fixButton
|
id: fixButton
|
||||||
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
|
||||||
height: Math.round(Theme.fontSizeMedium * 2.5)
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
visible: warningBox.showError || warningBox.showSetup
|
visible: warningBox.showError || warningBox.showSetup
|
||||||
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
text: {
|
||||||
|
if (KeybindsService.fixing)
|
||||||
|
return I18n.tr("Fixing...")
|
||||||
|
if (warningBox.showSetup)
|
||||||
|
return I18n.tr("Setup")
|
||||||
|
return I18n.tr("Fix Now")
|
||||||
|
}
|
||||||
|
backgroundColor: Theme.primary
|
||||||
|
textColor: Theme.primaryText
|
||||||
|
enabled: !KeybindsService.fixing
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: KeybindsService.fixDmsBindsInclude()
|
||||||
StyledText {
|
|
||||||
id: fixButtonText
|
|
||||||
text: {
|
|
||||||
if (KeybindsService.fixing)
|
|
||||||
return I18n.tr("Fixing...");
|
|
||||||
if (warningBox.showSetup)
|
|
||||||
return I18n.tr("Setup");
|
|
||||||
return I18n.tr("Fix Now");
|
|
||||||
}
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surface
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: !KeybindsService.fixing
|
|
||||||
onClicked: KeybindsService.fixDmsBindsInclude()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
readonly property string mangoDmsDir: configDir + "/mango/dms"
|
||||||
|
readonly property string outputsPath: mangoDmsDir + "/outputs.conf"
|
||||||
|
|
||||||
property bool dwlAvailable: false
|
property bool dwlAvailable: false
|
||||||
property var outputs: ({})
|
property var outputs: ({})
|
||||||
property var tagCount: 9
|
property var tagCount: 9
|
||||||
@@ -263,4 +269,80 @@ Singleton {
|
|||||||
|
|
||||||
return Array.from(visibleTags).sort((a, b) => a - b);
|
return Array.from(visibleTags).sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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", ""];
|
||||||
|
|
||||||
|
for (const outputName in outputsData) {
|
||||||
|
const output = outputsData[outputName];
|
||||||
|
if (!output)
|
||||||
|
continue;
|
||||||
|
let width = 1920;
|
||||||
|
let height = 1080;
|
||||||
|
let refreshRate = 60;
|
||||||
|
if (output.modes && output.current_mode !== undefined) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(",");
|
||||||
|
|
||||||
|
lines.push("monitorrule=" + rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
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.info("DwlService: Generated outputs config at", outputsPath);
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
reloadConfig();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfig() {
|
||||||
|
Proc.runCommand("mango-reload", ["mmsg", "-d", "reload_config"], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
readonly property string hyprDmsDir: configDir + "/hypr/dms"
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOutputsConfig(outputsData) {
|
||||||
|
if (!outputsData || Object.keys(outputsData).length === 0)
|
||||||
|
return;
|
||||||
|
let lines = ["# Auto-generated by DMS - do not edit manually", ""];
|
||||||
|
|
||||||
|
for (const outputName in outputsData) {
|
||||||
|
const output = outputsData[outputName];
|
||||||
|
if (!output)
|
||||||
|
continue;
|
||||||
|
let resolution = "preferred";
|
||||||
|
if (output.modes && output.current_mode !== undefined) {
|
||||||
|
const mode = output.modes[output.current_mode];
|
||||||
|
if (mode)
|
||||||
|
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 identifier = getOutputIdentifier(output, outputName);
|
||||||
|
let monitorLine = "monitor = " + identifier + ", " + resolution + ", " + position + ", " + scale;
|
||||||
|
|
||||||
|
const transform = transformToHyprland(output.logical?.transform ?? "Normal");
|
||||||
|
if (transform !== 0)
|
||||||
|
monitorLine += ", transform, " + transform;
|
||||||
|
|
||||||
|
if (output.vrr_supported && output.vrr_enabled)
|
||||||
|
monitorLine += ", vrr, 1";
|
||||||
|
|
||||||
|
lines.push(monitorLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
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.info("HyprlandService: Generated outputs config at", outputsPath);
|
||||||
|
if (CompositorService.isHyprland)
|
||||||
|
reloadConfig();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfig() {
|
||||||
|
Proc.runCommand("hyprctl-reload", ["hyprctl", "reload"], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
@@ -23,6 +23,8 @@ Singleton {
|
|||||||
property var windows: []
|
property var windows: []
|
||||||
property var displayScales: ({})
|
property var displayScales: ({})
|
||||||
|
|
||||||
|
property var _realOutputs: ({})
|
||||||
|
|
||||||
property bool inOverview: false
|
property bool inOverview: false
|
||||||
|
|
||||||
property int currentKeyboardLayoutIndex: 0
|
property int currentKeyboardLayoutIndex: 0
|
||||||
@@ -214,12 +216,12 @@ Singleton {
|
|||||||
const ws = workspaces[w.workspace_id];
|
const ws = workspaces[w.workspace_id];
|
||||||
if (!ws) {
|
if (!ws) {
|
||||||
return {
|
return {
|
||||||
window: w,
|
"window": w,
|
||||||
outputX: 999999,
|
"outputX": 999999,
|
||||||
outputY: 999999,
|
"outputY": 999999,
|
||||||
wsIdx: 999999,
|
"wsIdx": 999999,
|
||||||
col: 999999,
|
"col": 999999,
|
||||||
row: 999999
|
"row": 999999
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,12 +234,12 @@ Singleton {
|
|||||||
const row = (pos && pos.length >= 2) ? pos[1] : 999999;
|
const row = (pos && pos.length >= 2) ? pos[1] : 999999;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
window: w,
|
"window": w,
|
||||||
outputX: outputX,
|
"outputX": outputX,
|
||||||
outputY: outputY,
|
"outputY": outputY,
|
||||||
wsIdx: ws.idx,
|
"wsIdx": ws.idx,
|
||||||
col: col,
|
"col": col,
|
||||||
row: row
|
"row": row
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -598,7 +600,7 @@ Singleton {
|
|||||||
const editor = Quickshell.env("DMS_SCREENSHOT_EDITOR");
|
const editor = Quickshell.env("DMS_SCREENSHOT_EDITOR");
|
||||||
const command = editor === "satty" ? ["satty", "-f", data.path] : ["swappy", "-f", data.path];
|
const command = editor === "satty" ? ["satty", "-f", data.path] : ["swappy", "-f", data.path];
|
||||||
Quickshell.execDetached({
|
Quickshell.execDetached({
|
||||||
command: command
|
"command": command
|
||||||
});
|
});
|
||||||
pendingScreenshotPath = "";
|
pendingScreenshotPath = "";
|
||||||
}
|
}
|
||||||
@@ -993,35 +995,35 @@ Singleton {
|
|||||||
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4;
|
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4;
|
||||||
|
|
||||||
const dmsWarning = `// ! DO NOT EDIT !
|
const dmsWarning = `// ! DO NOT EDIT !
|
||||||
// ! AUTO-GENERATED BY DMS !
|
// ! AUTO-GENERATED BY DMS !
|
||||||
// ! CHANGES WILL BE OVERWRITTEN !
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const configContent = dmsWarning + `layout {
|
const configContent = dmsWarning + `layout {
|
||||||
gaps ${gaps}
|
gaps ${gaps}
|
||||||
|
|
||||||
border {
|
border {
|
||||||
width 2
|
width 2
|
||||||
}
|
}
|
||||||
|
|
||||||
focus-ring {
|
focus-ring {
|
||||||
width 2
|
width 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window-rule {
|
window-rule {
|
||||||
geometry-corner-radius ${cornerRadius}
|
geometry-corner-radius ${cornerRadius}
|
||||||
clip-to-geometry true
|
clip-to-geometry true
|
||||||
tiled-state true
|
tiled-state true
|
||||||
draw-border-with-background false
|
draw-border-with-background false
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const alttabContent = dmsWarning + `recent-windows {
|
const alttabContent = dmsWarning + `recent-windows {
|
||||||
highlight {
|
highlight {
|
||||||
corner-radius ${cornerRadius}
|
corner-radius ${cornerRadius}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
|
||||||
const niriDmsDir = configDir + "/niri/dms";
|
const niriDmsDir = configDir + "/niri/dms";
|
||||||
@@ -1054,6 +1056,141 @@ window-rule {
|
|||||||
writeBlurruleProcess.running = true;
|
writeBlurruleProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateOutputPosition(outputName, x, y) {
|
||||||
|
if (!outputs || !outputs[outputName])
|
||||||
|
return;
|
||||||
|
const updatedOutputs = {};
|
||||||
|
for (const name in outputs) {
|
||||||
|
const output = outputs[name];
|
||||||
|
if (name === outputName && output.logical) {
|
||||||
|
updatedOutputs[name] = JSON.parse(JSON.stringify(output));
|
||||||
|
updatedOutputs[name].logical.x = x;
|
||||||
|
updatedOutputs[name].logical.y = y;
|
||||||
|
} else {
|
||||||
|
updatedOutputs[name] = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputs = updatedOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyOutputConfig(outputName, config, callback) {
|
||||||
|
if (!CompositorService.isNiri || !outputName) {
|
||||||
|
if (callback)
|
||||||
|
callback(false, "Invalid config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands = [];
|
||||||
|
|
||||||
|
if (config.position !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" position ${config.position.x} ${config.position.y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.mode !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" mode ${config.mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.vrr !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" vrr ${config.vrr ? "on" : "off"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.scale !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" scale ${config.scale}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.transform !== undefined) {
|
||||||
|
commands.push(`niri msg output "${outputName}" transform "${config.transform}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commands.length === 0) {
|
||||||
|
if (callback)
|
||||||
|
callback(true, "No changes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullCommand = commands.join(" && ");
|
||||||
|
Proc.runCommand("niri-output-config", ["sh", "-c", fullCommand], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.warn("NiriService: Failed to apply output config:", output);
|
||||||
|
if (callback)
|
||||||
|
callback(false, output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info("NiriService: Applied output config for", outputName);
|
||||||
|
fetchOutputs();
|
||||||
|
if (callback)
|
||||||
|
callback(true, "Success");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputIdentifier(output, outputName) {
|
||||||
|
if (SettingsData.displayNameMode === "model" && output.make && output.model) {
|
||||||
|
const serial = output.serial || "Unknown";
|
||||||
|
return output.make + " " + output.model + " " + serial;
|
||||||
|
}
|
||||||
|
return outputName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOutputsConfig(outputsData) {
|
||||||
|
const data = outputsData || outputs;
|
||||||
|
if (!data || Object.keys(data).length === 0)
|
||||||
|
return;
|
||||||
|
let kdlContent = `// Auto-generated by DMS - do not edit manually\n\n`;
|
||||||
|
|
||||||
|
for (const outputName in data) {
|
||||||
|
const output = data[outputName];
|
||||||
|
const identifier = getOutputIdentifier(output, outputName);
|
||||||
|
kdlContent += `output "${identifier}" {\n`;
|
||||||
|
|
||||||
|
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`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.logical) {
|
||||||
|
if (output.logical.scale && output.logical.scale !== 1.0) {
|
||||||
|
kdlContent += ` scale ${output.logical.scale}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.logical.transform && output.logical.transform !== "Normal") {
|
||||||
|
const transformMap = {
|
||||||
|
"Normal": "normal",
|
||||||
|
"90": "90",
|
||||||
|
"180": "180",
|
||||||
|
"270": "270",
|
||||||
|
"Flipped": "flipped",
|
||||||
|
"Flipped90": "flipped-90",
|
||||||
|
"Flipped180": "flipped-180",
|
||||||
|
"Flipped270": "flipped-270"
|
||||||
|
};
|
||||||
|
kdlContent += ` transform "${transformMap[output.logical.transform] || "normal"}"\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.logical.x !== undefined && output.logical.y !== undefined) {
|
||||||
|
kdlContent += ` position x=${output.logical.x} y=${output.logical.y}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.vrr_enabled) {
|
||||||
|
kdlContent += ` variable-refresh-rate\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
kdlContent += `}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
|
||||||
|
const niriDmsDir = configDir + "/niri/dms";
|
||||||
|
const outputsPath = niriDmsDir + "/outputs.kdl";
|
||||||
|
|
||||||
|
Proc.runCommand("niri-write-outputs", ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${kdlContent}EOF`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.warn("NiriService: Failed to write outputs config:", output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info("NiriService: Generated outputs config at", outputsPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function screenshot(): string {
|
function screenshot(): string {
|
||||||
if (!CompositorService.isNiri) {
|
if (!CompositorService.isNiri) {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
[templates.dmsghostty]
|
[templates.dmsghostty]
|
||||||
input_path = 'SHELL_DIR/matugen/templates/ghostty.conf'
|
input_path = 'SHELL_DIR/matugen/templates/ghostty.conf'
|
||||||
output_path = '~/.config/ghostty/config-dankcolors'
|
output_path = '~/.config/ghostty/themes/dankcolors'
|
||||||
|
|||||||
Reference in New Issue
Block a user