mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-22 03:04:01 -04:00
Compare commits
17
Commits
stable-2.0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47c9d8d89b | ||
|
|
d8786eaaf7 | ||
|
|
4f969c8e10 | ||
|
|
1b59a474dd | ||
|
|
28f921fa02 | ||
|
|
8f2a486006 | ||
|
|
27a703367f | ||
|
|
8e851b3dc4 | ||
|
|
112d05901f | ||
|
|
afeea489c9 | ||
|
|
90d3c9731c | ||
|
|
b81954454a | ||
|
|
479b1434de | ||
|
|
203b1369b3 | ||
|
|
5ffc581ecf | ||
|
|
3284acab28 | ||
|
|
1fb9f5271a |
@@ -26,6 +26,11 @@ jobs:
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: core/go.mod
|
||||
cache-dependency-path: core/go.sum
|
||||
|
||||
- name: Download Go modules
|
||||
working-directory: core
|
||||
run: for attempt in 1 2 3; do go mod download && exit 0; sleep 15; done; exit 1
|
||||
|
||||
- name: run pre-commit hooks
|
||||
uses: j178/prek-action@v2
|
||||
|
||||
@@ -73,6 +73,7 @@ jobs:
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
cache-dependency-path: core/go.sum
|
||||
|
||||
- name: Format check
|
||||
if: matrix.goos == 'linux'
|
||||
@@ -247,6 +248,7 @@ jobs:
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
cache-dependency-path: core/go.sum
|
||||
|
||||
- name: Download core artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
|
||||
@@ -308,6 +308,7 @@ jobs:
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
cache-dependency-path: core/go.sum
|
||||
|
||||
- name: Install OSC
|
||||
run: |
|
||||
|
||||
@@ -100,6 +100,7 @@ jobs:
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
cache-dependency-path: core/go.sum
|
||||
cache: false
|
||||
|
||||
- name: Install build dependencies
|
||||
|
||||
@@ -100,7 +100,7 @@ Notification center with grouping, rich text support, and keyboard navigation.
|
||||
MPRIS player controls, calendar sync, weather widgets, and clipboard history with image previews.
|
||||
|
||||
**Session Management**
|
||||
Lock screen, idle detection, auto-lock/suspend with separate AC/battery settings, and a settings front-end for [dank-greeter](https://github.com/duxlabs-dev/dux-greeter).
|
||||
Lock screen, idle detection, auto-lock/suspend with separate AC/battery settings, and a settings front-end for [dux-greeter](https://github.com/duxlabs-dev/dux-greeter).
|
||||
|
||||
**Plugin System**
|
||||
Extend functionality with the [plugin registry](https://plugins.duxlabs.dev). DMS keeps
|
||||
@@ -138,16 +138,16 @@ dms plugins restore ~/plugins.lock.json
|
||||
- **Plugins:** [Development guide](https://duxlabs.dev/docs/dms/plugins-overview)
|
||||
- **Support:** [Ko-fi](https://ko-fi.com/duxlabs)
|
||||
|
||||
## Dank Projects
|
||||
## Dux Projects
|
||||
|
||||
DMS is one piece of the suite. The rest lives in its own repos:
|
||||
|
||||
- [dank-greeter](https://github.com/duxlabs-dev/dux-greeter) - greetd login screen with the Dux Shell aesthetic. The Greeter tab in DMS settings is the front-end for it.
|
||||
- [dankcalendar](https://github.com/duxlabs-dev/dux-calendar) - Local, Google, Microsoft, and CalDAV calendars for the Dux desktop.
|
||||
- [dux-greeter](https://github.com/duxlabs-dev/dux-greeter) - greetd login screen with the Dux Shell aesthetic. The Greeter tab in DMS settings is the front-end for it.
|
||||
- [dux-calendar](https://github.com/duxlabs-dev/dux-calendar) - Local, Google, Microsoft, and CalDAV calendars for the Dux desktop.
|
||||
- [dgop](https://github.com/duxlabs-dev/dux-gop) - System monitoring TUI and Go library; its library powers the process list and dashboard widgets inside the dms daemon.
|
||||
- [dsearch](https://github.com/duxlabs-dev/dux-search) - Fast file search that powers file results in the launcher.
|
||||
- [dux-qml-common](https://github.com/duxlabs-dev/dux-qml-common) - Shared QML widgets and components used by DMS, dank-greeter, and dankcalendar.
|
||||
- [dankgo](https://github.com/duxlabs-dev/dux-go) - Common Go modules behind the single binary apps.
|
||||
- [dux-qml-common](https://github.com/duxlabs-dev/dux-qml-common) - Shared QML widgets and components used by DMS, dux-greeter, and dux-calendar.
|
||||
- [dux-go](https://github.com/duxlabs-dev/dux-go) - Common Go modules behind the single binary apps.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -193,7 +193,7 @@ Contributions welcome. Bug fixes, widgets, features, documentation, and plugins
|
||||
3. Test thoroughly
|
||||
4. Open a pull request
|
||||
|
||||
For documentation contributions, see [DankLinux-Docs](https://github.com/duxlabs-dev/dux-docs).
|
||||
For documentation contributions, see [dux-docs](https://github.com/duxlabs-dev/dux-docs).
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/duxlabs-dev/dux-go/shellapp"
|
||||
@@ -83,12 +85,36 @@ func preLaunch() {
|
||||
ensureFontCache()
|
||||
}
|
||||
|
||||
// The elapsed-timer driver keeps the scrolling wallpaper spring in step above 60 Hz,
|
||||
// but it makes every other animation jump after a dropped frame (#3837).
|
||||
func scrollingWallpaperConfigured() bool {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(configDir, "dms", "settings.json"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return wantsSimpleAnimationDriver(data)
|
||||
}
|
||||
|
||||
func wantsSimpleAnimationDriver(settings []byte) bool {
|
||||
var parsed struct {
|
||||
WallpaperFillMode string `json:"wallpaperFillMode"`
|
||||
}
|
||||
if json.Unmarshal(settings, &parsed) != nil {
|
||||
return false
|
||||
}
|
||||
return parsed.WallpaperFillMode == "Scrolling"
|
||||
}
|
||||
|
||||
func dmsExtraEnv(string) []string {
|
||||
var env []string
|
||||
if selfPath, err := os.Executable(); err == nil {
|
||||
env = append(env, "DMS_EXECUTABLE="+selfPath)
|
||||
}
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" && scrollingWallpaperConfigured() {
|
||||
env = append(env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
||||
}
|
||||
if _, set := os.LookupEnv("MALLOC_CONF"); !set {
|
||||
|
||||
@@ -965,6 +965,7 @@ var vscodeEditors = []vscodeEditor{
|
||||
{"cursor", ".cursor"},
|
||||
{"windsurf", ".windsurf"},
|
||||
{"positron", ".positron"},
|
||||
{"antigravity", ".antigravity-ide"},
|
||||
{"vscode-insiders", ".vscode-insiders"},
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule dux-qml-common updated: ebf5e6c002...5715e8ea86
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1792513666,
|
||||
"narHash": "sha256-RDqPdPrM5lnwRrfFGLz3IwgTjCV2Ie+nrTD3ldj1t/s=",
|
||||
"lastModified": 1792597131,
|
||||
"narHash": "sha256-2t9btOQ2owoRv/fgeonlr8cHVt2NUvUu9iXdZd9+QFc=",
|
||||
"owner": "duxlabs-dev",
|
||||
"repo": "dux-qml-common",
|
||||
"rev": "ebf5e6c002da1bdaa4aaa2f319fea7b8507abc42",
|
||||
"rev": "5715e8ea868f3a131a0f54c7a5875e9a95f2f253",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -160,6 +160,7 @@ Singleton {
|
||||
"children": [
|
||||
{
|
||||
"id": "dock_general",
|
||||
"hidden": true,
|
||||
"text": I18n.tr("General"),
|
||||
"icon": "tune",
|
||||
"tabIndex": 5,
|
||||
@@ -167,6 +168,7 @@ Singleton {
|
||||
},
|
||||
{
|
||||
"id": "dock_widgets",
|
||||
"hidden": true,
|
||||
"text": I18n.tr("Apps & widgets"),
|
||||
"icon": "widgets",
|
||||
"tabIndex": 57,
|
||||
@@ -174,6 +176,7 @@ Singleton {
|
||||
},
|
||||
{
|
||||
"id": "dock_appearance",
|
||||
"hidden": true,
|
||||
"text": I18n.tr("Appearance"),
|
||||
"icon": "palette",
|
||||
"tabIndex": 58,
|
||||
|
||||
@@ -1876,9 +1876,9 @@ Singleton {
|
||||
}
|
||||
|
||||
const runKey = Qt.md5(JSON.stringify(args));
|
||||
const skipUnchanged = !_matugenRunKey && !colorsFileLoadFailed && typeof SettingsData !== "undefined" && !SettingsData.generateThemeAtStartup;
|
||||
if (skipUnchanged && runKey === CacheData.matugenAppliedKey) {
|
||||
log.info("Theme inputs unchanged since the last run, skipping startup generation");
|
||||
const startupRun = !_matugenRunKey && typeof SettingsData !== "undefined" && SettingsData.generateThemeAtStartup;
|
||||
if (!startupRun && !colorsFileLoadFailed && runKey === CacheData.matugenAppliedKey) {
|
||||
log.info("Theme inputs unchanged since the last run, skipping generation");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2496,6 +2496,36 @@ Singleton {
|
||||
onTriggered: dynamicColorsFileView.reload()
|
||||
}
|
||||
|
||||
readonly property bool publishTypography: typeof SettingsData !== "undefined" && SettingsData._hasLoaded && !SessionData.isGreeterMode
|
||||
readonly property string typographyJson: JSON.stringify({
|
||||
"fontFamily": fontFamily,
|
||||
"monoFontFamily": monoFontFamily,
|
||||
"displayFontFamily": displayFontFamily,
|
||||
"fontWeight": fontWeight,
|
||||
"fontScale": fontScale
|
||||
}, null, 2)
|
||||
|
||||
onPublishTypographyChanged: typographyPublishDebounce.restart()
|
||||
onTypographyJsonChanged: typographyPublishDebounce.restart()
|
||||
|
||||
FileView {
|
||||
id: typographyFileView
|
||||
path: root.publishTypography ? root.stateDir + "/dms-typography.json" : ""
|
||||
blockLoading: false
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: typographyPublishDebounce
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!root.publishTypography)
|
||||
return;
|
||||
typographyFileView.setText(root.typographyJson);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "theme"
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ function mangoActiveWorkspace(raw, screenName) {
|
||||
|
||||
function mangoWindowsOnWorkspace(windows, workspace) {
|
||||
// mangoTags are 1-based; workspace.id is 0-based.
|
||||
return windows.filter(win => win && (win.mangoTags || []).includes(workspace.id + 1));
|
||||
return windows.filter(win => win && win.mangoMonitor === workspace.output && (win.mangoTags || []).includes(workspace.id + 1));
|
||||
}
|
||||
|
||||
function i3Key(ws) {
|
||||
|
||||
@@ -744,8 +744,12 @@ Item {
|
||||
readonly property real barSurfaceThickness: Theme.px(effectiveBarThickness + surfaceSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel && spansEdge ? _wingR : 0), _dpr) + _shadowBuffer
|
||||
readonly property bool islandSheetOut: islandHost?.sheetOut ?? false
|
||||
readonly property real islandSatelliteShadowBuffer: isIsland && islandSatellitesEnabled && !islandSatelliteBackground ? _shadowBuffer : 0
|
||||
readonly property real hostThickness: isIsland ? Math.max(islandHost?.hostThickness ?? 0, islandStripThickness + islandSatelliteShadowBuffer) : Math.max(barSurfaceThickness, islandSheetOut ? islandHost.hostThickness : 0)
|
||||
readonly property real hideSlideThickness: isIsland ? hostThickness : barSurfaceThickness
|
||||
// A grown layer surface jumps to its new origin before the client draws at the new size, so a far-edge host never grows for the sheet.
|
||||
readonly property bool islandReservesSheet: !!islandHost && (axis.edge === "bottom" || axis.edge === "right")
|
||||
readonly property real hostThickness: isIsland ? Math.max(islandHost?.hostThickness ?? 0, islandStripThickness + islandSatelliteShadowBuffer) : Math.max(barSurfaceThickness, islandSheetOut || islandReservesSheet ? islandHost.hostThickness : 0)
|
||||
// The host reserves the expanded island's height, so sliding by it would move a compact island off-screen within a frame.
|
||||
readonly property real islandCompactThickness: Math.max(islandStripThickness + islandSatelliteShadowBuffer, (islandHost?.outerGap ?? 0) + (islandHost?.compactThickness ?? 0) + (islandHost?.shadowPadding ?? 0))
|
||||
readonly property real hideSlideThickness: isIsland ? islandCompactThickness : barSurfaceThickness
|
||||
readonly property real surfaceImplicitHeight: !isVertical ? hostThickness : 0
|
||||
readonly property real surfaceImplicitWidth: isVertical ? hostThickness : 0
|
||||
|
||||
|
||||
@@ -39,26 +39,20 @@ BasePill {
|
||||
return Qt.rgba(from.r + (to.r - from.r) * progress, from.g + (to.g - from.g) * progress, from.b + (to.b - from.b) * progress, from.a + (to.a - from.a) * progress);
|
||||
}
|
||||
|
||||
SequentialAnimation on criticalPulse {
|
||||
property int criticalTick: 0
|
||||
|
||||
// Stepped on purpose: an Animation repaints the bar at the full refresh rate.
|
||||
Timer {
|
||||
interval: 50
|
||||
repeat: true
|
||||
running: battery.critical && battery.surfaceLive && !SettingsData.reduceMotion
|
||||
loops: Animation.Infinite
|
||||
onRunningChanged: {
|
||||
if (!running)
|
||||
battery.criticalPulse = 0;
|
||||
battery.criticalTick = 0;
|
||||
battery.criticalPulse = 0;
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 1000
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
from: 1
|
||||
to: 0
|
||||
duration: 1000
|
||||
easing.type: Easing.Linear
|
||||
onTriggered: {
|
||||
battery.criticalTick = (battery.criticalTick + 1) % 40;
|
||||
battery.criticalPulse = 1 - Math.abs(20 - battery.criticalTick) / 20;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ BasePill {
|
||||
}
|
||||
property bool isOccupied: root.useNativeWorkspaces && CompositorService.workspaceOccupied(record)
|
||||
property bool isPlaceholder: !!(record && record.placeholder)
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
property bool isHovered: pillHover.hovered
|
||||
|
||||
property bool loadedIsUrgent: false
|
||||
property bool isUrgent: root.useNativeWorkspaces ? CompositorService.workspaceUrgent(record, loadedIsUrgent) : (modelData?.urgent ?? false)
|
||||
@@ -615,6 +615,11 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: pillHover
|
||||
enabled: !isPlaceholder
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -680,6 +680,8 @@ DPopout {
|
||||
readonly property bool ready: targetHeight >= 0 && settledHeight === targetHeight
|
||||
readonly property real availableHeight: root.screen ? root.screen.height - mainContainer.horizontalChromeHeight - DashMetrics.contentPadding * 2 - Theme.barHeight - Theme.spacingL * 2 : contentHeight
|
||||
readonly property int rowBudget: DashMetrics.rowCapFor(availableHeight)
|
||||
// Growing a panel not hung from a top bar moves the grid under a held card, which retargets the drop.
|
||||
readonly property bool heightHeld: (currentItem?.cardInteracting ?? false) && root.effectiveBarPosition % 4 !== SettingsData.Top
|
||||
|
||||
x: (root.verticalNavigation && root.navigationEdge === SettingsData.Left ? root.navigationWidth : 0) - root.editGutter
|
||||
y: (!root.verticalNavigation && root.navigationEdge !== SettingsData.Bottom ? mainContainer.horizontalChromeHeight : 0) - root.editGutter
|
||||
@@ -691,12 +693,13 @@ DPopout {
|
||||
clip: contentHeight > height
|
||||
|
||||
function updateContentHeight() {
|
||||
if (targetHeight < 0)
|
||||
if (targetHeight < 0 || heightHeld)
|
||||
return;
|
||||
settledHeight = targetHeight;
|
||||
}
|
||||
|
||||
onTargetHeightChanged: Qt.callLater(updateContentHeight)
|
||||
onHeightHeldChanged: Qt.callLater(updateContentHeight)
|
||||
|
||||
Column {
|
||||
id: emptyContent
|
||||
|
||||
@@ -214,6 +214,7 @@ Singleton {
|
||||
readonly property int mediaLyricsRequestTimeout: 16000
|
||||
readonly property int mediaLyricsLoadingDelay: 300
|
||||
readonly property real mediaLyricsPositionTolerance: 0.05
|
||||
readonly property real mediaLyricsWholeSecondSlack: 0.005
|
||||
readonly property int mediaLyricsPositionProbeInterval: 5000
|
||||
readonly property real mediaLyricsPositionProbeDrift: 1.5
|
||||
readonly property real mediaLyricsPositionProbeThreshold: 1
|
||||
|
||||
@@ -25,6 +25,7 @@ Item {
|
||||
readonly property Item focusTarget: grid
|
||||
readonly property bool blocksTabNavigation: editMode || addMenu.open || optionsSheet.shown
|
||||
readonly property bool cardResizing: grid.sizePreview !== null
|
||||
readonly property bool cardInteracting: grid.interacting
|
||||
|
||||
implicitHeight: Math.max(DashMetrics.tabMinHeight, grid.implicitHeight)
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
|
||||
@@ -123,7 +123,7 @@ Item {
|
||||
interval: DashMetrics.mediaPositionPollInterval
|
||||
running: root.live && root.activePlayer?.playbackState === MprisPlaybackState.Playing && !root.isSeeking
|
||||
repeat: true
|
||||
onTriggered: root.activePlayer?.positionChanged()
|
||||
onTriggered: MprisController.refreshPosition(root.activePlayer)
|
||||
}
|
||||
|
||||
function syncAudioRef(wanted) {
|
||||
|
||||
@@ -53,7 +53,7 @@ Card {
|
||||
interval: DashMetrics.mediaPositionPollInterval
|
||||
running: root.live && root.visible && root.interactive && root.showSeekbar && root.playing && !root.isSeeking
|
||||
repeat: true
|
||||
onTriggered: root.activePlayer?.positionSupported && root.activePlayer.positionChanged()
|
||||
onTriggered: MprisController.refreshPosition(root.activePlayer)
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -26,6 +26,7 @@ FocusScope {
|
||||
readonly property var addable: grid.addableEntries
|
||||
readonly property int usedColumns: grid.usedColumns
|
||||
readonly property bool cardResizing: grid.sizePreview !== null
|
||||
readonly property bool cardInteracting: grid.interacting
|
||||
readonly property bool blocksTabNavigation: grid.blocksTabNavigation || detailSheet.shown || editorSheet.shown || optionsSheet.shown || addMenu.open
|
||||
|
||||
implicitWidth: DashMetrics.contentWidthFor(SettingsData.showWeekNumber)
|
||||
|
||||
@@ -36,6 +36,7 @@ Item {
|
||||
}
|
||||
]
|
||||
readonly property bool blocksTabNavigation: widgets.blocksTabNavigation
|
||||
readonly property bool cardInteracting: widgets.cardInteracting
|
||||
readonly property var forecastOptions: [DashRegistry.choice("forecast", I18n.tr("Forecast", "weather widget option label for forecast display type"), "chart", [
|
||||
{
|
||||
"value": "chart",
|
||||
|
||||
@@ -55,7 +55,7 @@ Rectangle {
|
||||
|
||||
function saveDisplayImage() {
|
||||
const data = root.notificationData;
|
||||
if (!root.persistImage || !data?.hasDisplayImage || data.persistedImagePath)
|
||||
if (!root.persistImage || !data?.notification || !data.hasDisplayImage || data.persistedImagePath)
|
||||
return;
|
||||
const image = data.image || "";
|
||||
if (!image.startsWith("image://qsimage/") && !NotificationService.notificationIconFromImage(image).startsWith("/"))
|
||||
|
||||
@@ -61,6 +61,7 @@ PanelWindow {
|
||||
readonly property bool cardShown: presenting && (presentationProgress > 0 || !exiting)
|
||||
readonly property bool layoutPinned: !exiting && (hovered || contextMenuActive || swipeActive)
|
||||
property bool exiting: false
|
||||
property var exitSnapshot: null
|
||||
property bool exitStarted: false
|
||||
property bool collapseChromeOnExit: false
|
||||
property bool _isDestroying: false
|
||||
@@ -124,6 +125,7 @@ PanelWindow {
|
||||
}
|
||||
enterAnimation.stop();
|
||||
enterDelay.stop();
|
||||
exitSnapshot = snapshotNotification(notificationData);
|
||||
exiting = true;
|
||||
if (NotificationService.removeFromVisibleNotifications)
|
||||
NotificationService.removeFromVisibleNotifications(win.notificationData);
|
||||
@@ -172,6 +174,29 @@ PanelWindow {
|
||||
win.exitFinished();
|
||||
}
|
||||
|
||||
// The wrapper is destroyed as soon as its notification closes, which would blank the card mid-exit.
|
||||
function snapshotNotification(data) {
|
||||
if (!data)
|
||||
return null;
|
||||
return {
|
||||
"appName": data.appName,
|
||||
"appIcon": data.appIcon,
|
||||
"desktopEntry": data.desktopEntry,
|
||||
"summary": data.summary,
|
||||
"body": data.body,
|
||||
"htmlBody": data.htmlBody,
|
||||
"image": data.image,
|
||||
"hasDisplayImage": data.hasDisplayImage,
|
||||
"displayImage": data.displayImage,
|
||||
"persistedImagePath": data.persistedImagePath,
|
||||
"timeStr": data.timeStr,
|
||||
"urgency": data.urgency,
|
||||
"actions": Array.from(data.actions || [], action => ({
|
||||
"text": action.text
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function closeTransientUi() {
|
||||
transientSurfaces.closeAll();
|
||||
popupContextMenuLoader.active = false;
|
||||
@@ -719,7 +744,7 @@ PanelWindow {
|
||||
chipColor: Theme.notificationChipSurface
|
||||
width: parent.width
|
||||
height: win.inlineHeightAnimating ? Math.min(targetHeight, cardSurface.height - win.timeoutRailClearance) : targetHeight
|
||||
notificationData: win.notificationData
|
||||
notificationData: win.exitSnapshot ?? win.notificationData
|
||||
descriptionExpanded: win.descriptionExpanded
|
||||
privacyMode: SettingsData.notificationPopupPrivacyMode
|
||||
bodyInvokesAction: win.bodyClickInvokesAction
|
||||
|
||||
@@ -557,6 +557,13 @@ QtObject {
|
||||
}
|
||||
|
||||
Component.onCompleted: _sync(NotificationService.visibleNotifications)
|
||||
Component.onDestruction: {
|
||||
for (const p of popupWindows)
|
||||
p?.destroy();
|
||||
const screenName = manager.modelData?.name || "";
|
||||
if (screenName)
|
||||
ConnectedModeState.releaseSurface(screenName, "notification", "notification:" + screenName);
|
||||
}
|
||||
onPopupSpacingChanged: _repositionAll()
|
||||
onNotificationConnectedModeChanged: _scheduleNotificationChromeSync()
|
||||
onCloseGapNotificationsChanged: _scheduleNotificationChromeSync()
|
||||
|
||||
@@ -12,6 +12,7 @@ Column {
|
||||
property var parentModal: null
|
||||
property string editingDockId: ""
|
||||
property string renameDraft: ""
|
||||
readonly property var dockPages: ["dock_general", "dock_widgets", "dock_appearance"].map(id => SettingsTabs.page(id)).filter(page => page)
|
||||
|
||||
function finishRename(value) {
|
||||
const id = editingDockId;
|
||||
@@ -44,11 +45,10 @@ Column {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "dock_to_bottom"
|
||||
title: I18n.tr("Docks", "noun plural, settings card title listing configured docks")
|
||||
settingKey: "dockConfiguration"
|
||||
tags: ["dock", "configuration", "add", "remove", "name", "show", "enable"]
|
||||
tags: ["dock", "configuration", "add", "remove", "show", "enable"]
|
||||
headerActions: DButton {
|
||||
text: I18n.tr("Add", "verb, button that adds a new item to a list")
|
||||
iconName: "add"
|
||||
@@ -75,11 +75,18 @@ Column {
|
||||
onDeleteRequested: root.removeDock(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
iconName: "dock_to_bottom"
|
||||
title: dock.config?.name ?? ""
|
||||
settingKey: "dockPlacement"
|
||||
tags: ["dock", "layout", "placement", "position", "edge", "name", "rename"]
|
||||
visible: dock.hasConfig
|
||||
|
||||
SettingsRow {
|
||||
title: I18n.tr("Name")
|
||||
subtitle: root.editingDockId ? "" : dock.config?.name ?? ""
|
||||
visible: dock.hasConfig
|
||||
|
||||
DActionButton {
|
||||
iconName: root.editingDockId ? "check" : "edit"
|
||||
@@ -120,25 +127,33 @@ Column {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
visible: dock.hasConfig
|
||||
iconName: "dock_to_bottom"
|
||||
title: I18n.tr("Layout")
|
||||
settingKey: "dockPlacement"
|
||||
tags: ["dock", "layout", "placement", "position", "edge"]
|
||||
SettingsRow {
|
||||
title: I18n.tr("Layout", "noun, settings section title for arrangement options")
|
||||
|
||||
SettingsLayoutPicker {
|
||||
edgePlacement: true
|
||||
choices: [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].map(position => ({
|
||||
key: String(position),
|
||||
label: dock.positionLabel(position),
|
||||
enabled: dock.positionChoices.includes(position)
|
||||
}))
|
||||
selectedKey: String(dock.config?.position ?? SettingsData.Position.Bottom)
|
||||
onSelected: key => dock.setEnabled(true, Number(key))
|
||||
body: SettingsLayoutPicker {
|
||||
edgePlacement: true
|
||||
choices: [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].map(position => ({
|
||||
key: String(position),
|
||||
label: dock.positionLabel(position),
|
||||
enabled: dock.positionChoices.includes(position)
|
||||
}))
|
||||
selectedKey: String(dock.config?.position ?? SettingsData.Position.Bottom)
|
||||
onSelected: key => dock.setEnabled(true, Number(key))
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.dockPages
|
||||
|
||||
delegate: SettingsNavRow {
|
||||
required property var modelData
|
||||
|
||||
iconName: modelData.icon
|
||||
title: modelData.text
|
||||
hint: modelData.hint ?? ""
|
||||
onClicked: keyboard => root.parentModal?.navigateTo(modelData.id, keyboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,12 +399,16 @@ Column {
|
||||
|
||||
readonly property var fillModes: ["Stretch", "Fit", "Fill", "Scrolling", "Tile", "TileVertically", "TileHorizontally", "Pad"]
|
||||
readonly property var fillModeLabels: [I18n.tr("Stretch", "wallpaper fill mode"), I18n.tr("Fit", "wallpaper fill mode"), I18n.tr("Fill", "wallpaper fill mode"), I18n.tr("Scroll", "wallpaper fill mode"), I18n.tr("Tile", "wallpaper fill mode"), I18n.tr("Tile Vertically", "wallpaper fill mode"), I18n.tr("Tile Horizontally", "wallpaper fill mode"), I18n.tr("Pad", "wallpaper fill mode")]
|
||||
// dms picks the animation driver at launch from this setting
|
||||
readonly property bool simpleAnimationDriver: Quickshell.env("QSG_USE_SIMPLE_ANIMATION_DRIVER") === "1"
|
||||
readonly property bool driverMismatch: (SettingsData.wallpaperFillMode === "Scrolling") !== simpleAnimationDriver
|
||||
|
||||
tab: "wallpaper"
|
||||
tags: ["background", "fill", "fit", "stretch", "tile", "scale"]
|
||||
settingKey: "wallpaperFillMode"
|
||||
resetKeys: root.perMonitor ? [] : ["wallpaperFillMode"]
|
||||
text: I18n.tr("Fill mode")
|
||||
description: driverMismatch ? I18n.tr("Requires restart") : ""
|
||||
visible: root.wallpaperIsImage
|
||||
dropdownWidth: 190
|
||||
options: fillModeLabels
|
||||
|
||||
@@ -17,10 +17,7 @@ SettingsDropdownRow {
|
||||
function _enumerate() {
|
||||
if (_enumerated)
|
||||
return;
|
||||
const bundled = DCommon.Fonts.bundledFamilies.filter(f => f !== root.defaultFamily);
|
||||
const system = Qt.fontFamilies().filter(f => !f.startsWith(".") && f !== root.defaultFamily && !bundled.includes(f));
|
||||
system.sort();
|
||||
_families = ["Default"].concat(bundled, system);
|
||||
_families = ["Default"].concat(DCommon.Fonts.selectableFamilies(root.defaultFamily));
|
||||
_enumerated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -468,7 +468,7 @@ Singleton {
|
||||
const yKey = y.niriWindowId !== undefined ? y.niriWindowId : y.mangoWindowId;
|
||||
if (xKey === undefined || yKey === undefined)
|
||||
return false;
|
||||
if (xKey !== yKey || x.niriWorkspaceId !== y.niriWorkspaceId || x.appId !== y.appId || x.title !== y.title || !!x.activated !== !!y.activated || !!x.fullscreen !== !!y.fullscreen || !!x.maximized !== !!y.maximized || !!x.minimized !== !!y.minimized)
|
||||
if (xKey !== yKey || x.niriWorkspaceId !== y.niriWorkspaceId || x.mangoMonitor !== y.mangoMonitor || x.appId !== y.appId || x.title !== y.title || !!x.activated !== !!y.activated || !!x.fullscreen !== !!y.fullscreen || !!x.maximized !== !!y.maximized || !!x.minimized !== !!y.minimized)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -90,7 +90,11 @@ QtObject {
|
||||
if (enabled && (state === "none" || state === "error"))
|
||||
useEmbeddedText(state);
|
||||
}
|
||||
onPositionChanged: scheduleAnchor()
|
||||
onPositionChanged: {
|
||||
if (MprisController.refreshingPosition || flooredEcho())
|
||||
return;
|
||||
scheduleAnchor();
|
||||
}
|
||||
onRateChanged: updatePlaybackClock()
|
||||
onPlayingChanged: {
|
||||
probe = null;
|
||||
@@ -379,6 +383,7 @@ QtObject {
|
||||
if (!line || !Number.isFinite(line.t))
|
||||
return false;
|
||||
player.position = Math.max(0.1, Math.min(line.t, duration * 0.99));
|
||||
reanchor();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -434,6 +439,13 @@ QtObject {
|
||||
resync();
|
||||
}
|
||||
|
||||
// pear-desktop floors Position to whole seconds on pause, resume and track events.
|
||||
function flooredEcho() {
|
||||
const lead = currentTime() - position;
|
||||
const whole = Math.abs(position - Math.round(position)) < DashMetrics.mediaLyricsWholeSecondSlack;
|
||||
return whole && lead >= 0 && lead < 1 + DashMetrics.mediaLyricsPositionTolerance;
|
||||
}
|
||||
|
||||
function scheduleAnchor() {
|
||||
positionWall = Date.now();
|
||||
if (enabled && synced && !stopped && !anchorUpdate.running)
|
||||
|
||||
@@ -34,6 +34,15 @@ Singleton {
|
||||
property bool _mprisPublishDirty: false
|
||||
property int _mprisConnectionEpoch: 0
|
||||
property int positionConsumers: 0
|
||||
property bool refreshingPosition: false
|
||||
|
||||
function refreshPosition(player) {
|
||||
if (!player?.positionSupported)
|
||||
return;
|
||||
refreshingPosition = true;
|
||||
player.positionChanged();
|
||||
refreshingPosition = false;
|
||||
}
|
||||
|
||||
function addPositionRef() {
|
||||
positionConsumers++;
|
||||
@@ -398,7 +407,7 @@ Singleton {
|
||||
interval: 1000
|
||||
running: root.positionConsumers > 0 && root.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
repeat: true
|
||||
onTriggered: root.activePlayer?.positionChanged()
|
||||
onTriggered: root.refreshPosition(root.activePlayer)
|
||||
}
|
||||
|
||||
function _hasMPRISSubscription(): bool {
|
||||
|
||||
@@ -334,9 +334,18 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
if (sameScales(displayScales, scales))
|
||||
return;
|
||||
displayScales = scales;
|
||||
}
|
||||
|
||||
function sameScales(a, b) {
|
||||
const names = Object.keys(a);
|
||||
if (names.length !== Object.keys(b).length)
|
||||
return false;
|
||||
return names.every(name => a[name] === b[name]);
|
||||
}
|
||||
|
||||
function sortWindowsByLayout(windowList) {
|
||||
const enriched = windowList.map(w => {
|
||||
const ws = workspaces[w.workspace_id];
|
||||
|
||||
@@ -227,7 +227,7 @@ Item {
|
||||
isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
onFrameTicked: {
|
||||
if (!root.isSeeking)
|
||||
activePlayer.positionChanged();
|
||||
MprisController.refreshPosition(activePlayer);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -12,7 +12,7 @@ local config_home = vim.env.XDG_CONFIG_HOME
|
||||
if config_home == nil or #config_home == 0 then
|
||||
config_home = vim.fs.joinpath(vim.env.HOME, ".config")
|
||||
end
|
||||
local settings_file_path = vim.fs.joinpath(config_home, "Dux Shell", "settings.json")
|
||||
local settings_file_path = vim.fs.joinpath(config_home, "dms", "settings.json")
|
||||
local settings_file = io.open(settings_file_path, "r")
|
||||
if settings_file == nil then
|
||||
vim.notify(
|
||||
|
||||
+2
-2
@@ -36,8 +36,8 @@
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{ "appId": "alacritty", "mangoTags": [3] },
|
||||
{ "appId": "firefox", "mangoTags": [2, 3] },
|
||||
{ "appId": "alacritty", "mangoMonitor": "DP-1", "mangoTags": [3] },
|
||||
{ "appId": "firefox", "mangoMonitor": "DP-1", "mangoTags": [2, 3] },
|
||||
{ "appId": "untagged" }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -20,6 +20,14 @@ const aqueousService = source("../Services/AqueousService.qml");
|
||||
const model = vm.createContext({});
|
||||
vm.runInContext(source("../Common/WindowModel.js").replace(/^\.pragma.*$/m, ""), model);
|
||||
|
||||
test("mango monitor changes invalidate cached toplevels", () => {
|
||||
const context = vm.createContext({});
|
||||
lift(compositorService, "_toplevelListEquivalent", context);
|
||||
const window = { mangoWindowId: 1, appId: "app1", mangoMonitor: "DP-1", mangoTags: [1] };
|
||||
assert.equal(context._toplevelListEquivalent([window], [{ ...window }]), true);
|
||||
assert.equal(context._toplevelListEquivalent([window], [{ ...window, mangoMonitor: "HDMI-A-1" }]), false);
|
||||
});
|
||||
|
||||
function niriState(scenario) {
|
||||
const s = fixture("niri")[scenario];
|
||||
const toplevels = s.toplevels.map(t => ({ ...t }));
|
||||
|
||||
@@ -291,8 +291,23 @@ test("mango scroll targets, active tag and windows", () => {
|
||||
assert.deepEqual(plain(model.mangoActiveWorkspace(dp1, "DP-1")), { id: 2, idx: 3, name: "", output: "DP-1", active: true, placeholder: false });
|
||||
assert.equal(model.mangoActiveWorkspace(mango("available", "HDMI-A-1"), "HDMI-A-1").id, 0);
|
||||
assert.equal(model.mangoActiveWorkspace(mango("available", "DP-2"), "DP-2"), null);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 2 }), "appId"), ["alacritty", "firefox"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 1 }), "appId"), ["firefox"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 2, output: "DP-1" }), "appId"), ["alacritty", "firefox"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(dp1.windows, { id: 1, output: "DP-1" }), "appId"), ["firefox"]);
|
||||
});
|
||||
|
||||
test("mango workspace apps follow their output when windows share a tag", () => {
|
||||
const windows = [
|
||||
{ appId: "app1", mangoMonitor: "DP-1", mangoTags: [1] },
|
||||
{ appId: "app2", mangoMonitor: "HDMI-A-1", mangoTags: [1] }
|
||||
];
|
||||
const dp1 = model.mangoRecord(0, {}, "DP-1");
|
||||
const hdmi = model.mangoRecord(0, {}, "HDMI-A-1");
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, dp1), "appId"), ["app1"]);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, hdmi), "appId"), ["app2"]);
|
||||
|
||||
windows[0].mangoMonitor = "HDMI-A-1";
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, dp1), "appId"), []);
|
||||
assert.deepEqual(pick(model.mangoWindowsOnWorkspace(windows, hdmi), "appId"), ["app1", "app2"]);
|
||||
});
|
||||
|
||||
test("i3 lists order numbered before named and strip the number prefix", () => {
|
||||
|
||||
@@ -286,6 +286,11 @@ def extract_i18n_string(value):
|
||||
return None
|
||||
|
||||
|
||||
def is_status_notice(value):
|
||||
"""A description with an empty branch only shows while some state holds; it is not searchable text."""
|
||||
return re.search(r"""\?\s*(""|'')\s*:|:\s*(""|'')\s*$""", value.strip()) is not None
|
||||
|
||||
|
||||
def extract_tags(value):
|
||||
match = re.search(r"\[([^\]]+)\]", value)
|
||||
if not match:
|
||||
@@ -489,7 +494,7 @@ def find_settings_components(content, filename, wrappers, tab_meta, hub_meta):
|
||||
|
||||
desc_raw = extract_property(own, "description") or extract_property(own, "summary") or defaults.get("description") or borrowed_description(block, setting_key)
|
||||
description = None
|
||||
if desc_raw:
|
||||
if desc_raw and not is_status_notice(desc_raw):
|
||||
description = extract_i18n_string(desc_raw)
|
||||
|
||||
visible_raw = extract_property(own_scope(block), "visible")
|
||||
|
||||
@@ -941,6 +941,26 @@
|
||||
"icon": "dock_to_bottom",
|
||||
"description": "Position, pinned apps, trash"
|
||||
},
|
||||
{
|
||||
"section": "dockPlacement",
|
||||
"label": "Dock",
|
||||
"tabIndex": -1,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"dock",
|
||||
"edge",
|
||||
"launcher bar",
|
||||
"layout",
|
||||
"name",
|
||||
"panel",
|
||||
"placement",
|
||||
"position",
|
||||
"rename",
|
||||
"taskbar"
|
||||
],
|
||||
"page": "dock",
|
||||
"icon": "dock_to_bottom"
|
||||
},
|
||||
{
|
||||
"section": "dockConfiguration",
|
||||
"label": "Docks",
|
||||
@@ -953,7 +973,6 @@
|
||||
"docks",
|
||||
"enable",
|
||||
"launcher bar",
|
||||
"name",
|
||||
"panel",
|
||||
"remove",
|
||||
"show",
|
||||
@@ -1407,24 +1426,6 @@
|
||||
"page": "theme_surfaces",
|
||||
"description": "Faint edge on cards, pills and widgets"
|
||||
},
|
||||
{
|
||||
"section": "dockPlacement",
|
||||
"label": "Layout",
|
||||
"tabIndex": -1,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"dock",
|
||||
"edge",
|
||||
"launcher bar",
|
||||
"layout",
|
||||
"panel",
|
||||
"placement",
|
||||
"position",
|
||||
"taskbar"
|
||||
],
|
||||
"page": "dock",
|
||||
"icon": "dock_to_bottom"
|
||||
},
|
||||
{
|
||||
"section": "iconThemeLight",
|
||||
"label": "Light mode theme",
|
||||
@@ -11275,7 +11276,6 @@
|
||||
"tabIndex": 25,
|
||||
"category": "Gamma control",
|
||||
"keywords": [
|
||||
"available",
|
||||
"blue light",
|
||||
"color",
|
||||
"color temperature",
|
||||
@@ -11290,15 +11290,13 @@
|
||||
"output",
|
||||
"outputs",
|
||||
"redshift",
|
||||
"requires",
|
||||
"resolution",
|
||||
"screen",
|
||||
"screens",
|
||||
"temperature",
|
||||
"warm"
|
||||
],
|
||||
"parentLabel": "Displays",
|
||||
"description": "Gamma control not available. Requires DMS API v6+."
|
||||
"parentLabel": "Displays"
|
||||
},
|
||||
{
|
||||
"section": "nightModeTransitionMinutes",
|
||||
@@ -15896,12 +15894,10 @@
|
||||
"schedule",
|
||||
"settings",
|
||||
"share",
|
||||
"shared",
|
||||
"tint",
|
||||
"wallpaper"
|
||||
],
|
||||
"parentLabel": "Wallpaper & colors",
|
||||
"description": "Using shared settings from Gamma Control"
|
||||
"parentLabel": "Wallpaper & colors"
|
||||
},
|
||||
{
|
||||
"section": "wallpaperCycling",
|
||||
|
||||
@@ -19,7 +19,7 @@ if [[ -n "${QS_ARGS:-}" ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
qs_args=($QS_ARGS)
|
||||
else
|
||||
state_file="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/danklinux.path"
|
||||
state_file="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/dms.path"
|
||||
config_path="$(cat "$state_file" 2>/dev/null || true)"
|
||||
[[ -n "$config_path" ]] || { echo "no running dms shell ($state_file missing)" >&2; exit 1; }
|
||||
qs_args=(-p "$config_path")
|
||||
|
||||
Reference in New Issue
Block a user