mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-13 17:22:08 -04:00
wallpaper: handle initial load better, add dms randr command for quick
physical scale retrieval
This commit is contained in:
@@ -525,5 +525,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
doctorCmd,
|
doctorCmd,
|
||||||
configCmd,
|
configCmd,
|
||||||
dlCmd,
|
dlCmd,
|
||||||
|
randrCmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
core/cmd/dms/commands_randr.go
Normal file
58
core/cmd/dms/commands_randr.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randrCmd = &cobra.Command{
|
||||||
|
Use: "randr",
|
||||||
|
Short: "Query output display information",
|
||||||
|
Long: "Query Wayland compositor for output names, scales, resolutions and refresh rates via zwlr-output-management",
|
||||||
|
Run: runRandr,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
randrCmd.Flags().Bool("json", false, "Output in JSON format")
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrJSON struct {
|
||||||
|
Outputs []randrOutput `json:"outputs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func runRandr(cmd *cobra.Command, args []string) {
|
||||||
|
outputs, err := queryRandr()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||||
|
|
||||||
|
if jsonFlag {
|
||||||
|
data, err := json.Marshal(randrJSON{Outputs: outputs})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to marshal JSON: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, out := range outputs {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
status := "enabled"
|
||||||
|
if !out.Enabled {
|
||||||
|
status = "disabled"
|
||||||
|
}
|
||||||
|
fmt.Printf("%s (%s)\n", out.Name, status)
|
||||||
|
fmt.Printf(" Scale: %.4g\n", out.Scale)
|
||||||
|
fmt.Printf(" Resolution: %dx%d\n", out.Width, out.Height)
|
||||||
|
if out.Refresh > 0 {
|
||||||
|
fmt.Printf(" Refresh: %.2f Hz\n", float64(out.Refresh)/1000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
core/cmd/dms/randr_client.go
Normal file
172
core/cmd/dms/randr_client.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||||
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type randrOutput struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Scale float64 `json:"scale"`
|
||||||
|
Width int32 `json:"width"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
Refresh int32 `json:"refresh"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrHead struct {
|
||||||
|
name string
|
||||||
|
enabled bool
|
||||||
|
scale float64
|
||||||
|
currentModeID uint32
|
||||||
|
modeIDs []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrMode struct {
|
||||||
|
width int32
|
||||||
|
height int32
|
||||||
|
refresh int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrClient struct {
|
||||||
|
display *wlclient.Display
|
||||||
|
ctx *wlclient.Context
|
||||||
|
manager *wlr_output_management.ZwlrOutputManagerV1
|
||||||
|
heads map[uint32]*randrHead
|
||||||
|
modes map[uint32]*randrMode
|
||||||
|
done bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryRandr() ([]randrOutput, error) {
|
||||||
|
display, err := wlclient.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &randrClient{
|
||||||
|
display: display,
|
||||||
|
ctx: display.Context(),
|
||||||
|
heads: make(map[uint32]*randrHead),
|
||||||
|
modes: make(map[uint32]*randrMode),
|
||||||
|
}
|
||||||
|
defer c.ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
|
if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName {
|
||||||
|
mgr := wlr_output_management.NewZwlrOutputManagerV1(c.ctx)
|
||||||
|
version := min(e.Version, 4)
|
||||||
|
|
||||||
|
mgr.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
|
||||||
|
c.handleHead(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
|
||||||
|
c.done = true
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, mgr); err == nil {
|
||||||
|
c.manager = mgr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// First roundtrip: discover globals and bind manager
|
||||||
|
syncCallback, err := display.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to sync display: %w", err)
|
||||||
|
}
|
||||||
|
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
|
||||||
|
if c.manager == nil {
|
||||||
|
c.err = fmt.Errorf("zwlr_output_manager_v1 protocol not supported by compositor")
|
||||||
|
c.done = true
|
||||||
|
}
|
||||||
|
// Otherwise wait for manager's DoneHandler
|
||||||
|
})
|
||||||
|
|
||||||
|
for !c.done {
|
||||||
|
if err := c.ctx.Dispatch(); err != nil {
|
||||||
|
return nil, fmt.Errorf("dispatch error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.err != nil {
|
||||||
|
return nil, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.buildOutputs(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *randrClient) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
|
||||||
|
handle := e.Head
|
||||||
|
headID := handle.ID()
|
||||||
|
|
||||||
|
head := &randrHead{
|
||||||
|
modeIDs: make([]uint32, 0),
|
||||||
|
}
|
||||||
|
c.heads[headID] = head
|
||||||
|
|
||||||
|
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
||||||
|
head.name = e.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) {
|
||||||
|
head.enabled = e.Enabled != 0
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
|
||||||
|
head.scale = e.Scale
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) {
|
||||||
|
head.currentModeID = e.Mode.ID()
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
|
||||||
|
modeHandle := e.Mode
|
||||||
|
modeID := modeHandle.ID()
|
||||||
|
|
||||||
|
head.modeIDs = append(head.modeIDs, modeID)
|
||||||
|
|
||||||
|
mode := &randrMode{}
|
||||||
|
c.modes[modeID] = mode
|
||||||
|
|
||||||
|
modeHandle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) {
|
||||||
|
mode.width = e.Width
|
||||||
|
mode.height = e.Height
|
||||||
|
})
|
||||||
|
|
||||||
|
modeHandle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) {
|
||||||
|
mode.refresh = e.Refresh
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *randrClient) buildOutputs() []randrOutput {
|
||||||
|
outputs := make([]randrOutput, 0, len(c.heads))
|
||||||
|
|
||||||
|
for _, head := range c.heads {
|
||||||
|
out := randrOutput{
|
||||||
|
Name: head.name,
|
||||||
|
Scale: head.scale,
|
||||||
|
Enabled: head.enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode, ok := c.modes[head.currentModeID]; ok {
|
||||||
|
out.Width = mode.width
|
||||||
|
out.Height = mode.height
|
||||||
|
out.Refresh = mode.refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs = append(outputs, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
}
|
||||||
@@ -85,22 +85,20 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
if (typeof blurWallpaperWindow.updatesEnabled !== "undefined")
|
||||||
|
blurWallpaperWindow.updatesEnabled = Qt.binding(() => root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
|
|
||||||
if (!source) {
|
if (!source) {
|
||||||
isInitialized = true;
|
root._renderSettling = false;
|
||||||
updatesBindingTimer.start();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
updatesBindingTimer.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool isInitialized: false
|
property bool isInitialized: false
|
||||||
property real transitionProgress: 0
|
property real transitionProgress: 0
|
||||||
readonly property bool transitioning: transitionAnimation.running
|
readonly property bool transitioning: transitionAnimation.running
|
||||||
property bool effectActive: false
|
property bool effectActive: false
|
||||||
property bool _renderSettling: false
|
property bool _renderSettling: true
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -119,15 +117,6 @@ Variants {
|
|||||||
onTriggered: root._renderSettling = false
|
onTriggered: root._renderSettling = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: updatesBindingTimer
|
|
||||||
interval: 500
|
|
||||||
onTriggered: {
|
|
||||||
if (typeof blurWallpaperWindow.updatesEnabled !== "undefined")
|
|
||||||
blurWallpaperWindow.updatesEnabled = Qt.binding(() => root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
if (!source || source.startsWith("#")) {
|
if (!source || source.startsWith("#")) {
|
||||||
setWallpaperImmediate("");
|
setWallpaperImmediate("");
|
||||||
|
|||||||
@@ -83,9 +83,10 @@ Variants {
|
|||||||
|
|
||||||
readonly property bool transitioning: transitionAnimation.running
|
readonly property bool transitioning: transitionAnimation.running
|
||||||
property bool effectActive: false
|
property bool effectActive: false
|
||||||
property bool _renderSettling: false
|
property bool _renderSettling: true
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
property string pendingWallpaper: ""
|
property string pendingWallpaper: ""
|
||||||
|
property string _deferredSource: ""
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: currentWallpaper
|
target: currentWallpaper
|
||||||
@@ -97,21 +98,47 @@ Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _recheckScreenScale() {
|
||||||
|
const newScale = CompositorService.getScreenScale(modelData);
|
||||||
|
if (newScale !== root.screenScale) {
|
||||||
|
console.info("WallpaperBackground: screen scale corrected for", modelData.name + ":", root.screenScale, "->", newScale);
|
||||||
|
root.screenScale = newScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NiriService
|
||||||
|
function onDisplayScalesChanged() {
|
||||||
|
root._recheckScreenScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: WlrOutputService
|
||||||
|
function onWlrOutputAvailableChanged() {
|
||||||
|
root._recheckScreenScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: CompositorService
|
||||||
|
function onRandrDataReady() {
|
||||||
|
if (root._deferredSource) {
|
||||||
|
const src = root._deferredSource;
|
||||||
|
root._deferredSource = "";
|
||||||
|
root.setWallpaperImmediate(src);
|
||||||
|
} else {
|
||||||
|
root._recheckScreenScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: renderSettleTimer
|
id: renderSettleTimer
|
||||||
interval: 100
|
interval: 100
|
||||||
onTriggered: root._renderSettling = false
|
onTriggered: root._renderSettling = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: updatesBindingTimer
|
|
||||||
interval: 500
|
|
||||||
onTriggered: {
|
|
||||||
if (typeof wallpaperWindow.updatesEnabled !== "undefined")
|
|
||||||
wallpaperWindow.updatesEnabled = Qt.binding(() => root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFillMode(modeName) {
|
function getFillMode(modeName) {
|
||||||
switch (modeName) {
|
switch (modeName) {
|
||||||
case "Stretch":
|
case "Stretch":
|
||||||
@@ -136,15 +163,13 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
if (typeof wallpaperWindow.updatesEnabled !== "undefined")
|
||||||
|
wallpaperWindow.updatesEnabled = Qt.binding(() => root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
|
|
||||||
if (!source) {
|
if (!source) {
|
||||||
isInitialized = true;
|
root._renderSettling = false;
|
||||||
updatesBindingTimer.start();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
updatesBindingTimer.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
@@ -156,8 +181,11 @@ Variants {
|
|||||||
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
|
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
|
||||||
|
|
||||||
if (!isInitialized || !currentWallpaper.source) {
|
if (!isInitialized || !currentWallpaper.source) {
|
||||||
|
if (!CompositorService.randrReady) {
|
||||||
|
_deferredSource = formattedSource;
|
||||||
|
return;
|
||||||
|
}
|
||||||
setWallpaperImmediate(formattedSource);
|
setWallpaperImmediate(formattedSource);
|
||||||
isInitialized = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (CompositorService.isNiri && SessionData.isSwitchingMode) {
|
if (CompositorService.isNiri && SessionData.isSwitchingMode) {
|
||||||
@@ -173,6 +201,7 @@ Variants {
|
|||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
root._renderSettling = true;
|
root._renderSettling = true;
|
||||||
renderSettleTimer.restart();
|
renderSettleTimer.restart();
|
||||||
|
root.screenScale = CompositorService.getScreenScale(modelData);
|
||||||
currentWallpaper.source = newSource;
|
currentWallpaper.source = newSource;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
}
|
}
|
||||||
@@ -201,6 +230,7 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
if (!newPath || newPath.startsWith("#"))
|
if (!newPath || newPath.startsWith("#"))
|
||||||
return;
|
return;
|
||||||
|
root.screenScale = CompositorService.getScreenScale(modelData);
|
||||||
if (root.transitioning || root.effectActive) {
|
if (root.transitioning || root.effectActive) {
|
||||||
root.pendingWallpaper = newPath;
|
root.pendingWallpaper = newPath;
|
||||||
return;
|
return;
|
||||||
@@ -252,7 +282,7 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property int maxTextureSize: 8192
|
readonly property int maxTextureSize: 8192
|
||||||
property real screenScale: CompositorService.getScreenScale(modelData)
|
property real screenScale: 1
|
||||||
property int textureWidth: Math.min(Math.round(modelData.width * screenScale), maxTextureSize)
|
property int textureWidth: Math.min(Math.round(modelData.width * screenScale), maxTextureSize)
|
||||||
property int textureHeight: Math.min(Math.round(modelData.height * screenScale), maxTextureSize)
|
property int textureHeight: Math.min(Math.round(modelData.height * screenScale), maxTextureSize)
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,37 @@ Singleton {
|
|||||||
readonly property string labwcPid: Quickshell.env("LABWC_PID")
|
readonly property string labwcPid: Quickshell.env("LABWC_PID")
|
||||||
property bool useNiriSorting: isNiri && NiriService
|
property bool useNiriSorting: isNiri && NiriService
|
||||||
|
|
||||||
|
property var randrScales: ({})
|
||||||
|
property bool randrReady: false
|
||||||
|
signal randrDataReady
|
||||||
|
|
||||||
property var sortedToplevels: []
|
property var sortedToplevels: []
|
||||||
property bool _sortScheduled: false
|
property bool _sortScheduled: false
|
||||||
|
|
||||||
signal toplevelsChanged
|
signal toplevelsChanged
|
||||||
|
|
||||||
|
function fetchRandrData() {
|
||||||
|
Proc.runCommand("randr", ["dms", "randr", "--json"], (output, exitCode) => {
|
||||||
|
if (exitCode === 0 && output) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(output.trim());
|
||||||
|
if (data.outputs && Array.isArray(data.outputs)) {
|
||||||
|
const scales = {};
|
||||||
|
for (const out of data.outputs) {
|
||||||
|
if (out.name && out.scale > 0)
|
||||||
|
scales[out.name] = out.scale;
|
||||||
|
}
|
||||||
|
randrScales = scales;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("CompositorService: failed to parse randr data:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
randrReady = true;
|
||||||
|
randrDataReady();
|
||||||
|
}, 0, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
function getScreenScale(screen) {
|
function getScreenScale(screen) {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return 1;
|
return 1;
|
||||||
@@ -42,6 +68,10 @@ Singleton {
|
|||||||
return screen.devicePixelRatio || 1;
|
return screen.devicePixelRatio || 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const randrScale = randrScales[screen.name];
|
||||||
|
if (randrScale !== undefined && randrScale > 0)
|
||||||
|
return Math.round(randrScale * 20) / 20;
|
||||||
|
|
||||||
if (WlrOutputService.wlrOutputAvailable && screen) {
|
if (WlrOutputService.wlrOutputAvailable && screen) {
|
||||||
const wlrOutput = WlrOutputService.getOutput(screen.name);
|
const wlrOutput = WlrOutputService.getOutput(screen.name);
|
||||||
if (wlrOutput?.enabled && wlrOutput.scale !== undefined && wlrOutput.scale > 0) {
|
if (wlrOutput?.enabled && wlrOutput.scale !== undefined && wlrOutput.scale > 0) {
|
||||||
@@ -137,6 +167,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
fetchRandrData();
|
||||||
detectCompositor();
|
detectCompositor();
|
||||||
scheduleSort();
|
scheduleSort();
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user