mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-22 03:04:01 -04:00
Compare commits
15
Commits
stable
...
stable-2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e48de4cc2a | ||
|
|
1683fceac5 | ||
|
|
2a83c5c69b | ||
|
|
1acfae1755 | ||
|
|
1e1dbebb70 | ||
|
|
53412d1f22 | ||
|
|
9ece8f26fa | ||
|
|
fd1295f959 | ||
|
|
1e3f16ac9d | ||
|
|
4fa6f58720 | ||
|
|
ff45b3b4b2 | ||
|
|
12e54e0225 | ||
|
|
f63d8bf4fb | ||
|
|
1e8423992d | ||
|
|
c4ecb988b4 |
@@ -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