mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-19 17:45:21 -04:00
Compare commits
5 Commits
hover
..
cc8d68d753
| Author | SHA1 | Date | |
|---|---|---|---|
| cc8d68d753 | |||
| 9d046847fa | |||
| 283a256898 | |||
| e95c80011f | |||
| f8b32cc298 |
@@ -115,5 +115,3 @@ core.*
|
|||||||
.direnv/
|
.direnv/
|
||||||
quickshell/dms-plugins
|
quickshell/dms-plugins
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
.vscode/
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func runResolveInclude(cmd *cobra.Command, args []string) {
|
|||||||
result, err = checkHyprlandInclude(filename)
|
result, err = checkHyprlandInclude(filename)
|
||||||
case "niri":
|
case "niri":
|
||||||
result, err = checkNiriInclude(filename)
|
result, err = checkNiriInclude(filename)
|
||||||
case "mangowc", "mango":
|
case "mangowc", "dwl", "mango":
|
||||||
result, err = checkMangoWCInclude(filename)
|
result, err = checkMangoWCInclude(filename)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown compositor: %s", compositor)
|
log.Fatalf("Unknown compositor: %s", compositor)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Modes:
|
|||||||
full - Capture the focused output
|
full - Capture the focused output
|
||||||
all - Capture all outputs combined
|
all - Capture all outputs combined
|
||||||
output - Capture a specific output by name
|
output - Capture a specific output by name
|
||||||
window - Capture the focused window (Hyprland/Mango)
|
window - Capture the focused window (Hyprland/DWL)
|
||||||
last - Capture the last selected region
|
last - Capture the last selected region
|
||||||
|
|
||||||
Output format (--format):
|
Output format (--format):
|
||||||
@@ -97,7 +97,7 @@ If no previous region exists, falls back to interactive selection.`,
|
|||||||
var ssWindowCmd = &cobra.Command{
|
var ssWindowCmd = &cobra.Command{
|
||||||
Use: "window",
|
Use: "window",
|
||||||
Short: "Capture the focused window",
|
Short: "Capture the focused window",
|
||||||
Long: `Capture the currently focused window. Supported on Hyprland and Mango.`,
|
Long: `Capture the currently focused window. Supported on Hyprland and DWL.`,
|
||||||
Run: runScreenshotWindow,
|
Run: runScreenshotWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,791 @@
|
|||||||
|
// Generated by go-wayland-scanner
|
||||||
|
// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner
|
||||||
|
// XML file : internal/proto/xml/dwl-ipc-unstable-v2.xml
|
||||||
|
//
|
||||||
|
// dwl_ipc_unstable_v2 Protocol Copyright:
|
||||||
|
|
||||||
|
package dwl_ipc
|
||||||
|
|
||||||
|
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
|
||||||
|
// ZdwlIpcManagerV2InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
|
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
||||||
|
// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies.
|
||||||
|
const ZdwlIpcManagerV2InterfaceName = "zdwl_ipc_manager_v2"
|
||||||
|
|
||||||
|
// ZdwlIpcManagerV2 : manage dwl state
|
||||||
|
//
|
||||||
|
// This interface is exposed as a global in wl_registry.
|
||||||
|
//
|
||||||
|
// Clients can use this interface to get a dwl_ipc_output.
|
||||||
|
// After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
|
||||||
|
// The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.
|
||||||
|
type ZdwlIpcManagerV2 struct {
|
||||||
|
client.BaseProxy
|
||||||
|
tagsHandler ZdwlIpcManagerV2TagsHandlerFunc
|
||||||
|
layoutHandler ZdwlIpcManagerV2LayoutHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZdwlIpcManagerV2 : manage dwl state
|
||||||
|
//
|
||||||
|
// This interface is exposed as a global in wl_registry.
|
||||||
|
//
|
||||||
|
// Clients can use this interface to get a dwl_ipc_output.
|
||||||
|
// After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
|
||||||
|
// The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.
|
||||||
|
func NewZdwlIpcManagerV2(ctx *client.Context) *ZdwlIpcManagerV2 {
|
||||||
|
zdwlIpcManagerV2 := &ZdwlIpcManagerV2{}
|
||||||
|
ctx.Register(zdwlIpcManagerV2)
|
||||||
|
return zdwlIpcManagerV2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release : release dwl_ipc_manager
|
||||||
|
//
|
||||||
|
// Indicates that the client will not the dwl_ipc_manager object anymore.
|
||||||
|
// Objects created through this instance are not affected.
|
||||||
|
func (i *ZdwlIpcManagerV2) Release() error {
|
||||||
|
defer i.MarkZombie()
|
||||||
|
const opcode = 0
|
||||||
|
const _reqBufLen = 8
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOutput : get a dwl_ipc_outout for a wl_output
|
||||||
|
//
|
||||||
|
// Get a dwl_ipc_outout for the specified wl_output.
|
||||||
|
func (i *ZdwlIpcManagerV2) GetOutput(output *client.Output) (*ZdwlIpcOutputV2, error) {
|
||||||
|
id := NewZdwlIpcOutputV2(i.Context())
|
||||||
|
const opcode = 1
|
||||||
|
const _reqBufLen = 8 + 4 + 4
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], id.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], output.ID())
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcManagerV2TagsEvent : Announces tag amount
|
||||||
|
//
|
||||||
|
// This event is sent after binding.
|
||||||
|
// A roundtrip after binding guarantees the client recieved all tags.
|
||||||
|
type ZdwlIpcManagerV2TagsEvent struct {
|
||||||
|
Amount uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcManagerV2TagsHandlerFunc func(ZdwlIpcManagerV2TagsEvent)
|
||||||
|
|
||||||
|
// SetTagsHandler : sets handler for ZdwlIpcManagerV2TagsEvent
|
||||||
|
func (i *ZdwlIpcManagerV2) SetTagsHandler(f ZdwlIpcManagerV2TagsHandlerFunc) {
|
||||||
|
i.tagsHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcManagerV2LayoutEvent : Announces a layout
|
||||||
|
//
|
||||||
|
// This event is sent after binding.
|
||||||
|
// A roundtrip after binding guarantees the client recieved all layouts.
|
||||||
|
type ZdwlIpcManagerV2LayoutEvent struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type ZdwlIpcManagerV2LayoutHandlerFunc func(ZdwlIpcManagerV2LayoutEvent)
|
||||||
|
|
||||||
|
// SetLayoutHandler : sets handler for ZdwlIpcManagerV2LayoutEvent
|
||||||
|
func (i *ZdwlIpcManagerV2) SetLayoutHandler(f ZdwlIpcManagerV2LayoutHandlerFunc) {
|
||||||
|
i.layoutHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ZdwlIpcManagerV2) Dispatch(opcode uint32, fd int, data []byte) {
|
||||||
|
switch opcode {
|
||||||
|
case 0:
|
||||||
|
if i.tagsHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcManagerV2TagsEvent
|
||||||
|
l := 0
|
||||||
|
e.Amount = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.tagsHandler(e)
|
||||||
|
case 1:
|
||||||
|
if i.layoutHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcManagerV2LayoutEvent
|
||||||
|
l := 0
|
||||||
|
nameLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.Name = client.String(data[l : l+nameLen])
|
||||||
|
l += nameLen
|
||||||
|
|
||||||
|
i.layoutHandler(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
|
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
||||||
|
// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies.
|
||||||
|
const ZdwlIpcOutputV2InterfaceName = "zdwl_ipc_output_v2"
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2 : control dwl output
|
||||||
|
//
|
||||||
|
// Observe and control a dwl output.
|
||||||
|
//
|
||||||
|
// Events are double-buffered:
|
||||||
|
// Clients should cache events and redraw when a dwl_ipc_output.frame event is sent.
|
||||||
|
//
|
||||||
|
// Request are not double-buffered:
|
||||||
|
// The compositor will update immediately upon request.
|
||||||
|
type ZdwlIpcOutputV2 struct {
|
||||||
|
client.BaseProxy
|
||||||
|
toggleVisibilityHandler ZdwlIpcOutputV2ToggleVisibilityHandlerFunc
|
||||||
|
activeHandler ZdwlIpcOutputV2ActiveHandlerFunc
|
||||||
|
tagHandler ZdwlIpcOutputV2TagHandlerFunc
|
||||||
|
layoutHandler ZdwlIpcOutputV2LayoutHandlerFunc
|
||||||
|
titleHandler ZdwlIpcOutputV2TitleHandlerFunc
|
||||||
|
appidHandler ZdwlIpcOutputV2AppidHandlerFunc
|
||||||
|
layoutSymbolHandler ZdwlIpcOutputV2LayoutSymbolHandlerFunc
|
||||||
|
frameHandler ZdwlIpcOutputV2FrameHandlerFunc
|
||||||
|
fullscreenHandler ZdwlIpcOutputV2FullscreenHandlerFunc
|
||||||
|
floatingHandler ZdwlIpcOutputV2FloatingHandlerFunc
|
||||||
|
xHandler ZdwlIpcOutputV2XHandlerFunc
|
||||||
|
yHandler ZdwlIpcOutputV2YHandlerFunc
|
||||||
|
widthHandler ZdwlIpcOutputV2WidthHandlerFunc
|
||||||
|
heightHandler ZdwlIpcOutputV2HeightHandlerFunc
|
||||||
|
lastLayerHandler ZdwlIpcOutputV2LastLayerHandlerFunc
|
||||||
|
kbLayoutHandler ZdwlIpcOutputV2KbLayoutHandlerFunc
|
||||||
|
keymodeHandler ZdwlIpcOutputV2KeymodeHandlerFunc
|
||||||
|
scalefactorHandler ZdwlIpcOutputV2ScalefactorHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZdwlIpcOutputV2 : control dwl output
|
||||||
|
//
|
||||||
|
// Observe and control a dwl output.
|
||||||
|
//
|
||||||
|
// Events are double-buffered:
|
||||||
|
// Clients should cache events and redraw when a dwl_ipc_output.frame event is sent.
|
||||||
|
//
|
||||||
|
// Request are not double-buffered:
|
||||||
|
// The compositor will update immediately upon request.
|
||||||
|
func NewZdwlIpcOutputV2(ctx *client.Context) *ZdwlIpcOutputV2 {
|
||||||
|
zdwlIpcOutputV2 := &ZdwlIpcOutputV2{}
|
||||||
|
ctx.Register(zdwlIpcOutputV2)
|
||||||
|
return zdwlIpcOutputV2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release : release dwl_ipc_outout
|
||||||
|
//
|
||||||
|
// Indicates to that the client no longer needs this dwl_ipc_output.
|
||||||
|
func (i *ZdwlIpcOutputV2) Release() error {
|
||||||
|
defer i.MarkZombie()
|
||||||
|
const opcode = 0
|
||||||
|
const _reqBufLen = 8
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTags : Set the active tags of this output
|
||||||
|
//
|
||||||
|
// tagmask: bitmask of the tags that should be set.
|
||||||
|
// toggleTagset: toggle the selected tagset, zero for invalid, nonzero for valid.
|
||||||
|
func (i *ZdwlIpcOutputV2) SetTags(tagmask, toggleTagset uint32) error {
|
||||||
|
const opcode = 1
|
||||||
|
const _reqBufLen = 8 + 4 + 4
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(tagmask))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(toggleTagset))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClientTags : Set the tags of the focused client.
|
||||||
|
//
|
||||||
|
// The tags are updated as follows:
|
||||||
|
// new_tags = (current_tags AND and_tags) XOR xor_tags
|
||||||
|
func (i *ZdwlIpcOutputV2) SetClientTags(andTags, xorTags uint32) error {
|
||||||
|
const opcode = 2
|
||||||
|
const _reqBufLen = 8 + 4 + 4
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(andTags))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(xorTags))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLayout : Set the layout of this output
|
||||||
|
//
|
||||||
|
// index: index of a layout recieved by dwl_ipc_manager.layout
|
||||||
|
func (i *ZdwlIpcOutputV2) SetLayout(index uint32) error {
|
||||||
|
const opcode = 3
|
||||||
|
const _reqBufLen = 8 + 4
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(index))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quit : Quit mango
|
||||||
|
// This request allows clients to instruct the compositor to quit mango.
|
||||||
|
func (i *ZdwlIpcOutputV2) Quit() error {
|
||||||
|
const opcode = 4
|
||||||
|
const _reqBufLen = 8
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendDispatch : Set the active tags of this output
|
||||||
|
//
|
||||||
|
// dispatch: dispatch name.
|
||||||
|
// arg1: arg1.
|
||||||
|
// arg2: arg2.
|
||||||
|
// arg3: arg3.
|
||||||
|
// arg4: arg4.
|
||||||
|
// arg5: arg5.
|
||||||
|
func (i *ZdwlIpcOutputV2) SendDispatch(dispatch, arg1, arg2, arg3, arg4, arg5 string) error {
|
||||||
|
const opcode = 5
|
||||||
|
dispatchLen := client.PaddedLen(len(dispatch) + 1)
|
||||||
|
arg1Len := client.PaddedLen(len(arg1) + 1)
|
||||||
|
arg2Len := client.PaddedLen(len(arg2) + 1)
|
||||||
|
arg3Len := client.PaddedLen(len(arg3) + 1)
|
||||||
|
arg4Len := client.PaddedLen(len(arg4) + 1)
|
||||||
|
arg5Len := client.PaddedLen(len(arg5) + 1)
|
||||||
|
_reqBufLen := 8 + (4 + dispatchLen) + (4 + arg1Len) + (4 + arg2Len) + (4 + arg3Len) + (4 + arg4Len) + (4 + arg5Len)
|
||||||
|
_reqBuf := make([]byte, _reqBufLen)
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutString(_reqBuf[l:l+(4+dispatchLen)], dispatch)
|
||||||
|
l += (4 + dispatchLen)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg1Len)], arg1)
|
||||||
|
l += (4 + arg1Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg2Len)], arg2)
|
||||||
|
l += (4 + arg2Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg3Len)], arg3)
|
||||||
|
l += (4 + arg3Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg4Len)], arg4)
|
||||||
|
l += (4 + arg4Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg5Len)], arg5)
|
||||||
|
l += (4 + arg5Len)
|
||||||
|
err := i.Context().WriteMsg(_reqBuf, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZdwlIpcOutputV2TagState uint32
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2TagState :
|
||||||
|
const (
|
||||||
|
// ZdwlIpcOutputV2TagStateNone : no state
|
||||||
|
ZdwlIpcOutputV2TagStateNone ZdwlIpcOutputV2TagState = 0
|
||||||
|
// ZdwlIpcOutputV2TagStateActive : tag is active
|
||||||
|
ZdwlIpcOutputV2TagStateActive ZdwlIpcOutputV2TagState = 1
|
||||||
|
// ZdwlIpcOutputV2TagStateUrgent : tag has at least one urgent client
|
||||||
|
ZdwlIpcOutputV2TagStateUrgent ZdwlIpcOutputV2TagState = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ZdwlIpcOutputV2TagState) Name() string {
|
||||||
|
switch e {
|
||||||
|
case ZdwlIpcOutputV2TagStateNone:
|
||||||
|
return "none"
|
||||||
|
case ZdwlIpcOutputV2TagStateActive:
|
||||||
|
return "active"
|
||||||
|
case ZdwlIpcOutputV2TagStateUrgent:
|
||||||
|
return "urgent"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ZdwlIpcOutputV2TagState) Value() string {
|
||||||
|
switch e {
|
||||||
|
case ZdwlIpcOutputV2TagStateNone:
|
||||||
|
return "0"
|
||||||
|
case ZdwlIpcOutputV2TagStateActive:
|
||||||
|
return "1"
|
||||||
|
case ZdwlIpcOutputV2TagStateUrgent:
|
||||||
|
return "2"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ZdwlIpcOutputV2TagState) String() string {
|
||||||
|
return e.Name() + "=" + e.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2ToggleVisibilityEvent : Toggle client visibilty
|
||||||
|
//
|
||||||
|
// Indicates the client should hide or show themselves.
|
||||||
|
// If the client is visible then hide, if hidden then show.
|
||||||
|
type ZdwlIpcOutputV2ToggleVisibilityEvent struct{}
|
||||||
|
type ZdwlIpcOutputV2ToggleVisibilityHandlerFunc func(ZdwlIpcOutputV2ToggleVisibilityEvent)
|
||||||
|
|
||||||
|
// SetToggleVisibilityHandler : sets handler for ZdwlIpcOutputV2ToggleVisibilityEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetToggleVisibilityHandler(f ZdwlIpcOutputV2ToggleVisibilityHandlerFunc) {
|
||||||
|
i.toggleVisibilityHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2ActiveEvent : Update the selected output.
|
||||||
|
//
|
||||||
|
// Indicates if the output is active. Zero is invalid, nonzero is valid.
|
||||||
|
type ZdwlIpcOutputV2ActiveEvent struct {
|
||||||
|
Active uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2ActiveHandlerFunc func(ZdwlIpcOutputV2ActiveEvent)
|
||||||
|
|
||||||
|
// SetActiveHandler : sets handler for ZdwlIpcOutputV2ActiveEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetActiveHandler(f ZdwlIpcOutputV2ActiveHandlerFunc) {
|
||||||
|
i.activeHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2TagEvent : Update the state of a tag.
|
||||||
|
//
|
||||||
|
// Indicates that a tag has been updated.
|
||||||
|
type ZdwlIpcOutputV2TagEvent struct {
|
||||||
|
Tag uint32
|
||||||
|
State uint32
|
||||||
|
Clients uint32
|
||||||
|
Focused uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2TagHandlerFunc func(ZdwlIpcOutputV2TagEvent)
|
||||||
|
|
||||||
|
// SetTagHandler : sets handler for ZdwlIpcOutputV2TagEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetTagHandler(f ZdwlIpcOutputV2TagHandlerFunc) {
|
||||||
|
i.tagHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2LayoutEvent : Update the layout.
|
||||||
|
//
|
||||||
|
// Indicates a new layout is selected.
|
||||||
|
type ZdwlIpcOutputV2LayoutEvent struct {
|
||||||
|
Layout uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2LayoutHandlerFunc func(ZdwlIpcOutputV2LayoutEvent)
|
||||||
|
|
||||||
|
// SetLayoutHandler : sets handler for ZdwlIpcOutputV2LayoutEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetLayoutHandler(f ZdwlIpcOutputV2LayoutHandlerFunc) {
|
||||||
|
i.layoutHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2TitleEvent : Update the title.
|
||||||
|
//
|
||||||
|
// Indicates the title has changed.
|
||||||
|
type ZdwlIpcOutputV2TitleEvent struct {
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2TitleHandlerFunc func(ZdwlIpcOutputV2TitleEvent)
|
||||||
|
|
||||||
|
// SetTitleHandler : sets handler for ZdwlIpcOutputV2TitleEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetTitleHandler(f ZdwlIpcOutputV2TitleHandlerFunc) {
|
||||||
|
i.titleHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2AppidEvent : Update the appid.
|
||||||
|
//
|
||||||
|
// Indicates the appid has changed.
|
||||||
|
type ZdwlIpcOutputV2AppidEvent struct {
|
||||||
|
Appid string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2AppidHandlerFunc func(ZdwlIpcOutputV2AppidEvent)
|
||||||
|
|
||||||
|
// SetAppidHandler : sets handler for ZdwlIpcOutputV2AppidEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetAppidHandler(f ZdwlIpcOutputV2AppidHandlerFunc) {
|
||||||
|
i.appidHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2LayoutSymbolEvent : Update the current layout symbol
|
||||||
|
//
|
||||||
|
// Indicates the layout has changed. Since layout symbols are dynamic.
|
||||||
|
// As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying.
|
||||||
|
// You can ignore the zdwl_ipc_output.layout event.
|
||||||
|
type ZdwlIpcOutputV2LayoutSymbolEvent struct {
|
||||||
|
Layout string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2LayoutSymbolHandlerFunc func(ZdwlIpcOutputV2LayoutSymbolEvent)
|
||||||
|
|
||||||
|
// SetLayoutSymbolHandler : sets handler for ZdwlIpcOutputV2LayoutSymbolEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetLayoutSymbolHandler(f ZdwlIpcOutputV2LayoutSymbolHandlerFunc) {
|
||||||
|
i.layoutSymbolHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2FrameEvent : The update sequence is done.
|
||||||
|
//
|
||||||
|
// Indicates that a sequence of status updates have finished and the client should redraw.
|
||||||
|
type ZdwlIpcOutputV2FrameEvent struct{}
|
||||||
|
type ZdwlIpcOutputV2FrameHandlerFunc func(ZdwlIpcOutputV2FrameEvent)
|
||||||
|
|
||||||
|
// SetFrameHandler : sets handler for ZdwlIpcOutputV2FrameEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetFrameHandler(f ZdwlIpcOutputV2FrameHandlerFunc) {
|
||||||
|
i.frameHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2FullscreenEvent : Update fullscreen status
|
||||||
|
//
|
||||||
|
// Indicates if the selected client on this output is fullscreen.
|
||||||
|
type ZdwlIpcOutputV2FullscreenEvent struct {
|
||||||
|
IsFullscreen uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2FullscreenHandlerFunc func(ZdwlIpcOutputV2FullscreenEvent)
|
||||||
|
|
||||||
|
// SetFullscreenHandler : sets handler for ZdwlIpcOutputV2FullscreenEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetFullscreenHandler(f ZdwlIpcOutputV2FullscreenHandlerFunc) {
|
||||||
|
i.fullscreenHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2FloatingEvent : Update the floating status
|
||||||
|
//
|
||||||
|
// Indicates if the selected client on this output is floating.
|
||||||
|
type ZdwlIpcOutputV2FloatingEvent struct {
|
||||||
|
IsFloating uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2FloatingHandlerFunc func(ZdwlIpcOutputV2FloatingEvent)
|
||||||
|
|
||||||
|
// SetFloatingHandler : sets handler for ZdwlIpcOutputV2FloatingEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetFloatingHandler(f ZdwlIpcOutputV2FloatingHandlerFunc) {
|
||||||
|
i.floatingHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2XEvent : Update the x coordinates
|
||||||
|
//
|
||||||
|
// Indicates if x coordinates of the selected client.
|
||||||
|
type ZdwlIpcOutputV2XEvent struct {
|
||||||
|
X int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2XHandlerFunc func(ZdwlIpcOutputV2XEvent)
|
||||||
|
|
||||||
|
// SetXHandler : sets handler for ZdwlIpcOutputV2XEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetXHandler(f ZdwlIpcOutputV2XHandlerFunc) {
|
||||||
|
i.xHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2YEvent : Update the y coordinates
|
||||||
|
//
|
||||||
|
// Indicates if y coordinates of the selected client.
|
||||||
|
type ZdwlIpcOutputV2YEvent struct {
|
||||||
|
Y int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2YHandlerFunc func(ZdwlIpcOutputV2YEvent)
|
||||||
|
|
||||||
|
// SetYHandler : sets handler for ZdwlIpcOutputV2YEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetYHandler(f ZdwlIpcOutputV2YHandlerFunc) {
|
||||||
|
i.yHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2WidthEvent : Update the width
|
||||||
|
//
|
||||||
|
// Indicates if width of the selected client.
|
||||||
|
type ZdwlIpcOutputV2WidthEvent struct {
|
||||||
|
Width int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2WidthHandlerFunc func(ZdwlIpcOutputV2WidthEvent)
|
||||||
|
|
||||||
|
// SetWidthHandler : sets handler for ZdwlIpcOutputV2WidthEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetWidthHandler(f ZdwlIpcOutputV2WidthHandlerFunc) {
|
||||||
|
i.widthHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2HeightEvent : Update the height
|
||||||
|
//
|
||||||
|
// Indicates if height of the selected client.
|
||||||
|
type ZdwlIpcOutputV2HeightEvent struct {
|
||||||
|
Height int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2HeightHandlerFunc func(ZdwlIpcOutputV2HeightEvent)
|
||||||
|
|
||||||
|
// SetHeightHandler : sets handler for ZdwlIpcOutputV2HeightEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetHeightHandler(f ZdwlIpcOutputV2HeightHandlerFunc) {
|
||||||
|
i.heightHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2LastLayerEvent : last map layer.
|
||||||
|
//
|
||||||
|
// last map layer.
|
||||||
|
type ZdwlIpcOutputV2LastLayerEvent struct {
|
||||||
|
LastLayer string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2LastLayerHandlerFunc func(ZdwlIpcOutputV2LastLayerEvent)
|
||||||
|
|
||||||
|
// SetLastLayerHandler : sets handler for ZdwlIpcOutputV2LastLayerEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetLastLayerHandler(f ZdwlIpcOutputV2LastLayerHandlerFunc) {
|
||||||
|
i.lastLayerHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2KbLayoutEvent : current keyboard layout.
|
||||||
|
//
|
||||||
|
// current keyboard layout.
|
||||||
|
type ZdwlIpcOutputV2KbLayoutEvent struct {
|
||||||
|
KbLayout string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2KbLayoutHandlerFunc func(ZdwlIpcOutputV2KbLayoutEvent)
|
||||||
|
|
||||||
|
// SetKbLayoutHandler : sets handler for ZdwlIpcOutputV2KbLayoutEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetKbLayoutHandler(f ZdwlIpcOutputV2KbLayoutHandlerFunc) {
|
||||||
|
i.kbLayoutHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2KeymodeEvent : current keybind mode.
|
||||||
|
//
|
||||||
|
// current keybind mode.
|
||||||
|
type ZdwlIpcOutputV2KeymodeEvent struct {
|
||||||
|
Keymode string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2KeymodeHandlerFunc func(ZdwlIpcOutputV2KeymodeEvent)
|
||||||
|
|
||||||
|
// SetKeymodeHandler : sets handler for ZdwlIpcOutputV2KeymodeEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetKeymodeHandler(f ZdwlIpcOutputV2KeymodeHandlerFunc) {
|
||||||
|
i.keymodeHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2ScalefactorEvent : scale factor of monitor.
|
||||||
|
//
|
||||||
|
// scale factor of monitor.
|
||||||
|
type ZdwlIpcOutputV2ScalefactorEvent struct {
|
||||||
|
Scalefactor uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2ScalefactorHandlerFunc func(ZdwlIpcOutputV2ScalefactorEvent)
|
||||||
|
|
||||||
|
// SetScalefactorHandler : sets handler for ZdwlIpcOutputV2ScalefactorEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetScalefactorHandler(f ZdwlIpcOutputV2ScalefactorHandlerFunc) {
|
||||||
|
i.scalefactorHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
|
||||||
|
switch opcode {
|
||||||
|
case 0:
|
||||||
|
if i.toggleVisibilityHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2ToggleVisibilityEvent
|
||||||
|
|
||||||
|
i.toggleVisibilityHandler(e)
|
||||||
|
case 1:
|
||||||
|
if i.activeHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2ActiveEvent
|
||||||
|
l := 0
|
||||||
|
e.Active = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.activeHandler(e)
|
||||||
|
case 2:
|
||||||
|
if i.tagHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2TagEvent
|
||||||
|
l := 0
|
||||||
|
e.Tag = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
e.State = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
e.Clients = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
e.Focused = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.tagHandler(e)
|
||||||
|
case 3:
|
||||||
|
if i.layoutHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2LayoutEvent
|
||||||
|
l := 0
|
||||||
|
e.Layout = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.layoutHandler(e)
|
||||||
|
case 4:
|
||||||
|
if i.titleHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2TitleEvent
|
||||||
|
l := 0
|
||||||
|
titleLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.Title = client.String(data[l : l+titleLen])
|
||||||
|
l += titleLen
|
||||||
|
|
||||||
|
i.titleHandler(e)
|
||||||
|
case 5:
|
||||||
|
if i.appidHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2AppidEvent
|
||||||
|
l := 0
|
||||||
|
appidLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.Appid = client.String(data[l : l+appidLen])
|
||||||
|
l += appidLen
|
||||||
|
|
||||||
|
i.appidHandler(e)
|
||||||
|
case 6:
|
||||||
|
if i.layoutSymbolHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2LayoutSymbolEvent
|
||||||
|
l := 0
|
||||||
|
layoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.Layout = client.String(data[l : l+layoutLen])
|
||||||
|
l += layoutLen
|
||||||
|
|
||||||
|
i.layoutSymbolHandler(e)
|
||||||
|
case 7:
|
||||||
|
if i.frameHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2FrameEvent
|
||||||
|
|
||||||
|
i.frameHandler(e)
|
||||||
|
case 8:
|
||||||
|
if i.fullscreenHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2FullscreenEvent
|
||||||
|
l := 0
|
||||||
|
e.IsFullscreen = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.fullscreenHandler(e)
|
||||||
|
case 9:
|
||||||
|
if i.floatingHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2FloatingEvent
|
||||||
|
l := 0
|
||||||
|
e.IsFloating = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.floatingHandler(e)
|
||||||
|
case 10:
|
||||||
|
if i.xHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2XEvent
|
||||||
|
l := 0
|
||||||
|
e.X = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.xHandler(e)
|
||||||
|
case 11:
|
||||||
|
if i.yHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2YEvent
|
||||||
|
l := 0
|
||||||
|
e.Y = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.yHandler(e)
|
||||||
|
case 12:
|
||||||
|
if i.widthHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2WidthEvent
|
||||||
|
l := 0
|
||||||
|
e.Width = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.widthHandler(e)
|
||||||
|
case 13:
|
||||||
|
if i.heightHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2HeightEvent
|
||||||
|
l := 0
|
||||||
|
e.Height = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.heightHandler(e)
|
||||||
|
case 14:
|
||||||
|
if i.lastLayerHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2LastLayerEvent
|
||||||
|
l := 0
|
||||||
|
lastLayerLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.LastLayer = client.String(data[l : l+lastLayerLen])
|
||||||
|
l += lastLayerLen
|
||||||
|
|
||||||
|
i.lastLayerHandler(e)
|
||||||
|
case 15:
|
||||||
|
if i.kbLayoutHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2KbLayoutEvent
|
||||||
|
l := 0
|
||||||
|
kbLayoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.KbLayout = client.String(data[l : l+kbLayoutLen])
|
||||||
|
l += kbLayoutLen
|
||||||
|
|
||||||
|
i.kbLayoutHandler(e)
|
||||||
|
case 16:
|
||||||
|
if i.keymodeHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2KeymodeEvent
|
||||||
|
l := 0
|
||||||
|
keymodeLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.Keymode = client.String(data[l : l+keymodeLen])
|
||||||
|
l += keymodeLen
|
||||||
|
|
||||||
|
i.keymodeHandler(e)
|
||||||
|
case 17:
|
||||||
|
if i.scalefactorHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2ScalefactorEvent
|
||||||
|
l := 0
|
||||||
|
e.Scalefactor = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.scalefactorHandler(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package qmlchecks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLockScreenPasswordFieldBypassesTextInputIME(t *testing.T) {
|
|
||||||
data, err := os.ReadFile("../../../quickshell/Modules/Lock/LockScreenContent.qml")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("read lock screen QML: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
content := string(data)
|
|
||||||
textInputPasswordField := regexp.MustCompile(`(?s)TextInput\s*\{[^{}]*id:\s*passwordField`)
|
|
||||||
if textInputPasswordField.MatchString(content) {
|
|
||||||
t.Fatalf("passwordField must not be a TextInput because TextInput can route physical keyboard input through IME")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(content, "Keys.onPressed") || !strings.Contains(content, "event.text") {
|
|
||||||
t.Fatalf("passwordField should handle physical key text manually instead of relying on a text input control")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||||
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
@@ -18,9 +19,9 @@ const (
|
|||||||
CompositorHyprland
|
CompositorHyprland
|
||||||
CompositorSway
|
CompositorSway
|
||||||
CompositorNiri
|
CompositorNiri
|
||||||
|
CompositorDWL
|
||||||
CompositorScroll
|
CompositorScroll
|
||||||
CompositorMiracle
|
CompositorMiracle
|
||||||
CompositorMango
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var detectedCompositor Compositor = -1
|
var detectedCompositor Compositor = -1
|
||||||
@@ -35,14 +36,8 @@ func DetectCompositor() Compositor {
|
|||||||
swaySocket := os.Getenv("SWAYSOCK")
|
swaySocket := os.Getenv("SWAYSOCK")
|
||||||
scrollSocket := os.Getenv("SCROLLSOCK")
|
scrollSocket := os.Getenv("SCROLLSOCK")
|
||||||
miracleSocket := os.Getenv("MIRACLESOCK")
|
miracleSocket := os.Getenv("MIRACLESOCK")
|
||||||
mangoSocket := os.Getenv("MANGO_INSTANCE_SIGNATURE")
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case mangoSocket != "":
|
|
||||||
if _, err := os.Stat(mangoSocket); err == nil {
|
|
||||||
detectedCompositor = CompositorMango
|
|
||||||
return detectedCompositor
|
|
||||||
}
|
|
||||||
case niriSocket != "":
|
case niriSocket != "":
|
||||||
if _, err := os.Stat(niriSocket); err == nil {
|
if _, err := os.Stat(niriSocket); err == nil {
|
||||||
detectedCompositor = CompositorNiri
|
detectedCompositor = CompositorNiri
|
||||||
@@ -68,29 +63,66 @@ func DetectCompositor() Compositor {
|
|||||||
return detectedCompositor
|
return detectedCompositor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if detectDWLProtocol() {
|
||||||
|
detectedCompositor = CompositorDWL
|
||||||
|
return detectedCompositor
|
||||||
|
}
|
||||||
|
|
||||||
detectedCompositor = CompositorUnknown
|
detectedCompositor = CompositorUnknown
|
||||||
return detectedCompositor
|
return detectedCompositor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func detectDWLProtocol() bool {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
if e.Interface == dwl_ipc.ZdwlIpcManagerV2InterfaceName {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetCompositorDWL() {
|
||||||
|
detectedCompositor = CompositorDWL
|
||||||
|
}
|
||||||
|
|
||||||
type WindowGeometry struct {
|
type WindowGeometry struct {
|
||||||
X int32
|
X int32
|
||||||
Y int32
|
Y int32
|
||||||
Width int32
|
Width int32
|
||||||
Height int32
|
Height int32
|
||||||
Output string
|
Output string
|
||||||
Scale float64
|
Scale float64
|
||||||
OutputX int32
|
OutputX int32
|
||||||
OutputY int32
|
OutputY int32
|
||||||
|
OutputTransform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActiveWindow() (*WindowGeometry, error) {
|
func GetActiveWindow() (*WindowGeometry, error) {
|
||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
return getHyprlandActiveWindow()
|
return getHyprlandActiveWindow()
|
||||||
case CompositorMango:
|
case CompositorDWL:
|
||||||
return getMangoActiveWindow()
|
return getDWLActiveWindow()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("window capture requires Hyprland or Mango")
|
return nil, fmt.Errorf("window capture requires Hyprland or DWL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,93 +285,6 @@ func getMiracleFocusedMonitor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type mangoMonitor struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Active bool `json:"active"`
|
|
||||||
X int32 `json:"x"`
|
|
||||||
Y int32 `json:"y"`
|
|
||||||
Scale float64 `json:"scale"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMangoMonitors() []mangoMonitor {
|
|
||||||
output, err := exec.Command("mmsg", "get", "all-monitors").Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var data struct {
|
|
||||||
Monitors []mangoMonitor `json:"monitors"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(output, &data); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return data.Monitors
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMangoFocusedMonitor() string {
|
|
||||||
for _, m := range getMangoMonitors() {
|
|
||||||
if m.Active {
|
|
||||||
return m.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type mangoClient struct {
|
|
||||||
Monitor string `json:"monitor"`
|
|
||||||
IsFocused bool `json:"is_focused"`
|
|
||||||
X int32 `json:"x"`
|
|
||||||
Y int32 `json:"y"`
|
|
||||||
Width int32 `json:"width"`
|
|
||||||
Height int32 `json:"height"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMangoActiveWindow() (*WindowGeometry, error) {
|
|
||||||
output, err := exec.Command("mmsg", "get", "all-clients").Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("mmsg get all-clients: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var data struct {
|
|
||||||
Clients []mangoClient `json:"clients"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(output, &data); err != nil {
|
|
||||||
return nil, fmt.Errorf("parse all-clients: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range data.Clients {
|
|
||||||
if !c.IsFocused {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.Width <= 0 || c.Height <= 0 {
|
|
||||||
return nil, fmt.Errorf("no active window")
|
|
||||||
}
|
|
||||||
|
|
||||||
geom := &WindowGeometry{
|
|
||||||
X: c.X,
|
|
||||||
Y: c.Y,
|
|
||||||
Width: c.Width,
|
|
||||||
Height: c.Height,
|
|
||||||
Output: c.Monitor,
|
|
||||||
Scale: 1.0,
|
|
||||||
}
|
|
||||||
for _, m := range getMangoMonitors() {
|
|
||||||
if m.Name != c.Monitor {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
geom.OutputX = m.X
|
|
||||||
geom.OutputY = m.Y
|
|
||||||
if m.Scale > 0 {
|
|
||||||
geom.Scale = m.Scale
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return geom, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("no focused window")
|
|
||||||
}
|
|
||||||
|
|
||||||
type niriWorkspace struct {
|
type niriWorkspace struct {
|
||||||
Output string `json:"output"`
|
Output string `json:"output"`
|
||||||
IsFocused bool `json:"is_focused"`
|
IsFocused bool `json:"is_focused"`
|
||||||
@@ -364,6 +309,121 @@ func getNiriFocusedMonitor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dwlActiveOutput string
|
||||||
|
|
||||||
|
func SetDWLActiveOutput(name string) {
|
||||||
|
dwlActiveOutput = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDWLFocusedMonitor() string {
|
||||||
|
if dwlActiveOutput != "" {
|
||||||
|
return dwlActiveOutput
|
||||||
|
}
|
||||||
|
return queryDWLActiveOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryDWLActiveOutput() string {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var dwlManager *dwl_ipc.ZdwlIpcManagerV2
|
||||||
|
outputs := make(map[uint32]*client.Output)
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
switch e.Interface {
|
||||||
|
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||||
|
mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
||||||
|
dwlManager = mgr
|
||||||
|
}
|
||||||
|
case client.OutputInterfaceName:
|
||||||
|
out := client.NewOutput(ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, out); err == nil {
|
||||||
|
outputs[e.Name] = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if dwlManager == nil || len(outputs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
outputNames := make(map[uint32]string)
|
||||||
|
for name, out := range outputs {
|
||||||
|
n := name
|
||||||
|
out.SetNameHandler(func(e client.OutputNameEvent) {
|
||||||
|
outputNames[n] = e.Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputState struct {
|
||||||
|
name string
|
||||||
|
active bool
|
||||||
|
gotFrame bool
|
||||||
|
}
|
||||||
|
states := make(map[uint32]*outputState)
|
||||||
|
|
||||||
|
for name, out := range outputs {
|
||||||
|
dwlOut, err := dwlManager.GetOutput(out)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state := &outputState{name: outputNames[name]}
|
||||||
|
states[name] = state
|
||||||
|
|
||||||
|
dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||||
|
state.active = e.Active != 0
|
||||||
|
})
|
||||||
|
dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||||
|
state.gotFrame = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
allFramesReceived := func() bool {
|
||||||
|
for _, s := range states {
|
||||||
|
if !s.gotFrame {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for !allFramesReceived() {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range states {
|
||||||
|
if state.active {
|
||||||
|
return state.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func GetFocusedMonitor() string {
|
func GetFocusedMonitor() string {
|
||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
@@ -376,8 +436,8 @@ func GetFocusedMonitor() string {
|
|||||||
return getMiracleFocusedMonitor()
|
return getMiracleFocusedMonitor()
|
||||||
case CompositorNiri:
|
case CompositorNiri:
|
||||||
return getNiriFocusedMonitor()
|
return getNiriFocusedMonitor()
|
||||||
case CompositorMango:
|
case CompositorDWL:
|
||||||
return getMangoFocusedMonitor()
|
return getDWLFocusedMonitor()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -474,3 +534,161 @@ func getAllOutputInfos() map[string]*outputInfo {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOutputInfo(outputName string) (*outputInfo, bool) {
|
||||||
|
infos := getAllOutputInfos()
|
||||||
|
if infos == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
info, ok := infos[outputName]
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDWLActiveWindow() (*WindowGeometry, error) {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("connect: %w", err)
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dwlManager *dwl_ipc.ZdwlIpcManagerV2
|
||||||
|
outputs := make(map[uint32]*client.Output)
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
switch e.Interface {
|
||||||
|
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||||
|
mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
||||||
|
dwlManager = mgr
|
||||||
|
}
|
||||||
|
case client.OutputInterfaceName:
|
||||||
|
out := client.NewOutput(ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, out); err == nil {
|
||||||
|
outputs[e.Name] = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dwlManager == nil {
|
||||||
|
return nil, fmt.Errorf("dwl_ipc_manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no outputs found")
|
||||||
|
}
|
||||||
|
|
||||||
|
outputNames := make(map[uint32]string)
|
||||||
|
for name, out := range outputs {
|
||||||
|
n := name
|
||||||
|
out.SetNameHandler(func(e client.OutputNameEvent) {
|
||||||
|
outputNames[n] = e.Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dwlOutputState struct {
|
||||||
|
output *dwl_ipc.ZdwlIpcOutputV2
|
||||||
|
name string
|
||||||
|
active bool
|
||||||
|
x, y int32
|
||||||
|
w, h int32
|
||||||
|
scalefactor uint32
|
||||||
|
gotFrame bool
|
||||||
|
}
|
||||||
|
|
||||||
|
dwlOutputs := make(map[uint32]*dwlOutputState)
|
||||||
|
for name, out := range outputs {
|
||||||
|
dwlOut, err := dwlManager.GetOutput(out)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state := &dwlOutputState{output: dwlOut, name: outputNames[name]}
|
||||||
|
dwlOutputs[name] = state
|
||||||
|
|
||||||
|
dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||||
|
state.active = e.Active != 0
|
||||||
|
})
|
||||||
|
dwlOut.SetXHandler(func(e dwl_ipc.ZdwlIpcOutputV2XEvent) {
|
||||||
|
state.x = e.X
|
||||||
|
})
|
||||||
|
dwlOut.SetYHandler(func(e dwl_ipc.ZdwlIpcOutputV2YEvent) {
|
||||||
|
state.y = e.Y
|
||||||
|
})
|
||||||
|
dwlOut.SetWidthHandler(func(e dwl_ipc.ZdwlIpcOutputV2WidthEvent) {
|
||||||
|
state.w = e.Width
|
||||||
|
})
|
||||||
|
dwlOut.SetHeightHandler(func(e dwl_ipc.ZdwlIpcOutputV2HeightEvent) {
|
||||||
|
state.h = e.Height
|
||||||
|
})
|
||||||
|
dwlOut.SetScalefactorHandler(func(e dwl_ipc.ZdwlIpcOutputV2ScalefactorEvent) {
|
||||||
|
state.scalefactor = e.Scalefactor
|
||||||
|
})
|
||||||
|
dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||||
|
state.gotFrame = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
allFramesReceived := func() bool {
|
||||||
|
for _, s := range dwlOutputs {
|
||||||
|
if !s.gotFrame {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for !allFramesReceived() {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
return nil, fmt.Errorf("dispatch: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range dwlOutputs {
|
||||||
|
if !state.active {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if state.w <= 0 || state.h <= 0 {
|
||||||
|
return nil, fmt.Errorf("no active window")
|
||||||
|
}
|
||||||
|
scale := float64(state.scalefactor) / 100.0
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
geom := &WindowGeometry{
|
||||||
|
X: state.x,
|
||||||
|
Y: state.y,
|
||||||
|
Width: state.w,
|
||||||
|
Height: state.h,
|
||||||
|
Output: state.name,
|
||||||
|
Scale: scale,
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, ok := getOutputInfo(state.name); ok {
|
||||||
|
geom.OutputX = info.x
|
||||||
|
geom.OutputY = info.y
|
||||||
|
geom.OutputTransform = info.transform
|
||||||
|
}
|
||||||
|
|
||||||
|
return geom, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no active output found")
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,14 +156,14 @@ func (s *Screenshoter) captureWindow() (*CaptureResult, error) {
|
|||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
return s.captureAndCrop(output, region)
|
return s.captureAndCrop(output, region)
|
||||||
case CompositorMango:
|
case CompositorDWL:
|
||||||
return s.captureMangoWindow(output, region, geom)
|
return s.captureDWLWindow(output, region, geom)
|
||||||
default:
|
default:
|
||||||
return s.captureRegionOnOutput(output, region)
|
return s.captureRegionOnOutput(output, region)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureMangoWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) {
|
func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) {
|
||||||
result, err := s.captureWholeOutput(output)
|
result, err := s.captureWholeOutput(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -628,7 +628,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
|||||||
w := int32(float64(region.Width) * scale)
|
w := int32(float64(region.Width) * scale)
|
||||||
h := int32(float64(region.Height) * scale)
|
h := int32(float64(region.Height) * scale)
|
||||||
|
|
||||||
if DetectCompositor() == CompositorMango {
|
if DetectCompositor() == CompositorDWL {
|
||||||
scaledOutW := int32(float64(output.width) * scale)
|
scaledOutW := int32(float64(output.width) * scale)
|
||||||
scaledOutH := int32(float64(output.height) * scale)
|
scaledOutH := int32(float64(output.height) * scale)
|
||||||
if localX >= scaledOutW {
|
if localX >= scaledOutW {
|
||||||
|
|||||||
@@ -935,7 +935,7 @@ func (m *Manager) CreateHistoryEntryFromPinned(pinnedEntry *Entry) error {
|
|||||||
Pinned: false,
|
Pinned: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.storeEntry(newEntry); err != nil {
|
if err := m.storeEntryWithoutDedup(newEntry); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,6 +945,36 @@ func (m *Manager) CreateHistoryEntryFromPinned(pinnedEntry *Entry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) storeEntryWithoutDedup(entry Entry) error {
|
||||||
|
if m.db == nil {
|
||||||
|
return fmt.Errorf("database not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Hash = computeHash(entry.Data)
|
||||||
|
|
||||||
|
return m.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
|
||||||
|
id, err := b.NextSequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.ID = id
|
||||||
|
|
||||||
|
encoded, err := encodeEntry(entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.Put(itob(id), encoded); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.trimLengthInTx(b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) ClearHistory() {
|
func (m *Manager) ClearHistory() {
|
||||||
if m.db == nil {
|
if m.db == nil {
|
||||||
return
|
return
|
||||||
@@ -1623,37 +1653,6 @@ func (m *Manager) UnpinEntry(id uint64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Pinned {
|
|
||||||
currentKey := itob(id)
|
|
||||||
var keepKey []byte
|
|
||||||
var deleteKeys [][]byte
|
|
||||||
|
|
||||||
c := b.Cursor()
|
|
||||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
|
||||||
if bytes.Equal(k, currentKey) || extractHash(v) != entry.Hash {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
duplicate, err := decodeEntryMeta(v)
|
|
||||||
if err == nil && !duplicate.Pinned {
|
|
||||||
key := append([]byte(nil), k...)
|
|
||||||
if keepKey == nil {
|
|
||||||
keepKey = key
|
|
||||||
} else {
|
|
||||||
deleteKeys = append(deleteKeys, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if keepKey != nil {
|
|
||||||
for _, key := range deleteKeys {
|
|
||||||
if err := b.Delete(key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.Delete(currentKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Pinned = false
|
entry.Pinned = false
|
||||||
encoded, err := encodeEntry(entry)
|
encoded, err := encodeEntry(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
|
|
||||||
mocks_wlcontext "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlcontext"
|
mocks_wlcontext "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlcontext"
|
||||||
)
|
)
|
||||||
@@ -274,110 +273,6 @@ func TestHandleGetEntry_MissingIDReturnsNullResult(t *testing.T) {
|
|||||||
assert.Nil(t, resp.Result)
|
assert.Nil(t, resp.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpinEntry_KeepsTopUnpinnedDuplicate(t *testing.T) {
|
|
||||||
m := newTestManagerWithDB(t)
|
|
||||||
|
|
||||||
require.NoError(t, m.storeEntry(Entry{
|
|
||||||
Data: []byte("saved content"),
|
|
||||||
MimeType: "text/plain;charset=utf-8",
|
|
||||||
Preview: "saved content",
|
|
||||||
Size: len("saved content"),
|
|
||||||
Timestamp: time.Now().Add(-time.Minute).Truncate(time.Second),
|
|
||||||
IsImage: false,
|
|
||||||
}))
|
|
||||||
|
|
||||||
history := m.GetHistory()
|
|
||||||
require.Len(t, history, 1)
|
|
||||||
pinnedID := history[0].ID
|
|
||||||
require.NoError(t, m.PinEntry(pinnedID))
|
|
||||||
|
|
||||||
pinnedEntry, err := m.GetEntry(pinnedID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, pinnedEntry.Pinned)
|
|
||||||
|
|
||||||
// Bypass storeEntry to simulate legacy duplicate ordinary history entries.
|
|
||||||
insertLegacyUnpinnedDuplicate := func(timestamp time.Time) Entry {
|
|
||||||
duplicate := Entry{
|
|
||||||
Data: pinnedEntry.Data,
|
|
||||||
MimeType: pinnedEntry.MimeType,
|
|
||||||
Preview: pinnedEntry.Preview,
|
|
||||||
Size: pinnedEntry.Size,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
IsImage: pinnedEntry.IsImage,
|
|
||||||
Pinned: false,
|
|
||||||
}
|
|
||||||
duplicate.Hash = computeHash(duplicate.Data)
|
|
||||||
|
|
||||||
require.NoError(t, m.db.Update(func(tx *bolt.Tx) error {
|
|
||||||
b := tx.Bucket([]byte("clipboard"))
|
|
||||||
id, err := b.NextSequence()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
duplicate.ID = id
|
|
||||||
|
|
||||||
encoded, err := encodeEntry(duplicate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return b.Put(itob(id), encoded)
|
|
||||||
}))
|
|
||||||
|
|
||||||
return duplicate
|
|
||||||
}
|
|
||||||
|
|
||||||
olderHistoryDuplicate := insertLegacyUnpinnedDuplicate(time.Now().Add(time.Hour))
|
|
||||||
topHistoryDuplicate := insertLegacyUnpinnedDuplicate(time.Now().Add(-time.Hour))
|
|
||||||
require.Greater(t, topHistoryDuplicate.ID, olderHistoryDuplicate.ID)
|
|
||||||
require.True(t, olderHistoryDuplicate.Timestamp.After(topHistoryDuplicate.Timestamp))
|
|
||||||
|
|
||||||
history = m.GetHistory()
|
|
||||||
require.Len(t, history, 3)
|
|
||||||
require.Equal(t, topHistoryDuplicate.ID, history[0].ID)
|
|
||||||
require.NoError(t, m.UnpinEntry(pinnedID))
|
|
||||||
|
|
||||||
history = m.GetHistory()
|
|
||||||
require.Len(t, history, 1)
|
|
||||||
assert.False(t, history[0].Pinned)
|
|
||||||
assert.Equal(t, pinnedEntry.Hash, history[0].Hash)
|
|
||||||
assert.Equal(t, topHistoryDuplicate.ID, history[0].ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateHistoryEntryFromPinned_KeepsLatestUnpinnedDuplicate(t *testing.T) {
|
|
||||||
m := newTestManagerWithDB(t)
|
|
||||||
|
|
||||||
require.NoError(t, m.storeEntry(Entry{
|
|
||||||
Data: []byte("saved content"),
|
|
||||||
MimeType: "text/plain;charset=utf-8",
|
|
||||||
Preview: "saved content",
|
|
||||||
Size: len("saved content"),
|
|
||||||
Timestamp: time.Now().Add(-time.Minute).Truncate(time.Second),
|
|
||||||
IsImage: false,
|
|
||||||
}))
|
|
||||||
|
|
||||||
history := m.GetHistory()
|
|
||||||
require.Len(t, history, 1)
|
|
||||||
pinnedID := history[0].ID
|
|
||||||
require.NoError(t, m.PinEntry(pinnedID))
|
|
||||||
|
|
||||||
pinnedEntry, err := m.GetEntry(pinnedID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, pinnedEntry.Pinned)
|
|
||||||
require.NoError(t, m.CreateHistoryEntryFromPinned(pinnedEntry))
|
|
||||||
firstDuplicate := m.GetHistory()[0]
|
|
||||||
require.NotEqual(t, pinnedID, firstDuplicate.ID)
|
|
||||||
require.NoError(t, m.CreateHistoryEntryFromPinned(pinnedEntry))
|
|
||||||
latestDuplicate := m.GetHistory()[0]
|
|
||||||
|
|
||||||
history = m.GetHistory()
|
|
||||||
require.Len(t, history, 2)
|
|
||||||
assert.Equal(t, latestDuplicate.ID, history[0].ID)
|
|
||||||
assert.False(t, history[0].Pinned)
|
|
||||||
assert.Equal(t, pinnedID, history[1].ID)
|
|
||||||
assert.True(t, history[1].Pinned)
|
|
||||||
assert.NotEqual(t, firstDuplicate.ID, latestDuplicate.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
subscribers: make(map[string]chan State),
|
subscribers: make(map[string]chan State),
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package dwl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SuccessResult struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
if manager == nil {
|
||||||
|
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Method {
|
||||||
|
case "dwl.getState":
|
||||||
|
handleGetState(conn, req, manager)
|
||||||
|
case "dwl.setTags":
|
||||||
|
handleSetTags(conn, req, manager)
|
||||||
|
case "dwl.setClientTags":
|
||||||
|
handleSetClientTags(conn, req, manager)
|
||||||
|
case "dwl.setLayout":
|
||||||
|
handleSetLayout(conn, req, manager)
|
||||||
|
case "dwl.subscribe":
|
||||||
|
handleSubscribe(conn, req, manager)
|
||||||
|
default:
|
||||||
|
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
state := manager.GetState()
|
||||||
|
models.Respond(conn, req.ID, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetTags(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
output, ok := models.Get[string](req, "output")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tagmask, ok := models.Get[float64](req, "tagmask")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'tagmask' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTagset, ok := models.Get[float64](req, "toggleTagset")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'toggleTagset' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.SetTags(output, uint32(tagmask), uint32(toggleTagset)); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetClientTags(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
output, ok := models.Get[string](req, "output")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
andTags, ok := models.Get[float64](req, "andTags")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'andTags' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xorTags, ok := models.Get[float64](req, "xorTags")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'xorTags' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.SetClientTags(output, uint32(andTags), uint32(xorTags)); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetLayout(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
output, ok := models.Get[string](req, "output")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index, ok := models.Get[float64](req, "index")
|
||||||
|
if !ok {
|
||||||
|
models.RespondError(conn, req.ID, "missing or invalid 'index' parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.SetLayout(output, uint32(index)); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
|
stateChan := manager.Subscribe(clientID)
|
||||||
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|
||||||
|
initialState := manager.GetState()
|
||||||
|
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||||
|
ID: req.ID,
|
||||||
|
Result: &initialState,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for state := range stateChan {
|
||||||
|
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||||
|
Result: &state,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
package dwl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewManager(display wlclient.WaylandDisplay) (*Manager, error) {
|
||||||
|
m := &Manager{
|
||||||
|
display: display,
|
||||||
|
ctx: display.Context(),
|
||||||
|
cmdq: make(chan cmd, 128),
|
||||||
|
outputSetupReq: make(chan uint32, 16),
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
|
||||||
|
dirty: make(chan struct{}, 1),
|
||||||
|
layouts: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.setupRegistry(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateState()
|
||||||
|
|
||||||
|
m.notifierWg.Add(1)
|
||||||
|
go m.notifier()
|
||||||
|
|
||||||
|
m.wg.Add(1)
|
||||||
|
go m.waylandActor()
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) post(fn func()) {
|
||||||
|
select {
|
||||||
|
case m.cmdq <- cmd{fn: fn}:
|
||||||
|
default:
|
||||||
|
log.Warn("DWL actor command queue full, dropping command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) waylandActor() {
|
||||||
|
defer m.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.stopChan:
|
||||||
|
return
|
||||||
|
case c := <-m.cmdq:
|
||||||
|
c.fn()
|
||||||
|
case outputID := <-m.outputSetupReq:
|
||||||
|
out, exists := m.outputs.Load(outputID)
|
||||||
|
if !exists {
|
||||||
|
log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.ipcOutput != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2)
|
||||||
|
if !ok || mgr == nil {
|
||||||
|
log.Errorf("DWL: Manager not available for output %d setup", outputID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("DWL: Setting up ipcOutput for dynamically added output %d", outputID)
|
||||||
|
if err := m.setupOutput(mgr, out.output); err != nil {
|
||||||
|
log.Errorf("DWL: Failed to setup output %d: %v", outputID, err)
|
||||||
|
} else {
|
||||||
|
m.updateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) setupRegistry() error {
|
||||||
|
log.Info("DWL: starting registry setup")
|
||||||
|
|
||||||
|
registry, err := m.display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get registry: %w", err)
|
||||||
|
}
|
||||||
|
m.registry = registry
|
||||||
|
|
||||||
|
outputs := make([]*wlclient.Output, 0)
|
||||||
|
outputRegNames := make(map[uint32]uint32)
|
||||||
|
var dwlMgr *dwl_ipc.ZdwlIpcManagerV2
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
|
switch e.Interface {
|
||||||
|
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||||
|
log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
|
||||||
|
manager := dwl_ipc.NewZdwlIpcManagerV2(m.ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 2 {
|
||||||
|
version = 2
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
|
||||||
|
dwlMgr = manager
|
||||||
|
log.Info("DWL: manager bound successfully")
|
||||||
|
|
||||||
|
// Set handlers immediately after binding, before roundtrips
|
||||||
|
manager.SetTagsHandler(func(e dwl_ipc.ZdwlIpcManagerV2TagsEvent) {
|
||||||
|
log.Infof("DWL: Tags count: %d", e.Amount)
|
||||||
|
m.tagCount = e.Amount
|
||||||
|
m.updateState()
|
||||||
|
})
|
||||||
|
|
||||||
|
manager.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcManagerV2LayoutEvent) {
|
||||||
|
log.Infof("DWL: Layout: %s", e.Name)
|
||||||
|
m.layouts = append(m.layouts, e.Name)
|
||||||
|
m.updateState()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log.Errorf("DWL: failed to bind manager: %v", err)
|
||||||
|
}
|
||||||
|
case "wl_output":
|
||||||
|
log.Debugf("DWL: found wl_output (name=%d)", e.Name)
|
||||||
|
output := wlclient.NewOutput(m.ctx)
|
||||||
|
|
||||||
|
outState := &outputState{
|
||||||
|
registryName: e.Name,
|
||||||
|
output: output,
|
||||||
|
tags: make([]TagState, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
||||||
|
log.Debugf("DWL: Output name: %s (registry=%d)", ev.Name, e.Name)
|
||||||
|
outState.name = ev.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
output.SetDescriptionHandler(func(ev wlclient.OutputDescriptionEvent) {
|
||||||
|
log.Debugf("DWL: Output description: %s", ev.Description)
|
||||||
|
})
|
||||||
|
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
||||||
|
outputID := output.ID()
|
||||||
|
outState.id = outputID
|
||||||
|
log.Infof("DWL: Bound wl_output id=%d registry_name=%d", outputID, e.Name)
|
||||||
|
outputs = append(outputs, output)
|
||||||
|
outputRegNames[outputID] = e.Name
|
||||||
|
|
||||||
|
m.outputs.Store(outputID, outState)
|
||||||
|
|
||||||
|
if m.manager != nil {
|
||||||
|
select {
|
||||||
|
case m.outputSetupReq <- outputID:
|
||||||
|
log.Debugf("DWL: Queued setup for output %d", outputID)
|
||||||
|
default:
|
||||||
|
log.Warnf("DWL: Setup queue full, output %d will not be initialized", outputID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("DWL: Failed to bind wl_output: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
|
||||||
|
m.post(func() {
|
||||||
|
var outToRelease *outputState
|
||||||
|
m.outputs.Range(func(id uint32, out *outputState) bool {
|
||||||
|
if out.registryName == e.Name {
|
||||||
|
log.Infof("DWL: Output %d removed", id)
|
||||||
|
outToRelease = out
|
||||||
|
m.outputs.Delete(id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if outToRelease != nil {
|
||||||
|
if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil {
|
||||||
|
m.wlMutex.Lock()
|
||||||
|
ipcOut.Release()
|
||||||
|
m.wlMutex.Unlock()
|
||||||
|
log.Debugf("DWL: Released ipcOutput for removed output %d", outToRelease.id)
|
||||||
|
}
|
||||||
|
m.updateState()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := m.display.Roundtrip(); err != nil {
|
||||||
|
return fmt.Errorf("first roundtrip failed: %w", err)
|
||||||
|
}
|
||||||
|
if err := m.display.Roundtrip(); err != nil {
|
||||||
|
return fmt.Errorf("second roundtrip failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dwlMgr == nil {
|
||||||
|
log.Info("DWL: manager not found in registry")
|
||||||
|
return fmt.Errorf("dwl_ipc_manager_v2 not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.manager = dwlMgr
|
||||||
|
|
||||||
|
for _, output := range outputs {
|
||||||
|
if err := m.setupOutput(dwlMgr, output); err != nil {
|
||||||
|
log.Warnf("DWL: Failed to setup output %d: %v", output.ID(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.display.Roundtrip(); err != nil {
|
||||||
|
return fmt.Errorf("final roundtrip failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("DWL: registry setup complete")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclient.Output) error {
|
||||||
|
m.wlMutex.Lock()
|
||||||
|
ipcOutput, err := manager.GetOutput(output)
|
||||||
|
m.wlMutex.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get dwl output: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outState, exists := m.outputs.Load(output.ID())
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("output state not found for id %d", output.ID())
|
||||||
|
}
|
||||||
|
outState.ipcOutput = ipcOutput
|
||||||
|
|
||||||
|
ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||||
|
outState.active = e.Active
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetTagHandler(func(e dwl_ipc.ZdwlIpcOutputV2TagEvent) {
|
||||||
|
updated := false
|
||||||
|
for i, tag := range outState.tags {
|
||||||
|
if tag.Tag == e.Tag {
|
||||||
|
outState.tags[i] = TagState{
|
||||||
|
Tag: e.Tag,
|
||||||
|
State: e.State,
|
||||||
|
Clients: e.Clients,
|
||||||
|
Focused: e.Focused,
|
||||||
|
}
|
||||||
|
updated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
outState.tags = append(outState.tags, TagState{
|
||||||
|
Tag: e.Tag,
|
||||||
|
State: e.State,
|
||||||
|
Clients: e.Clients,
|
||||||
|
Focused: e.Focused,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateState()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutEvent) {
|
||||||
|
outState.layout = e.Layout
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetTitleHandler(func(e dwl_ipc.ZdwlIpcOutputV2TitleEvent) {
|
||||||
|
outState.title = e.Title
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetAppidHandler(func(e dwl_ipc.ZdwlIpcOutputV2AppidEvent) {
|
||||||
|
outState.appID = e.Appid
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetLayoutSymbolHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutSymbolEvent) {
|
||||||
|
outState.layoutSymbol = e.Layout
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetKbLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2KbLayoutEvent) {
|
||||||
|
outState.kbLayout = e.KbLayout
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetKeymodeHandler(func(e dwl_ipc.ZdwlIpcOutputV2KeymodeEvent) {
|
||||||
|
outState.keymode = e.Keymode
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||||
|
m.updateState()
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) updateState() {
|
||||||
|
outputs := make(map[string]*OutputState)
|
||||||
|
activeOutput := ""
|
||||||
|
|
||||||
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
|
name := out.name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsCopy := make([]TagState, len(out.tags))
|
||||||
|
copy(tagsCopy, out.tags)
|
||||||
|
|
||||||
|
outputs[name] = &OutputState{
|
||||||
|
Name: name,
|
||||||
|
Active: out.active,
|
||||||
|
Tags: tagsCopy,
|
||||||
|
Layout: out.layout,
|
||||||
|
LayoutSymbol: out.layoutSymbol,
|
||||||
|
Title: out.title,
|
||||||
|
AppID: out.appID,
|
||||||
|
KbLayout: out.kbLayout,
|
||||||
|
Keymode: out.keymode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.active != 0 {
|
||||||
|
activeOutput = name
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
newState := State{
|
||||||
|
Outputs: outputs,
|
||||||
|
TagCount: m.tagCount,
|
||||||
|
Layouts: m.layouts,
|
||||||
|
ActiveOutput: activeOutput,
|
||||||
|
}
|
||||||
|
|
||||||
|
m.stateMutex.Lock()
|
||||||
|
m.state = &newState
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
|
||||||
|
m.notifySubscribers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) notifier() {
|
||||||
|
defer m.notifierWg.Done()
|
||||||
|
const minGap = 100 * time.Millisecond
|
||||||
|
timer := time.NewTimer(minGap)
|
||||||
|
timer.Stop()
|
||||||
|
var pending bool
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.stopChan:
|
||||||
|
timer.Stop()
|
||||||
|
return
|
||||||
|
case <-m.dirty:
|
||||||
|
if pending {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pending = true
|
||||||
|
timer.Reset(minGap)
|
||||||
|
case <-timer.C:
|
||||||
|
if !pending {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
currentState := m.GetState()
|
||||||
|
|
||||||
|
if m.lastNotified != nil && !stateChanged(m.lastNotified, ¤tState) {
|
||||||
|
pending = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
select {
|
||||||
|
case ch <- currentState:
|
||||||
|
default:
|
||||||
|
log.Warn("DWL: subscriber channel full, dropping update")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
stateCopy := currentState
|
||||||
|
m.lastNotified = &stateCopy
|
||||||
|
pending = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ensureOutputSetup(out *outputState) error {
|
||||||
|
if out.ipcOutput != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("output not yet initialized - setup in progress, retry in a moment")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error {
|
||||||
|
availableOutputs := make([]string, 0)
|
||||||
|
var targetOut *outputState
|
||||||
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
|
name := out.name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
|
}
|
||||||
|
availableOutputs = append(availableOutputs, name)
|
||||||
|
if name == outputName {
|
||||||
|
targetOut = out
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if targetOut == nil {
|
||||||
|
return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.ensureOutputSetup(targetOut); err != nil {
|
||||||
|
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.wlMutex.Lock()
|
||||||
|
err := ipcOut.SetTags(tagmask, toggleTagset)
|
||||||
|
m.wlMutex.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error {
|
||||||
|
var targetOut *outputState
|
||||||
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
|
name := out.name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
|
}
|
||||||
|
if name == outputName {
|
||||||
|
targetOut = out
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if targetOut == nil {
|
||||||
|
return fmt.Errorf("output not found: %s", outputName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.ensureOutputSetup(targetOut); err != nil {
|
||||||
|
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.wlMutex.Lock()
|
||||||
|
err := ipcOut.SetClientTags(andTags, xorTags)
|
||||||
|
m.wlMutex.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetLayout(outputName string, index uint32) error {
|
||||||
|
var targetOut *outputState
|
||||||
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
|
name := out.name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
|
}
|
||||||
|
if name == outputName {
|
||||||
|
targetOut = out
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if targetOut == nil {
|
||||||
|
return fmt.Errorf("output not found: %s", outputName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.ensureOutputSetup(targetOut); err != nil {
|
||||||
|
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.wlMutex.Lock()
|
||||||
|
err := ipcOut.SetLayout(index)
|
||||||
|
m.wlMutex.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() {
|
||||||
|
close(m.stopChan)
|
||||||
|
m.wg.Wait()
|
||||||
|
m.notifierWg.Wait()
|
||||||
|
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
close(ch)
|
||||||
|
m.subscribers.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
|
if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok {
|
||||||
|
ipcOut.Release()
|
||||||
|
}
|
||||||
|
m.outputs.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok {
|
||||||
|
mgr.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
package dwl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateChanged_BothNil(t *testing.T) {
|
||||||
|
assert.True(t, stateChanged(nil, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_OneNil(t *testing.T) {
|
||||||
|
s := &State{TagCount: 9}
|
||||||
|
assert.True(t, stateChanged(s, nil))
|
||||||
|
assert.True(t, stateChanged(nil, s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_TagCountDiffers(t *testing.T) {
|
||||||
|
a := &State{TagCount: 9, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||||
|
b := &State{TagCount: 10, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_LayoutLengthDiffers(t *testing.T) {
|
||||||
|
a := &State{TagCount: 9, Layouts: []string{"tile"}, Outputs: make(map[string]*OutputState)}
|
||||||
|
b := &State{TagCount: 9, Layouts: []string{"tile", "monocle"}, Outputs: make(map[string]*OutputState)}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_ActiveOutputDiffers(t *testing.T) {
|
||||||
|
a := &State{TagCount: 9, ActiveOutput: "eDP-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||||
|
b := &State{TagCount: 9, ActiveOutput: "HDMI-A-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_OutputCountDiffers(t *testing.T) {
|
||||||
|
a := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Outputs: map[string]*OutputState{"eDP-1": {}},
|
||||||
|
Layouts: []string{},
|
||||||
|
}
|
||||||
|
b := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Outputs: map[string]*OutputState{},
|
||||||
|
Layouts: []string{},
|
||||||
|
}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_OutputFieldsDiffer(t *testing.T) {
|
||||||
|
a := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Active: 1, Layout: 0, Title: "Firefox"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Active: 0, Layout: 0, Title: "Firefox"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
|
||||||
|
b.Outputs["eDP-1"].Active = 1
|
||||||
|
b.Outputs["eDP-1"].Layout = 1
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
|
||||||
|
b.Outputs["eDP-1"].Layout = 0
|
||||||
|
b.Outputs["eDP-1"].Title = "Code"
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_TagsDiffer(t *testing.T) {
|
||||||
|
a := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Tags: []TagState{{Tag: 1, State: 2, Clients: 2, Focused: 1}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
|
||||||
|
b.Outputs["eDP-1"].Tags[0].State = 1
|
||||||
|
b.Outputs["eDP-1"].Tags[0].Clients = 3
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_Equal(t *testing.T) {
|
||||||
|
a := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
ActiveOutput: "eDP-1",
|
||||||
|
Layouts: []string{"tile", "monocle"},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {
|
||||||
|
Name: "eDP-1",
|
||||||
|
Active: 1,
|
||||||
|
Layout: 0,
|
||||||
|
LayoutSymbol: "[]=",
|
||||||
|
Title: "Firefox",
|
||||||
|
AppID: "firefox",
|
||||||
|
KbLayout: "us",
|
||||||
|
Keymode: "",
|
||||||
|
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
ActiveOutput: "eDP-1",
|
||||||
|
Layouts: []string{"tile", "monocle"},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {
|
||||||
|
Name: "eDP-1",
|
||||||
|
Active: 1,
|
||||||
|
Layout: 0,
|
||||||
|
LayoutSymbol: "[]=",
|
||||||
|
Title: "Firefox",
|
||||||
|
AppID: "firefox",
|
||||||
|
KbLayout: "us",
|
||||||
|
Keymode: "",
|
||||||
|
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.False(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_ConcurrentGetState(t *testing.T) {
|
||||||
|
m := &Manager{
|
||||||
|
state: &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{"tile"},
|
||||||
|
Outputs: map[string]*OutputState{"eDP-1": {Name: "eDP-1"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
const goroutines = 50
|
||||||
|
const iterations = 100
|
||||||
|
|
||||||
|
for i := 0; i < goroutines/2; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
s := m.GetState()
|
||||||
|
_ = s.TagCount
|
||||||
|
_ = s.Outputs
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < goroutines/2; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
m.stateMutex.Lock()
|
||||||
|
m.state = &State{
|
||||||
|
TagCount: uint32(j % 10),
|
||||||
|
Layouts: []string{"tile", "monocle"},
|
||||||
|
Outputs: map[string]*OutputState{"eDP-1": {Active: uint32(j % 2)}},
|
||||||
|
}
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||||
|
m := &Manager{
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
dirty: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
const goroutines = 20
|
||||||
|
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(id int) {
|
||||||
|
defer wg.Done()
|
||||||
|
subID := string(rune('a' + id))
|
||||||
|
ch := m.Subscribe(subID)
|
||||||
|
assert.NotNil(t, ch)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
m.Unsubscribe(subID)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
|
||||||
|
m := &Manager{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
const goroutines = 30
|
||||||
|
const iterations = 50
|
||||||
|
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(id int) {
|
||||||
|
defer wg.Done()
|
||||||
|
key := uint32(id)
|
||||||
|
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
state := &outputState{
|
||||||
|
id: key,
|
||||||
|
name: "test-output",
|
||||||
|
active: uint32(j % 2),
|
||||||
|
tags: []TagState{{Tag: uint32(j), State: 1}},
|
||||||
|
}
|
||||||
|
m.outputs.Store(key, state)
|
||||||
|
|
||||||
|
if loaded, ok := m.outputs.Load(key); ok {
|
||||||
|
assert.Equal(t, key, loaded.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.outputs.Range(func(k uint32, v *outputState) bool {
|
||||||
|
_ = v.name
|
||||||
|
_ = v.active
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.outputs.Delete(key)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
||||||
|
m := &Manager{
|
||||||
|
dirty: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
m.notifySubscribers()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, m.dirty, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_PostQueueFull(t *testing.T) {
|
||||||
|
m := &Manager{
|
||||||
|
cmdq: make(chan cmd, 2),
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
m.post(func() {})
|
||||||
|
m.post(func() {})
|
||||||
|
m.post(func() {})
|
||||||
|
m.post(func() {})
|
||||||
|
|
||||||
|
assert.Len(t, m.cmdq, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_GetStateNilState(t *testing.T) {
|
||||||
|
m := &Manager{}
|
||||||
|
|
||||||
|
s := m.GetState()
|
||||||
|
assert.NotNil(t, s.Outputs)
|
||||||
|
assert.NotNil(t, s.Layouts)
|
||||||
|
assert.Equal(t, uint32(0), s.TagCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTagState_Fields(t *testing.T) {
|
||||||
|
tag := TagState{
|
||||||
|
Tag: 1,
|
||||||
|
State: 2,
|
||||||
|
Clients: 3,
|
||||||
|
Focused: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, uint32(1), tag.Tag)
|
||||||
|
assert.Equal(t, uint32(2), tag.State)
|
||||||
|
assert.Equal(t, uint32(3), tag.Clients)
|
||||||
|
assert.Equal(t, uint32(1), tag.Focused)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputState_Fields(t *testing.T) {
|
||||||
|
out := OutputState{
|
||||||
|
Name: "eDP-1",
|
||||||
|
Active: 1,
|
||||||
|
Tags: []TagState{{Tag: 1}},
|
||||||
|
Layout: 0,
|
||||||
|
LayoutSymbol: "[]=",
|
||||||
|
Title: "Firefox",
|
||||||
|
AppID: "firefox",
|
||||||
|
KbLayout: "us",
|
||||||
|
Keymode: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "eDP-1", out.Name)
|
||||||
|
assert.Equal(t, uint32(1), out.Active)
|
||||||
|
assert.Len(t, out.Tags, 1)
|
||||||
|
assert.Equal(t, "[]=", out.LayoutSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_NewOutputAppears(t *testing.T) {
|
||||||
|
a := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Name: "eDP-1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Name: "eDP-1"},
|
||||||
|
"HDMI-A-1": {Name: "HDMI-A-1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateChanged_TagsLengthDiffers(t *testing.T) {
|
||||||
|
a := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Tags: []TagState{{Tag: 1}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &State{
|
||||||
|
TagCount: 9,
|
||||||
|
Layouts: []string{},
|
||||||
|
Outputs: map[string]*OutputState{
|
||||||
|
"eDP-1": {Tags: []TagState{{Tag: 1}, {Tag: 2}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(t, stateChanged(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewManager_GetRegistryError(t *testing.T) {
|
||||||
|
mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
|
||||||
|
|
||||||
|
mockDisplay.EXPECT().Context().Return(nil)
|
||||||
|
mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
|
||||||
|
|
||||||
|
_, err := NewManager(mockDisplay)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "failed to get registry")
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package dwl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TagState struct {
|
||||||
|
Tag uint32 `json:"tag"`
|
||||||
|
State uint32 `json:"state"`
|
||||||
|
Clients uint32 `json:"clients"`
|
||||||
|
Focused uint32 `json:"focused"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputState struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Active uint32 `json:"active"`
|
||||||
|
Tags []TagState `json:"tags"`
|
||||||
|
Layout uint32 `json:"layout"`
|
||||||
|
LayoutSymbol string `json:"layoutSymbol"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
AppID string `json:"appId"`
|
||||||
|
KbLayout string `json:"kbLayout"`
|
||||||
|
Keymode string `json:"keymode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Outputs map[string]*OutputState `json:"outputs"`
|
||||||
|
TagCount uint32 `json:"tagCount"`
|
||||||
|
Layouts []string `json:"layouts"`
|
||||||
|
ActiveOutput string `json:"activeOutput"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmd struct {
|
||||||
|
fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
display wlclient.WaylandDisplay
|
||||||
|
ctx *wlclient.Context
|
||||||
|
registry *wlclient.Registry
|
||||||
|
manager any
|
||||||
|
|
||||||
|
outputs syncmap.Map[uint32, *outputState]
|
||||||
|
|
||||||
|
tagCount uint32
|
||||||
|
layouts []string
|
||||||
|
|
||||||
|
wlMutex sync.Mutex
|
||||||
|
cmdq chan cmd
|
||||||
|
outputSetupReq chan uint32
|
||||||
|
stopChan chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
subscribers syncmap.Map[string, chan State]
|
||||||
|
dirty chan struct{}
|
||||||
|
notifierWg sync.WaitGroup
|
||||||
|
lastNotified *State
|
||||||
|
|
||||||
|
stateMutex sync.RWMutex
|
||||||
|
state *State
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputState struct {
|
||||||
|
id uint32
|
||||||
|
registryName uint32
|
||||||
|
output *wlclient.Output
|
||||||
|
ipcOutput any
|
||||||
|
name string
|
||||||
|
active uint32
|
||||||
|
tags []TagState
|
||||||
|
layout uint32
|
||||||
|
layoutSymbol string
|
||||||
|
title string
|
||||||
|
appID string
|
||||||
|
kbLayout string
|
||||||
|
keymode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetState() State {
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
defer m.stateMutex.RUnlock()
|
||||||
|
if m.state == nil {
|
||||||
|
return State{
|
||||||
|
Outputs: make(map[string]*OutputState),
|
||||||
|
Layouts: []string{},
|
||||||
|
TagCount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stateCopy := *m.state
|
||||||
|
return stateCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
|
ch := make(chan State, 64)
|
||||||
|
|
||||||
|
m.subscribers.Store(id, ch)
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
|
close(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) notifySubscribers() {
|
||||||
|
select {
|
||||||
|
case m.dirty <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateChanged(old, new *State) bool {
|
||||||
|
if old == nil || new == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if old.TagCount != new.TagCount {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(old.Layouts) != len(new.Layouts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if old.ActiveOutput != new.ActiveOutput {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(old.Outputs) != len(new.Outputs) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, newOut := range new.Outputs {
|
||||||
|
oldOut, exists := old.Outputs[name]
|
||||||
|
if !exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.Active != newOut.Active {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.Layout != newOut.Layout {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.LayoutSymbol != newOut.LayoutSymbol {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.Title != newOut.Title {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.AppID != newOut.AppID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.KbLayout != newOut.KbLayout {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.Keymode != newOut.Keymode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(oldOut.Tags) != len(newOut.Tags) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for i, newTag := range newOut.Tags {
|
||||||
|
if i >= len(oldOut.Tags) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
oldTag := oldOut.Tags[i]
|
||||||
|
if oldTag.Tag != newTag.Tag || oldTag.State != newTag.State ||
|
||||||
|
oldTag.Clients != newTag.Clients || oldTag.Focused != newTag.Focused {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
||||||
@@ -124,6 +125,15 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(req.Method, "dwl.") {
|
||||||
|
if dwlManager == nil {
|
||||||
|
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dwl.HandleRequest(conn, req, dwlManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "brightness.") {
|
if strings.HasPrefix(req.Method, "brightness.") {
|
||||||
if brightnessManager == nil {
|
if brightnessManager == nil {
|
||||||
models.RespondError(conn, req.ID, "brightness manager not initialized")
|
models.RespondError(conn, req.ID, "brightness manager not initialized")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/location"
|
||||||
@@ -38,7 +39,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIVersion = 25
|
const APIVersion = 24
|
||||||
|
|
||||||
var CLIVersion = "dev"
|
var CLIVersion = "dev"
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ var bluezManager *bluez.Manager
|
|||||||
var appPickerManager *apppicker.Manager
|
var appPickerManager *apppicker.Manager
|
||||||
var cupsManager *cups.Manager
|
var cupsManager *cups.Manager
|
||||||
var tailscaleManager *tailscale.Manager
|
var tailscaleManager *tailscale.Manager
|
||||||
|
var dwlManager *dwl.Manager
|
||||||
var brightnessManager *brightness.Manager
|
var brightnessManager *brightness.Manager
|
||||||
var wlrOutputManager *wlroutput.Manager
|
var wlrOutputManager *wlroutput.Manager
|
||||||
var evdevManager *evdev.Manager
|
var evdevManager *evdev.Manager
|
||||||
@@ -250,6 +252,30 @@ func InitializeCupsManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitializeDwlManager() error {
|
||||||
|
log.Info("Attempting to initialize DWL IPC...")
|
||||||
|
|
||||||
|
if wlContext == nil {
|
||||||
|
ctx, err := wlcontext.New()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create shared Wayland context: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wlContext = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, err := dwl.NewManager(wlContext.Display())
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to initialize dwl manager: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dwlManager = manager
|
||||||
|
|
||||||
|
log.Info("DWL IPC initialized successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func InitializeBrightnessManager() error {
|
func InitializeBrightnessManager() error {
|
||||||
manager, err := brightness.NewManager()
|
manager, err := brightness.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -442,6 +468,10 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "tailscale")
|
caps = append(caps, "tailscale")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dwlManager != nil {
|
||||||
|
caps = append(caps, "dwl")
|
||||||
|
}
|
||||||
|
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
caps = append(caps, "brightness")
|
caps = append(caps, "brightness")
|
||||||
}
|
}
|
||||||
@@ -508,6 +538,10 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "tailscale")
|
caps = append(caps, "tailscale")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dwlManager != nil {
|
||||||
|
caps = append(caps, "dwl")
|
||||||
|
}
|
||||||
|
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
caps = append(caps, "brightness")
|
caps = append(caps, "brightness")
|
||||||
}
|
}
|
||||||
@@ -1012,6 +1046,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldSubscribe("dwl") && dwlManager != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
dwlChan := dwlManager.Subscribe(clientID + "-dwl")
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer dwlManager.Unsubscribe(clientID + "-dwl")
|
||||||
|
|
||||||
|
initialState := dwlManager.GetState()
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "dwl", Data: initialState}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case state, ok := <-dwlChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "dwl", Data: state}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if shouldSubscribe("brightness") && brightnessManager != nil {
|
if shouldSubscribe("brightness") && brightnessManager != nil {
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state")
|
brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state")
|
||||||
@@ -1267,6 +1333,9 @@ func cleanupManagers() {
|
|||||||
if cupsManager != nil {
|
if cupsManager != nil {
|
||||||
cupsManager.Close()
|
cupsManager.Close()
|
||||||
}
|
}
|
||||||
|
if dwlManager != nil {
|
||||||
|
dwlManager.Close()
|
||||||
|
}
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
brightnessManager.Close()
|
brightnessManager.Close()
|
||||||
}
|
}
|
||||||
@@ -1433,6 +1502,19 @@ func Start(printDocs bool) error {
|
|||||||
log.Info(" cups.resumePrinter - Resume printer (params: printerName)")
|
log.Info(" cups.resumePrinter - Resume printer (params: printerName)")
|
||||||
log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)")
|
log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)")
|
||||||
log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)")
|
log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)")
|
||||||
|
log.Info("DWL:")
|
||||||
|
log.Info(" dwl.getState - Get current dwl state (tags, windows, layouts, keyboard)")
|
||||||
|
log.Info(" dwl.setTags - Set active tags (params: output, tagmask, toggleTagset)")
|
||||||
|
log.Info(" dwl.setClientTags - Set focused client tags (params: output, andTags, xorTags)")
|
||||||
|
log.Info(" dwl.setLayout - Set layout (params: output, index)")
|
||||||
|
log.Info(" dwl.subscribe - Subscribe to dwl state changes (streaming)")
|
||||||
|
log.Info(" Output state includes:")
|
||||||
|
log.Info(" - tags : Tag states (active, clients, focused)")
|
||||||
|
log.Info(" - layoutSymbol : Current layout name")
|
||||||
|
log.Info(" - title : Focused window title")
|
||||||
|
log.Info(" - appId : Focused window app ID")
|
||||||
|
log.Info(" - kbLayout : Current keyboard layout")
|
||||||
|
log.Info(" - keymode : Current keybind mode")
|
||||||
log.Info("Brightness:")
|
log.Info("Brightness:")
|
||||||
log.Info(" brightness.getState - Get current brightness state for all devices")
|
log.Info(" brightness.getState - Get current brightness state for all devices")
|
||||||
log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)")
|
log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)")
|
||||||
@@ -1609,6 +1691,10 @@ func Start(printDocs bool) error {
|
|||||||
log.Debugf("AppPicker manager unavailable: %v", err)
|
log.Debugf("AppPicker manager unavailable: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := InitializeDwlManager(); err != nil {
|
||||||
|
log.Debugf("DWL manager unavailable: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := InitializeWlrOutputManager(); err != nil {
|
if err := InitializeWlrOutputManager(); err != nil {
|
||||||
log.Debugf("WlrOutput manager unavailable: %v", err)
|
log.Debugf("WlrOutput manager unavailable: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ Singleton {
|
|||||||
}, descriptor);
|
}, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function legacySurfaceState(screenName, kind) {
|
||||||
|
return SurfaceDescriptor.toLegacyState(surfaceDescriptor(screenName, kind));
|
||||||
|
}
|
||||||
|
|
||||||
function hasSurfaceDescriptor(screenName, kind, ownerId) {
|
function hasSurfaceDescriptor(screenName, kind, ownerId) {
|
||||||
const descriptor = surfaceDescriptor(screenName, kind);
|
const descriptor = surfaceDescriptor(screenName, kind);
|
||||||
return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId);
|
return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId);
|
||||||
@@ -120,6 +124,20 @@ Singleton {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _setSurfaceAnimation(screenName, kind, ownerId, x, y) {
|
||||||
|
const current = surfaceDescriptor(screenName, kind);
|
||||||
|
if (current.phase === "hidden" || (ownerId && current.ownerId !== ownerId))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setSurfaceBody(screenName, kind, ownerId, x, y, width, height) {
|
||||||
|
const current = surfaceDescriptor(screenName, kind);
|
||||||
|
if (current.phase === "hidden" || (ownerId && current.ownerId !== ownerId))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var emptyDockState: ({
|
readonly property var emptyDockState: ({
|
||||||
"reveal": false,
|
"reveal": false,
|
||||||
"barSide": "bottom",
|
"barSide": "bottom",
|
||||||
@@ -131,6 +149,7 @@ Singleton {
|
|||||||
"slideY": 0
|
"slideY": 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Popout state (updated by DankPopout when connectedFrameModeActive)
|
||||||
property string popoutOwnerId: ""
|
property string popoutOwnerId: ""
|
||||||
property bool popoutVisible: false
|
property bool popoutVisible: false
|
||||||
property string popoutBarSide: "top"
|
property string popoutBarSide: "top"
|
||||||
@@ -144,10 +163,14 @@ Singleton {
|
|||||||
property bool popoutOmitStartConnector: false
|
property bool popoutOmitStartConnector: false
|
||||||
property bool popoutOmitEndConnector: false
|
property bool popoutOmitEndConnector: false
|
||||||
|
|
||||||
|
// Dock state (updated by Dock when connectedFrameModeActive), keyed by screen.name
|
||||||
property var dockStates: ({})
|
property var dockStates: ({})
|
||||||
|
|
||||||
|
// Dock slide offsets — hot-path updates separated from full geometry state
|
||||||
property var dockSlides: ({})
|
property var dockSlides: ({})
|
||||||
|
|
||||||
|
// Surfaces are keyed by screen.name. FrameWindow watches to refresh connected chrome
|
||||||
|
// after claim/release boundaries without tracking each animation frame
|
||||||
property var surfaceRevisions: ({})
|
property var surfaceRevisions: ({})
|
||||||
|
|
||||||
function _cloneDict(src) {
|
function _cloneDict(src) {
|
||||||
@@ -266,6 +289,7 @@ Singleton {
|
|||||||
if (!isNaN(nextY) && popoutAnimY !== nextY)
|
if (!isNaN(nextY) && popoutAnimY !== nextY)
|
||||||
popoutAnimY = nextY;
|
popoutAnimY = nextY;
|
||||||
}
|
}
|
||||||
|
_setSurfaceAnimation(popoutScreen, "popout", claimId, animX, animY);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +316,7 @@ Singleton {
|
|||||||
if (!isNaN(nextH) && popoutBodyH !== nextH)
|
if (!isNaN(nextH) && popoutBodyH !== nextH)
|
||||||
popoutBodyH = nextH;
|
popoutBodyH = nextH;
|
||||||
}
|
}
|
||||||
|
_setSurfaceBody(popoutScreen, "popout", claimId, bodyX, bodyY, bodyW, bodyH);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,8 +352,8 @@ Singleton {
|
|||||||
"phase": normalized.reveal ? (state.phase || "open") : "hidden"
|
"phase": normalized.reveal ? (state.phase || "open") : "hidden"
|
||||||
});
|
});
|
||||||
const previous = dockStates[screenName] || emptyDockState;
|
const previous = dockStates[screenName] || emptyDockState;
|
||||||
const stateChanged = !_sameDockState(dockStates[screenName], normalized);
|
const legacyChanged = !_sameDockState(dockStates[screenName], normalized);
|
||||||
if (stateChanged) {
|
if (legacyChanged) {
|
||||||
const next = _cloneDict(dockStates);
|
const next = _cloneDict(dockStates);
|
||||||
next[screenName] = normalized;
|
next[screenName] = normalized;
|
||||||
dockStates = next;
|
dockStates = next;
|
||||||
@@ -348,6 +373,7 @@ Singleton {
|
|||||||
dockStates = next;
|
dockStates = next;
|
||||||
_clearSurfaceDescriptor(screenName, "dock");
|
_clearSurfaceDescriptor(screenName, "dock");
|
||||||
|
|
||||||
|
// Also clear corresponding slide
|
||||||
if (dockSlides[screenName]) {
|
if (dockSlides[screenName]) {
|
||||||
const nextSlides = _cloneDict(dockSlides);
|
const nextSlides = _cloneDict(dockSlides);
|
||||||
delete nextSlides[screenName];
|
delete nextSlides[screenName];
|
||||||
@@ -371,6 +397,7 @@ Singleton {
|
|||||||
"y": numY
|
"y": numY
|
||||||
};
|
};
|
||||||
dockSlides = next;
|
dockSlides = next;
|
||||||
|
_setSurfaceAnimation(screenName, "dock", "dock:" + screenName, numX, numY);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,8 +451,8 @@ Singleton {
|
|||||||
"phase": normalized.visible ? (state.phase || "open") : "hidden"
|
"phase": normalized.visible ? (state.phase || "open") : "hidden"
|
||||||
});
|
});
|
||||||
const previous = notificationStates[screenName] || emptyNotificationState;
|
const previous = notificationStates[screenName] || emptyNotificationState;
|
||||||
const stateChanged = !_sameNotificationState(notificationStates[screenName], normalized);
|
const legacyChanged = !_sameNotificationState(notificationStates[screenName], normalized);
|
||||||
if (stateChanged) {
|
if (legacyChanged) {
|
||||||
const next = _cloneDict(notificationStates);
|
const next = _cloneDict(notificationStates);
|
||||||
next[screenName] = normalized;
|
next[screenName] = normalized;
|
||||||
notificationStates = next;
|
notificationStates = next;
|
||||||
@@ -448,6 +475,7 @@ Singleton {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DankModal / DankLauncherV2Modal State
|
||||||
readonly property var emptyModalState: ({
|
readonly property var emptyModalState: ({
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"barSide": "bottom",
|
"barSide": "bottom",
|
||||||
@@ -545,6 +573,10 @@ Singleton {
|
|||||||
return updateModalState(screenName, state, ownerId);
|
return updateModalState(screenName, state, ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setModalState(screenName, state) {
|
||||||
|
return updateModalState(screenName, state, null);
|
||||||
|
}
|
||||||
|
|
||||||
function clearModalState(screenName, ownerId) {
|
function clearModalState(screenName, ownerId) {
|
||||||
if (!screenName)
|
if (!screenName)
|
||||||
return false;
|
return false;
|
||||||
@@ -585,6 +617,7 @@ Singleton {
|
|||||||
"animY": nay
|
"animY": nay
|
||||||
});
|
});
|
||||||
modalStates = next;
|
modalStates = next;
|
||||||
|
_setSurfaceAnimation(screenName, "modal", ownerId, animX, animY);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,6 +641,7 @@ Singleton {
|
|||||||
"bodyH": nh
|
"bodyH": nh
|
||||||
});
|
});
|
||||||
modalStates = next;
|
modalStates = next;
|
||||||
|
_setSurfaceBody(screenName, "modal", ownerId, bodyX, bodyY, bodyW, bodyH);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,6 +682,9 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prune state for screens that are no longer connected. Stale entries
|
||||||
|
// accumulate across hotplug cycles otherwise — Frame's per-screen
|
||||||
|
// FrameInstance doesn't notice when its peer dicts go orphan.
|
||||||
function _pruneToLiveScreens() {
|
function _pruneToLiveScreens() {
|
||||||
const live = {};
|
const live = {};
|
||||||
const screens = Quickshell.screens || [];
|
const screens = Quickshell.screens || [];
|
||||||
|
|||||||
@@ -157,3 +157,23 @@ function same(a, b, threshold) {
|
|||||||
&& a.omitEndConnector === b.omitEndConnector
|
&& a.omitEndConnector === b.omitEndConnector
|
||||||
&& a.dockRetractSide === b.dockRetractSide;
|
&& a.dockRetractSide === b.dockRetractSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toLegacyState(descriptor) {
|
||||||
|
var d = normalize(descriptor);
|
||||||
|
return {
|
||||||
|
"visible": d.visible,
|
||||||
|
"reveal": d.visible,
|
||||||
|
"barSide": d.barSide,
|
||||||
|
"bodyX": d.bodyRect.x,
|
||||||
|
"bodyY": d.bodyRect.y,
|
||||||
|
"bodyW": d.bodyRect.width,
|
||||||
|
"bodyH": d.bodyRect.height,
|
||||||
|
"animX": d.animationOffset.x,
|
||||||
|
"animY": d.animationOffset.y,
|
||||||
|
"slideX": d.animationOffset.x,
|
||||||
|
"slideY": d.animationOffset.y,
|
||||||
|
"screen": d.screenName,
|
||||||
|
"omitStartConnector": d.omitStartConnector,
|
||||||
|
"omitEndConnector": d.omitEndConnector
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Item {
|
|||||||
property color borderColor: "transparent"
|
property color borderColor: "transparent"
|
||||||
property real borderWidth: 0
|
property real borderWidth: 0
|
||||||
|
|
||||||
|
// Rounded-rect geometry within the item; defaults fill the item.
|
||||||
property real sourceX: 0
|
property real sourceX: 0
|
||||||
property real sourceY: 0
|
property real sourceY: 0
|
||||||
property real sourceWidth: width
|
property real sourceWidth: width
|
||||||
@@ -35,6 +36,8 @@ Item {
|
|||||||
readonly property var _ambient: Theme.elevationAmbient(level)
|
readonly property var _ambient: Theme.elevationAmbient(level)
|
||||||
readonly property real _pad: shadowEnabled ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), _ambient.blurPx + _ambient.spreadPx) + 2) : 0
|
readonly property real _pad: shadowEnabled ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), _ambient.blurPx + _ambient.spreadPx) + 2) : 0
|
||||||
|
|
||||||
|
// Fill + border + key/ambient shadows drawn analytically on one oversized
|
||||||
|
// quad — no FBO, no blur passes.
|
||||||
ShaderEffect {
|
ShaderEffect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -root._pad
|
anchors.margins: -root._pad
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -17,76 +16,8 @@ Singleton {
|
|||||||
signal popoutOpening
|
signal popoutOpening
|
||||||
signal popoutChanged
|
signal popoutChanged
|
||||||
|
|
||||||
property real hoverCursorGlobalX: 0
|
|
||||||
property real hoverCursorGlobalY: 0
|
|
||||||
|
|
||||||
function updateHoverCursor(gx, gy) {
|
|
||||||
hoverCursorGlobalX = gx;
|
|
||||||
hoverCursorGlobalY = gy;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cursorOverBar(gx, gy, padding) {
|
|
||||||
const pad = padding !== undefined ? padding : 16;
|
|
||||||
const bars = KeyboardFocus.barWindows || [];
|
|
||||||
for (let i = 0; i < bars.length; i++) {
|
|
||||||
const w = bars[i];
|
|
||||||
if (!w?.visible)
|
|
||||||
continue;
|
|
||||||
if (typeof w.containsGlobalPoint === "function") {
|
|
||||||
if (w.containsGlobalPoint(gx, gy, pad))
|
|
||||||
return true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const item = w.contentItem;
|
|
||||||
if (!item || typeof item.mapToItem !== "function")
|
|
||||||
continue;
|
|
||||||
const topLeft = item.mapToItem(null, 0, 0);
|
|
||||||
if (!topLeft)
|
|
||||||
continue;
|
|
||||||
if (gx >= topLeft.x - pad && gx < topLeft.x + item.width + pad && gy >= topLeft.y - pad && gy < topLeft.y + item.height + pad)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _isPopoutPresented(popout) {
|
|
||||||
if (!popout)
|
|
||||||
return false;
|
|
||||||
try {
|
|
||||||
if (popout.dashVisible !== undefined)
|
|
||||||
return !!popout.dashVisible;
|
|
||||||
if (popout.notificationHistoryVisible !== undefined)
|
|
||||||
return !!popout.notificationHistoryVisible;
|
|
||||||
return !!(popout.shouldBeVisible || popout.isClosing);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _openPopout(popout) {
|
|
||||||
if (popout.dashVisible !== undefined) {
|
|
||||||
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing)
|
|
||||||
popout.dashVisible = false;
|
|
||||||
popout.dashVisible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (popout.notificationHistoryVisible !== undefined) {
|
|
||||||
popout.notificationHistoryVisible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
popout.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function _closePopout(popout) {
|
function _closePopout(popout) {
|
||||||
try {
|
try {
|
||||||
if (popout?.hoverDismissEnabled) {
|
|
||||||
if (typeof popout.closeFromHoverDismiss === "function") {
|
|
||||||
popout.closeFromHoverDismiss();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
|
||||||
popout.hoverDismissEnabled = false;
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case popout.dashVisible !== undefined:
|
case popout.dashVisible !== undefined:
|
||||||
popout.dashVisible = false;
|
popout.dashVisible = false;
|
||||||
@@ -158,26 +89,7 @@ Singleton {
|
|||||||
continue;
|
continue;
|
||||||
_closePopout(popout);
|
_closePopout(popout);
|
||||||
}
|
}
|
||||||
// Keep map entries until each popout's close animation finishes (hidePopout).
|
currentPopoutsByScreen = {};
|
||||||
}
|
|
||||||
|
|
||||||
function closePopoutForScreen(screen) {
|
|
||||||
if (!screen)
|
|
||||||
return;
|
|
||||||
const screenName = screen.name;
|
|
||||||
const popout = currentPopoutsByScreen[screenName];
|
|
||||||
if (!popout || _isStale(popout)) {
|
|
||||||
currentPopoutsByScreen[screenName] = null;
|
|
||||||
currentPopoutTriggers[screenName] = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_closePopout(popout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelHoverDismiss(screen) {
|
|
||||||
const popout = getActivePopout(screen);
|
|
||||||
if (popout?.cancelHoverDismiss)
|
|
||||||
popout.cancelHoverDismiss();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActivePopout(screen) {
|
function getActivePopout(screen) {
|
||||||
@@ -194,8 +106,6 @@ Singleton {
|
|||||||
function requestPopout(popout, tabIndex, triggerSource) {
|
function requestPopout(popout, tabIndex, triggerSource) {
|
||||||
if (!popout || !popout.screen)
|
if (!popout || !popout.screen)
|
||||||
return;
|
return;
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
|
||||||
popout.hoverDismissEnabled = false;
|
|
||||||
screenshotActive = false;
|
screenshotActive = false;
|
||||||
const screenName = popout.screen.name;
|
const screenName = popout.screen.name;
|
||||||
const currentPopout = currentPopoutsByScreen[screenName];
|
const currentPopout = currentPopoutsByScreen[screenName];
|
||||||
@@ -271,81 +181,16 @@ Singleton {
|
|||||||
ModalManager.closeAllModalsExcept(null);
|
ModalManager.closeAllModalsExcept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_openPopout(popout);
|
if (movedFromOtherScreen) {
|
||||||
}
|
popout.open();
|
||||||
|
} else {
|
||||||
function requestHoverPopout(popout, tabIndex, triggerSource) {
|
if (popout.dashVisible !== undefined) {
|
||||||
if (!popout || !popout.screen)
|
popout.dashVisible = true;
|
||||||
return;
|
} else if (popout.notificationHistoryVisible !== undefined) {
|
||||||
screenshotActive = false;
|
popout.notificationHistoryVisible = true;
|
||||||
const screenName = popout.screen.name;
|
|
||||||
const currentPopout = currentPopoutsByScreen[screenName];
|
|
||||||
const triggerId = triggerSource !== undefined ? triggerSource : tabIndex;
|
|
||||||
|
|
||||||
const willOpen = !(currentPopout === popout && _isPopoutPresented(popout) && triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId);
|
|
||||||
if (willOpen)
|
|
||||||
popoutOpening();
|
|
||||||
|
|
||||||
let movedFromOtherScreen = false;
|
|
||||||
for (const otherScreenName in currentPopoutsByScreen) {
|
|
||||||
if (otherScreenName === screenName)
|
|
||||||
continue;
|
|
||||||
const otherPopout = currentPopoutsByScreen[otherScreenName];
|
|
||||||
if (!otherPopout)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (_isStale(otherPopout)) {
|
|
||||||
currentPopoutsByScreen[otherScreenName] = null;
|
|
||||||
currentPopoutTriggers[otherScreenName] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherPopout === popout) {
|
|
||||||
movedFromOtherScreen = true;
|
|
||||||
currentPopoutsByScreen[otherScreenName] = null;
|
|
||||||
currentPopoutTriggers[otherScreenName] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_closePopout(otherPopout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPopout && currentPopout !== popout) {
|
|
||||||
if (_isStale(currentPopout)) {
|
|
||||||
currentPopoutsByScreen[screenName] = null;
|
|
||||||
currentPopoutTriggers[screenName] = null;
|
|
||||||
} else {
|
} else {
|
||||||
_closePopout(currentPopout);
|
popout.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPopout === popout && _isPopoutPresented(popout) && !movedFromOtherScreen) {
|
|
||||||
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined)
|
|
||||||
popout.currentTabIndex = tabIndex;
|
|
||||||
if (popout.updateSurfacePosition)
|
|
||||||
popout.updateSurfacePosition();
|
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
|
||||||
popout.hoverDismissEnabled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
|
||||||
currentPopoutsByScreen[screenName] = popout;
|
|
||||||
popoutChanged();
|
|
||||||
|
|
||||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined)
|
|
||||||
popout.currentTabIndex = tabIndex;
|
|
||||||
|
|
||||||
if (currentPopout !== popout)
|
|
||||||
ModalManager.closeAllModalsExcept(null);
|
|
||||||
|
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
|
||||||
popout.hoverDismissEnabled = true;
|
|
||||||
|
|
||||||
_openPopout(popout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property bool clipboardEnterToPaste: false
|
property bool clipboardEnterToPaste: false
|
||||||
property var clipboardVisibleEntryActions: ["pin", "edit", "delete"]
|
|
||||||
|
|
||||||
property var launcherPluginVisibility: ({})
|
property var launcherPluginVisibility: ({})
|
||||||
|
|
||||||
@@ -490,6 +489,9 @@ Singleton {
|
|||||||
"hideOnTouch": false,
|
"hideOnTouch": false,
|
||||||
"inactiveTimeout": 0
|
"inactiveTimeout": 0
|
||||||
},
|
},
|
||||||
|
"dwl": {
|
||||||
|
"cursorHideTimeout": 0
|
||||||
|
},
|
||||||
"mango": {
|
"mango": {
|
||||||
"cursorHideTimeout": 0
|
"cursorHideTimeout": 0
|
||||||
}
|
}
|
||||||
@@ -516,8 +518,6 @@ Singleton {
|
|||||||
property bool notepadUseMonospace: true
|
property bool notepadUseMonospace: true
|
||||||
property string notepadFontFamily: ""
|
property string notepadFontFamily: ""
|
||||||
property real notepadFontSize: 14
|
property real notepadFontSize: 14
|
||||||
property real notificationSummaryFontSize: Spec.SPEC.notificationSummaryFontSize.def
|
|
||||||
property real notificationBodyFontSize: Spec.SPEC.notificationBodyFontSize.def
|
|
||||||
property bool notepadShowLineNumbers: false
|
property bool notepadShowLineNumbers: false
|
||||||
property real notepadTransparencyOverride: -1
|
property real notepadTransparencyOverride: -1
|
||||||
property real notepadLastCustomTransparency: 0.7
|
property real notepadLastCustomTransparency: 0.7
|
||||||
@@ -698,7 +698,6 @@ Singleton {
|
|||||||
property int notificationTimeoutNormal: 5000
|
property int notificationTimeoutNormal: 5000
|
||||||
property int notificationTimeoutCritical: 0
|
property int notificationTimeoutCritical: 0
|
||||||
property bool notificationCompactMode: false
|
property bool notificationCompactMode: false
|
||||||
property bool notificationShowTimeoutBar: false
|
|
||||||
property bool notificationDedupeEnabled: true
|
property bool notificationDedupeEnabled: true
|
||||||
property int notificationPopupPosition: SettingsData.Position.Top
|
property int notificationPopupPosition: SettingsData.Position.Top
|
||||||
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
|
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
|
||||||
@@ -810,8 +809,7 @@ Singleton {
|
|||||||
"shadowOpacity": 60,
|
"shadowOpacity": 60,
|
||||||
"shadowColorMode": "default",
|
"shadowColorMode": "default",
|
||||||
"shadowCustomColor": "#000000",
|
"shadowCustomColor": "#000000",
|
||||||
"clickThrough": false,
|
"clickThrough": false
|
||||||
"hoverPopouts": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1226,6 +1224,8 @@ Singleton {
|
|||||||
NiriService.generateNiriLayoutConfig();
|
NiriService.generateNiriLayoutConfig();
|
||||||
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
|
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
|
||||||
HyprlandService.generateLayoutConfig();
|
HyprlandService.generateLayoutConfig();
|
||||||
|
if (CompositorService.isDwl && typeof DwlService !== "undefined")
|
||||||
|
DwlService.generateLayoutConfig();
|
||||||
if (CompositorService.isMango && typeof MangoService !== "undefined")
|
if (CompositorService.isMango && typeof MangoService !== "undefined")
|
||||||
MangoService.generateLayoutConfig();
|
MangoService.generateLayoutConfig();
|
||||||
}
|
}
|
||||||
@@ -1652,15 +1652,6 @@ Singleton {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function effectiveBarConfigForRender(config, usesFrameBarChrome) {
|
|
||||||
if (!config || !connectedFrameModeActive || usesFrameBarChrome)
|
|
||||||
return config;
|
|
||||||
const backup = connectedFrameBarStyleBackups[config.id];
|
|
||||||
if (!backup)
|
|
||||||
return config;
|
|
||||||
return Object.assign({}, config, backup);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single entry point for connected-mode settings state.
|
// Single entry point for connected-mode settings state.
|
||||||
// !active → restore backups
|
// !active → restore backups
|
||||||
function _reconcileConnectedFrameBarStyles() {
|
function _reconcileConnectedFrameBarStyles() {
|
||||||
@@ -2460,6 +2451,10 @@ Singleton {
|
|||||||
HyprlandService.generateCursorConfig();
|
HyprlandService.generateCursorConfig();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (CompositorService.isDwl && typeof DwlService !== "undefined") {
|
||||||
|
DwlService.generateCursorConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (CompositorService.isMango && typeof MangoService !== "undefined") {
|
if (CompositorService.isMango && typeof MangoService !== "undefined") {
|
||||||
MangoService.generateCursorConfig();
|
MangoService.generateCursorConfig();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -911,6 +911,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
return Qt.rgba(r, g, b, alpha);
|
return Qt.rgba(r, g, b, alpha);
|
||||||
}
|
}
|
||||||
|
// Non-directional ambient layer of the M3 two-part shadow model (key +
|
||||||
|
// ambient). Derived from the key level so user intensity/opacity settings
|
||||||
|
// scale both layers together.
|
||||||
function elevationAmbient(level) {
|
function elevationAmbient(level) {
|
||||||
const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0;
|
const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0;
|
||||||
const alpha = ((level && level.alpha !== undefined) ? level.alpha : 0.3) * 0.5;
|
const alpha = ((level && level.alpha !== undefined) ? level.alpha : 0.3) * 0.5;
|
||||||
|
|||||||
@@ -570,7 +570,7 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
|
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showWarning(enabling ? I18n.tr("Applying auto-login on startup...") : I18n.tr("Disabling auto-login on startup..."), "", "dms greeter sync --autologin", "greeter-autologin-sync");
|
ToastService.showWarning(enabling ? I18n.tr("Applying auto-login on startup…") : I18n.tr("Disabling auto-login on startup…"), "", "dms greeter sync --autologin", "greeter-autologin-sync");
|
||||||
root.greeterAutoLoginSyncProcess.running = true;
|
root.greeterAutoLoginSyncProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -645,7 +645,7 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const err = (root.authApplySudoProbeStderr || "").trim();
|
const err = (root.authApplySudoProbeStderr || "").trim();
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showInfo(I18n.tr("Applying authentication changes..."), "", "", "auth-sync");
|
ToastService.showInfo(I18n.tr("Applying authentication changes…"), "", "", "auth-sync");
|
||||||
root.authApplyProcess.running = true;
|
root.authApplyProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,8 +260,6 @@ var SPEC = {
|
|||||||
notepadUseMonospace: { def: true },
|
notepadUseMonospace: { def: true },
|
||||||
notepadFontFamily: { def: "" },
|
notepadFontFamily: { def: "" },
|
||||||
notepadFontSize: { def: 14 },
|
notepadFontSize: { def: 14 },
|
||||||
notificationSummaryFontSize: { def: 0 },
|
|
||||||
notificationBodyFontSize: { def: 0 },
|
|
||||||
notepadShowLineNumbers: { def: false },
|
notepadShowLineNumbers: { def: false },
|
||||||
notepadTransparencyOverride: { def: -1 },
|
notepadTransparencyOverride: { def: -1 },
|
||||||
notepadLastCustomTransparency: { def: 0.7 },
|
notepadLastCustomTransparency: { def: 0.7 },
|
||||||
@@ -408,7 +406,6 @@ var SPEC = {
|
|||||||
notificationTimeoutNormal: { def: 5000 },
|
notificationTimeoutNormal: { def: 5000 },
|
||||||
notificationTimeoutCritical: { def: 0 },
|
notificationTimeoutCritical: { def: 0 },
|
||||||
notificationCompactMode: { def: false },
|
notificationCompactMode: { def: false },
|
||||||
notificationShowTimeoutBar: { def: false },
|
|
||||||
notificationDedupeEnabled: { def: true },
|
notificationDedupeEnabled: { def: true },
|
||||||
notificationPopupPosition: { def: 0 },
|
notificationPopupPosition: { def: 0 },
|
||||||
notificationAnimationSpeed: { def: 1 },
|
notificationAnimationSpeed: { def: 1 },
|
||||||
@@ -572,7 +569,6 @@ var SPEC = {
|
|||||||
|
|
||||||
builtInPluginSettings: { def: {} },
|
builtInPluginSettings: { def: {} },
|
||||||
clipboardEnterToPaste: { def: false },
|
clipboardEnterToPaste: { def: false },
|
||||||
clipboardVisibleEntryActions: { def: ["pin", "edit", "delete"] },
|
|
||||||
|
|
||||||
launcherPluginVisibility: { def: {} },
|
launcherPluginVisibility: { def: {} },
|
||||||
launcherPluginOrder: { def: [] },
|
launcherPluginOrder: { def: [] },
|
||||||
|
|||||||
+19
-2
@@ -64,15 +64,27 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool wallpaperSurfacesLoaded: true
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: blurredWallpaperBackgroundLoader
|
id: blurredWallpaperBackgroundLoader
|
||||||
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
|
active: root.wallpaperSurfacesLoaded && SettingsData.blurredWallpaperLayer && CompositorService.isNiri
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
|
|
||||||
sourceComponent: BlurredWallpaperBackground {}
|
sourceComponent: BlurredWallpaperBackground {}
|
||||||
}
|
}
|
||||||
|
|
||||||
WallpaperBackground {}
|
DeferredAction {
|
||||||
|
id: wallpaperSurfaceReloadAction
|
||||||
|
onTriggered: root.wallpaperSurfacesLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: wallpaperBackgroundLoader
|
||||||
|
active: root.wallpaperSurfacesLoaded
|
||||||
|
asynchronous: false
|
||||||
|
sourceComponent: WallpaperBackground {}
|
||||||
|
}
|
||||||
|
|
||||||
DesktopWidgetLayer {}
|
DesktopWidgetLayer {}
|
||||||
|
|
||||||
@@ -386,6 +398,11 @@ Item {
|
|||||||
frameSurfaceReloadAction.schedule();
|
frameSurfaceReloadAction.schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (root.wallpaperSurfacesLoaded) {
|
||||||
|
root.wallpaperSurfacesLoaded = false;
|
||||||
|
wallpaperSurfaceReloadAction.schedule();
|
||||||
|
}
|
||||||
|
|
||||||
root.dockEnabled = false;
|
root.dockEnabled = false;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.dockEnabled = true;
|
root.dockEnabled = true;
|
||||||
|
|||||||
@@ -337,6 +337,9 @@ Item {
|
|||||||
const focusedWs = I3.workspaces.values.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces.values.find(ws => ws.focused === true);
|
||||||
return focusedWs?.monitor?.name || "";
|
return focusedWs?.monitor?.name || "";
|
||||||
}
|
}
|
||||||
|
if (CompositorService.isDwl && DwlService.activeOutput) {
|
||||||
|
return DwlService.activeOutput;
|
||||||
|
}
|
||||||
if (CompositorService.isMango && MangoService.activeOutput) {
|
if (CompositorService.isMango && MangoService.activeOutput) {
|
||||||
return MangoService.activeOutput;
|
return MangoService.activeOutput;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Item {
|
|||||||
id: clipboardContent
|
id: clipboardContent
|
||||||
|
|
||||||
required property var modal
|
required property var modal
|
||||||
|
required property var clearConfirmDialog
|
||||||
|
|
||||||
property alias searchField: searchField
|
property alias searchField: searchField
|
||||||
property alias clipboardListView: clipboardListView
|
property alias clipboardListView: clipboardListView
|
||||||
@@ -32,7 +33,14 @@ Item {
|
|||||||
pinnedCount: modal.pinnedCount
|
pinnedCount: modal.pinnedCount
|
||||||
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
|
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
|
||||||
onTabChanged: tabName => modal.activeTab = tabName
|
onTabChanged: tabName => modal.activeTab = tabName
|
||||||
onClearAllClicked: modal.confirmClearAll()
|
onClearAllClicked: {
|
||||||
|
const hasPinned = modal.pinnedCount > 0;
|
||||||
|
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(modal.pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
|
||||||
|
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
|
||||||
|
modal.clearAll();
|
||||||
|
modal.hide();
|
||||||
|
}, function () {});
|
||||||
|
}
|
||||||
onCloseClicked: modal.hide()
|
onCloseClicked: modal.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +128,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service...")
|
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service…")
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
@@ -141,8 +149,8 @@ Item {
|
|||||||
listView: clipboardListView
|
listView: clipboardListView
|
||||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||||
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
|
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
|
||||||
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
|
onPinRequested: clipboardContent.modal.pinEntry(modelData)
|
||||||
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
|
onUnpinRequested: clipboardContent.modal.unpinEntry(modelData)
|
||||||
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +202,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service...")
|
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service…")
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
@@ -215,8 +223,8 @@ Item {
|
|||||||
listView: savedListView
|
listView: savedListView
|
||||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||||
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
|
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
|
||||||
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
|
onPinRequested: clipboardContent.modal.pinEntry(modelData)
|
||||||
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
|
onUnpinRequested: clipboardContent.modal.unpinEntry(modelData)
|
||||||
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,21 +15,13 @@ Rectangle {
|
|||||||
|
|
||||||
signal copyRequested
|
signal copyRequested
|
||||||
signal deleteRequested
|
signal deleteRequested
|
||||||
signal pinRequested(var targetEntry)
|
signal pinRequested
|
||||||
signal unpinRequested(var targetEntry)
|
signal unpinRequested
|
||||||
signal editRequested
|
signal editRequested
|
||||||
|
|
||||||
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
||||||
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
||||||
readonly property var pinnedDuplicateEntry: !entry.pinned ? ClipboardService.getPinnedEntryByHash(entry.hash) : null
|
readonly property bool hasPinnedDuplicate: !entry.pinned && ClipboardService.hashedPinnedEntry(entry.hash)
|
||||||
readonly property bool hasPinnedDuplicate: pinnedDuplicateEntry !== null
|
|
||||||
readonly property bool effectivePinned: entry.pinned || hasPinnedDuplicate
|
|
||||||
readonly property var visibleEntryActions: SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"]
|
|
||||||
readonly property bool showPinAction: visibleEntryActions.includes("pin")
|
|
||||||
readonly property bool showEditAction: visibleEntryActions.includes("edit")
|
|
||||||
readonly property bool showDeleteAction: visibleEntryActions.includes("delete")
|
|
||||||
readonly property bool showPinnedIndicator: hasPinnedDuplicate && !showPinAction
|
|
||||||
readonly property bool showAnyAction: showPinAction || showEditAction || showDeleteAction || showPinnedIndicator
|
|
||||||
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
@@ -70,46 +62,19 @@ Rectangle {
|
|||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
visible: root.showAnyAction
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
visible: root.showPinnedIndicator
|
|
||||||
|
|
||||||
// Status indicator only; the Pin action remains hidden.
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "push_pin"
|
|
||||||
size: Theme.iconSize - 6
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
iconName: "push_pin"
|
iconName: "push_pin"
|
||||||
iconSize: Theme.iconSize - 6
|
iconSize: Theme.iconSize - 6
|
||||||
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
|
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
|
||||||
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
|
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
|
||||||
visible: root.showPinAction
|
onClicked: entry.pinned ? unpinRequested() : pinRequested()
|
||||||
onClicked: {
|
|
||||||
if (entry.pinned) {
|
|
||||||
unpinRequested(entry);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pinnedDuplicateEntry) {
|
|
||||||
unpinRequested(pinnedDuplicateEntry);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pinRequested(entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
iconName: "edit"
|
iconName: "edit"
|
||||||
iconSize: Theme.iconSize - 6
|
iconSize: Theme.iconSize - 6
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
visible: root.showEditAction
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (entryType === "image") {
|
if (entryType === "image") {
|
||||||
@@ -123,7 +88,6 @@ Rectangle {
|
|||||||
iconName: "close"
|
iconName: "close"
|
||||||
iconSize: Theme.iconSize - 6
|
iconSize: Theme.iconSize - 6
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
visible: root.showDeleteAction
|
|
||||||
onClicked: deleteRequested()
|
onClicked: deleteRequested()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,8 +95,8 @@ Rectangle {
|
|||||||
Item {
|
Item {
|
||||||
anchors.left: indexBadge.right
|
anchors.left: indexBadge.right
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
anchors.right: root.showAnyAction ? actionButtons.left : parent.right
|
anchors.right: actionButtons.left
|
||||||
anchors.rightMargin: root.showAnyAction ? Theme.spacingM : Theme.spacingS
|
anchors.rightMargin: Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
// height: contentColumn.implicitHeight
|
// height: contentColumn.implicitHeight
|
||||||
height: ClipboardConstants.itemHeight
|
height: ClipboardConstants.itemHeight
|
||||||
@@ -193,8 +157,8 @@ Rectangle {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: root.showAnyAction ? actionButtons.left : parent.right
|
anchors.right: actionButtons.left
|
||||||
anchors.rightMargin: root.showAnyAction ? Theme.spacingS : 0
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|||||||
@@ -82,15 +82,6 @@ FocusScope {
|
|||||||
ClipboardService.clearAll();
|
ClipboardService.clearAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmClearAll() {
|
|
||||||
const hasPinned = pinnedCount > 0;
|
|
||||||
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
|
|
||||||
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
|
|
||||||
clearAll();
|
|
||||||
hide();
|
|
||||||
}, function () {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEntryPreview(entry) {
|
function getEntryPreview(entry) {
|
||||||
return ClipboardService.getEntryPreview(entry);
|
return ClipboardService.getEntryPreview(entry);
|
||||||
}
|
}
|
||||||
@@ -144,6 +135,7 @@ FocusScope {
|
|||||||
id: historyContent
|
id: historyContent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
modal: root
|
modal: root
|
||||||
|
clearConfirmDialog: root.clearConfirmDialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ DankModal {
|
|||||||
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
|
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
keepContentLoaded: true
|
|
||||||
modalWidth: ClipboardConstants.modalWidth
|
modalWidth: ClipboardConstants.modalWidth
|
||||||
modalHeight: ClipboardConstants.modalHeight
|
modalHeight: ClipboardConstants.modalHeight
|
||||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
@@ -77,35 +76,22 @@ DankModal {
|
|||||||
id: clearConfirmDialog
|
id: clearConfirmDialog
|
||||||
confirmButtonText: I18n.tr("Clear All")
|
confirmButtonText: I18n.tr("Clear All")
|
||||||
confirmButtonColor: Theme.primary
|
confirmButtonColor: Theme.primary
|
||||||
onShouldBeVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (shouldBeVisible) {
|
if (visible) {
|
||||||
clipboardHistoryModal.shouldHaveFocus = false;
|
clipboardHistoryModal.shouldHaveFocus = false;
|
||||||
selectedButton = 0;
|
|
||||||
keyboardNavigation = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
if (!clipboardHistoryModal.shouldBeVisible) {
|
if (!clipboardHistoryModal.shouldBeVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clipboardHistoryModal.shouldHaveFocus = Qt.binding(() => clipboardHistoryModal.shouldBeVisible);
|
clipboardHistoryModal.shouldHaveFocus = true;
|
||||||
clipboardHistoryModal.modalFocusScope.forceActiveFocus();
|
clipboardHistoryModal.modalFocusScope.forceActiveFocus();
|
||||||
if (clipboardHistoryModal.contentLoader.item?.searchField) {
|
if (clipboardHistoryModal.contentLoader.item?.searchField) {
|
||||||
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
|
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Connections {
|
|
||||||
target: clearConfirmDialog.modalFocusScope.Keys
|
|
||||||
function onPressed(event) {
|
|
||||||
if (!clearConfirmDialog.shouldBeVisible || event.key !== Qt.Key_Backtab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clearConfirmDialog.selectedButton = clearConfirmDialog.selectedButton === -1 ? 1 : (clearConfirmDialog.selectedButton - 1 + 2) % 2;
|
|
||||||
clearConfirmDialog.keyboardNavigation = true;
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Clipboard
|
import qs.Modals.Clipboard
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -96,35 +95,6 @@ DankPopout {
|
|||||||
id: clearConfirmDialog
|
id: clearConfirmDialog
|
||||||
confirmButtonText: I18n.tr("Clear All")
|
confirmButtonText: I18n.tr("Clear All")
|
||||||
confirmButtonColor: Theme.primary
|
confirmButtonColor: Theme.primary
|
||||||
onShouldBeVisibleChanged: {
|
|
||||||
if (shouldBeVisible) {
|
|
||||||
root.customKeyboardFocus = WlrKeyboardFocus.None;
|
|
||||||
selectedButton = 0;
|
|
||||||
keyboardNavigation = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.customKeyboardFocus = null;
|
|
||||||
Qt.callLater(function () {
|
|
||||||
if (!root.shouldBeVisible || !root.contentLoader.item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.contentLoader.item.forceActiveFocus();
|
|
||||||
if (root.contentLoader.item.searchField) {
|
|
||||||
root.contentLoader.item.searchField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: clearConfirmDialog.modalFocusScope.Keys
|
|
||||||
function onPressed(event) {
|
|
||||||
if (!clearConfirmDialog.shouldBeVisible || event.key !== Qt.Key_Backtab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clearConfirmDialog.selectedButton = clearConfirmDialog.selectedButton === -1 ? 1 : (clearConfirmDialog.selectedButton - 1 + 2) % 2;
|
|
||||||
clearConfirmDialog.keyboardNavigation = true;
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
|
|||||||
@@ -59,13 +59,8 @@ QtObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selectedEntry = entries[ClipboardService.selectedIndex];
|
const selectedEntry = entries[ClipboardService.selectedIndex];
|
||||||
if (selectedEntry.pinned) {
|
if (modal.activeTab === "saved") {
|
||||||
modal.unpinEntry(selectedEntry);
|
modal.unpinEntry(selectedEntry);
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pinnedDuplicate = ClipboardService.getPinnedEntryByHash(selectedEntry.hash);
|
|
||||||
if (pinnedDuplicate) {
|
|
||||||
modal.unpinEntry(pinnedDuplicate);
|
|
||||||
} else {
|
} else {
|
||||||
modal.pinEntry(selectedEntry);
|
modal.pinEntry(selectedEntry);
|
||||||
}
|
}
|
||||||
@@ -125,6 +120,8 @@ QtObject {
|
|||||||
if (!ClipboardService.keyboardNavigationActive) {
|
if (!ClipboardService.keyboardNavigationActive) {
|
||||||
ClipboardService.keyboardNavigationActive = true;
|
ClipboardService.keyboardNavigationActive = true;
|
||||||
ClipboardService.selectedIndex = 0;
|
ClipboardService.selectedIndex = 0;
|
||||||
|
} else if (ClipboardService.selectedIndex === 0) {
|
||||||
|
ClipboardService.keyboardNavigationActive = false;
|
||||||
} else {
|
} else {
|
||||||
selectPrevious();
|
selectPrevious();
|
||||||
}
|
}
|
||||||
@@ -153,6 +150,8 @@ QtObject {
|
|||||||
if (!ClipboardService.keyboardNavigationActive) {
|
if (!ClipboardService.keyboardNavigationActive) {
|
||||||
ClipboardService.keyboardNavigationActive = true;
|
ClipboardService.keyboardNavigationActive = true;
|
||||||
ClipboardService.selectedIndex = 0;
|
ClipboardService.selectedIndex = 0;
|
||||||
|
} else if (ClipboardService.selectedIndex === 0) {
|
||||||
|
ClipboardService.keyboardNavigationActive = false;
|
||||||
} else {
|
} else {
|
||||||
selectPrevious();
|
selectPrevious();
|
||||||
}
|
}
|
||||||
@@ -180,7 +179,8 @@ QtObject {
|
|||||||
if (event.modifiers & Qt.ShiftModifier) {
|
if (event.modifiers & Qt.ShiftModifier) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Delete:
|
case Qt.Key_Delete:
|
||||||
modal.confirmClearAll();
|
modal.clearAll();
|
||||||
|
modal.hide();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
case Qt.Key_Return:
|
case Qt.Key_Return:
|
||||||
|
|||||||
@@ -54,12 +54,23 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hyprland OnDemand grab delivers keyboard focus to the modal content surface.
|
// One focus grab for every modal; on Hyprland this is what delivers
|
||||||
|
// keyboard focus to the OnDemand surface, identically in both modes.
|
||||||
|
// The clickCatcher is whitelisted so an outside click is delivered to
|
||||||
|
// it (closing the modal) instead of being consumed clearing the grab.
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
windows: root.contentWindow ? [root.contentWindow] : []
|
windows: {
|
||||||
|
const list = [];
|
||||||
|
if (root.contentWindow)
|
||||||
|
list.push(root.contentWindow);
|
||||||
|
if (root.clickCatcher)
|
||||||
|
list.push(root.clickCatcher);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
|
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
|
||||||
}
|
}
|
||||||
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
|
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
|
||||||
|
readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null
|
||||||
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
|
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
|
||||||
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
|
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
|
||||||
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
|
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
|
||||||
@@ -102,6 +113,8 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defer Loader source-component swap until impl is fully closed; avoids
|
||||||
|
// tearing down a modal mid-animation when frame mode is toggled.
|
||||||
function _maybeResolveBackend() {
|
function _maybeResolveBackend() {
|
||||||
if (_resolvedBackend === _desiredBackend)
|
if (_resolvedBackend === _desiredBackend)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ Item {
|
|||||||
property bool closeOnBackgroundClick: true
|
property bool closeOnBackgroundClick: true
|
||||||
property string animationType: "scale"
|
property string animationType: "scale"
|
||||||
|
|
||||||
|
// Opposite side from the launcher by default; subclasses may override
|
||||||
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
|
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
|
||||||
|
|
||||||
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
|
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
|
||||||
@@ -86,13 +87,16 @@ Item {
|
|||||||
property real frozenMotionOffsetX: 0
|
property real frozenMotionOffsetX: 0
|
||||||
property real frozenMotionOffsetY: 0
|
property real frozenMotionOffsetY: 0
|
||||||
readonly property alias contentWindow: contentWindow
|
readonly property alias contentWindow: contentWindow
|
||||||
|
readonly property alias clickCatcher: clickCatcher
|
||||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||||
readonly property bool useBackground: false
|
readonly property bool useBackground: false
|
||||||
|
readonly property bool useSingleWindow: CompositorService.isHyprland
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
|
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
||||||
Timer {
|
Timer {
|
||||||
id: _syncTimer
|
id: _syncTimer
|
||||||
interval: 0
|
interval: 0
|
||||||
@@ -240,16 +244,22 @@ Item {
|
|||||||
const focusedScreen = CompositorService.getFocusedScreen();
|
const focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
|
if (!useSingleWindow)
|
||||||
|
clickCatcher.screen = focusedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalManager.openModal(modalHandle);
|
ModalManager.openModal(modalHandle);
|
||||||
if (Theme.isDirectionalEffect || root.useBackground) {
|
if (Theme.isDirectionalEffect || root.useBackground) {
|
||||||
|
if (!useSingleWindow)
|
||||||
|
clickCatcher.visible = true;
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
animationsEnabled = true;
|
animationsEnabled = true;
|
||||||
shouldBeVisible = true;
|
shouldBeVisible = true;
|
||||||
|
if (!useSingleWindow && !clickCatcher.visible)
|
||||||
|
clickCatcher.visible = true;
|
||||||
if (!contentWindow.visible)
|
if (!contentWindow.visible)
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
opened();
|
opened();
|
||||||
@@ -276,6 +286,8 @@ Item {
|
|||||||
ModalManager.closeModal(modalHandle);
|
ModalManager.closeModal(modalHandle);
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
|
if (!useSingleWindow)
|
||||||
|
clickCatcher.visible = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
Qt.callLater(() => animationsEnabled = true);
|
Qt.callLater(() => animationsEnabled = true);
|
||||||
}
|
}
|
||||||
@@ -314,6 +326,8 @@ Item {
|
|||||||
const newScreen = CompositorService.getFocusedScreen();
|
const newScreen = CompositorService.getFocusedScreen();
|
||||||
if (newScreen) {
|
if (newScreen) {
|
||||||
contentWindow.screen = newScreen;
|
contentWindow.screen = newScreen;
|
||||||
|
if (!useSingleWindow)
|
||||||
|
clickCatcher.screen = newScreen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,12 +339,29 @@ Item {
|
|||||||
if (shouldBeVisible)
|
if (shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
|
if (!useSingleWindow)
|
||||||
|
clickCatcher.visible = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shadowRenderPadding is zeroed when frame owns the chrome
|
||||||
|
// Wayland then clips any content translating past
|
||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
readonly property real shadowFallbackOffset: 6
|
readonly property real shadowFallbackOffset: 6
|
||||||
|
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||||
|
readonly property real shadowMotionPadding: {
|
||||||
|
if (frameOwnsConnectedChrome)
|
||||||
|
return 0;
|
||||||
|
if (animationType === "slide")
|
||||||
|
return 30;
|
||||||
|
if (Theme.isDirectionalEffect)
|
||||||
|
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9);
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35);
|
||||||
|
return Math.max(0, animationOffset);
|
||||||
|
}
|
||||||
|
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
|
|
||||||
@@ -340,6 +371,7 @@ Item {
|
|||||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
|
||||||
readonly property real _connectedAlignedX: {
|
readonly property real _connectedAlignedX: {
|
||||||
switch (resolvedConnectedBarSide) {
|
switch (resolvedConnectedBarSide) {
|
||||||
case "top":
|
case "top":
|
||||||
@@ -402,6 +434,57 @@ Item {
|
|||||||
}
|
}
|
||||||
})(), dpr)
|
})(), dpr)
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: clickCatcher
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
||||||
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: Rectangle {
|
||||||
|
x: root.alignedX
|
||||||
|
y: root.alignedY
|
||||||
|
width: root.alignedWidth
|
||||||
|
height: root.alignedHeight
|
||||||
|
}
|
||||||
|
intersection: Intersection.Xor
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
|
onClicked: root.backgroundClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
color: "black"
|
||||||
|
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: contentWindow
|
id: contentWindow
|
||||||
visible: false
|
visible: false
|
||||||
@@ -411,8 +494,8 @@ Item {
|
|||||||
targetWindow: contentWindow
|
targetWindow: contentWindow
|
||||||
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
||||||
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
||||||
blurX: connectedReveal.x + modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
||||||
blurY: connectedReveal.y + modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
||||||
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
|
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
|
||||||
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
|
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
|
||||||
blurRadius: root.effectiveCornerRadius
|
blurRadius: root.effectiveCornerRadius
|
||||||
@@ -431,10 +514,23 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
right: true
|
right: root.useSingleWindow
|
||||||
bottom: true
|
bottom: root.useSingleWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||||
|
readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||||
|
|
||||||
|
WlrLayershell.margins {
|
||||||
|
left: actualMarginLeft
|
||||||
|
top: actualMarginTop
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
||||||
|
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible)
|
if (visible)
|
||||||
return;
|
return;
|
||||||
@@ -446,7 +542,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.backgroundClicked()
|
onClicked: root.backgroundClicked()
|
||||||
}
|
}
|
||||||
@@ -455,7 +551,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: -1
|
z: -1
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@@ -469,256 +565,249 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: connectedReveal
|
id: modalContainer
|
||||||
// Clip to final footprint while frame-owned chrome grows from the bar edge.
|
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
|
||||||
x: root.alignedX
|
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
|
||||||
y: root.alignedY
|
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
clip: root.frameOwnsConnectedChrome
|
|
||||||
|
|
||||||
Item {
|
MouseArea {
|
||||||
id: modalContainer
|
anchors.fill: parent
|
||||||
x: Theme.snap(animX, root.dpr)
|
enabled: root.useSingleWindow && root.shouldBeVisible
|
||||||
y: Theme.snap(animY, root.dpr)
|
hoverEnabled: false
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
onPressed: mouse.accepted = true
|
||||||
|
onClicked: mouse.accepted = true
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
width: root.alignedWidth
|
readonly property bool slide: root.animationType === "slide"
|
||||||
height: root.alignedHeight
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
MouseArea {
|
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||||
anchors.fill: parent
|
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||||
enabled: root.shouldBeVisible
|
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||||
hoverEnabled: false
|
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||||
acceptedButtons: Qt.AllButtons
|
readonly property real customDistLeft: customAnchorX
|
||||||
onPressed: mouse.accepted = true
|
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||||
onClicked: mouse.accepted = true
|
readonly property real customDistTop: customAnchorY
|
||||||
z: -1
|
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||||
}
|
// Connected emergence: travel from the resolved bar edge, matching DankPopout cadence.
|
||||||
|
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||||
readonly property bool slide: root.animationType === "slide"
|
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
readonly property real offsetX: {
|
||||||
readonly property bool depthEffect: Theme.isDepthEffect
|
if (root.frameOwnsConnectedChrome) {
|
||||||
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
switch (root.resolvedConnectedBarSide) {
|
||||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
case "left":
|
||||||
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
return -connectedEmergenceTravelX;
|
||||||
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
case "right":
|
||||||
readonly property real customDistLeft: customAnchorX
|
return connectedEmergenceTravelX;
|
||||||
readonly property real customDistRight: root.screenWidth - customAnchorX
|
|
||||||
readonly property real customDistTop: customAnchorY
|
|
||||||
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
|
||||||
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
|
||||||
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
|
||||||
readonly property real offsetX: {
|
|
||||||
if (root.frameOwnsConnectedChrome) {
|
|
||||||
switch (root.resolvedConnectedBarSide) {
|
|
||||||
case "left":
|
|
||||||
return -connectedEmergenceTravelX;
|
|
||||||
case "right":
|
|
||||||
return connectedEmergenceTravelX;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (slide && !directionalEffect && !depthEffect)
|
|
||||||
return 15;
|
|
||||||
if (directionalEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return 0;
|
|
||||||
case "custom":
|
|
||||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
|
||||||
return -directionalTravel;
|
|
||||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
|
||||||
return directionalTravel;
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depthEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return 0;
|
|
||||||
case "custom":
|
|
||||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
|
||||||
return -depthTravel;
|
|
||||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
|
||||||
return depthTravel;
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
readonly property real offsetY: {
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
if (root.frameOwnsConnectedChrome) {
|
return 15;
|
||||||
switch (root.resolvedConnectedBarSide) {
|
if (directionalEffect) {
|
||||||
case "top":
|
switch (root.positioning) {
|
||||||
return -connectedEmergenceTravelY;
|
case "top-right":
|
||||||
case "bottom":
|
return 0;
|
||||||
return connectedEmergenceTravelY;
|
case "custom":
|
||||||
}
|
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||||
|
return -directionalTravel;
|
||||||
|
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||||
|
return directionalTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (slide && !directionalEffect && !depthEffect)
|
|
||||||
return -30;
|
|
||||||
if (directionalEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return -Math.max(directionalTravel * 0.65, 96);
|
|
||||||
case "custom":
|
|
||||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
|
||||||
return -directionalTravel;
|
|
||||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
|
||||||
return directionalTravel;
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depthEffect) {
|
|
||||||
switch (root.positioning) {
|
|
||||||
case "top-right":
|
|
||||||
return -depthTravel * 0.75;
|
|
||||||
case "custom":
|
|
||||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
|
||||||
return -depthTravel;
|
|
||||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
|
||||||
return depthTravel;
|
|
||||||
return depthTravel * 0.45;
|
|
||||||
default:
|
|
||||||
return -depthTravel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return root.animationOffset;
|
|
||||||
}
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return 0;
|
||||||
|
case "custom":
|
||||||
|
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||||
|
return -depthTravel;
|
||||||
|
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||||
|
return depthTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
readonly property real offsetY: {
|
||||||
|
if (root.frameOwnsConnectedChrome) {
|
||||||
|
switch (root.resolvedConnectedBarSide) {
|
||||||
|
case "top":
|
||||||
|
return -connectedEmergenceTravelY;
|
||||||
|
case "bottom":
|
||||||
|
return connectedEmergenceTravelY;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
|
return -30;
|
||||||
|
if (directionalEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return -Math.max(directionalTravel * 0.65, 96);
|
||||||
|
case "custom":
|
||||||
|
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||||
|
return -directionalTravel;
|
||||||
|
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||||
|
return directionalTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
// Default to sliding down from top when centered
|
||||||
|
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return -depthTravel * 0.75;
|
||||||
|
case "custom":
|
||||||
|
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||||
|
return -depthTravel;
|
||||||
|
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||||
|
return depthTravel;
|
||||||
|
return depthTravel * 0.45;
|
||||||
|
default:
|
||||||
|
return -depthTravel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root.animationOffset;
|
||||||
|
}
|
||||||
|
|
||||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||||
|
|
||||||
QtObject {
|
// openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||||
id: morph
|
QtObject {
|
||||||
property real openProgress: root.shouldBeVisible ? 1 : 0
|
id: morph
|
||||||
Behavior on openProgress {
|
property real openProgress: root.shouldBeVisible ? 1 : 0
|
||||||
enabled: root.animationsEnabled
|
Behavior on openProgress {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
|
||||||
|
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
|
||||||
|
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
|
||||||
|
|
||||||
|
onAnimXChanged: if (root.frameOwnsConnectedChrome)
|
||||||
|
root._queueAnimSync()
|
||||||
|
onAnimYChanged: if (root.frameOwnsConnectedChrome)
|
||||||
|
root._queueAnimSync()
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: contentContainer
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: animatedContent
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||||
|
|
||||||
|
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||||
|
scale: modalContainer.scaleValue
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
|
Behavior on publishedOpacity {
|
||||||
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
|
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||||
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
|
NumberAnimation {
|
||||||
|
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onAnimXChanged: if (root.frameOwnsConnectedChrome)
|
ElevationShadow {
|
||||||
root._queueAnimSync()
|
id: modalShadowLayer
|
||||||
onAnimYChanged: if (root.frameOwnsConnectedChrome)
|
|
||||||
root._queueAnimSync()
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: contentContainer
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height
|
|
||||||
clip: false
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: animatedContent
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
level: root.shadowLevel
|
||||||
|
fallbackOffset: root.shadowFallbackOffset
|
||||||
|
targetRadius: root.effectiveCornerRadius
|
||||||
|
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
||||||
|
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
||||||
|
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
||||||
|
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.effectiveCornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
||||||
|
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: root.shouldBeVisible
|
||||||
clip: false
|
clip: false
|
||||||
|
|
||||||
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
Item {
|
||||||
|
id: directContentWrapper
|
||||||
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
|
||||||
scale: modalContainer.scaleValue
|
|
||||||
transformOrigin: Item.Center
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on publishedOpacity {
|
|
||||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevationShadow {
|
|
||||||
id: modalShadowLayer
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
level: root.shadowLevel
|
visible: root.directContent !== null
|
||||||
fallbackOffset: root.shadowFallbackOffset
|
focus: true
|
||||||
targetRadius: root.effectiveCornerRadius
|
|
||||||
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
|
||||||
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
|
||||||
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
|
||||||
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: root.effectiveCornerRadius
|
|
||||||
color: "transparent"
|
|
||||||
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
|
||||||
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
|
||||||
z: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: root.shouldBeVisible
|
|
||||||
clip: false
|
clip: false
|
||||||
|
|
||||||
Item {
|
Component.onCompleted: {
|
||||||
id: directContentWrapper
|
if (root.directContent) {
|
||||||
anchors.fill: parent
|
root.directContent.parent = directContentWrapper;
|
||||||
visible: root.directContent !== null
|
root.directContent.anchors.fill = directContentWrapper;
|
||||||
focus: true
|
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||||
clip: false
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onDirectContentChanged() {
|
||||||
if (root.directContent) {
|
if (root.directContent) {
|
||||||
root.directContent.parent = directContentWrapper;
|
root.directContent.parent = directContentWrapper;
|
||||||
root.directContent.anchors.fill = directContentWrapper;
|
root.directContent.anchors.fill = directContentWrapper;
|
||||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root
|
|
||||||
function onDirectContentChanged() {
|
|
||||||
if (root.directContent) {
|
|
||||||
root.directContent.parent = directContentWrapper;
|
|
||||||
root.directContent.anchors.fill = directContentWrapper;
|
|
||||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
focus: true
|
focus: true
|
||||||
clip: false
|
clip: false
|
||||||
visible: root.directContent === null
|
visible: root.directContent === null
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item) {
|
if (item) {
|
||||||
Qt.callLater(() => item.forceActiveFocus());
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,6 @@ Item {
|
|||||||
id: clickCatcher
|
id: clickCatcher
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
updatesEnabled: false
|
|
||||||
|
|
||||||
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ Item {
|
|||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
||||||
|
|
||||||
|
// Animation state — matches DankPopout/DankModal pattern
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
property bool _motionActive: false
|
property bool _motionActive: false
|
||||||
property real _frozenMotionX: 0
|
property real _frozenMotionX: 0
|
||||||
@@ -107,6 +108,8 @@ Item {
|
|||||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frameEdgeInsetForSide is the full inset; do not add frameBarSize.
|
||||||
|
// Positions the modal flush to the emerge side, centered on the cross axis.
|
||||||
readonly property var _connectedModalPos: {
|
readonly property var _connectedModalPos: {
|
||||||
const fallback = {
|
const fallback = {
|
||||||
"x": (screenWidth - modalWidth) / 2,
|
"x": (screenWidth - modalWidth) / 2,
|
||||||
@@ -172,6 +175,8 @@ Item {
|
|||||||
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
|
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
|
||||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||||
|
|
||||||
|
// Shadow padding for the content window (render padding only, no motion padding).
|
||||||
|
// Zeroed when frame owns the chrome and Wayland clips past the bar edge
|
||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
readonly property real shadowFallbackOffset: 6
|
readonly property real shadowFallbackOffset: 6
|
||||||
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||||
@@ -198,11 +203,29 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
||||||
|
|
||||||
readonly property real _ccX: _connectedChromeX
|
// For directional/depth: window extends from screen top (content slides within)
|
||||||
readonly property real _ccY: _connectedChromeY
|
// For standard: small window tightly around the modal + shadow padding
|
||||||
|
readonly property bool _needsExtendedWindow: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) || Theme.isDepthEffect
|
||||||
|
// Content window geometry
|
||||||
|
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
|
||||||
|
readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr))
|
||||||
|
readonly property real _cwWidth: alignedWidth + shadowPad * 2
|
||||||
|
readonly property real _cwHeight: {
|
||||||
|
if (launcherArcExtenderActive)
|
||||||
|
return _connectedChromeHeight;
|
||||||
|
if (Theme.isDirectionalEffect && !Theme.isConnectedEffect)
|
||||||
|
return screenHeight + shadowPad;
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return alignedY + alignedHeight + shadowPad;
|
||||||
|
return alignedHeight + shadowPad * 2;
|
||||||
|
}
|
||||||
|
// Where the content container sits inside the content window
|
||||||
|
readonly property real _ccX: shadowPad
|
||||||
|
readonly property real _ccY: launcherArcExtenderActive ? 0 : (_needsExtendedWindow ? alignedY : shadowPad)
|
||||||
|
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
|
|
||||||
|
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
||||||
Timer {
|
Timer {
|
||||||
id: _syncTimer
|
id: _syncTimer
|
||||||
interval: 0
|
interval: 0
|
||||||
@@ -358,6 +381,8 @@ Item {
|
|||||||
return;
|
return;
|
||||||
contentVisible = true;
|
contentVisible = true;
|
||||||
spotlightContent.closeTransientUi?.();
|
spotlightContent.closeTransientUi?.();
|
||||||
|
// NOTE: forceActiveFocus() is deliberately NOT called here.
|
||||||
|
// It is deferred to after animation starts to avoid compositor IPC stalls.
|
||||||
|
|
||||||
if (spotlightContent.searchField) {
|
if (spotlightContent.searchField) {
|
||||||
spotlightContent.searchField.text = query;
|
spotlightContent.searchField.text = query;
|
||||||
@@ -395,29 +420,38 @@ Item {
|
|||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
|
||||||
|
// Disable animations so the snap is instant
|
||||||
animationsEnabled = false;
|
animationsEnabled = false;
|
||||||
|
|
||||||
|
// Freeze the collapsed offsets (they depend on height which could change)
|
||||||
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
||||||
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
var focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
|
backgroundWindow.screen = focusedScreen;
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// _motionActive = false ensures motionX/Y snap to frozen collapsed position
|
||||||
_motionActive = false;
|
_motionActive = false;
|
||||||
|
|
||||||
|
// Make windows visible but do NOT request keyboard focus yet
|
||||||
ModalManager.openModal(modalHandle);
|
ModalManager.openModal(modalHandle);
|
||||||
spotlightOpen = true;
|
spotlightOpen = true;
|
||||||
|
backgroundWindow.visible = true;
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
|
|
||||||
|
// Load content and initialize (but no forceActiveFocus — that's deferred)
|
||||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||||
|
|
||||||
// Defer focus until after enter motion starts (avoids compositor IPC stalls).
|
// Frame 1: enable animations and trigger enter motion
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.animationsEnabled = true;
|
root.animationsEnabled = true;
|
||||||
root._motionActive = true;
|
root._motionActive = true;
|
||||||
|
|
||||||
|
// Frame 2: request keyboard focus + activate search field
|
||||||
|
// Double-deferred to avoid compositor IPC competing with animation frames
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root.keyboardActive = true;
|
root.keyboardActive = true;
|
||||||
if (root.spotlightContent && root.spotlightContent.searchField)
|
if (root.spotlightContent && root.spotlightContent.searchField)
|
||||||
@@ -440,9 +474,11 @@ Item {
|
|||||||
spotlightContent?.closeTransientUi?.();
|
spotlightContent?.closeTransientUi?.();
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
|
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
||||||
if (!Theme.isDirectionalEffect)
|
if (!Theme.isDirectionalEffect)
|
||||||
contentVisible = false;
|
contentVisible = false;
|
||||||
|
|
||||||
|
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
|
||||||
_motionActive = false;
|
_motionActive = false;
|
||||||
|
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
@@ -483,6 +519,7 @@ Item {
|
|||||||
isClosing = false;
|
isClosing = false;
|
||||||
contentVisible = false;
|
contentVisible = false;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
|
backgroundWindow.visible = false;
|
||||||
if (root.unloadContentOnClose)
|
if (root.unloadContentOnClose)
|
||||||
launcherContentLoader.active = false;
|
launcherContentLoader.active = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
@@ -551,6 +588,7 @@ Item {
|
|||||||
|
|
||||||
root._releaseModalChrome();
|
root._releaseModalChrome();
|
||||||
root._windowEnabled = false;
|
root._windowEnabled = false;
|
||||||
|
backgroundWindow.screen = newScreen;
|
||||||
contentWindow.screen = newScreen;
|
contentWindow.screen = newScreen;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root._windowEnabled = true;
|
root._windowEnabled = true;
|
||||||
@@ -558,6 +596,73 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: backgroundWindow
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
readonly property real _topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
readonly property real _bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
readonly property real _leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
||||||
|
|
||||||
|
WlrLayershell.namespace: "dms:spotlight:bg"
|
||||||
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
WlrLayershell.margins {
|
||||||
|
top: backgroundWindow._topMargin
|
||||||
|
bottom: backgroundWindow._bottomMargin
|
||||||
|
left: backgroundWindow._leftMargin
|
||||||
|
right: backgroundWindow._rightMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
|
||||||
|
|
||||||
|
Region {
|
||||||
|
item: bgContentHole
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: bgFullScreenMask
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: bgContentHole
|
||||||
|
visible: false
|
||||||
|
x: root._cwMarginLeft + contentContainer.x - backgroundWindow._leftMargin
|
||||||
|
y: root._cwMarginTop + contentContainer.y - backgroundWindow._topMargin
|
||||||
|
width: root.alignedWidth
|
||||||
|
height: root.contentSurfaceHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: backgroundDarken
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "black"
|
||||||
|
opacity: 0
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: spotlightOpen
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: contentWindow
|
id: contentWindow
|
||||||
visible: false
|
visible: false
|
||||||
@@ -582,26 +687,18 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WlrLayershell.margins {
|
||||||
|
left: root._cwMarginLeft
|
||||||
|
top: root._cwMarginTop
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: root._cwWidth
|
||||||
|
implicitHeight: root._cwHeight
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: (root.spotlightOpen || root.isClosing) ? dismissArea : contentInputMask
|
item: contentInputMask
|
||||||
|
|
||||||
Region {
|
|
||||||
item: (root.spotlightOpen || root.isClosing) ? contentInputMask : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: dismissArea
|
|
||||||
visible: false
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
anchors.bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
anchors.leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
anchors.rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -613,31 +710,16 @@ Item {
|
|||||||
height: root.contentSurfaceHeight
|
height: root.contentSurfaceHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: dismissArea
|
|
||||||
enabled: root.spotlightOpen
|
|
||||||
z: -2
|
|
||||||
onClicked: root.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentContainer
|
id: contentContainer
|
||||||
|
|
||||||
|
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
|
||||||
|
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
|
||||||
x: root._ccX
|
x: root._ccX
|
||||||
y: root._ccY
|
y: root._ccY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.contentSurfaceHeight
|
height: root.contentSurfaceHeight
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.spotlightOpen
|
|
||||||
hoverEnabled: false
|
|
||||||
acceptedButtons: Qt.AllButtons
|
|
||||||
onPressed: mouse.accepted = true
|
|
||||||
onClicked: mouse.accepted = true
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
|
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
|
||||||
readonly property bool dockTop: dockEdge === 0
|
readonly property bool dockTop: dockEdge === 0
|
||||||
readonly property bool dockBottom: dockEdge === 1
|
readonly property bool dockBottom: dockEdge === 1
|
||||||
@@ -692,6 +774,7 @@ Item {
|
|||||||
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
|
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// openProgress: 0 = closed (at frozenMotion, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||||
QtObject {
|
QtObject {
|
||||||
id: morph
|
id: morph
|
||||||
property real openProgress: root._motionActive ? 1 : 0
|
property real openProgress: root._motionActive ? 1 : 0
|
||||||
@@ -750,6 +833,7 @@ Item {
|
|||||||
width: contentContainer.width
|
width: contentContainer.width
|
||||||
height: contentContainer.height
|
height: contentContainer.height
|
||||||
|
|
||||||
|
// Shadow mirrors contentWrapper position/scale/opacity
|
||||||
ElevationShadow {
|
ElevationShadow {
|
||||||
id: launcherShadowLayer
|
id: launcherShadowLayer
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -767,6 +851,7 @@ Item {
|
|||||||
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// contentWrapper moves inside static contentContainer — DankPopout pattern
|
||||||
Item {
|
Item {
|
||||||
id: contentWrapper
|
id: contentWrapper
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -84,14 +84,14 @@ Item {
|
|||||||
readonly property real alignedX: Theme.snap(modalX, dpr)
|
readonly property real alignedX: Theme.snap(modalX, dpr)
|
||||||
readonly property real alignedY: Theme.snap(modalY, dpr)
|
readonly property real alignedY: Theme.snap(modalY, dpr)
|
||||||
|
|
||||||
// Extra headroom above the content for the slide-in animation
|
// Extra headroom above the window for the slide-in animation
|
||||||
readonly property real _animHeadroom: 16
|
readonly property real _animHeadroom: 16
|
||||||
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
|
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
|
||||||
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
|
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
|
||||||
readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
|
readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
|
||||||
readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
|
readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
|
||||||
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
|
|
||||||
readonly property real windowWidth: alignedWidth + contentX + shadowPad
|
readonly property real windowWidth: alignedWidth + contentX + shadowPad
|
||||||
|
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
|
||||||
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
|
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
|
||||||
|
|
||||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
@@ -114,7 +114,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
||||||
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
|
|
||||||
|
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
|
|
||||||
@@ -268,9 +267,8 @@ Item {
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: clickCatcher
|
id: clickCatcher
|
||||||
screen: launcherWindow.screen
|
screen: launcherWindow.screen
|
||||||
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
|
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
updatesEnabled: false
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
@@ -341,19 +339,19 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: root.useSingleWindow
|
right: root.useBackgroundDarken
|
||||||
bottom: root.useSingleWindow
|
bottom: root.useBackgroundDarken
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: root.useSingleWindow ? 0 : root.windowX
|
left: root.useBackgroundDarken ? 0 : root.windowX
|
||||||
top: root.useSingleWindow ? 0 : root.windowY
|
top: root.useBackgroundDarken ? 0 : root.windowY
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
|
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
|
||||||
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
|
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: inputMask
|
item: inputMask
|
||||||
@@ -363,15 +361,15 @@ Item {
|
|||||||
id: inputMask
|
id: inputMask
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
x: root.useSingleWindow ? 0 : modalContainer.x
|
x: root.useBackgroundDarken ? 0 : modalContainer.x
|
||||||
y: root.useSingleWindow ? 0 : modalContainer.y + modalContainer.slideOffset
|
y: root.useBackgroundDarken ? 0 : modalContainer.y + modalContainer.slideOffset
|
||||||
width: root.useSingleWindow ? launcherWindow.width : root.alignedWidth
|
width: root.useBackgroundDarken ? launcherWindow.width : root.alignedWidth
|
||||||
height: root.useSingleWindow ? launcherWindow.height : root._contentImplicitH
|
height: root.useBackgroundDarken ? launcherWindow.height : root._contentImplicitH
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useSingleWindow && spotlightOpen
|
enabled: root.useBackgroundDarken && spotlightOpen
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.hide()
|
onClicked: root.hide()
|
||||||
}
|
}
|
||||||
@@ -395,23 +393,13 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useSingleWindow ? root.alignedX : root.contentX
|
x: root.useBackgroundDarken ? root.alignedX : root.contentX
|
||||||
y: root.useSingleWindow ? root.alignedY : root.contentY
|
y: root.useBackgroundDarken ? root.alignedY : root.contentY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root._animatedContentH
|
height: root._animatedContentH
|
||||||
visible: _renderActive
|
visible: _renderActive
|
||||||
z: 0
|
z: 0
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: spotlightOpen
|
|
||||||
hoverEnabled: false
|
|
||||||
acceptedButtons: Qt.AllButtons
|
|
||||||
onPressed: mouse.accepted = true
|
|
||||||
onClicked: mouse.accepted = true
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool _renderActive: contentVisible
|
property bool _renderActive: contentVisible
|
||||||
property real slideOffset: contentVisible ? 0 : -root._animHeadroom
|
property real slideOffset: contentVisible ? 0 : -root._animHeadroom
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ Item {
|
|||||||
|
|
||||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
||||||
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
|
|
||||||
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
||||||
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||||
"allow": ["top", "overlay"],
|
"allow": ["top", "overlay"],
|
||||||
@@ -304,9 +303,8 @@ Item {
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: clickCatcher
|
id: clickCatcher
|
||||||
screen: launcherWindow.screen
|
screen: launcherWindow.screen
|
||||||
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
|
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
updatesEnabled: false
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
||||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||||
@@ -377,19 +375,19 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
left: true
|
left: true
|
||||||
right: root.useSingleWindow
|
right: root.useBackgroundDarken
|
||||||
bottom: root.useSingleWindow
|
bottom: root.useBackgroundDarken
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: root.useSingleWindow ? 0 : root.windowX
|
left: root.useBackgroundDarken ? 0 : root.windowX
|
||||||
top: root.useSingleWindow ? 0 : root.windowY
|
top: root.useBackgroundDarken ? 0 : root.windowY
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
|
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
|
||||||
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
|
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: launcherInputMask
|
item: launcherInputMask
|
||||||
@@ -399,15 +397,15 @@ Item {
|
|||||||
id: launcherInputMask
|
id: launcherInputMask
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
x: root.useSingleWindow ? 0 : modalContainer.x
|
x: root.useBackgroundDarken ? 0 : modalContainer.x
|
||||||
y: root.useSingleWindow ? 0 : modalContainer.y
|
y: root.useBackgroundDarken ? 0 : modalContainer.y
|
||||||
width: root.useSingleWindow ? launcherWindow.width : modalContainer.width
|
width: root.useBackgroundDarken ? launcherWindow.width : modalContainer.width
|
||||||
height: root.useSingleWindow ? launcherWindow.height : modalContainer.height
|
height: root.useBackgroundDarken ? launcherWindow.height : modalContainer.height
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useSingleWindow && spotlightOpen
|
enabled: root.useBackgroundDarken && spotlightOpen
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.hide()
|
onClicked: root.hide()
|
||||||
}
|
}
|
||||||
@@ -431,23 +429,13 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useSingleWindow ? root.alignedX : root.contentX
|
x: root.useBackgroundDarken ? root.alignedX : root.contentX
|
||||||
y: root.useSingleWindow ? root.alignedY : root.contentY
|
y: root.useBackgroundDarken ? root.alignedY : root.contentY
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
visible: _renderActive
|
visible: _renderActive
|
||||||
z: 0
|
z: 0
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: spotlightOpen
|
|
||||||
hoverEnabled: false
|
|
||||||
acceptedButtons: Qt.AllButtons
|
|
||||||
onPressed: mouse.accepted = true
|
|
||||||
onClicked: mouse.accepted = true
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool _renderActive: contentVisible
|
property bool _renderActive: contentVisible
|
||||||
property real publishedScale: contentVisible ? 1 : 0.96
|
property real publishedScale: contentVisible ? 1 : 0.96
|
||||||
property real publishedOpacity: contentVisible ? 1 : 0
|
property real publishedOpacity: contentVisible ? 1 : 0
|
||||||
|
|||||||
@@ -320,6 +320,8 @@ Item {
|
|||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings";
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings";
|
||||||
else if (CompositorService.isHyprland)
|
else if (CompositorService.isHyprland)
|
||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-1";
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-1";
|
||||||
|
else if (CompositorService.isDwl)
|
||||||
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
|
||||||
else if (CompositorService.isMango)
|
else if (CompositorService.isMango)
|
||||||
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
|
url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2";
|
||||||
Qt.openUrlExternally(url);
|
Qt.openUrlExternally(url);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ Item {
|
|||||||
title: I18n.tr("Multi-Monitor", "greeter feature card title")
|
title: I18n.tr("Multi-Monitor", "greeter feature card title")
|
||||||
description: I18n.tr("Per-screen config", "greeter feature card description")
|
description: I18n.tr("Per-screen config", "greeter feature card description")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango;
|
const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango;
|
||||||
PopoutService.openSettingsWithTab(hasDisplayConfig ? "display_config" : "display_widgets");
|
PopoutService.openSettingsWithTab(hasDisplayConfig ? "display_config" : "display_widgets");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ FocusScope {
|
|||||||
visible: active
|
visible: active
|
||||||
focus: active
|
focus: active
|
||||||
|
|
||||||
sourceComponent: WorkspacesTab {}
|
sourceComponent: CompositorTab {}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active && item)
|
if (active && item)
|
||||||
@@ -106,44 +106,6 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: compositorLayoutLoader
|
|
||||||
anchors.fill: parent
|
|
||||||
active: root.currentIndex === 37
|
|
||||||
visible: active
|
|
||||||
focus: active
|
|
||||||
|
|
||||||
sourceComponent: CompositorLayoutTab {}
|
|
||||||
|
|
||||||
onActiveChanged: {
|
|
||||||
if (active && item)
|
|
||||||
Qt.callLater(() => item.forceActiveFocus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: windowRulesLoader
|
|
||||||
|
|
||||||
property bool loadedOnce: false
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
active: root.currentIndex === 38 || loadedOnce
|
|
||||||
visible: root.currentIndex === 38 && status === Loader.Ready
|
|
||||||
focus: visible
|
|
||||||
asynchronous: true
|
|
||||||
|
|
||||||
sourceComponent: WindowRulesTab {
|
|
||||||
pageActive: root.currentIndex === 38
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded: loadedOnce = true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankSpinner {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: root.currentIndex === 38 && windowRulesLoader.status === Loader.Loading
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: dankBarAppearanceLoader
|
id: dankBarAppearanceLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -426,7 +388,7 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: defaultAppsLoader
|
id: defaultAppsLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 34
|
active: root.currentIndex === 34
|
||||||
@@ -512,9 +474,12 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankSpinner {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: root.currentIndex === 22 && widgetsLoader.status === Loader.Loading
|
visible: root.currentIndex === 22 && widgetsLoader.status === Loader.Loading
|
||||||
|
text: I18n.tr("Loading...", "loading indicator")
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|||||||
@@ -102,13 +102,6 @@ Rectangle {
|
|||||||
"icon": "volume_up",
|
"icon": "volume_up",
|
||||||
"tabIndex": 15,
|
"tabIndex": 15,
|
||||||
"soundsOnly": true
|
"soundsOnly": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "compositor_layout",
|
|
||||||
"text": CompositorService.isNiri ? "Niri" : (CompositorService.isHyprland ? "Hyprland" : "MangoWC"),
|
|
||||||
"icon": "layers",
|
|
||||||
"tabIndex": 37,
|
|
||||||
"layoutCapable": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -117,30 +110,24 @@ Rectangle {
|
|||||||
"text": I18n.tr("Dank Bar"),
|
"text": I18n.tr("Dank Bar"),
|
||||||
"icon": "toolbar",
|
"icon": "toolbar",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
|
||||||
"id": "dankbar_appearance",
|
|
||||||
"text": I18n.tr("Appearance"),
|
|
||||||
"icon": "palette",
|
|
||||||
"tabIndex": 6
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "dankbar_settings",
|
"id": "dankbar_settings",
|
||||||
"text": I18n.tr("Settings"),
|
"text": I18n.tr("Settings"),
|
||||||
"icon": "tune",
|
"icon": "tune",
|
||||||
"tabIndex": 3
|
"tabIndex": 3
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "dankbar_appearance",
|
||||||
|
"text": I18n.tr("Appearance"),
|
||||||
|
"icon": "palette",
|
||||||
|
"tabIndex": 6
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "dankbar_widgets",
|
"id": "dankbar_widgets",
|
||||||
"text": I18n.tr("Widgets"),
|
"text": I18n.tr("Widgets"),
|
||||||
"icon": "widgets",
|
"icon": "widgets",
|
||||||
"tabIndex": 22
|
"tabIndex": 22
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "workspaces",
|
|
||||||
"text": I18n.tr("Workspaces"),
|
|
||||||
"icon": "view_module",
|
|
||||||
"tabIndex": 4
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "frame",
|
"id": "frame",
|
||||||
"text": I18n.tr("Frame"),
|
"text": I18n.tr("Frame"),
|
||||||
@@ -201,6 +188,12 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "compositor",
|
||||||
|
"text": I18n.tr("Compositor"),
|
||||||
|
"icon": "layers",
|
||||||
|
"tabIndex": 4
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "keybinds",
|
"id": "keybinds",
|
||||||
"text": I18n.tr("Keyboard Shortcuts"),
|
"text": I18n.tr("Keyboard Shortcuts"),
|
||||||
@@ -266,13 +259,6 @@ Rectangle {
|
|||||||
"icon": "line_start",
|
"icon": "line_start",
|
||||||
"tabIndex": 36,
|
"tabIndex": 36,
|
||||||
"autostartOnly": true
|
"autostartOnly": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "window_rules",
|
|
||||||
"text": I18n.tr("Window Rules"),
|
|
||||||
"icon": "select_window",
|
|
||||||
"tabIndex": 38,
|
|
||||||
"windowRulesCapable": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -386,8 +372,6 @@ Rectangle {
|
|||||||
return false;
|
return false;
|
||||||
if (item.windowRulesCapable && !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
if (item.windowRulesCapable && !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
||||||
return false;
|
return false;
|
||||||
if (item.layoutCapable && !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
|
||||||
return false;
|
|
||||||
if (item.niriOnly && !CompositorService.isNiri)
|
if (item.niriOnly && !CompositorService.isNiri)
|
||||||
return false;
|
return false;
|
||||||
if (item.clipboardOnly && (!DMSService.isConnected || DMSService.apiVersion < 23))
|
if (item.clipboardOnly && (!DMSService.isConnected || DMSService.apiVersion < 23))
|
||||||
@@ -560,8 +544,8 @@ Rectangle {
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
var normalized = name.toLowerCase().replace(/[_\-\s]/g, "");
|
var normalized = name.toLowerCase().replace(/[_\-\s]/g, "");
|
||||||
if (normalized === "compositor")
|
if (normalized === "workspaces")
|
||||||
normalized = "workspaces";
|
normalized = "compositor";
|
||||||
|
|
||||||
for (var i = 0; i < categoryStructure.length; i++) {
|
for (var i = 0; i < categoryStructure.length; i++) {
|
||||||
var cat = categoryStructure[i];
|
var cat = categoryStructure[i];
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import qs.Widgets
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
readonly property var log: Log.scoped("BlurredWallpaperBackground")
|
|
||||||
model: {
|
model: {
|
||||||
if (SessionData.isGreeterMode) {
|
if (SessionData.isGreeterMode) {
|
||||||
return Quickshell.screens;
|
return Quickshell.screens;
|
||||||
@@ -33,8 +32,6 @@ Variants {
|
|||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
updatesEnabled: root.renderActive || root._settleFrames > 0
|
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: Item {}
|
item: Item {}
|
||||||
}
|
}
|
||||||
@@ -88,6 +85,7 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
blurWallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,67 +93,51 @@ Variants {
|
|||||||
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: true
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
readonly property var backingWindow: Window.window
|
|
||||||
readonly property bool renderActive: !source || effectActive || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading
|
|
||||||
property int _settleFrames: 3
|
|
||||||
|
|
||||||
function invalidate() {
|
|
||||||
_settleFrames = 3;
|
|
||||||
backingWindow?.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
onRenderActiveChanged: invalidate()
|
|
||||||
onBackingWindowChanged: invalidate()
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.backingWindow
|
target: currentWallpaper
|
||||||
function onFrameSwapped() {
|
function onStatusChanged() {
|
||||||
if (root._settleFrames > 0)
|
if (currentWallpaper.status !== Image.Ready && currentWallpaper.status !== Image.Error)
|
||||||
root._settleFrames--;
|
return;
|
||||||
}
|
root._renderSettling = true;
|
||||||
function onVisibleChanged() {
|
renderSettleTimer.restart();
|
||||||
root.invalidate();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: blurWallpaperWindow
|
||||||
function onWidthChanged() {
|
function onWidthChanged() {
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
function onHeightChanged() {
|
function onHeightChanged() {
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Quickshell
|
target: Quickshell
|
||||||
function onScreensChanged() {
|
function onScreensChanged() {
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onWallpaperFillModeChanged() {
|
function onWallpaperFillModeChanged() {
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Timer {
|
||||||
target: IdleService
|
id: renderSettleTimer
|
||||||
function onIsShellLockedChanged() {
|
interval: 1000
|
||||||
if (IdleService.isShellLocked)
|
onTriggered: root._renderSettling = false
|
||||||
return;
|
|
||||||
root.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTransitionLoadError(failedSource) {
|
|
||||||
log.warn("failed to load candidate wallpaper for", modelData.name + ":", failedSource);
|
|
||||||
transitionDelayTimer.stop();
|
|
||||||
transitionAnimation.stop();
|
|
||||||
root.useNextForEffect = false;
|
|
||||||
root.effectActive = false;
|
|
||||||
root.transitionProgress = 0.0;
|
|
||||||
nextWallpaper.source = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
@@ -182,6 +164,8 @@ Variants {
|
|||||||
transitionAnimation.stop();
|
transitionAnimation.stop();
|
||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
currentWallpaper.source = newSource;
|
currentWallpaper.source = newSource;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
}
|
}
|
||||||
@@ -210,6 +194,8 @@ Variants {
|
|||||||
transitionAnimation.stop();
|
transitionAnimation.stop();
|
||||||
root.transitionProgress = 0;
|
root.transitionProgress = 0;
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
currentWallpaper.source = nextWallpaper.source;
|
currentWallpaper.source = nextWallpaper.source;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
}
|
}
|
||||||
@@ -218,6 +204,9 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
|
||||||
nextWallpaper.source = newPath;
|
nextWallpaper.source = newPath;
|
||||||
|
|
||||||
if (nextWallpaper.status === Image.Ready)
|
if (nextWallpaper.status === Image.Ready)
|
||||||
@@ -226,7 +215,7 @@ Variants {
|
|||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: !root.source || root.isColorSource || currentWallpaper.status === Image.Error
|
active: !root.source || root.isColorSource
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: DankBackdrop {
|
sourceComponent: DankBackdrop {
|
||||||
@@ -249,12 +238,6 @@ Variants {
|
|||||||
cache: true
|
cache: true
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
||||||
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(modelData.name))
|
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(modelData.name))
|
||||||
|
|
||||||
onStatusChanged: {
|
|
||||||
if (status === Image.Error) {
|
|
||||||
log.warn("failed to load active wallpaper for", modelData.name + ":", source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
@@ -270,10 +253,6 @@ Variants {
|
|||||||
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(modelData.name))
|
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(modelData.name))
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status === Image.Error) {
|
|
||||||
root.handleTransitionLoadError(source);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (status !== Image.Ready)
|
if (status !== Image.Ready)
|
||||||
return;
|
return;
|
||||||
if (!root.transitioning) {
|
if (!root.transitioning) {
|
||||||
@@ -350,6 +329,8 @@ Variants {
|
|||||||
root.useNextForEffect = false;
|
root.useNextForEffect = false;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Typography {
|
Typography {
|
||||||
text: DgopService.uptime ? I18n.tr("up", "uptime prefix, e.g. 'up 4h 2m'") + " " + DgopService.uptime.slice(3) : I18n.tr("Unknown")
|
text: DgopService.uptime ? I18n.tr("up") + " " + DgopService.uptime.slice(3) : I18n.tr("Unknown")
|
||||||
style: Typography.Style.Caption
|
style: Typography.Style.Caption
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ Item {
|
|||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
focusedScreenName = focusedWs?.monitor?.name || "";
|
focusedScreenName = focusedWs?.monitor?.name || "";
|
||||||
|
} else if (CompositorService.isDwl && DwlService.activeOutput) {
|
||||||
|
focusedScreenName = DwlService.activeOutput;
|
||||||
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
||||||
focusedScreenName = MangoService.activeOutput;
|
focusedScreenName = MangoService.activeOutput;
|
||||||
}
|
}
|
||||||
@@ -137,6 +139,8 @@ Item {
|
|||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
focusedScreenName = focusedWs?.monitor?.name || "";
|
focusedScreenName = focusedWs?.monitor?.name || "";
|
||||||
|
} else if (CompositorService.isDwl && DwlService.activeOutput) {
|
||||||
|
focusedScreenName = DwlService.activeOutput;
|
||||||
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
} else if (CompositorService.isMango && MangoService.activeOutput) {
|
||||||
focusedScreenName = MangoService.activeOutput;
|
focusedScreenName = MangoService.activeOutput;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -286,6 +286,9 @@ PanelWindow {
|
|||||||
|
|
||||||
readonly property bool isVertical: axis.isVertical
|
readonly property bool isVertical: axis.isVertical
|
||||||
|
|
||||||
|
property bool gothCornersEnabled: barConfig?.gothCornersEnabled ?? false
|
||||||
|
property real wingtipsRadius: barConfig?.gothCornerRadiusOverride ? (barConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
|
||||||
|
readonly property real _wingR: Math.max(0, wingtipsRadius)
|
||||||
readonly property color _surfaceContainer: Theme.surfaceContainer
|
readonly property color _surfaceContainer: Theme.surfaceContainer
|
||||||
readonly property string _barId: barConfig?.id ?? "default"
|
readonly property string _barId: barConfig?.id ?? "default"
|
||||||
property real _backgroundAlpha: barConfig?.transparency ?? 1.0
|
property real _backgroundAlpha: barConfig?.transparency ?? 1.0
|
||||||
@@ -297,30 +300,25 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
|
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
|
||||||
|
|
||||||
property string screenName: modelData.name
|
|
||||||
|
|
||||||
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
|
|
||||||
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
|
|
||||||
readonly property var renderBarConfig: SettingsData.effectiveBarConfigForRender(barConfig, usesFrameBarChrome)
|
|
||||||
|
|
||||||
property bool gothCornersEnabled: renderBarConfig?.gothCornersEnabled ?? false
|
|
||||||
property real wingtipsRadius: renderBarConfig?.gothCornerRadiusOverride ? (renderBarConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
|
|
||||||
readonly property real _wingR: Math.max(0, wingtipsRadius)
|
|
||||||
|
|
||||||
// Shadow buffer: extra window space for shadow to render beyond bar bounds
|
// Shadow buffer: extra window space for shadow to render beyond bar bounds
|
||||||
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (renderBarConfig?.shadowIntensity ?? 0) > 0
|
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (barConfig?.shadowIntensity ?? 0) > 0
|
||||||
readonly property real _shadowBuffer: {
|
readonly property real _shadowBuffer: {
|
||||||
if (!_shadowActive)
|
if (!_shadowActive)
|
||||||
return 0;
|
return 0;
|
||||||
const hasOverride = (renderBarConfig?.shadowIntensity ?? 0) > 0;
|
const hasOverride = (barConfig?.shadowIntensity ?? 0) > 0;
|
||||||
if (hasOverride) {
|
if (hasOverride) {
|
||||||
const blur = (renderBarConfig.shadowIntensity ?? 0) * 0.2;
|
const blur = (barConfig.shadowIntensity ?? 0) * 0.2;
|
||||||
const offset = blur * 0.5;
|
const offset = blur * 0.5;
|
||||||
return Theme.snap(Math.max(16, blur + offset + 8), _dpr);
|
return Theme.snap(Math.max(16, blur + offset + 8), _dpr);
|
||||||
}
|
}
|
||||||
return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr);
|
return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property string screenName: modelData.name
|
||||||
|
|
||||||
|
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
|
||||||
|
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
|
||||||
|
|
||||||
// Flatten/spacing collapse for maximized windows is only for frame-integrated layout.
|
// Flatten/spacing collapse for maximized windows is only for frame-integrated layout.
|
||||||
// When the bar draws its own pill, keep rounded corners and spacing like the dock.
|
// When the bar draws its own pill, keep rounded corners and spacing like the dock.
|
||||||
readonly property bool flattenForMaximizedWindow: !SettingsData.frameEnabled || usesFrameBarChrome
|
readonly property bool flattenForMaximizedWindow: !SettingsData.frameEnabled || usesFrameBarChrome
|
||||||
@@ -556,8 +554,8 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
|
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
|
||||||
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
|
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -709,14 +707,6 @@ PanelWindow {
|
|||||||
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
|
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
|
||||||
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
|
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
|
||||||
|
|
||||||
function containsGlobalPoint(gx, gy, padding) {
|
|
||||||
const pad = padding !== undefined ? padding : 16;
|
|
||||||
if (!inputMask.showing)
|
|
||||||
return false;
|
|
||||||
const topLeft = inputMask.mapToItem(null, 0, 0);
|
|
||||||
return gx >= topLeft.x - pad && gx < topLeft.x + inputMask.width + pad && gy >= topLeft.y - pad && gy < topLeft.y + inputMask.height + pad;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sectionRect(section, isCenter, _dep) {
|
function sectionRect(section, isCenter, _dep) {
|
||||||
if (!section)
|
if (!section)
|
||||||
return {
|
return {
|
||||||
@@ -962,7 +952,7 @@ PanelWindow {
|
|||||||
id: barBackground
|
id: barBackground
|
||||||
barWindow: barWindow
|
barWindow: barWindow
|
||||||
axis: axis
|
axis: axis
|
||||||
barConfig: barWindow.renderBarConfig
|
barConfig: barWindow.barConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -1018,7 +1008,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processWheel(wheel) {
|
onWheel: wheel => {
|
||||||
if (!(barConfig?.scrollEnabled ?? true) || actionInProgress) {
|
if (!(barConfig?.scrollEnabled ?? true) || actionInProgress) {
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
return;
|
return;
|
||||||
@@ -1087,8 +1077,6 @@ PanelWindow {
|
|||||||
|
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onWheel: wheel => processWheel(wheel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankBarContent {
|
DankBarContent {
|
||||||
@@ -1100,36 +1088,6 @@ PanelWindow {
|
|||||||
centerWidgetsModel: barWindow.centerWidgetsModel
|
centerWidgetsModel: barWindow.centerWidgetsModel
|
||||||
rightWidgetsModel: barWindow.rightWidgetsModel
|
rightWidgetsModel: barWindow.rightWidgetsModel
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: hoverPopoutArea
|
|
||||||
anchors.fill: parent
|
|
||||||
z: 1
|
|
||||||
hoverEnabled: barConfig?.hoverPopouts ?? false
|
|
||||||
enabled: hoverPopoutArea.hoverEnabled && !barWindow.clickThroughEnabled
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
propagateComposedEvents: true
|
|
||||||
|
|
||||||
property real lastGlobalX: 0
|
|
||||||
property real lastGlobalY: 0
|
|
||||||
|
|
||||||
onPositionChanged: mouse => {
|
|
||||||
const gp = mapToItem(null, mouse.x, mouse.y);
|
|
||||||
lastGlobalX = gp.x;
|
|
||||||
lastGlobalY = gp.y;
|
|
||||||
topBarContent.checkHoverPopout(gp.x, gp.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
onWheel: wheel => scrollArea.processWheel(wheel)
|
|
||||||
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
if (containsMouse)
|
|
||||||
return;
|
|
||||||
if (topBarContent.cursorOverHoverChain(lastGlobalX, lastGlobalY))
|
|
||||||
return;
|
|
||||||
topBarContent.closeHoverSurfaces();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ DankPopout {
|
|||||||
|
|
||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
|
|
||||||
readonly property bool isMango: CompositorService.isMango
|
// mango shares dwl's layout model; route to the right service.
|
||||||
|
readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango
|
||||||
|
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
|
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) {
|
||||||
triggerX = x;
|
triggerX = x;
|
||||||
@@ -35,8 +37,8 @@ DankPopout {
|
|||||||
onScreenChanged: updateOutputState()
|
onScreenChanged: updateOutputState()
|
||||||
|
|
||||||
function updateOutputState() {
|
function updateOutputState() {
|
||||||
if (screen && MangoService.available) {
|
if (screen && root.dwlSvc.available) {
|
||||||
outputState = MangoService.getOutputState(screen.name);
|
outputState = root.dwlSvc.getOutputState(screen.name);
|
||||||
} else {
|
} else {
|
||||||
outputState = null;
|
outputState = null;
|
||||||
}
|
}
|
||||||
@@ -82,7 +84,7 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: MangoService
|
target: DwlService
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
updateOutputState();
|
updateOutputState();
|
||||||
}
|
}
|
||||||
@@ -217,7 +219,7 @@ DankPopout {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: MangoService.layouts
|
model: root.dwlSvc.layouts
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
required property string modelData
|
required property string modelData
|
||||||
@@ -271,11 +273,11 @@ DankPopout {
|
|||||||
if (!root.triggerScreen) {
|
if (!root.triggerScreen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!MangoService.available) {
|
if (!root.dwlSvc.available) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MangoService.setLayout(root.triggerScreen.name, index);
|
root.dwlSvc.setLayout(root.triggerScreen.name, index);
|
||||||
root.close();
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ Loader {
|
|||||||
"cpuTemp": dgopAvailable,
|
"cpuTemp": dgopAvailable,
|
||||||
"gpuTemp": dgopAvailable,
|
"gpuTemp": dgopAvailable,
|
||||||
"network_speed_monitor": dgopAvailable,
|
"network_speed_monitor": dgopAvailable,
|
||||||
"layout": CompositorService.isMango && MangoService.available
|
"layout": (CompositorService.isDwl && DwlService.dwlAvailable) || (CompositorService.isMango && MangoService.available)
|
||||||
};
|
};
|
||||||
|
|
||||||
return widgetVisibility[widgetId] ?? true;
|
return widgetVisibility[widgetId] ?? true;
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ BasePill {
|
|||||||
signal toggleLayoutPopup
|
signal toggleLayoutPopup
|
||||||
|
|
||||||
// mango shares dwl's tag/layout model; route to the right service.
|
// mango shares dwl's tag/layout model; route to the right service.
|
||||||
readonly property bool isMango: CompositorService.isMango
|
readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango
|
||||||
|
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
||||||
|
|
||||||
visible: layout.isMango && MangoService.available
|
visible: layout.isDwlLike && layout.dwlSvc.available
|
||||||
|
|
||||||
property var outputState: parentScreen ? MangoService.getOutputState(parentScreen.name) : null
|
property var outputState: parentScreen ? layout.dwlSvc.getOutputState(parentScreen.name) : null
|
||||||
property string currentLayoutSymbol: outputState?.layoutSymbol || ""
|
property string currentLayoutSymbol: outputState?.layoutSymbol || ""
|
||||||
property int currentLayoutIndex: outputState?.layout || 0
|
property int currentLayoutIndex: outputState?.layout || 0
|
||||||
|
|
||||||
@@ -40,9 +41,9 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: MangoService
|
target: layout.dwlSvc
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
outputState = parentScreen ? MangoService.getOutputState(parentScreen.name) : null;
|
outputState = parentScreen ? layout.dwlSvc.getOutputState(parentScreen.name) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +101,13 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
if (!parentScreen || !MangoService.available || MangoService.layouts.length === 0) {
|
if (!parentScreen || !layout.dwlSvc.available || layout.dwlSvc.layouts.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentIndex = layout.currentLayoutIndex;
|
const currentIndex = layout.currentLayoutIndex;
|
||||||
const nextIndex = (currentIndex + 1) % MangoService.layouts.length;
|
const nextIndex = (currentIndex + 1) % layout.dwlSvc.layouts.length;
|
||||||
|
|
||||||
MangoService.setLayout(parentScreen.name, nextIndex);
|
layout.dwlSvc.setLayout(parentScreen.name, nextIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ BasePill {
|
|||||||
property string currentLayout: {
|
property string currentLayout: {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
return NiriService.getCurrentKeyboardLayoutName();
|
return NiriService.getCurrentKeyboardLayoutName();
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
return DwlService.currentKeyboardLayout;
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
return MangoService.currentKeyboardLayout;
|
return MangoService.currentKeyboardLayout;
|
||||||
}
|
}
|
||||||
@@ -207,6 +209,8 @@ BasePill {
|
|||||||
NiriService.cycleKeyboardLayout();
|
NiriService.cycleKeyboardLayout();
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
Quickshell.execDetached(["hyprctl", "switchxkblayout", root.hyprlandKeyboard, "next"]);
|
Quickshell.execDetached(["hyprctl", "switchxkblayout", root.hyprlandKeyboard, "next"]);
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
Quickshell.execDetached(["mmsg", "dispatch", "switch_keyboard_layout"]);
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
MangoService.cycleKeyboardLayout();
|
MangoService.cycleKeyboardLayout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
@@ -66,6 +66,8 @@ BasePill {
|
|||||||
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||||
} else if (CompositorService.isSway) {
|
} else if (CompositorService.isSway) {
|
||||||
|
|||||||
@@ -1922,53 +1922,4 @@ BasePill {
|
|||||||
return;
|
return;
|
||||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _trayLayoutRoot() {
|
|
||||||
const contentChildren = root.visualContent?.children;
|
|
||||||
if (!contentChildren || contentChildren.length === 0)
|
|
||||||
return null;
|
|
||||||
const contentRoot = contentChildren[0];
|
|
||||||
return contentRoot?.layoutLoader?.item || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _trayHitAtGlobalPoint(gx, gy) {
|
|
||||||
if (!root.visible || root.width <= 0 || root.height <= 0)
|
|
||||||
return null;
|
|
||||||
const local = root.mapFromItem(null, gx, gy);
|
|
||||||
if (local.x < 0 || local.y < 0 || local.x > root.width || local.y > root.height)
|
|
||||||
return null;
|
|
||||||
const layout = _trayLayoutRoot();
|
|
||||||
if (!layout)
|
|
||||||
return null;
|
|
||||||
const layoutLocal = layout.mapFromItem(null, gx, gy);
|
|
||||||
const children = layout.children || [];
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
if (!child.visible || child.width <= 0 || child.height <= 0)
|
|
||||||
continue;
|
|
||||||
if (layoutLocal.x < child.x || layoutLocal.x >= child.x + child.width)
|
|
||||||
continue;
|
|
||||||
if (layoutLocal.y < child.y || layoutLocal.y >= child.y + child.height)
|
|
||||||
continue;
|
|
||||||
if (child.trayItem)
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hoverTriggerAtGlobalPoint(gx, gy) {
|
|
||||||
const hit = _trayHitAtGlobalPoint(gx, gy);
|
|
||||||
if (!hit?.trayItem?.hasMenu)
|
|
||||||
return "";
|
|
||||||
return "tray-" + (hit.trayItem.id || hit.itemKey || "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function openHoverAtGlobalPoint(gx, gy) {
|
|
||||||
const hit = _trayHitAtGlobalPoint(gx, gy);
|
|
||||||
if (!hit?.trayItem?.hasMenu)
|
|
||||||
return false;
|
|
||||||
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
|
||||||
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ Item {
|
|||||||
property var hyprlandOverviewLoader: null
|
property var hyprlandOverviewLoader: null
|
||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
|
|
||||||
readonly property bool isMango: CompositorService.isMango
|
// mango shares dwl's tag model; route to the right service so one set of
|
||||||
|
// branches serves both.
|
||||||
|
readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango
|
||||||
|
readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService
|
||||||
|
|
||||||
readonly property real _leftMargin: {
|
readonly property real _leftMargin: {
|
||||||
if (isVertical)
|
if (isVertical)
|
||||||
@@ -77,8 +80,9 @@ Item {
|
|||||||
return NiriService.currentOutput || root.screenName;
|
return NiriService.currentOutput || root.screenName;
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
|
return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
return MangoService.activeOutput || root.screenName;
|
return root.dwlSvc.activeOutput || root.screenName;
|
||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
case "miracle":
|
case "miracle":
|
||||||
@@ -97,6 +101,7 @@ Item {
|
|||||||
switch (CompositorService.compositor) {
|
switch (CompositorService.compositor) {
|
||||||
case "niri":
|
case "niri":
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
@@ -123,6 +128,7 @@ Item {
|
|||||||
return getNiriActiveWorkspace();
|
return getNiriActiveWorkspace();
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
return getHyprlandActiveWorkspace();
|
return getHyprlandActiveWorkspace();
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
const activeTags = getDwlActiveTags();
|
const activeTags = getDwlActiveTags();
|
||||||
return activeTags.length > 0 ? activeTags[0] : -1;
|
return activeTags.length > 0 ? activeTags[0] : -1;
|
||||||
@@ -135,7 +141,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
property var dwlActiveTags: {
|
property var dwlActiveTags: {
|
||||||
if (root.isMango) {
|
if (root.isDwlLike) {
|
||||||
return getDwlActiveTags();
|
return getDwlActiveTags();
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -154,6 +160,9 @@ Item {
|
|||||||
case "hyprland":
|
case "hyprland":
|
||||||
baseList = getHyprlandWorkspaces();
|
baseList = getHyprlandWorkspaces();
|
||||||
break;
|
break;
|
||||||
|
case "dwl":
|
||||||
|
baseList = getDwlTags();
|
||||||
|
break;
|
||||||
case "mango":
|
case "mango":
|
||||||
if (root.mangoOverviewActive)
|
if (root.mangoOverviewActive)
|
||||||
return [];
|
return [];
|
||||||
@@ -293,7 +302,7 @@ Item {
|
|||||||
}
|
}
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
|
targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
if (typeof ws !== "object" || ws.tag === undefined) {
|
if (typeof ws !== "object" || ws.tag === undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -313,8 +322,8 @@ Item {
|
|||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
|
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
const output = MangoService.getOutputState(root.effectiveScreenName);
|
const output = root.dwlSvc.getOutputState(root.effectiveScreenName);
|
||||||
if (output && output.tags) {
|
if (output && output.tags) {
|
||||||
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
|
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
|
||||||
isActiveWs = tag ? (tag.state === 1) : false;
|
isActiveWs = tag ? (tag.state === 1) : false;
|
||||||
@@ -402,7 +411,7 @@ Item {
|
|||||||
"id": -1,
|
"id": -1,
|
||||||
"name": ""
|
"name": ""
|
||||||
};
|
};
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
placeholder = {
|
placeholder = {
|
||||||
"tag": -1
|
"tag": -1
|
||||||
};
|
};
|
||||||
@@ -484,11 +493,11 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDwlTags() {
|
function getDwlTags() {
|
||||||
if (!MangoService.available)
|
if (!root.dwlSvc.available)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
const targetScreen = root.effectiveScreenName;
|
const targetScreen = root.effectiveScreenName;
|
||||||
const output = MangoService.getOutputState(targetScreen);
|
const output = root.dwlSvc.getOutputState(targetScreen);
|
||||||
if (!output || !output.tags || output.tags.length === 0)
|
if (!output || !output.tags || output.tags.length === 0)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
@@ -501,7 +510,7 @@ Item {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleTagIndices = MangoService.getVisibleTags(targetScreen);
|
const visibleTagIndices = root.dwlSvc.getVisibleTags(targetScreen);
|
||||||
return visibleTagIndices.map(tagIndex => {
|
return visibleTagIndices.map(tagIndex => {
|
||||||
const tagData = output.tags.find(t => t.tag === tagIndex);
|
const tagData = output.tags.find(t => t.tag === tagIndex);
|
||||||
return {
|
return {
|
||||||
@@ -514,10 +523,10 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDwlActiveTags() {
|
function getDwlActiveTags() {
|
||||||
if (!MangoService.available)
|
if (!root.dwlSvc.available)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
return MangoService.getActiveTags(root.effectiveScreenName);
|
return root.dwlSvc.getActiveTags(root.effectiveScreenName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExtWorkspaceWorkspaces() {
|
function getExtWorkspaceWorkspaces() {
|
||||||
@@ -568,7 +577,7 @@ Item {
|
|||||||
return ws && ws.idx !== -1;
|
return ws && ws.idx !== -1;
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return ws && ws.id !== -1;
|
return ws && ws.id !== -1;
|
||||||
if (root.isMango)
|
if (root.isDwlLike)
|
||||||
return ws && ws.tag !== -1;
|
return ws && ws.tag !== -1;
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return ws && ws.num !== -1;
|
return ws && ws.num !== -1;
|
||||||
@@ -596,9 +605,10 @@ Item {
|
|||||||
HyprlandService.focusWorkspace(data.id);
|
HyprlandService.focusWorkspace(data.id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
if (data.tag !== undefined)
|
if (data.tag !== undefined)
|
||||||
MangoService.switchToTag(root.screenName, data.tag);
|
root.dwlSvc.switchToTag(root.screenName, data.tag);
|
||||||
break;
|
break;
|
||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
@@ -684,7 +694,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
const realWorkspaces = getRealWorkspaces();
|
const realWorkspaces = getRealWorkspaces();
|
||||||
if (realWorkspaces.length < 2) {
|
if (realWorkspaces.length < 2) {
|
||||||
return;
|
return;
|
||||||
@@ -698,7 +708,7 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MangoService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
|
root.dwlSvc.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
const realWorkspaces = getRealWorkspaces();
|
const realWorkspaces = getRealWorkspaces();
|
||||||
if (realWorkspaces.length < 2) {
|
if (realWorkspaces.length < 2) {
|
||||||
@@ -726,7 +736,7 @@ Item {
|
|||||||
return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
|
return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return modelData?.id || "";
|
return modelData?.id || "";
|
||||||
if (root.isMango)
|
if (root.isDwlLike)
|
||||||
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return modelData?.num || "";
|
return modelData?.num || "";
|
||||||
@@ -741,7 +751,7 @@ Item {
|
|||||||
isPlaceholder = modelData?.idx === -1;
|
isPlaceholder = modelData?.idx === -1;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
isPlaceholder = modelData?.id === -1;
|
isPlaceholder = modelData?.id === -1;
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
isPlaceholder = modelData?.tag === -1;
|
isPlaceholder = modelData?.tag === -1;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
isPlaceholder = modelData?.num === -1;
|
isPlaceholder = modelData?.num === -1;
|
||||||
@@ -776,7 +786,7 @@ Item {
|
|||||||
return getWorkspaceIndexFallback(modelData, index);
|
return getWorkspaceIndexFallback(modelData, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isDwlLike || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
||||||
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
|
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
|
||||||
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
|
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
|
||||||
|
|
||||||
@@ -1041,7 +1051,7 @@ Item {
|
|||||||
return !!(modelData && modelData.idx === root.currentWorkspace);
|
return !!(modelData && modelData.idx === root.currentWorkspace);
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return !!(modelData && modelData.id === root.currentWorkspace);
|
return !!(modelData && modelData.id === root.currentWorkspace);
|
||||||
if (root.isMango)
|
if (root.isDwlLike)
|
||||||
return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
|
return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return !!(modelData && modelData.num === root.currentWorkspace);
|
return !!(modelData && modelData.num === root.currentWorkspace);
|
||||||
@@ -1050,7 +1060,7 @@ Item {
|
|||||||
property bool isOccupied: {
|
property bool isOccupied: {
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
||||||
if (root.isMango)
|
if (root.isDwlLike)
|
||||||
return modelData.clients > 0;
|
return modelData.clients > 0;
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
||||||
@@ -1065,7 +1075,7 @@ Item {
|
|||||||
return !!(modelData && modelData.idx === -1);
|
return !!(modelData && modelData.idx === -1);
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return !!(modelData && modelData.id === -1);
|
return !!(modelData && modelData.id === -1);
|
||||||
if (root.isMango)
|
if (root.isDwlLike)
|
||||||
return !!(modelData && modelData.tag === -1);
|
return !!(modelData && modelData.tag === -1);
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return !!(modelData && modelData.num === -1);
|
return !!(modelData && modelData.num === -1);
|
||||||
@@ -1082,7 +1092,7 @@ Item {
|
|||||||
return modelData?.urgent ?? false;
|
return modelData?.urgent ?? false;
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri)
|
||||||
return loadedIsUrgent;
|
return loadedIsUrgent;
|
||||||
if (root.isMango)
|
if (root.isDwlLike)
|
||||||
return modelData?.state === 2;
|
return modelData?.state === 2;
|
||||||
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
||||||
return loadedIsUrgent;
|
return loadedIsUrgent;
|
||||||
@@ -1110,7 +1120,7 @@ Item {
|
|||||||
targetWorkspaceId = modelData?.id;
|
targetWorkspaceId = modelData?.id;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
targetWorkspaceId = modelData?.id;
|
targetWorkspaceId = modelData?.id;
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
targetWorkspaceId = modelData?.tag;
|
targetWorkspaceId = modelData?.tag;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
targetWorkspaceId = modelData?.num;
|
targetWorkspaceId = modelData?.num;
|
||||||
@@ -1373,8 +1383,8 @@ Item {
|
|||||||
}
|
}
|
||||||
} else if (CompositorService.isHyprland && modelData?.id) {
|
} else if (CompositorService.isHyprland && modelData?.id) {
|
||||||
HyprlandService.focusWorkspace(modelData.id);
|
HyprlandService.focusWorkspace(modelData.id);
|
||||||
} else if (root.isMango && modelData?.tag !== undefined) {
|
} else if (root.isDwlLike && modelData?.tag !== undefined) {
|
||||||
MangoService.switchToTag(root.screenName, modelData.tag);
|
root.dwlSvc.switchToTag(root.screenName, modelData.tag);
|
||||||
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
|
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
|
||||||
try {
|
try {
|
||||||
I3.dispatch(`workspace number ${modelData.num}`);
|
I3.dispatch(`workspace number ${modelData.num}`);
|
||||||
@@ -1385,8 +1395,8 @@ Item {
|
|||||||
NiriService.toggleOverview();
|
NiriService.toggleOverview();
|
||||||
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
|
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
|
||||||
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
|
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
|
||||||
} else if (root.isMango && modelData?.tag !== undefined) {
|
} else if (root.isDwlLike && modelData?.tag !== undefined) {
|
||||||
MangoService.toggleTag(root.screenName, modelData.tag);
|
root.dwlSvc.toggleTag(root.screenName, modelData.tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1410,7 +1420,7 @@ Item {
|
|||||||
wsData = modelData || null;
|
wsData = modelData || null;
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
} else if (root.isMango) {
|
} else if (root.isDwlLike) {
|
||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
wsData = modelData;
|
wsData = modelData;
|
||||||
@@ -1424,7 +1434,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SettingsData.showWorkspaceApps) {
|
if (SettingsData.showWorkspaceApps) {
|
||||||
if (root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
if (root.isDwlLike || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
||||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
|
||||||
} else if (CompositorService.isNiri) {
|
} else if (CompositorService.isNiri) {
|
||||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
|
||||||
@@ -1984,8 +1994,8 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: MangoService
|
target: root.dwlSvc
|
||||||
enabled: root.isMango
|
enabled: root.isDwlLike
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
delegateRoot.updateAllData();
|
delegateRoot.updateAllData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ Rectangle {
|
|||||||
text: {
|
text: {
|
||||||
const dateStr = Qt.formatDate(selectedDate, "MMM d");
|
const dateStr = Qt.formatDate(selectedDate, "MMM d");
|
||||||
if (selectedDateEvents && selectedDateEvents.length > 0) {
|
if (selectedDateEvents && selectedDateEvents.length > 0) {
|
||||||
const eventCount = selectedDateEvents.length === 1 ? I18n.tr("1 task", "task count next to a date") : I18n.tr("%1 tasks", "task count next to a date, %1 is the number of tasks").arg(selectedDateEvents.length);
|
const eventCount = selectedDateEvents.length === 1 ? I18n.tr("1 task") : selectedDateEvents.length + " " + I18n.tr("tasks");
|
||||||
return dateStr + " • " + eventCount;
|
return dateStr + " • " + eventCount;
|
||||||
}
|
}
|
||||||
return dateStr;
|
return dateStr;
|
||||||
@@ -775,7 +775,7 @@ Rectangle {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: {
|
text: {
|
||||||
if (!modelData || modelData.allDay) {
|
if (!modelData || modelData.allDay) {
|
||||||
return I18n.tr("All day", "calendar task with no specific time");
|
return I18n.tr("All day");
|
||||||
} else if (modelData.start && modelData.end) {
|
} else if (modelData.start && modelData.end) {
|
||||||
const timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
|
const timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
|
||||||
const startTime = Qt.formatTime(modelData.start, timeFormat);
|
const startTime = Qt.formatTime(modelData.start, timeFormat);
|
||||||
@@ -950,8 +950,9 @@ Rectangle {
|
|||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
// Hint placeholder text
|
||||||
Text {
|
Text {
|
||||||
text: I18n.tr("Add a task...", "placeholder in the new-task input field")
|
text: I18n.tr("Add a task...")
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||||
visible: !taskInput.text && !taskInput.activeFocus
|
visible: !taskInput.text && !taskInput.activeFocus
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ Card {
|
|||||||
return I18n.tr("on Niri");
|
return I18n.tr("on Niri");
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return I18n.tr("on Hyprland");
|
return I18n.tr("on Hyprland");
|
||||||
|
// technically they might not be on mangowc, but its what we support in the docs
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
return I18n.tr("on MangoWC");
|
||||||
if (CompositorService.isMango)
|
if (CompositorService.isMango)
|
||||||
return I18n.tr("on MangoWC");
|
return I18n.tr("on MangoWC");
|
||||||
if (CompositorService.isSway)
|
if (CompositorService.isSway)
|
||||||
@@ -98,7 +101,9 @@ Card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: DgopService.shortUptime ? I18n.tr("up", "uptime prefix, e.g. 'up 4h 2m'") + DgopService.shortUptime.slice(2) : I18n.tr("up", "uptime prefix, e.g. 'up 4h 2m'")
|
text: DgopService.shortUptime
|
||||||
|
? I18n.tr("up") + DgopService.shortUptime.slice(2)
|
||||||
|
: I18n.tr("up")
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -20,25 +20,17 @@ Card {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: !WeatherService.weather.available
|
visible: !WeatherService.weather.available
|
||||||
|
|
||||||
DankSpinner {
|
|
||||||
size: 24
|
|
||||||
visible: WeatherService.weather.loading
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "cloud_off"
|
name: "cloud_off"
|
||||||
size: 24
|
size: 24
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||||
visible: !WeatherService.weather.loading
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("No Weather")
|
text: WeatherService.weather.loading ? I18n.tr("Loading...") : I18n.tr("No Weather")
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
visible: !WeatherService.weather.loading
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -747,36 +747,16 @@ Variants {
|
|||||||
onHeightChanged: dock._syncDockChromeState()
|
onHeightChanged: dock._syncDockChromeState()
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
ConnectedShape {
|
||||||
id: dockConnectedChrome
|
|
||||||
visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive
|
visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive
|
||||||
readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
|
barSide: dock.connectedBarSide
|
||||||
readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
|
bodyWidth: dockBackground.width
|
||||||
readonly property real bodyRadius: dock.surfaceRadius
|
bodyHeight: dockBackground.height
|
||||||
readonly property bool barTop: dock.connectedBarSide === "top"
|
connectorRadius: Theme.connectedCornerRadius
|
||||||
readonly property bool barBottom: dock.connectedBarSide === "bottom"
|
surfaceRadius: dock.surfaceRadius
|
||||||
readonly property bool barLeft: dock.connectedBarSide === "left"
|
fillColor: dock.surfaceColor
|
||||||
readonly property bool barRight: dock.connectedBarSide === "right"
|
x: dockBackground.x - bodyX
|
||||||
|
y: dockBackground.y - bodyY
|
||||||
x: dockBackground.x - extraLeft
|
|
||||||
y: dockBackground.y - extraTop
|
|
||||||
width: dockBackground.width + extraLeft * 2
|
|
||||||
height: dockBackground.height + extraTop * 2
|
|
||||||
|
|
||||||
ShaderEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb")
|
|
||||||
|
|
||||||
property real widthPx: width
|
|
||||||
property real heightPx: height
|
|
||||||
property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a)
|
|
||||||
property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0)
|
|
||||||
property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0)
|
|
||||||
property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0)
|
|
||||||
property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height)
|
|
||||||
property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius)
|
|
||||||
property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc)
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||||
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||||
@@ -247,6 +247,8 @@ Item {
|
|||||||
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||||
} else if (CompositorService.isSway) {
|
} else if (CompositorService.isSway) {
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
// Frame perimeter ring with rounded cutout (SDF).
|
// Frame perimeter ring: the full window rectangle with a rounded-rectangle
|
||||||
|
// cutout, rendered as a signed-distance field with analytic antialiasing.
|
||||||
|
// One primitive: no full-output mask textures, no corner double-blend, crisp
|
||||||
|
// edges at any scale without an FBO.
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ PanelWindow {
|
|||||||
readonly property var _dockDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "dock")
|
readonly property var _dockDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "dock")
|
||||||
readonly property var _notifDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "notification")
|
readonly property var _notifDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "notification")
|
||||||
readonly property var _modalDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "modal")
|
readonly property var _modalDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "modal")
|
||||||
|
readonly property var _popoutState: ConnectedModeState.legacySurfaceState(win._screenName, "popout")
|
||||||
|
readonly property var _dockState: ConnectedModeState.legacySurfaceState(win._screenName, "dock")
|
||||||
|
readonly property var _notifState: ConnectedModeState.legacySurfaceState(win._screenName, "notification")
|
||||||
|
readonly property var _modalState: ConnectedModeState.legacySurfaceState(win._screenName, "modal")
|
||||||
|
|
||||||
readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen)
|
readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen)
|
||||||
readonly property string _barSide: {
|
readonly property string _barSide: {
|
||||||
@@ -64,7 +68,7 @@ PanelWindow {
|
|||||||
readonly property real _ccr: Theme.connectedCornerRadius
|
readonly property real _ccr: Theme.connectedCornerRadius
|
||||||
|
|
||||||
readonly property bool _popoutHorizontal: SurfaceGeometry.isHorizontal(win._popoutDescriptor.barSide)
|
readonly property bool _popoutHorizontal: SurfaceGeometry.isHorizontal(win._popoutDescriptor.barSide)
|
||||||
readonly property bool _modalHorizontal: SurfaceGeometry.isHorizontal(win._modalDescriptor.barSide)
|
readonly property bool _modalHorizontal: ConnectorGeometry.isHorizontal(win._modalState.barSide)
|
||||||
readonly property var _popoutBodyGeometry: SurfaceGeometry.animatedBodyRect(win._popoutDescriptor, win._dpr)
|
readonly property var _popoutBodyGeometry: SurfaceGeometry.animatedBodyRect(win._popoutDescriptor, win._dpr)
|
||||||
readonly property var _modalBodyGeometry: SurfaceGeometry.animatedBodyRect(win._modalDescriptor, win._dpr)
|
readonly property var _modalBodyGeometry: SurfaceGeometry.animatedBodyRect(win._modalDescriptor, win._dpr)
|
||||||
readonly property var _notifBodyGeometry: SurfaceGeometry.bodyRect(win._notifDescriptor, win._dpr)
|
readonly property var _notifBodyGeometry: SurfaceGeometry.bodyRect(win._notifDescriptor, win._dpr)
|
||||||
@@ -82,16 +86,18 @@ PanelWindow {
|
|||||||
readonly property real _dockConnectorRadiusValue: {
|
readonly property real _dockConnectorRadiusValue: {
|
||||||
if (!_dockBodyBlurAnchor._active)
|
if (!_dockBodyBlurAnchor._active)
|
||||||
return win._ccr;
|
return win._ccr;
|
||||||
const thickness = SurfaceGeometry.isVertical(win._dockDescriptor.barSide) ? _dockBodyBlurAnchor.width : _dockBodyBlurAnchor.height;
|
const thickness = (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? _dockBodyBlurAnchor.width : _dockBodyBlurAnchor.height;
|
||||||
const bodyRadius = win._dockBodyBlurRadiusValue;
|
const bodyRadius = win._dockBodyBlurRadiusValue;
|
||||||
const maxConnectorRadius = Math.max(0, thickness - bodyRadius - win._seamOverlap);
|
const maxConnectorRadius = Math.max(0, thickness - bodyRadius - win._seamOverlap);
|
||||||
return Math.max(0, Math.min(win._ccr, bodyRadius, maxConnectorRadius));
|
return Math.max(0, Math.min(win._ccr, bodyRadius, maxConnectorRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property real _notifSideUnderlapValue: SurfaceGeometry.isVertical(win._notifDescriptor.barSide) ? win._seamOverlap : 0
|
readonly property real _notifSideUnderlapValue: ConnectorGeometry.isVertical(win._notifState.barSide) ? win._seamOverlap : 0
|
||||||
readonly property real _notifStartUnderlapValue: win._notifDescriptor.omitStartConnector ? win._seamOverlap : 0
|
readonly property real _notifStartUnderlapValue: win._notifState.omitStartConnector ? win._seamOverlap : 0
|
||||||
readonly property real _notifEndUnderlapValue: win._notifDescriptor.omitEndConnector ? win._seamOverlap : 0
|
readonly property real _notifEndUnderlapValue: win._notifState.omitEndConnector ? win._seamOverlap : 0
|
||||||
|
|
||||||
|
// Theme.snap rounds to integer pixel: equal rounded values suppress
|
||||||
|
// downstream Changed during sub-pixel morph jitter.
|
||||||
readonly property var _popoutRadii: SurfaceGeometry.connectorRadii(win._popoutDescriptor, win._popoutBodyGeometry, win._ccr, win._surfaceRadius, win._dpr, false)
|
readonly property var _popoutRadii: SurfaceGeometry.connectorRadii(win._popoutDescriptor, win._popoutBodyGeometry, win._ccr, win._surfaceRadius, win._dpr, false)
|
||||||
readonly property real _effectivePopoutCcr: win._popoutRadii.near
|
readonly property real _effectivePopoutCcr: win._popoutRadii.near
|
||||||
readonly property real _effectivePopoutFarCcr: win._popoutRadii.far
|
readonly property real _effectivePopoutFarCcr: win._popoutRadii.far
|
||||||
@@ -123,8 +129,12 @@ PanelWindow {
|
|||||||
readonly property real _surfaceRadius: Theme.connectedSurfaceRadius
|
readonly property real _surfaceRadius: Theme.connectedSurfaceRadius
|
||||||
readonly property real _seamOverlap: Theme.hairline(win._dpr)
|
readonly property real _seamOverlap: Theme.hairline(win._dpr)
|
||||||
readonly property bool _disableLayer: Quickshell.env("DMS_DISABLE_LAYER") === "true" || Quickshell.env("DMS_DISABLE_LAYER") === "1"
|
readonly property bool _disableLayer: Quickshell.env("DMS_DISABLE_LAYER") === "true" || Quickshell.env("DMS_DISABLE_LAYER") === "1"
|
||||||
|
// Both elevation states render through the SDF shader; this only toggles
|
||||||
|
// the shadow term inside it.
|
||||||
readonly property bool _elevationShadow: win._connectedActive && Theme.elevationEnabled && !win._disableLayer
|
readonly property bool _elevationShadow: win._connectedActive && Theme.elevationEnabled && !win._disableLayer
|
||||||
// Pack active connected surfaces into four fixed SDF slots (near edges clamp to cutout).
|
// Active surfaces packed into four fixed SDF-shader slots. Each near (bar)
|
||||||
|
// edge is clamped to the cutout edge so the smooth-min connector attaches
|
||||||
|
// there; the per-corner smin radius is that corner's junction fillet.
|
||||||
readonly property var _sdfSlots: {
|
readonly property var _sdfSlots: {
|
||||||
const T = win.cutoutTopInset;
|
const T = win.cutoutTopInset;
|
||||||
const L = win.cutoutLeftInset;
|
const L = win.cutoutLeftInset;
|
||||||
@@ -152,7 +162,16 @@ PanelWindow {
|
|||||||
const s = src[i];
|
const s = src[i];
|
||||||
const b = clampNear(s.side, s.body);
|
const b = clampNear(s.side, s.body);
|
||||||
const active = b.width > 0 && b.height > 0 ? 1 : 0;
|
const active = b.width > 0 && b.height > 0 ? 1 : 0;
|
||||||
|
// Map start/end (left/top, right/bottom) onto corners
|
||||||
|
// (tl,tr,br,bl): bar-side corners take their near connector
|
||||||
|
// fillet, far corners always take the far fillet so a body
|
||||||
|
// meeting a perpendicular border joins with an arc (smin is
|
||||||
|
// inert when nothing is within k). A bar-side corner is sharp
|
||||||
|
// where its connector is present; an omitted connector makes
|
||||||
|
// its far corner sharp instead (the far-cap join).
|
||||||
const sc = s.radii.startCr, ec = s.radii.endCr;
|
const sc = s.radii.startCr, ec = s.radii.endCr;
|
||||||
|
// Clamp the far fillet to the body extent so it cannot flare
|
||||||
|
// back across a shallow body into the bar mid-animation.
|
||||||
const extent = (s.side === "top" || s.side === "bottom") ? b.height : b.width;
|
const extent = (s.side === "top" || s.side === "bottom") ? b.height : b.width;
|
||||||
const fc = Math.min(s.radii.farCr, extent);
|
const fc = Math.min(s.radii.farCr, extent);
|
||||||
const omitS = s.radii.farStartCr > 0;
|
const omitS = s.radii.farStartCr > 0;
|
||||||
@@ -160,6 +179,9 @@ PanelWindow {
|
|||||||
const bodyR = s.radii.surfaceRadius;
|
const bodyR = s.radii.surfaceRadius;
|
||||||
const nearS = omitS ? bodyR : 0, nearE = omitE ? bodyR : 0;
|
const nearS = omitS ? bodyR : 0, nearE = omitE ? bodyR : 0;
|
||||||
const farS = omitS ? 0 : bodyR, farE = omitE ? 0 : bodyR;
|
const farS = omitS ? 0 : bodyR, farE = omitE ? 0 : bodyR;
|
||||||
|
// An omitted bar-side corner sits flush against the border, so
|
||||||
|
// it keeps a nonzero fillet (a zero k hard-joins the coincident
|
||||||
|
// edges and shows a half-coverage hairline along the seam).
|
||||||
const kS = omitS ? fc : sc, kE = omitE ? fc : ec;
|
const kS = omitS ? fc : sc, kE = omitE ? fc : ec;
|
||||||
let ks, cr;
|
let ks, cr;
|
||||||
if (s.side === "top") {
|
if (s.side === "top") {
|
||||||
@@ -203,6 +225,8 @@ PanelWindow {
|
|||||||
return Math.max(0, Math.min(requested, maxRadius));
|
return Math.max(0, Math.min(requested, maxRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pins every surface blur region to zero while frame blur cannot be
|
||||||
|
// consumed, so animations stop dirtying the region tree.
|
||||||
readonly property bool _blurSurfacesActive: BlurService.enabled && SettingsData.frameBlurEnabled && win._frameActive
|
readonly property bool _blurSurfacesActive: BlurService.enabled && SettingsData.frameBlurEnabled && win._frameActive
|
||||||
readonly property int _blurCutoutCompensation: SettingsData.frameOpacity <= 0.2 ? 1 : 0
|
readonly property int _blurCutoutCompensation: SettingsData.frameOpacity <= 0.2 ? 1 : 0
|
||||||
readonly property int _blurCutoutLeft: Math.max(0, win.cutoutLeftInset - win._blurCutoutCompensation)
|
readonly property int _blurCutoutLeft: Math.max(0, win.cutoutLeftInset - win._blurCutoutCompensation)
|
||||||
@@ -215,6 +239,9 @@ PanelWindow {
|
|||||||
return Math.max(0, Math.min(requested, maxRadius));
|
return Math.max(0, Math.min(requested, maxRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blur regions bind rounded integer pixels directly: equal rounded values
|
||||||
|
// suppress Changed during sub-pixel motion, and inactive children pin to
|
||||||
|
// all-zero so the region build skips them.
|
||||||
QtObject {
|
QtObject {
|
||||||
id: _notifBodyBlurAnchor
|
id: _notifBodyBlurAnchor
|
||||||
|
|
||||||
@@ -232,6 +259,7 @@ PanelWindow {
|
|||||||
width: win._windowRegionWidth
|
width: win._windowRegionWidth
|
||||||
height: win._windowRegionHeight
|
height: win._windowRegionHeight
|
||||||
|
|
||||||
|
// Frame cutout (always active when frame is on)
|
||||||
Region {
|
Region {
|
||||||
id: _blurCutout
|
id: _blurCutout
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -256,7 +284,7 @@ PanelWindow {
|
|||||||
Region {
|
Region {
|
||||||
id: _popoutBodyBlurCap
|
id: _popoutBodyBlurCap
|
||||||
|
|
||||||
readonly property string _side: win._popoutDescriptor.barSide
|
readonly property string _side: win._popoutState.barSide
|
||||||
readonly property real _capThickness: win._popoutBlurCapThickness()
|
readonly property real _capThickness: win._popoutBlurCapThickness()
|
||||||
readonly property bool _active: _popoutBodyBlurAnchor._active && _capThickness > 0 && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0
|
readonly property bool _active: _popoutBodyBlurAnchor._active && _capThickness > 0 && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0
|
||||||
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capThickness, _popoutBodyBlurAnchor.width)) : _popoutBodyBlurAnchor.width
|
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capThickness, _popoutBodyBlurAnchor.width)) : _popoutBodyBlurAnchor.width
|
||||||
@@ -283,7 +311,7 @@ PanelWindow {
|
|||||||
id: _popoutLeftConnectorCutout
|
id: _popoutLeftConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0
|
readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutDescriptor.barSide, "left")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutState.barSide, "left")
|
||||||
readonly property real _radius: win._popoutConnectorRadiusLeft
|
readonly property real _radius: win._popoutConnectorRadiusLeft
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -310,7 +338,7 @@ PanelWindow {
|
|||||||
id: _popoutRightConnectorCutout
|
id: _popoutRightConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0
|
readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutDescriptor.barSide, "right")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutState.barSide, "right")
|
||||||
readonly property real _radius: win._popoutConnectorRadiusRight
|
readonly property real _radius: win._popoutConnectorRadiusRight
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -361,8 +389,8 @@ PanelWindow {
|
|||||||
id: _popoutFarStartConnectorCutout
|
id: _popoutFarStartConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _popoutFarStartConnectorBlurAnchor.width > 0 && _popoutFarStartConnectorBlurAnchor.height > 0
|
readonly property bool _active: _popoutFarStartConnectorBlurAnchor.width > 0 && _popoutFarStartConnectorBlurAnchor.height > 0
|
||||||
readonly property string _barSide: win._farConnectorBarSide(win._popoutDescriptor.barSide, "left")
|
readonly property string _barSide: win._farConnectorBarSide(win._popoutState.barSide, "left")
|
||||||
readonly property string _placement: win._farConnectorPlacement(win._popoutDescriptor.barSide, "left")
|
readonly property string _placement: win._farConnectorPlacement(win._popoutState.barSide, "left")
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
||||||
readonly property real _radius: win._effectivePopoutFarStartCcr
|
readonly property real _radius: win._effectivePopoutFarStartCcr
|
||||||
|
|
||||||
@@ -390,8 +418,8 @@ PanelWindow {
|
|||||||
id: _popoutFarEndConnectorCutout
|
id: _popoutFarEndConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _popoutFarEndConnectorBlurAnchor.width > 0 && _popoutFarEndConnectorBlurAnchor.height > 0
|
readonly property bool _active: _popoutFarEndConnectorBlurAnchor.width > 0 && _popoutFarEndConnectorBlurAnchor.height > 0
|
||||||
readonly property string _barSide: win._farConnectorBarSide(win._popoutDescriptor.barSide, "right")
|
readonly property string _barSide: win._farConnectorBarSide(win._popoutState.barSide, "right")
|
||||||
readonly property string _placement: win._farConnectorPlacement(win._popoutDescriptor.barSide, "right")
|
readonly property string _placement: win._farConnectorPlacement(win._popoutState.barSide, "right")
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
||||||
readonly property real _radius: win._effectivePopoutFarEndCcr
|
readonly property real _radius: win._effectivePopoutFarEndCcr
|
||||||
|
|
||||||
@@ -418,7 +446,7 @@ PanelWindow {
|
|||||||
Region {
|
Region {
|
||||||
id: _dockBodyBlurCap
|
id: _dockBodyBlurCap
|
||||||
|
|
||||||
readonly property string _side: win._dockDescriptor.barSide
|
readonly property string _side: win._dockState.barSide
|
||||||
readonly property bool _active: _dockBodyBlurAnchor._active && _dockBodyBlurAnchor.width > 0 && _dockBodyBlurAnchor.height > 0
|
readonly property bool _active: _dockBodyBlurAnchor._active && _dockBodyBlurAnchor.width > 0 && _dockBodyBlurAnchor.height > 0
|
||||||
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.width)) : _dockBodyBlurAnchor.width
|
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.width)) : _dockBodyBlurAnchor.width
|
||||||
readonly property int _capHeight: (_side === "top" || _side === "bottom") ? Math.round(Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.height)) : _dockBodyBlurAnchor.height
|
readonly property int _capHeight: (_side === "top" || _side === "bottom") ? Math.round(Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.height)) : _dockBodyBlurAnchor.height
|
||||||
@@ -443,7 +471,7 @@ PanelWindow {
|
|||||||
id: _dockLeftConnectorCutout
|
id: _dockLeftConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _dockLeftConnectorBlurAnchor.width > 0 && _dockLeftConnectorBlurAnchor.height > 0
|
readonly property bool _active: _dockLeftConnectorBlurAnchor.width > 0 && _dockLeftConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockDescriptor.barSide, "left")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockState.barSide, "left")
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
radius: win._dockConnectorRadiusValue
|
radius: win._dockConnectorRadiusValue
|
||||||
@@ -468,7 +496,7 @@ PanelWindow {
|
|||||||
id: _dockRightConnectorCutout
|
id: _dockRightConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _dockRightConnectorBlurAnchor.width > 0 && _dockRightConnectorBlurAnchor.height > 0
|
readonly property bool _active: _dockRightConnectorBlurAnchor.width > 0 && _dockRightConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockDescriptor.barSide, "right")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockState.barSide, "right")
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
radius: win._dockConnectorRadiusValue
|
radius: win._dockConnectorRadiusValue
|
||||||
@@ -494,7 +522,7 @@ PanelWindow {
|
|||||||
Region {
|
Region {
|
||||||
id: _notifBodyBlurCap
|
id: _notifBodyBlurCap
|
||||||
|
|
||||||
readonly property string _side: win._notifDescriptor.barSide
|
readonly property string _side: win._notifState.barSide
|
||||||
readonly property real _capRadius: win._effectiveNotifMaxCcr
|
readonly property real _capRadius: win._effectiveNotifMaxCcr
|
||||||
readonly property bool _active: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 && _capRadius > 0
|
readonly property bool _active: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 && _capRadius > 0
|
||||||
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capRadius, _notifBodySceneBlurAnchor.width)) : _notifBodySceneBlurAnchor.width
|
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capRadius, _notifBodySceneBlurAnchor.width)) : _notifBodySceneBlurAnchor.width
|
||||||
@@ -521,7 +549,7 @@ PanelWindow {
|
|||||||
id: _notifLeftConnectorCutout
|
id: _notifLeftConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0
|
readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifDescriptor.barSide, "left")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifState.barSide, "left")
|
||||||
readonly property real _radius: win._notifConnectorRadiusLeft
|
readonly property real _radius: win._notifConnectorRadiusLeft
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -548,7 +576,7 @@ PanelWindow {
|
|||||||
id: _notifRightConnectorCutout
|
id: _notifRightConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0
|
readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifDescriptor.barSide, "right")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifState.barSide, "right")
|
||||||
readonly property real _radius: win._notifConnectorRadiusRight
|
readonly property real _radius: win._notifConnectorRadiusRight
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -599,8 +627,8 @@ PanelWindow {
|
|||||||
id: _notifFarStartConnectorCutout
|
id: _notifFarStartConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _notifFarStartConnectorBlurAnchor.width > 0 && _notifFarStartConnectorBlurAnchor.height > 0
|
readonly property bool _active: _notifFarStartConnectorBlurAnchor.width > 0 && _notifFarStartConnectorBlurAnchor.height > 0
|
||||||
readonly property string _barSide: win._farConnectorBarSide(win._notifDescriptor.barSide, "left")
|
readonly property string _barSide: win._farConnectorBarSide(win._notifState.barSide, "left")
|
||||||
readonly property string _placement: win._farConnectorPlacement(win._notifDescriptor.barSide, "left")
|
readonly property string _placement: win._farConnectorPlacement(win._notifState.barSide, "left")
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
||||||
readonly property real _radius: win._effectiveNotifFarStartCcr
|
readonly property real _radius: win._effectiveNotifFarStartCcr
|
||||||
|
|
||||||
@@ -628,8 +656,8 @@ PanelWindow {
|
|||||||
id: _notifFarEndConnectorCutout
|
id: _notifFarEndConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _notifFarEndConnectorBlurAnchor.width > 0 && _notifFarEndConnectorBlurAnchor.height > 0
|
readonly property bool _active: _notifFarEndConnectorBlurAnchor.width > 0 && _notifFarEndConnectorBlurAnchor.height > 0
|
||||||
readonly property string _barSide: win._farConnectorBarSide(win._notifDescriptor.barSide, "right")
|
readonly property string _barSide: win._farConnectorBarSide(win._notifState.barSide, "right")
|
||||||
readonly property string _placement: win._farConnectorPlacement(win._notifDescriptor.barSide, "right")
|
readonly property string _placement: win._farConnectorPlacement(win._notifState.barSide, "right")
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
||||||
readonly property real _radius: win._effectiveNotifFarEndCcr
|
readonly property real _radius: win._effectiveNotifFarEndCcr
|
||||||
|
|
||||||
@@ -656,7 +684,7 @@ PanelWindow {
|
|||||||
Region {
|
Region {
|
||||||
id: _modalBodyBlurCap
|
id: _modalBodyBlurCap
|
||||||
|
|
||||||
readonly property string _side: win._modalDescriptor.barSide
|
readonly property string _side: win._modalState.barSide
|
||||||
readonly property real _capThickness: win._modalBlurCapThickness()
|
readonly property real _capThickness: win._modalBlurCapThickness()
|
||||||
readonly property bool _active: _modalBodyBlurAnchor._active && _capThickness > 0 && _modalBodyBlurAnchor.width > 0 && _modalBodyBlurAnchor.height > 0
|
readonly property bool _active: _modalBodyBlurAnchor._active && _capThickness > 0 && _modalBodyBlurAnchor.width > 0 && _modalBodyBlurAnchor.height > 0
|
||||||
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capThickness, _modalBodyBlurAnchor.width)) : _modalBodyBlurAnchor.width
|
readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capThickness, _modalBodyBlurAnchor.width)) : _modalBodyBlurAnchor.width
|
||||||
@@ -683,7 +711,7 @@ PanelWindow {
|
|||||||
id: _modalLeftConnectorCutout
|
id: _modalLeftConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0
|
readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalDescriptor.barSide, "left")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalState.barSide, "left")
|
||||||
readonly property real _radius: win._modalConnectorRadiusLeft
|
readonly property real _radius: win._modalConnectorRadiusLeft
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -710,7 +738,7 @@ PanelWindow {
|
|||||||
id: _modalRightConnectorCutout
|
id: _modalRightConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0
|
readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalDescriptor.barSide, "right")
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalState.barSide, "right")
|
||||||
readonly property real _radius: win._modalConnectorRadiusRight
|
readonly property real _radius: win._modalConnectorRadiusRight
|
||||||
|
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
@@ -761,8 +789,8 @@ PanelWindow {
|
|||||||
id: _modalFarStartConnectorCutout
|
id: _modalFarStartConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _modalFarStartConnectorBlurAnchor.width > 0 && _modalFarStartConnectorBlurAnchor.height > 0
|
readonly property bool _active: _modalFarStartConnectorBlurAnchor.width > 0 && _modalFarStartConnectorBlurAnchor.height > 0
|
||||||
readonly property string _barSide: win._farConnectorBarSide(win._modalDescriptor.barSide, "left")
|
readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "left")
|
||||||
readonly property string _placement: win._farConnectorPlacement(win._modalDescriptor.barSide, "left")
|
readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "left")
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
||||||
readonly property real _radius: win._effectiveModalFarStartCcr
|
readonly property real _radius: win._effectiveModalFarStartCcr
|
||||||
|
|
||||||
@@ -790,8 +818,8 @@ PanelWindow {
|
|||||||
id: _modalFarEndConnectorCutout
|
id: _modalFarEndConnectorCutout
|
||||||
|
|
||||||
readonly property bool _active: _modalFarEndConnectorBlurAnchor.width > 0 && _modalFarEndConnectorBlurAnchor.height > 0
|
readonly property bool _active: _modalFarEndConnectorBlurAnchor.width > 0 && _modalFarEndConnectorBlurAnchor.height > 0
|
||||||
readonly property string _barSide: win._farConnectorBarSide(win._modalDescriptor.barSide, "right")
|
readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "right")
|
||||||
readonly property string _placement: win._farConnectorPlacement(win._modalDescriptor.barSide, "right")
|
readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "right")
|
||||||
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement)
|
||||||
readonly property real _radius: win._effectiveModalFarEndCcr
|
readonly property real _radius: win._effectiveModalFarEndCcr
|
||||||
|
|
||||||
@@ -805,8 +833,9 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notif body scene rect, accounting for start/end/side underlaps per bar orientation.
|
||||||
function _notifBodyScene() {
|
function _notifBodyScene() {
|
||||||
const isHoriz = SurfaceGeometry.isHorizontal(win._notifDescriptor.barSide);
|
const isHoriz = ConnectorGeometry.isHorizontal(win._notifState.barSide);
|
||||||
const start = win._notifStartUnderlapValue;
|
const start = win._notifStartUnderlapValue;
|
||||||
const end = win._notifEndUnderlapValue;
|
const end = win._notifEndUnderlapValue;
|
||||||
const side = win._notifSideUnderlapValue;
|
const side = win._notifSideUnderlapValue;
|
||||||
@@ -819,7 +848,7 @@ PanelWindow {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"x": _notifBodyBlurAnchor.x - (win._notifDescriptor.barSide === "left" ? side : 0),
|
"x": _notifBodyBlurAnchor.x - (win._notifState.barSide === "left" ? side : 0),
|
||||||
"y": _notifBodyBlurAnchor.y - start,
|
"y": _notifBodyBlurAnchor.y - start,
|
||||||
"width": _notifBodyBlurAnchor.width + side,
|
"width": _notifBodyBlurAnchor.width + side,
|
||||||
"height": _notifBodyBlurAnchor.height + start + end
|
"height": _notifBodyBlurAnchor.height + start + end
|
||||||
@@ -836,10 +865,13 @@ PanelWindow {
|
|||||||
return Math.max(0, Math.min(win._effectivePopoutMaxCcr, extent - win._surfaceRadius));
|
return Math.max(0, Math.min(win._effectivePopoutMaxCcr, extent - win._surfaceRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active connected surfaces fed to the unified silhouette path. Raw animated
|
||||||
|
// body rects (no seam/fill overlap); the builder anchors each to the cutout
|
||||||
|
// edge. Connector radii use the same per-surface helpers as the blur regions.
|
||||||
function _unifiedSurfaces() {
|
function _unifiedSurfaces() {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
const p = win._popoutBodyGeometry;
|
const p = win._popoutBodyGeometry;
|
||||||
if (win._popoutDescriptor.visible && win._popoutDescriptor.screenName === win._screenName && p.width > 0 && p.height > 0)
|
if (win._popoutDescriptor.visible && win._popoutState.screen === win._screenName && p.width > 0 && p.height > 0)
|
||||||
arr.push({
|
arr.push({
|
||||||
"side": win._popoutDescriptor.barSide,
|
"side": win._popoutDescriptor.barSide,
|
||||||
"body": {"x": p.x, "y": p.y, "width": p.width, "height": p.height},
|
"body": {"x": p.x, "y": p.y, "width": p.width, "height": p.height},
|
||||||
@@ -943,6 +975,8 @@ PanelWindow {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Coalesce bursts of settings-change signals into a single _buildBlur() call
|
||||||
|
// on the next event loop tick.
|
||||||
DeferredAction {
|
DeferredAction {
|
||||||
id: blurRebuildAction
|
id: blurRebuildAction
|
||||||
onTriggered: win._runBlurRebuild()
|
onTriggered: win._runBlurRebuild()
|
||||||
@@ -1066,6 +1100,10 @@ PanelWindow {
|
|||||||
cutoutRadius: win.cutoutRadius
|
cutoutRadius: win.cutoutRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The entire connected silhouette (frame ring + every active chrome) as one
|
||||||
|
// SDF in a fragment shader. Analytic fwidth AA → crisp at any scale, no FBO;
|
||||||
|
// the smooth-min radius is the connector. The elevation shadow is derived
|
||||||
|
// from the same distance field, so elevation-on needs no grouping layer.
|
||||||
ShaderEffect {
|
ShaderEffect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: win._connectedActive
|
visible: win._connectedActive
|
||||||
|
|||||||
@@ -753,46 +753,9 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
TextInput {
|
||||||
id: passwordField
|
id: passwordField
|
||||||
|
|
||||||
property string text: root.passwordBuffer
|
|
||||||
property int cursorPosition: text.length
|
|
||||||
|
|
||||||
signal accepted()
|
|
||||||
|
|
||||||
function clampCursorPosition() {
|
|
||||||
cursorPosition = Math.max(0, Math.min(cursorPosition, text.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
text = "";
|
|
||||||
cursorPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertText(value) {
|
|
||||||
if (value.length === 0)
|
|
||||||
return;
|
|
||||||
clampCursorPosition();
|
|
||||||
text = text.slice(0, cursorPosition) + value + text.slice(cursorPosition);
|
|
||||||
cursorPosition += value.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function backspace() {
|
|
||||||
clampCursorPosition();
|
|
||||||
if (cursorPosition === 0)
|
|
||||||
return;
|
|
||||||
text = text.slice(0, cursorPosition - 1) + text.slice(cursorPosition);
|
|
||||||
cursorPosition -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrintableText(value) {
|
|
||||||
if (value.length === 0)
|
|
||||||
return false;
|
|
||||||
const code = value.charCodeAt(0);
|
|
||||||
return code >= 0x20 && code !== 0x7f;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
|
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
|
||||||
anchors.rightMargin: {
|
anchors.rightMargin: {
|
||||||
@@ -818,6 +781,7 @@ Item {
|
|||||||
focus: true
|
focus: true
|
||||||
enabled: !demoMode
|
enabled: !demoMode
|
||||||
activeFocusOnTab: !demoMode
|
activeFocusOnTab: !demoMode
|
||||||
|
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (!demoMode) {
|
if (!demoMode) {
|
||||||
root.passwordBuffer = text;
|
root.passwordBuffer = text;
|
||||||
@@ -845,8 +809,6 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pam.passwd.active) {
|
if (pam.passwd.active) {
|
||||||
@@ -854,23 +816,6 @@ Item {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
||||||
accepted();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === Qt.Key_Backspace) {
|
|
||||||
backspace();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPrintableText(event.text)) {
|
|
||||||
insertText(event.text);
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -904,17 +849,6 @@ Item {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root
|
|
||||||
|
|
||||||
function onPasswordBufferChanged() {
|
|
||||||
if (passwordField.text === root.passwordBuffer)
|
|
||||||
return;
|
|
||||||
passwordField.text = root.passwordBuffer;
|
|
||||||
passwordField.cursorPosition = passwordField.text.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardController {
|
KeyboardController {
|
||||||
|
|||||||
@@ -721,51 +721,6 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout progress bar: drains as the dismiss timer runs; inset by
|
|
||||||
// the corner radius and frozen while hovered or during exit.
|
|
||||||
Rectangle {
|
|
||||||
id: timeoutBar
|
|
||||||
|
|
||||||
readonly property bool active: SettingsData.notificationShowTimeoutBar && notificationData && notificationData.timer && notificationData.timer.interval > 0
|
|
||||||
property real progress: 1
|
|
||||||
readonly property real surfaceRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
|
||||||
|
|
||||||
visible: active && progress > 0
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: surfaceRadius
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
width: Math.max(0, parent.width - surfaceRadius * 2) * progress
|
|
||||||
height: Math.max(2, Theme.snap(3, win.dpr))
|
|
||||||
radius: height / 2
|
|
||||||
z: 50
|
|
||||||
opacity: 0.9
|
|
||||||
color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.error : Theme.primary
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: progressAnim
|
|
||||||
target: timeoutBar
|
|
||||||
property: "progress"
|
|
||||||
from: 1
|
|
||||||
to: 0
|
|
||||||
duration: (notificationData && notificationData.timer && notificationData.timer.interval > 0) ? notificationData.timer.interval : 5000
|
|
||||||
running: timeoutBar.active && notificationData && notificationData.timer && notificationData.timer.running && !win.exiting
|
|
||||||
easing.type: Easing.Linear
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset to full on every (re)start, including an in-place
|
|
||||||
// restart on a deduped notification (running stays true, so the
|
|
||||||
// bound animation alone wouldn't re-fire).
|
|
||||||
Connections {
|
|
||||||
target: timeoutBar.active ? notificationData.timer : null
|
|
||||||
function onRunningChanged() {
|
|
||||||
if (notificationData && notificationData.timer && notificationData.timer.running && !win.exiting) {
|
|
||||||
timeoutBar.progress = 1;
|
|
||||||
progressAnim.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
@@ -916,11 +871,10 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: notificationData ? (notificationData.summary || "") : ""
|
text: notificationData ? (notificationData.summary || "") : ""
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
font.pixelSize: SettingsData.notificationSummaryFontSize || Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
width: parent.width
|
width: parent.width
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -936,7 +890,7 @@ PanelWindow {
|
|||||||
text: notificationData ? (notificationData.htmlBody || "") : ""
|
text: notificationData ? (notificationData.htmlBody || "") : ""
|
||||||
textFormat: Text.StyledText
|
textFormat: Text.StyledText
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
font.pixelSize: SettingsData.notificationBodyFontSize || Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
width: parent.width
|
width: parent.width
|
||||||
elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
|
elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
|||||||
@@ -330,24 +330,6 @@ Item {
|
|||||||
pluginPopout.toggle();
|
pluginPopout.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerHoverPopout(widgetHostId) {
|
|
||||||
if (pillClickAction) {
|
|
||||||
triggerPopout();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!hasPopout)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const pill = isVertical ? verticalPill : horizontalPill;
|
|
||||||
const globalPos = pill.visualContent.mapToItem(null, 0, 0);
|
|
||||||
const currentScreen = parentScreen || Screen;
|
|
||||||
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, pill.visualWidth, barSpacing, barPosition, barConfig);
|
|
||||||
|
|
||||||
pluginPopout.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barThickness, barSpacing, barConfig);
|
|
||||||
PopoutManager.requestHoverPopout(pluginPopout, undefined, widgetHostId || pluginId);
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginPopout {
|
PluginPopout {
|
||||||
id: pluginPopout
|
id: pluginPopout
|
||||||
contentWidth: root.popoutWidth
|
contentWidth: root.popoutWidth
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Item {
|
|||||||
property bool isSway: CompositorService.isSway
|
property bool isSway: CompositorService.isSway
|
||||||
property bool isScroll: CompositorService.isScroll
|
property bool isScroll: CompositorService.isScroll
|
||||||
property bool isMiracle: CompositorService.isMiracle
|
property bool isMiracle: CompositorService.isMiracle
|
||||||
property bool isMango: CompositorService.isMango
|
property bool isDwl: CompositorService.isDwl || CompositorService.isMango
|
||||||
property bool isLabwc: CompositorService.isLabwc
|
property bool isLabwc: CompositorService.isLabwc
|
||||||
|
|
||||||
property string compositorName: {
|
property string compositorName: {
|
||||||
@@ -27,7 +27,7 @@ Item {
|
|||||||
return "scroll";
|
return "scroll";
|
||||||
if (isMiracle)
|
if (isMiracle)
|
||||||
return "miracle";
|
return "miracle";
|
||||||
if (isMango)
|
if (isDwl)
|
||||||
return "mangowc";
|
return "mangowc";
|
||||||
if (isLabwc)
|
if (isLabwc)
|
||||||
return "labwc";
|
return "labwc";
|
||||||
@@ -43,7 +43,7 @@ Item {
|
|||||||
return "/assets/sway.svg";
|
return "/assets/sway.svg";
|
||||||
if (isMiracle)
|
if (isMiracle)
|
||||||
return "/assets/miraclewm.svg";
|
return "/assets/miraclewm.svg";
|
||||||
if (isMango)
|
if (isDwl)
|
||||||
return "/assets/mango.png";
|
return "/assets/mango.png";
|
||||||
if (isLabwc)
|
if (isLabwc)
|
||||||
return "/assets/labwc.png";
|
return "/assets/labwc.png";
|
||||||
@@ -59,7 +59,7 @@ Item {
|
|||||||
return "https://github.com/dawsers/scroll";
|
return "https://github.com/dawsers/scroll";
|
||||||
if (isMiracle)
|
if (isMiracle)
|
||||||
return "https://github.com/miracle-wm-org/miracle-wm";
|
return "https://github.com/miracle-wm-org/miracle-wm";
|
||||||
if (isMango)
|
if (isDwl)
|
||||||
return "https://github.com/DreamMaoMao/mangowc";
|
return "https://github.com/DreamMaoMao/mangowc";
|
||||||
if (isLabwc)
|
if (isLabwc)
|
||||||
return "https://labwc.github.io/";
|
return "https://labwc.github.io/";
|
||||||
@@ -75,7 +75,7 @@ Item {
|
|||||||
return I18n.tr("Scroll GitHub");
|
return I18n.tr("Scroll GitHub");
|
||||||
if (isMiracle)
|
if (isMiracle)
|
||||||
return I18n.tr("Scroll GitHub");
|
return I18n.tr("Scroll GitHub");
|
||||||
if (isMango)
|
if (isDwl)
|
||||||
return I18n.tr("mangowc GitHub");
|
return I18n.tr("mangowc GitHub");
|
||||||
if (isLabwc)
|
if (isLabwc)
|
||||||
return I18n.tr("LabWC Website");
|
return I18n.tr("LabWC Website");
|
||||||
@@ -88,7 +88,7 @@ Item {
|
|||||||
property string compositorDiscordUrl: {
|
property string compositorDiscordUrl: {
|
||||||
if (isHyprland)
|
if (isHyprland)
|
||||||
return "https://discord.com/invite/hQ9XvMUjjr";
|
return "https://discord.com/invite/hQ9XvMUjjr";
|
||||||
if (isMango)
|
if (isDwl)
|
||||||
return "https://discord.gg/CPjbDxesh5";
|
return "https://discord.gg/CPjbDxesh5";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ Item {
|
|||||||
property string compositorDiscordTooltip: {
|
property string compositorDiscordTooltip: {
|
||||||
if (isHyprland)
|
if (isHyprland)
|
||||||
return I18n.tr("Hyprland Discord Server");
|
return I18n.tr("Hyprland Discord Server");
|
||||||
if (isMango)
|
if (isDwl)
|
||||||
return I18n.tr("mangowc Discord Server");
|
return I18n.tr("mangowc Discord Server");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -107,9 +107,9 @@ Item {
|
|||||||
property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc"
|
property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc"
|
||||||
property string ircTooltip: I18n.tr("LabWC IRC Channel")
|
property string ircTooltip: I18n.tr("LabWC IRC Channel")
|
||||||
|
|
||||||
property bool showMatrix: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isMango && !isLabwc
|
property bool showMatrix: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isDwl && !isLabwc
|
||||||
property bool showCompositorDiscord: isHyprland || isMango
|
property bool showCompositorDiscord: isHyprland || isDwl
|
||||||
property bool showReddit: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isMango && !isLabwc
|
property bool showReddit: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isDwl && !isLabwc
|
||||||
property bool showIrc: isLabwc
|
property bool showIrc: isLabwc
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
|
|||||||
@@ -722,7 +722,7 @@ Item {
|
|||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "handyman"
|
iconName: "system_tray"
|
||||||
title: I18n.tr("Tray Icon Fix")
|
title: I18n.tr("Tray Icon Fix")
|
||||||
visible: DesktopService.isSystemd
|
visible: DesktopService.isSystemd
|
||||||
|
|
||||||
|
|||||||
@@ -152,9 +152,6 @@ Item {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
readonly property var entryActionKeys: ["pin", "edit", "delete"]
|
|
||||||
readonly property var entryActionLabels: [I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")]
|
|
||||||
|
|
||||||
function getMaxHistoryText(value) {
|
function getMaxHistoryText(value) {
|
||||||
if (value <= 0)
|
if (value <= 0)
|
||||||
return "∞";
|
return "∞";
|
||||||
@@ -190,29 +187,6 @@ Item {
|
|||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function visibleEntryActionKeys() {
|
|
||||||
return SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"];
|
|
||||||
}
|
|
||||||
|
|
||||||
function visibleEntryActionLabels() {
|
|
||||||
const visibleKeys = visibleEntryActionKeys();
|
|
||||||
return entryActionKeys.map((key, index) => visibleKeys.includes(key) ? entryActionLabels[index] : null).filter(label => label !== null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVisibleEntryAction(index, selected) {
|
|
||||||
const actionKey = entryActionKeys[index];
|
|
||||||
if (!actionKey)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let actions = visibleEntryActionKeys().slice();
|
|
||||||
if (selected && !actions.includes(actionKey)) {
|
|
||||||
actions.push(actionKey);
|
|
||||||
} else if (!selected && actions.includes(actionKey)) {
|
|
||||||
actions = actions.filter(action => action !== actionKey);
|
|
||||||
}
|
|
||||||
SettingsData.set("clipboardVisibleEntryActions", actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadConfig() {
|
function loadConfig() {
|
||||||
configLoaded = false;
|
configLoaded = false;
|
||||||
configError = false;
|
configError = false;
|
||||||
@@ -463,24 +437,6 @@ Item {
|
|||||||
checked: SettingsData.clipboardEnterToPaste
|
checked: SettingsData.clipboardEnterToPaste
|
||||||
onToggled: checked => SettingsData.set("clipboardEnterToPaste", checked)
|
onToggled: checked => SettingsData.set("clipboardEnterToPaste", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsButtonGroupRow {
|
|
||||||
tab: "clipboard"
|
|
||||||
tags: ["clipboard", "actions", "buttons", "hide", "density", "pin", "edit", "delete"]
|
|
||||||
settingKey: "clipboardVisibleEntryActions"
|
|
||||||
text: I18n.tr("Visible Entry Actions")
|
|
||||||
description: I18n.tr("Choose which action buttons appear on clipboard entries")
|
|
||||||
selectionMode: "multi"
|
|
||||||
model: root.entryActionLabels
|
|
||||||
currentSelection: root.visibleEntryActionLabels()
|
|
||||||
checkEnabled: false
|
|
||||||
buttonHeight: 28
|
|
||||||
minButtonWidth: 56
|
|
||||||
buttonPadding: Theme.spacingS
|
|
||||||
textSize: Theme.fontSizeSmall
|
|
||||||
spacing: 1
|
|
||||||
onSelectionChanged: (index, selected) => root.setVisibleEntryAction(index, selected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ Item {
|
|||||||
SettingsCard {
|
SettingsCard {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
||||||
title: I18n.tr("Niri Layout Overrides")
|
title: I18n.tr("Niri Layout Overrides").replace("Niri", "niri")
|
||||||
settingKey: "niriLayout"
|
settingKey: "niriLayout"
|
||||||
iconName: "layers"
|
iconName: "crop_square"
|
||||||
visible: CompositorService.isNiri
|
visible: CompositorService.isNiri
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
@@ -145,7 +145,7 @@ Item {
|
|||||||
tags: ["hyprland", "gaps", "override"]
|
tags: ["hyprland", "gaps", "override"]
|
||||||
settingKey: "hyprlandLayoutGapsOverride"
|
settingKey: "hyprlandLayoutGapsOverride"
|
||||||
text: I18n.tr("Window Gaps")
|
text: I18n.tr("Window Gaps")
|
||||||
description: I18n.tr("Space between windows") + " (gaps_in/gaps_out)"
|
description: I18n.tr("Space between windows (gaps_in and gaps_out)")
|
||||||
visible: SettingsData.hyprlandLayoutGapsOverride >= 0
|
visible: SettingsData.hyprlandLayoutGapsOverride >= 0
|
||||||
value: Math.max(0, SettingsData.hyprlandLayoutGapsOverride)
|
value: Math.max(0, SettingsData.hyprlandLayoutGapsOverride)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
@@ -159,7 +159,7 @@ Item {
|
|||||||
tags: ["hyprland", "radius", "override", "rounding"]
|
tags: ["hyprland", "radius", "override", "rounding"]
|
||||||
settingKey: "hyprlandLayoutRadiusOverrideEnabled"
|
settingKey: "hyprlandLayoutRadiusOverrideEnabled"
|
||||||
text: I18n.tr("Override Corner Radius")
|
text: I18n.tr("Override Corner Radius")
|
||||||
description: I18n.tr("Use custom window radius instead of theme radius")
|
description: I18n.tr("Use custom window rounding instead of theme radius")
|
||||||
checked: SettingsData.hyprlandLayoutRadiusOverride >= 0
|
checked: SettingsData.hyprlandLayoutRadiusOverride >= 0
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
@@ -173,8 +173,8 @@ Item {
|
|||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
tags: ["hyprland", "radius", "override", "rounding"]
|
tags: ["hyprland", "radius", "override", "rounding"]
|
||||||
settingKey: "hyprlandLayoutRadiusOverride"
|
settingKey: "hyprlandLayoutRadiusOverride"
|
||||||
text: I18n.tr("Window Corner Radius")
|
text: I18n.tr("Window Rounding")
|
||||||
description: I18n.tr("Rounded corners for windows") + " (decoration.rounding)"
|
description: I18n.tr("Rounded corners for windows (decoration.rounding)")
|
||||||
visible: SettingsData.hyprlandLayoutRadiusOverride >= 0
|
visible: SettingsData.hyprlandLayoutRadiusOverride >= 0
|
||||||
value: Math.max(0, SettingsData.hyprlandLayoutRadiusOverride)
|
value: Math.max(0, SettingsData.hyprlandLayoutRadiusOverride)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
@@ -203,7 +203,7 @@ Item {
|
|||||||
tags: ["hyprland", "border", "override"]
|
tags: ["hyprland", "border", "override"]
|
||||||
settingKey: "hyprlandLayoutBorderSize"
|
settingKey: "hyprlandLayoutBorderSize"
|
||||||
text: I18n.tr("Border Size")
|
text: I18n.tr("Border Size")
|
||||||
description: I18n.tr("Width of window border") + " (general.border_size)"
|
description: I18n.tr("Width of window border (general.border_size)")
|
||||||
visible: SettingsData.hyprlandLayoutBorderSize >= 0
|
visible: SettingsData.hyprlandLayoutBorderSize >= 0
|
||||||
value: Math.max(0, SettingsData.hyprlandLayoutBorderSize)
|
value: Math.max(0, SettingsData.hyprlandLayoutBorderSize)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
@@ -229,7 +229,7 @@ Item {
|
|||||||
title: I18n.tr("MangoWC Layout Overrides")
|
title: I18n.tr("MangoWC Layout Overrides")
|
||||||
settingKey: "mangoLayout"
|
settingKey: "mangoLayout"
|
||||||
iconName: "crop_square"
|
iconName: "crop_square"
|
||||||
visible: CompositorService.isMango
|
visible: CompositorService.isDwl || CompositorService.isMango
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
tags: ["mangowc", "mango", "gaps", "override"]
|
tags: ["mangowc", "mango", "gaps", "override"]
|
||||||
@@ -251,7 +251,7 @@ Item {
|
|||||||
tags: ["mangowc", "mango", "gaps", "override"]
|
tags: ["mangowc", "mango", "gaps", "override"]
|
||||||
settingKey: "mangoLayoutGapsOverride"
|
settingKey: "mangoLayoutGapsOverride"
|
||||||
text: I18n.tr("Window Gaps")
|
text: I18n.tr("Window Gaps")
|
||||||
description: I18n.tr("Space between windows") + " (gappih/gappiv/gappoh/gappov)"
|
description: I18n.tr("Space between windows (gappih/gappiv/gappoh/gappov)")
|
||||||
visible: SettingsData.mangoLayoutGapsOverride >= 0
|
visible: SettingsData.mangoLayoutGapsOverride >= 0
|
||||||
value: Math.max(0, SettingsData.mangoLayoutGapsOverride)
|
value: Math.max(0, SettingsData.mangoLayoutGapsOverride)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
@@ -280,7 +280,7 @@ Item {
|
|||||||
tags: ["mangowc", "mango", "radius", "override"]
|
tags: ["mangowc", "mango", "radius", "override"]
|
||||||
settingKey: "mangoLayoutRadiusOverride"
|
settingKey: "mangoLayoutRadiusOverride"
|
||||||
text: I18n.tr("Window Corner Radius")
|
text: I18n.tr("Window Corner Radius")
|
||||||
description: I18n.tr("Rounded corners for windows") + " (border_radius)"
|
description: I18n.tr("Rounded corners for windows (border_radius)")
|
||||||
visible: SettingsData.mangoLayoutRadiusOverride >= 0
|
visible: SettingsData.mangoLayoutRadiusOverride >= 0
|
||||||
value: Math.max(0, SettingsData.mangoLayoutRadiusOverride)
|
value: Math.max(0, SettingsData.mangoLayoutRadiusOverride)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
@@ -309,7 +309,7 @@ Item {
|
|||||||
tags: ["mangowc", "mango", "border", "override"]
|
tags: ["mangowc", "mango", "border", "override"]
|
||||||
settingKey: "mangoLayoutBorderSize"
|
settingKey: "mangoLayoutBorderSize"
|
||||||
text: I18n.tr("Border Size")
|
text: I18n.tr("Border Size")
|
||||||
description: I18n.tr("Width of window border") + " (borderpx)"
|
description: I18n.tr("Width of window border (borderpx)")
|
||||||
visible: SettingsData.mangoLayoutBorderSize >= 0
|
visible: SettingsData.mangoLayoutBorderSize >= 0
|
||||||
value: Math.max(0, SettingsData.mangoLayoutBorderSize)
|
value: Math.max(0, SettingsData.mangoLayoutBorderSize)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
property int subTabIndex: 0
|
||||||
|
|
||||||
|
readonly property var workspaceSections: ({
|
||||||
|
"workspaceSettings": true,
|
||||||
|
"showWorkspaceIndex": true,
|
||||||
|
"showWorkspaceName": true,
|
||||||
|
"showWorkspacePadding": true,
|
||||||
|
"showWorkspaceApps": true,
|
||||||
|
"groupWorkspaceApps": true,
|
||||||
|
"groupActiveWorkspaceApps": true,
|
||||||
|
"workspaceActiveAppHighlightEnabled": true,
|
||||||
|
"workspaceFollowFocus": true,
|
||||||
|
"showOccupiedWorkspacesOnly": true,
|
||||||
|
"reverseScrolling": true,
|
||||||
|
"workspaceDragReorder": true,
|
||||||
|
"dwlShowAllTags": true,
|
||||||
|
"workspaceIcons": true
|
||||||
|
})
|
||||||
|
readonly property var layoutSections: ({
|
||||||
|
"niriLayout": true,
|
||||||
|
"niriLayoutGapsOverrideEnabled": true,
|
||||||
|
"niriLayoutGapsOverride": true,
|
||||||
|
"niriLayoutRadiusOverrideEnabled": true,
|
||||||
|
"niriLayoutRadiusOverride": true,
|
||||||
|
"niriLayoutBorderSizeEnabled": true,
|
||||||
|
"niriLayoutBorderSize": true,
|
||||||
|
"hyprlandLayout": true,
|
||||||
|
"hyprlandLayoutGapsOverrideEnabled": true,
|
||||||
|
"hyprlandLayoutGapsOverride": true,
|
||||||
|
"hyprlandLayoutRadiusOverrideEnabled": true,
|
||||||
|
"hyprlandLayoutRadiusOverride": true,
|
||||||
|
"hyprlandLayoutBorderSizeEnabled": true,
|
||||||
|
"hyprlandLayoutBorderSize": true,
|
||||||
|
"hyprlandResizeOnBorder": true,
|
||||||
|
"mangoLayout": true,
|
||||||
|
"mangoLayoutGapsOverrideEnabled": true,
|
||||||
|
"mangoLayoutGapsOverride": true,
|
||||||
|
"mangoLayoutRadiusOverrideEnabled": true,
|
||||||
|
"mangoLayoutRadiusOverride": true,
|
||||||
|
"mangoLayoutBorderSizeEnabled": true,
|
||||||
|
"mangoLayoutBorderSize": true
|
||||||
|
})
|
||||||
|
|
||||||
|
function routeSearchTarget(target) {
|
||||||
|
if (!target)
|
||||||
|
return;
|
||||||
|
if (workspaceSections[target]) {
|
||||||
|
subTabIndex = 0;
|
||||||
|
} else if (layoutSections[target]) {
|
||||||
|
subTabIndex = 1;
|
||||||
|
} else if (target === "windowRules" || target.startsWith("windowRule")) {
|
||||||
|
subTabIndex = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: routeSearchTarget(SettingsSearchService.targetSection)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsSearchService
|
||||||
|
|
||||||
|
function onTargetSectionChanged() {
|
||||||
|
root.routeSearchTarget(SettingsSearchService.targetSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 60
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
DankTabBar {
|
||||||
|
id: compositorTabBar
|
||||||
|
|
||||||
|
width: Math.min(500, parent.width - Theme.spacingL * 2)
|
||||||
|
height: 45
|
||||||
|
anchors.centerIn: parent
|
||||||
|
model: [
|
||||||
|
{
|
||||||
|
"text": I18n.tr("Workspaces"),
|
||||||
|
"icon": "view_module"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": I18n.tr("Window Layout"),
|
||||||
|
"icon": "crop_square"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": I18n.tr("Window Rules"),
|
||||||
|
"icon": "select_window"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
currentIndex: root.subTabIndex
|
||||||
|
onTabClicked: index => root.subTabIndex = index
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: compositorTabBar.y + compositorTabBar.height + 10
|
||||||
|
width: compositorTabBar.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.surface
|
||||||
|
opacity: 0.56
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.subTabIndex === 0
|
||||||
|
visible: active
|
||||||
|
sourceComponent: WorkspacesTab {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.subTabIndex === 1
|
||||||
|
visible: active
|
||||||
|
sourceComponent: CompositorLayoutTab {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: windowRulesLoader
|
||||||
|
|
||||||
|
property bool loadedOnce: false
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.subTabIndex === 2 || loadedOnce
|
||||||
|
visible: root.subTabIndex === 2 && status === Loader.Ready
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: WindowRulesTab {
|
||||||
|
pageActive: root.subTabIndex === 2
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoaded: loadedOnce = true
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: root.subTabIndex === 2 && windowRulesLoader.status === Loader.Loading
|
||||||
|
text: I18n.tr("Loading...", "loading indicator")
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,8 +41,8 @@ Item {
|
|||||||
SettingsData.barConfigs;
|
SettingsData.barConfigs;
|
||||||
const index = SettingsData.barConfigs.findIndex(config => config.id === selectedBarId);
|
const index = SettingsData.barConfigs.findIndex(config => config.id === selectedBarId);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
return I18n.tr("Bar", "fallback name for an unnamed bar");
|
return I18n.tr("Bar");
|
||||||
return SettingsData.barConfigs[index].name || I18n.tr("Bar %1", "numbered name for an unnamed bar, %1 is its position").arg(index + 1);
|
return SettingsData.barConfigs[index].name || I18n.tr("Bar %1").arg(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool selectedBarIsVertical: {
|
property bool selectedBarIsVertical: {
|
||||||
@@ -164,7 +164,6 @@ Item {
|
|||||||
scrollEnabled: defaultBar.scrollEnabled ?? true,
|
scrollEnabled: defaultBar.scrollEnabled ?? true,
|
||||||
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
||||||
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
|
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
|
||||||
hoverPopouts: defaultBar.hoverPopouts ?? false,
|
|
||||||
shadowIntensity: defaultBar.shadowIntensity ?? 0,
|
shadowIntensity: defaultBar.shadowIntensity ?? 0,
|
||||||
shadowOpacity: defaultBar.shadowOpacity ?? 60,
|
shadowOpacity: defaultBar.shadowOpacity ?? 60,
|
||||||
shadowDirectionMode: defaultBar.shadowDirectionMode ?? "inherit",
|
shadowDirectionMode: defaultBar.shadowDirectionMode ?? "inherit",
|
||||||
@@ -797,81 +796,18 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
tab: "appearance"
|
|
||||||
iconName: "opacity"
|
|
||||||
title: I18n.tr("Opacity")
|
|
||||||
settingKey: "barTransparency"
|
|
||||||
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
|
||||||
|
|
||||||
SettingsSliderRow {
|
|
||||||
id: barTransparencySlider
|
|
||||||
visible: !SettingsData.frameEnabled
|
|
||||||
text: I18n.tr("Bar Opacity")
|
|
||||||
description: I18n.tr("Controls opacity of the bar background")
|
|
||||||
value: (selectedBarConfig?.transparency ?? 1.0) * 100
|
|
||||||
minimum: 0
|
|
||||||
maximum: 100
|
|
||||||
unit: "%"
|
|
||||||
defaultValue: 100
|
|
||||||
onSliderDragFinished: finalValue => {
|
|
||||||
SettingsData.updateBarConfig(selectedBarId, {
|
|
||||||
transparency: finalValue / 100
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Binding {
|
|
||||||
target: barTransparencySlider
|
|
||||||
property: "value"
|
|
||||||
value: (selectedBarConfig?.transparency ?? 1.0) * 100
|
|
||||||
restoreMode: Binding.RestoreBinding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsSliderRow {
|
|
||||||
id: widgetTransparencySlider
|
|
||||||
text: I18n.tr("Widget Opacity")
|
|
||||||
description: I18n.tr("Controls opacity of widget backgrounds")
|
|
||||||
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
|
|
||||||
minimum: 0
|
|
||||||
maximum: 100
|
|
||||||
unit: "%"
|
|
||||||
defaultValue: 100
|
|
||||||
onSliderDragFinished: finalValue => {
|
|
||||||
SettingsData.updateBarConfig(selectedBarId, {
|
|
||||||
widgetTransparency: finalValue / 100
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Binding {
|
|
||||||
target: widgetTransparencySlider
|
|
||||||
property: "value"
|
|
||||||
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
|
|
||||||
restoreMode: Binding.RestoreBinding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsControlledByFrame {
|
|
||||||
visible: SettingsData.frameEnabled
|
|
||||||
parentModal: dankBarTab.parentModal
|
|
||||||
settingLabel: I18n.tr("Bar Opacity")
|
|
||||||
reason: I18n.tr("Managed by Frame")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsControlledByFrame {
|
SettingsControlledByFrame {
|
||||||
visible: dankBarTab.appearanceOnly && SettingsData.frameEnabled
|
visible: !dankBarTab.appearanceOnly && SettingsData.frameEnabled
|
||||||
parentModal: dankBarTab.parentModal
|
parentModal: dankBarTab.parentModal
|
||||||
settingLabel: I18n.tr("Bar spacing and size")
|
settingLabel: I18n.tr("Bar spacing and size")
|
||||||
reason: I18n.tr("Managed by Frame")
|
reason: I18n.tr("Managed by Frame")
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "appearance"
|
|
||||||
iconName: "space_bar"
|
iconName: "space_bar"
|
||||||
title: I18n.tr("Spacing")
|
title: I18n.tr("Spacing")
|
||||||
settingKey: "barSpacing"
|
settingKey: "barSpacing"
|
||||||
visible: dankBarTab.appearanceOnly && (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
|
visible: !dankBarTab.appearanceOnly && (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
|
||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: edgeSpacingSlider
|
id: edgeSpacingSlider
|
||||||
@@ -1020,6 +956,68 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "appearance"
|
||||||
|
iconName: "opacity"
|
||||||
|
title: I18n.tr("Transparency")
|
||||||
|
settingKey: "barTransparency"
|
||||||
|
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
id: barTransparencySlider
|
||||||
|
visible: !SettingsData.frameEnabled
|
||||||
|
text: I18n.tr("Bar Transparency")
|
||||||
|
description: I18n.tr("Opacity of the bar background")
|
||||||
|
value: (selectedBarConfig?.transparency ?? 1.0) * 100
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: "%"
|
||||||
|
defaultValue: 100
|
||||||
|
onSliderDragFinished: finalValue => {
|
||||||
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
transparency: finalValue / 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: barTransparencySlider
|
||||||
|
property: "value"
|
||||||
|
value: (selectedBarConfig?.transparency ?? 1.0) * 100
|
||||||
|
restoreMode: Binding.RestoreBinding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
id: widgetTransparencySlider
|
||||||
|
text: I18n.tr("Widget Transparency")
|
||||||
|
description: I18n.tr("Opacity of widget backgrounds")
|
||||||
|
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: "%"
|
||||||
|
defaultValue: 100
|
||||||
|
onSliderDragFinished: finalValue => {
|
||||||
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
widgetTransparency: finalValue / 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: widgetTransparencySlider
|
||||||
|
property: "value"
|
||||||
|
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
|
||||||
|
restoreMode: Binding.RestoreBinding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsControlledByFrame {
|
||||||
|
visible: SettingsData.frameEnabled
|
||||||
|
parentModal: dankBarTab.parentModal
|
||||||
|
settingLabel: I18n.tr("Bar Transparency")
|
||||||
|
reason: I18n.tr("Managed by Frame")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsSliderCard {
|
SettingsSliderCard {
|
||||||
id: fontScaleSliderCard
|
id: fontScaleSliderCard
|
||||||
tab: "appearance"
|
tab: "appearance"
|
||||||
@@ -1360,7 +1358,7 @@ Item {
|
|||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: borderOpacitySlider
|
id: borderOpacitySlider
|
||||||
text: I18n.tr("Opacity")
|
text: I18n.tr("Opacity")
|
||||||
description: I18n.tr("Controls opacity of the border")
|
description: I18n.tr("Transparency of the border")
|
||||||
value: (selectedBarConfig?.borderOpacity ?? 1.0) * 100
|
value: (selectedBarConfig?.borderOpacity ?? 1.0) * 100
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 100
|
maximum: 100
|
||||||
@@ -1455,7 +1453,7 @@ Item {
|
|||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: widgetOutlineOpacitySlider
|
id: widgetOutlineOpacitySlider
|
||||||
text: I18n.tr("Opacity")
|
text: I18n.tr("Opacity")
|
||||||
description: I18n.tr("Controls opacity of the widget outline")
|
description: I18n.tr("Transparency of the widget outline")
|
||||||
value: (selectedBarConfig?.widgetOutlineOpacity ?? 1.0) * 100
|
value: (selectedBarConfig?.widgetOutlineOpacity ?? 1.0) * 100
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 100
|
maximum: 100
|
||||||
@@ -1564,7 +1562,7 @@ Item {
|
|||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
visible: shadowCard.shadowActive
|
visible: shadowCard.shadowActive
|
||||||
text: I18n.tr("Opacity")
|
text: I18n.tr("Opacity")
|
||||||
description: I18n.tr("Controls opacity of the shadow layer")
|
description: I18n.tr("Transparency of the shadow layer")
|
||||||
minimum: 10
|
minimum: 10
|
||||||
maximum: 100
|
maximum: 100
|
||||||
unit: "%"
|
unit: "%"
|
||||||
@@ -1742,19 +1740,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleCard {
|
|
||||||
iconName: "touch_app"
|
|
||||||
title: I18n.tr("Hover Popouts")
|
|
||||||
description: I18n.tr("Open widget popouts by hovering over the bar. Moving to another widget switches the popout.")
|
|
||||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
|
||||||
enabled: !(selectedBarConfig?.clickThrough ?? false)
|
|
||||||
opacity: (selectedBarConfig?.clickThrough ?? false) ? 0.5 : 1.0
|
|
||||||
checked: selectedBarConfig?.hoverPopouts ?? false
|
|
||||||
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
|
|
||||||
hoverPopouts: checked
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsToggleCard {
|
SettingsToggleCard {
|
||||||
iconName: "mouse"
|
iconName: "mouse"
|
||||||
title: I18n.tr("Scroll Wheel")
|
title: I18n.tr("Scroll Wheel")
|
||||||
@@ -1769,9 +1754,6 @@ Item {
|
|||||||
text: I18n.tr("Y Axis")
|
text: I18n.tr("Y Axis")
|
||||||
description: I18n.tr("Action performed when scrolling vertically on the bar")
|
description: I18n.tr("Action performed when scrolling vertically on the bar")
|
||||||
model: CompositorService.isNiri ? [I18n.tr("None"), I18n.tr("Workspace"), I18n.tr("Column")] : [I18n.tr("None"), I18n.tr("Workspace")]
|
model: CompositorService.isNiri ? [I18n.tr("None"), I18n.tr("Workspace"), I18n.tr("Column")] : [I18n.tr("None"), I18n.tr("Workspace")]
|
||||||
buttonPadding: Theme.spacingS
|
|
||||||
minButtonWidth: 44
|
|
||||||
textSize: Theme.fontSizeSmall
|
|
||||||
currentIndex: {
|
currentIndex: {
|
||||||
switch (selectedBarConfig?.scrollYBehavior || "workspace") {
|
switch (selectedBarConfig?.scrollYBehavior || "workspace") {
|
||||||
case "none":
|
case "none":
|
||||||
@@ -1810,9 +1792,6 @@ Item {
|
|||||||
description: I18n.tr("Action performed when scrolling horizontally on the bar")
|
description: I18n.tr("Action performed when scrolling horizontally on the bar")
|
||||||
visible: CompositorService.isNiri
|
visible: CompositorService.isNiri
|
||||||
model: [I18n.tr("None"), I18n.tr("Workspace"), I18n.tr("Column")]
|
model: [I18n.tr("None"), I18n.tr("Workspace"), I18n.tr("Column")]
|
||||||
buttonPadding: Theme.spacingS
|
|
||||||
minButtonWidth: 44
|
|
||||||
textSize: Theme.fontSizeSmall
|
|
||||||
currentIndex: {
|
currentIndex: {
|
||||||
switch (selectedBarConfig?.scrollXBehavior || "column") {
|
switch (selectedBarConfig?.scrollXBehavior || "column") {
|
||||||
case "none":
|
case "none":
|
||||||
|
|||||||
@@ -1023,6 +1023,7 @@ Singleton {
|
|||||||
return parseNiriOutputs(content);
|
return parseNiriOutputs(content);
|
||||||
case "hyprland":
|
case "hyprland":
|
||||||
return parseHyprlandOutputs(content);
|
return parseHyprlandOutputs(content);
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
return parseMangoOutputs(content);
|
return parseMangoOutputs(content);
|
||||||
default:
|
default:
|
||||||
@@ -1361,6 +1362,7 @@ Singleton {
|
|||||||
"grepPattern": "dms.outputs",
|
"grepPattern": "dms.outputs",
|
||||||
"includeLine": "require(\"dms.outputs\")"
|
"includeLine": "require(\"dms.outputs\")"
|
||||||
};
|
};
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
return {
|
return {
|
||||||
"configFile": configDir + "/mango/config.conf",
|
"configFile": configDir + "/mango/config.conf",
|
||||||
@@ -1375,7 +1377,7 @@ Singleton {
|
|||||||
|
|
||||||
function checkIncludeStatus() {
|
function checkIncludeStatus() {
|
||||||
const compositor = CompositorService.compositor;
|
const compositor = CompositorService.compositor;
|
||||||
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") {
|
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl" && compositor !== "mango") {
|
||||||
includeStatus = {
|
includeStatus = {
|
||||||
"exists": false,
|
"exists": false,
|
||||||
"included": false,
|
"included": false,
|
||||||
@@ -1386,7 +1388,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filename = (compositor === "niri") ? "outputs.kdl" : ((compositor === "hyprland") ? "outputs.lua" : "outputs.conf");
|
const filename = (compositor === "niri") ? "outputs.kdl" : ((compositor === "hyprland") ? "outputs.lua" : "outputs.conf");
|
||||||
const compositorArg = (compositor === "mango") ? "mangowc" : compositor;
|
// mango and dwl both use outputs.conf under ~/.config/mango
|
||||||
|
const compositorArg = (compositor === "dwl" || compositor === "mango") ? "mangowc" : compositor;
|
||||||
|
|
||||||
checkingInclude = true;
|
checkingInclude = true;
|
||||||
Proc.runCommand("check-outputs-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => {
|
Proc.runCommand("check-outputs-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => {
|
||||||
@@ -1586,6 +1589,9 @@ Singleton {
|
|||||||
case "mango":
|
case "mango":
|
||||||
MangoService.generateOutputsConfig(outputsData, finish);
|
MangoService.generateOutputsConfig(outputsData, finish);
|
||||||
break;
|
break;
|
||||||
|
case "dwl":
|
||||||
|
DwlService.generateOutputsConfig(outputsData, finish);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
WlrOutputService.applyOutputsConfig(outputsData, outputs);
|
WlrOutputService.applyOutputsConfig(outputsData, outputs);
|
||||||
finish(true);
|
finish(true);
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ StyledRect {
|
|||||||
DankToggle {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Variable Refresh Rate")
|
text: I18n.tr("Variable Refresh Rate")
|
||||||
visible: root.isConnected && !root.isDisabled && !CompositorService.isMango && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
visible: root.isConnected && !root.isDisabled && !CompositorService.isDwl && !CompositorService.isMango && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||||
checked: {
|
checked: {
|
||||||
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
|
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
|
||||||
if (pendingVrr !== undefined)
|
if (pendingVrr !== undefined)
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ Item {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: displayFormatColumn
|
id: displayFormatColumn
|
||||||
visible: !CompositorService.isMango
|
visible: !CompositorService.isDwl && !CompositorService.isMango
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
|||||||
@@ -282,6 +282,8 @@ Item {
|
|||||||
modes.push("niri");
|
modes.push("niri");
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
modes.push("Hyprland");
|
modes.push("Hyprland");
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
modes.push("mango");
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
modes.push("mango");
|
modes.push("mango");
|
||||||
} else if (CompositorService.isSway) {
|
} else if (CompositorService.isSway) {
|
||||||
@@ -643,19 +645,19 @@ Item {
|
|||||||
SettingsControlledByFrame {
|
SettingsControlledByFrame {
|
||||||
visible: root.connectedFrameModeActive
|
visible: root.connectedFrameModeActive
|
||||||
parentModal: root.parentModal
|
parentModal: root.parentModal
|
||||||
settingLabel: I18n.tr("Dock margin, opacity, and border")
|
settingLabel: I18n.tr("Dock margin, transparency, and border")
|
||||||
reason: I18n.tr("Managed by Frame in Connected Mode")
|
reason: I18n.tr("Managed by Frame in Connected Mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "opacity"
|
iconName: "opacity"
|
||||||
title: I18n.tr("Opacity")
|
title: I18n.tr("Transparency")
|
||||||
settingKey: "dockTransparency"
|
settingKey: "dockTransparency"
|
||||||
visible: !root.connectedFrameModeActive
|
visible: !root.connectedFrameModeActive
|
||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
text: I18n.tr("Dock Opacity")
|
text: I18n.tr("Dock Transparency")
|
||||||
value: Math.round(SettingsData.dockTransparency * 100)
|
value: Math.round(SettingsData.dockTransparency * 100)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 100
|
maximum: 100
|
||||||
|
|||||||
@@ -205,9 +205,6 @@ Item {
|
|||||||
tags: ["frame", "border", "color", "theme", "primary", "surface", "default"]
|
tags: ["frame", "border", "color", "theme", "primary", "surface", "default"]
|
||||||
text: I18n.tr("Border Color")
|
text: I18n.tr("Border Color")
|
||||||
model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")]
|
model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")]
|
||||||
buttonPadding: Theme.spacingS
|
|
||||||
minButtonWidth: 44
|
|
||||||
textSize: Theme.fontSizeSmall
|
|
||||||
currentIndex: {
|
currentIndex: {
|
||||||
const fc = SettingsData.frameColor;
|
const fc = SettingsData.frameColor;
|
||||||
if (!fc || fc === "default")
|
if (!fc || fc === "default")
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ Item {
|
|||||||
|
|
||||||
function runGreeterInstallAction() {
|
function runGreeterInstallAction() {
|
||||||
root.greeterPendingAction = !root.greeterInstalled ? "install" : !root.greeterEnabled ? "activate" : "uninstall";
|
root.greeterPendingAction = !root.greeterInstalled ? "install" : !root.greeterEnabled ? "activate" : "uninstall";
|
||||||
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "...";
|
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "…";
|
||||||
greeterInstallActionRunning = true;
|
greeterInstallActionRunning = true;
|
||||||
greeterInstallActionProcess.running = true;
|
greeterInstallActionProcess.running = true;
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ Item {
|
|||||||
greeterSudoProbeStderr = "";
|
greeterSudoProbeStderr = "";
|
||||||
greeterTerminalFallbackStderr = "";
|
greeterTerminalFallbackStderr = "";
|
||||||
greeterTerminalFallbackFromPrecheck = false;
|
greeterTerminalFallbackFromPrecheck = false;
|
||||||
greeterStatusText = I18n.tr("Checking whether sudo authentication is needed...");
|
greeterStatusText = I18n.tr("Checking whether sudo authentication is needed…");
|
||||||
greeterSyncRunning = true;
|
greeterSyncRunning = true;
|
||||||
greeterSudoProbeProcess.running = true;
|
greeterSudoProbeProcess.running = true;
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ Item {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const err = (root.greeterSudoProbeStderr || "").trim();
|
const err = (root.greeterSudoProbeStderr || "").trim();
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
root.greeterStatusText = I18n.tr("Running greeter sync...");
|
root.greeterStatusText = I18n.tr("Running greeter sync…");
|
||||||
greeterSyncProcess.running = true;
|
greeterSyncProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -468,7 +468,7 @@ Item {
|
|||||||
id: statusTextArea
|
id: statusTextArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
text: root.greeterStatusRunning ? I18n.tr("Checking...", "greeter status loading") : (root.greeterStatusText || I18n.tr("Click Refresh to check status.", "greeter status placeholder"))
|
text: root.greeterStatusRunning ? I18n.tr("Checking…", "greeter status loading") : (root.greeterStatusText || I18n.tr("Click Refresh to check status.", "greeter status placeholder"))
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.family: "monospace"
|
font.family: "monospace"
|
||||||
color: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
|
color: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
|
||||||
|
|||||||
@@ -304,6 +304,8 @@ Item {
|
|||||||
modes.push("niri");
|
modes.push("niri");
|
||||||
} else if (CompositorService.isHyprland) {
|
} else if (CompositorService.isHyprland) {
|
||||||
modes.push("Hyprland");
|
modes.push("Hyprland");
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
modes.push("mango");
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
modes.push("mango");
|
modes.push("mango");
|
||||||
} else if (CompositorService.isSway) {
|
} else if (CompositorService.isSway) {
|
||||||
|
|||||||
@@ -643,9 +643,41 @@ Item {
|
|||||||
height: NetworkService.networkWiredInfoLoading ? 40 : 0
|
height: NetworkService.networkWiredInfoLoading ? 40 : 0
|
||||||
visible: NetworkService.networkWiredInfoLoading
|
visible: NetworkService.networkWiredInfoLoading
|
||||||
|
|
||||||
DankSpinner {
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: 20
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: wiredLoadIcon
|
||||||
|
name: "sync"
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
running: NetworkService.networkWiredInfoLoading
|
||||||
|
loops: Animation.Infinite
|
||||||
|
OpacityAnimator {
|
||||||
|
target: wiredLoadIcon
|
||||||
|
to: 0.3
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
OpacityAnimator {
|
||||||
|
target: wiredLoadIcon
|
||||||
|
to: 1.0
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
onRunningChanged: if (!running)
|
||||||
|
wiredLoadIcon.opacity = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Loading...")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1328,9 +1360,41 @@ Item {
|
|||||||
height: NetworkService.networkInfoLoading ? 40 : 0
|
height: NetworkService.networkInfoLoading ? 40 : 0
|
||||||
visible: NetworkService.networkInfoLoading
|
visible: NetworkService.networkInfoLoading
|
||||||
|
|
||||||
DankSpinner {
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: 20
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: wifiInfoLoadIcon
|
||||||
|
name: "sync"
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
running: NetworkService.networkInfoLoading
|
||||||
|
loops: Animation.Infinite
|
||||||
|
OpacityAnimator {
|
||||||
|
target: wifiInfoLoadIcon
|
||||||
|
to: 0.3
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
OpacityAnimator {
|
||||||
|
target: wifiInfoLoadIcon
|
||||||
|
to: 1.0
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
onRunningChanged: if (!running)
|
||||||
|
wifiInfoLoadIcon.opacity = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Loading...")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1785,9 +1849,41 @@ Item {
|
|||||||
height: VPNService.configLoading ? 40 : 0
|
height: VPNService.configLoading ? 40 : 0
|
||||||
visible: VPNService.configLoading
|
visible: VPNService.configLoading
|
||||||
|
|
||||||
DankSpinner {
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: 20
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
id: vpnLoadIcon
|
||||||
|
name: "sync"
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
running: VPNService.configLoading
|
||||||
|
loops: Animation.Infinite
|
||||||
|
OpacityAnimator {
|
||||||
|
target: vpnLoadIcon
|
||||||
|
to: 0.3
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
OpacityAnimator {
|
||||||
|
target: vpnLoadIcon
|
||||||
|
to: 1.0
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
onRunningChanged: if (!running)
|
||||||
|
vpnLoadIcon.opacity = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Loading...")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -200,40 +200,12 @@ Item {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: Theme.spacingXL
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "notifications"
|
iconName: "notifications"
|
||||||
title: I18n.tr("Notification Popups")
|
title: I18n.tr("Notification Popups")
|
||||||
settingKey: "notificationPopups"
|
settingKey: "notificationPopups"
|
||||||
|
|
||||||
// Font size selectors for summary and body
|
|
||||||
SettingsDropdownRow {
|
|
||||||
settingKey: "notificationSummaryFontSize"
|
|
||||||
tags: ["notification", "font", "summary", "size"]
|
|
||||||
text: I18n.tr("Summary Font Size")
|
|
||||||
description: I18n.tr("Set the font size for notification summary text")
|
|
||||||
options: [I18n.tr("Unset"), "10", "12", "14", "16", "18"]
|
|
||||||
currentValue: (SettingsData.notificationSummaryFontSize || I18n.tr("Unset")).toString()
|
|
||||||
onValueChanged: value => {
|
|
||||||
SettingsData.set("notificationSummaryFontSize", Number(value === I18n.tr("Unset") ? 0 : value));
|
|
||||||
SettingsData.sendTestNotifications();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsDropdownRow {
|
|
||||||
settingKey: "notificationBodyFontSize"
|
|
||||||
tags: ["notification", "font", "body", "size"]
|
|
||||||
text: I18n.tr("Body Font Size")
|
|
||||||
description: I18n.tr("Set the font size for notification body text (htmlBody)")
|
|
||||||
options: [I18n.tr("Unset"), "10", "12", "14", "16", "18"]
|
|
||||||
currentValue: (SettingsData.notificationBodyFontSize || I18n.tr("Unset")).toString()
|
|
||||||
onValueChanged: value => {
|
|
||||||
SettingsData.set("notificationBodyFontSize", Number(value === I18n.tr("Unset") ? 0 : value));
|
|
||||||
SettingsData.sendTestNotifications();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
settingKey: "notificationPopupPosition"
|
settingKey: "notificationPopupPosition"
|
||||||
tags: ["notification", "popup", "position", "screen", "location"]
|
tags: ["notification", "popup", "position", "screen", "location"]
|
||||||
@@ -301,15 +273,6 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("notificationCompactMode", checked)
|
onToggled: checked => SettingsData.set("notificationCompactMode", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
|
||||||
settingKey: "notificationShowTimeoutBar"
|
|
||||||
tags: ["notification", "timeout", "progress", "bar", "timer", "countdown"]
|
|
||||||
text: I18n.tr("Timeout Progress Bar")
|
|
||||||
description: I18n.tr("Show a bar that drains as the popup's auto-dismiss timer runs")
|
|
||||||
checked: SettingsData.notificationShowTimeoutBar
|
|
||||||
onToggled: checked => SettingsData.set("notificationShowTimeoutBar", checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
settingKey: "notificationDedupeEnabled"
|
settingKey: "notificationDedupeEnabled"
|
||||||
tags: ["notification", "duplicate", "dedupe", "stack", "coalesce", "repeat"]
|
tags: ["notification", "duplicate", "dedupe", "stack", "coalesce", "repeat"]
|
||||||
|
|||||||
@@ -33,31 +33,11 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property var sortChipOptions: [
|
readonly property var sortChipOptions: [
|
||||||
{
|
{ id: "installed", label: I18n.tr("Installed", "plugin browser filter chip"), toggle: true },
|
||||||
id: "installed",
|
{ id: "default", label: I18n.tr("Default", "plugin browser sort option"), toggle: false },
|
||||||
label: I18n.tr("Installed", "plugin browser filter chip"),
|
{ id: "name", label: I18n.tr("Name", "plugin browser sort option"), toggle: false },
|
||||||
toggle: true
|
{ id: "author", label: I18n.tr("Contributor", "plugin browser sort option"), toggle: false },
|
||||||
},
|
{ id: "category", label: I18n.tr("Category", "plugin browser sort option"), toggle: false }
|
||||||
{
|
|
||||||
id: "default",
|
|
||||||
label: I18n.tr("Default", "plugin browser sort option"),
|
|
||||||
toggle: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "name",
|
|
||||||
label: I18n.tr("Name", "plugin browser sort option"),
|
|
||||||
toggle: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "author",
|
|
||||||
label: I18n.tr("Contributor", "plugin browser sort option"),
|
|
||||||
toggle: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "category",
|
|
||||||
label: I18n.tr("Category", "plugin browser sort option"),
|
|
||||||
toggle: false
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
function normalizedSortMode(mode) {
|
function normalizedSortMode(mode) {
|
||||||
@@ -127,13 +107,11 @@ FloatingWindow {
|
|||||||
counts[cat] = (counts[cat] || 0) + 1;
|
counts[cat] = (counts[cat] || 0) + 1;
|
||||||
}
|
}
|
||||||
var keys = Object.keys(counts).sort();
|
var keys = Object.keys(counts).sort();
|
||||||
var options = [
|
var options = [{
|
||||||
{
|
key: "all",
|
||||||
key: "all",
|
label: I18n.tr("All", "plugin browser category filter"),
|
||||||
label: I18n.tr("All", "plugin browser category filter"),
|
count: plugins.length
|
||||||
count: plugins.length
|
}];
|
||||||
}
|
|
||||||
];
|
|
||||||
for (var j = 0; j < keys.length; j++) {
|
for (var j = 0; j < keys.length; j++) {
|
||||||
var key = keys[j];
|
var key = keys[j];
|
||||||
options.push({
|
options.push({
|
||||||
@@ -748,9 +726,32 @@ FloatingWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: root.isLoading
|
visible: root.isLoading
|
||||||
|
|
||||||
DankSpinner {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
running: root.isLoading
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "sync"
|
||||||
|
size: 48
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
smoothTransform: root.isLoading
|
||||||
|
|
||||||
|
RotationAnimator on rotation {
|
||||||
|
from: 0
|
||||||
|
to: -360
|
||||||
|
duration: 1000
|
||||||
|
loops: Animation.Infinite
|
||||||
|
running: root.isLoading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Loading...", "loading indicator")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -335,9 +335,32 @@ FloatingWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: root.isLoading
|
visible: root.isLoading
|
||||||
|
|
||||||
DankSpinner {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
running: root.isLoading
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "sync"
|
||||||
|
size: 48
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
smoothTransform: root.isLoading
|
||||||
|
|
||||||
|
RotationAnimator on rotation {
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
duration: 1000
|
||||||
|
loops: Animation.Infinite
|
||||||
|
running: root.isLoading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Loading...", "loading indicator")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Item {
|
|||||||
"grepPattern": "dms.cursor",
|
"grepPattern": "dms.cursor",
|
||||||
"includeLine": "require(\"dms.cursor\")"
|
"includeLine": "require(\"dms.cursor\")"
|
||||||
};
|
};
|
||||||
|
case "dwl":
|
||||||
case "mango":
|
case "mango":
|
||||||
return {
|
return {
|
||||||
"configFile": configDir + "/mango/config.conf",
|
"configFile": configDir + "/mango/config.conf",
|
||||||
@@ -62,7 +63,7 @@ Item {
|
|||||||
|
|
||||||
function checkCursorIncludeStatus() {
|
function checkCursorIncludeStatus() {
|
||||||
const compositor = CompositorService.compositor;
|
const compositor = CompositorService.compositor;
|
||||||
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") {
|
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl" && compositor !== "mango") {
|
||||||
cursorIncludeStatus = {
|
cursorIncludeStatus = {
|
||||||
"exists": false,
|
"exists": false,
|
||||||
"included": false,
|
"included": false,
|
||||||
@@ -73,7 +74,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filename = (compositor === "niri") ? "cursor.kdl" : ((compositor === "hyprland") ? "cursor.lua" : "cursor.conf");
|
const filename = (compositor === "niri") ? "cursor.kdl" : ((compositor === "hyprland") ? "cursor.lua" : "cursor.conf");
|
||||||
const compositorArg = (compositor === "mango") ? "mangowc" : compositor;
|
const compositorArg = (compositor === "dwl" || compositor === "mango") ? "mangowc" : compositor;
|
||||||
|
|
||||||
checkingCursorInclude = true;
|
checkingCursorInclude = true;
|
||||||
Proc.runCommand("check-cursor-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => {
|
Proc.runCommand("check-cursor-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => {
|
||||||
@@ -193,7 +194,7 @@ Item {
|
|||||||
themeColorsTab.templateDetection = JSON.parse(output.trim());
|
themeColorsTab.templateDetection = JSON.parse(output.trim());
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
|
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango)
|
||||||
checkCursorIncludeStatus();
|
checkCursorIncludeStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1639,7 +1640,7 @@ Item {
|
|||||||
SettingsControlledByFrame {
|
SettingsControlledByFrame {
|
||||||
visible: themeColorsTab.connectedFrameModeActive
|
visible: themeColorsTab.connectedFrameModeActive
|
||||||
parentModal: themeColorsTab.parentModal
|
parentModal: themeColorsTab.parentModal
|
||||||
settingLabel: I18n.tr("Surface Opacity")
|
settingLabel: I18n.tr("Transparency")
|
||||||
reason: I18n.tr("Managed by Frame in Connected Mode")
|
reason: I18n.tr("Managed by Frame in Connected Mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1647,8 +1648,8 @@ Item {
|
|||||||
tab: "theme"
|
tab: "theme"
|
||||||
tags: ["surface", "popup", "transparency", "opacity", "modal"]
|
tags: ["surface", "popup", "transparency", "opacity", "modal"]
|
||||||
settingKey: "popupTransparency"
|
settingKey: "popupTransparency"
|
||||||
text: I18n.tr("Surface Opacity")
|
text: I18n.tr("Transparency")
|
||||||
description: I18n.tr("Controls opacity of shell surfaces, popouts, and modals")
|
description: I18n.tr("Controls opacity of all popouts, modals, and their content layers")
|
||||||
visible: !themeColorsTab.connectedFrameModeActive
|
visible: !themeColorsTab.connectedFrameModeActive
|
||||||
value: Math.round(SettingsData.popupTransparency * 100)
|
value: Math.round(SettingsData.popupTransparency * 100)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
@@ -1671,113 +1672,6 @@ Item {
|
|||||||
defaultValue: 12
|
defaultValue: 12
|
||||||
onSliderValueChanged: newValue => SettingsData.setCornerRadius(newValue)
|
onSliderValueChanged: newValue => SettingsData.setCornerRadius(newValue)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["blur", "background", "transparency", "glass", "frosted"]
|
|
||||||
title: I18n.tr("Background Blur")
|
|
||||||
settingKey: "blurEnabled"
|
|
||||||
iconName: "blur_on"
|
|
||||||
|
|
||||||
SettingsToggleRow {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["blur", "background", "transparency", "glass", "frosted"]
|
|
||||||
settingKey: "blurEnabled"
|
|
||||||
text: I18n.tr("Background Blur")
|
|
||||||
description: !BlurService.available ? I18n.tr("Your compositor does not support background blur (ext-background-effect-v1)") : I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support. Adjust Opacity accordingly.")
|
|
||||||
checked: SettingsData.blurEnabled ?? false
|
|
||||||
enabled: BlurService.available
|
|
||||||
onToggled: checked => SettingsData.set("blurEnabled", checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsToggleRow {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["blur", "foreground", "layers", "contrast", "glass", "frosted"]
|
|
||||||
settingKey: "blurForegroundLayers"
|
|
||||||
text: I18n.tr("Foreground Layers")
|
|
||||||
description: I18n.tr("Show foreground surfaces on blurred panels for stronger contrast")
|
|
||||||
checked: SettingsData.blurForegroundLayers ?? true
|
|
||||||
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
|
|
||||||
enabled: BlurService.available
|
|
||||||
onToggled: checked => SettingsData.set("blurForegroundLayers", checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsSliderRow {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["blur", "foreground", "layers", "outline", "border", "cards", "widgets", "notifications", "control center"]
|
|
||||||
settingKey: "blurLayerOutlineOpacity"
|
|
||||||
text: I18n.tr("Layer Outline Opacity")
|
|
||||||
description: I18n.tr("Controls outlines around blurred foreground cards, pills, and notification cards")
|
|
||||||
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
|
|
||||||
value: Math.round((SettingsData.blurLayerOutlineOpacity ?? 0.12) * 100)
|
|
||||||
minimum: 0
|
|
||||||
maximum: 40
|
|
||||||
unit: "%"
|
|
||||||
defaultValue: 12
|
|
||||||
onSliderValueChanged: newValue => SettingsData.set("blurLayerOutlineOpacity", newValue / 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsDropdownRow {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["blur", "border", "outline", "edge"]
|
|
||||||
settingKey: "blurBorderColor"
|
|
||||||
text: I18n.tr("Blur Border Color")
|
|
||||||
description: I18n.tr("Border color around blurred surfaces")
|
|
||||||
visible: SettingsData.blurEnabled
|
|
||||||
options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")]
|
|
||||||
currentValue: {
|
|
||||||
switch (SettingsData.blurBorderColor) {
|
|
||||||
case "primary":
|
|
||||||
return I18n.tr("Primary", "blur border color");
|
|
||||||
case "secondary":
|
|
||||||
return I18n.tr("Secondary", "blur border color");
|
|
||||||
case "surfaceText":
|
|
||||||
return I18n.tr("Text Color", "blur border color");
|
|
||||||
case "custom":
|
|
||||||
return I18n.tr("Custom", "blur border color");
|
|
||||||
default:
|
|
||||||
return I18n.tr("Outline", "blur border color");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onValueChanged: value => {
|
|
||||||
if (value === I18n.tr("Primary", "blur border color")) {
|
|
||||||
SettingsData.set("blurBorderColor", "primary");
|
|
||||||
} else if (value === I18n.tr("Secondary", "blur border color")) {
|
|
||||||
SettingsData.set("blurBorderColor", "secondary");
|
|
||||||
} else if (value === I18n.tr("Text Color", "blur border color")) {
|
|
||||||
SettingsData.set("blurBorderColor", "surfaceText");
|
|
||||||
} else if (value === I18n.tr("Custom", "blur border color")) {
|
|
||||||
SettingsData.set("blurBorderColor", "custom");
|
|
||||||
openBlurBorderColorPicker();
|
|
||||||
} else {
|
|
||||||
SettingsData.set("blurBorderColor", "outline");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsSliderRow {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["blur", "border", "opacity"]
|
|
||||||
settingKey: "blurBorderOpacity"
|
|
||||||
text: I18n.tr("Blur Border Opacity")
|
|
||||||
description: I18n.tr("Controls the outer edge of protocol-blurred windows")
|
|
||||||
visible: SettingsData.blurEnabled
|
|
||||||
value: Math.round((SettingsData.blurBorderOpacity ?? 0.35) * 100)
|
|
||||||
minimum: 0
|
|
||||||
maximum: 100
|
|
||||||
unit: "%"
|
|
||||||
defaultValue: 35
|
|
||||||
onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
tab: "theme"
|
|
||||||
tags: ["elevation", "shadow", "lift", "m3", "material"]
|
|
||||||
title: I18n.tr("Shadows")
|
|
||||||
settingKey: "m3ElevationEnabled"
|
|
||||||
iconName: "layers"
|
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
tab: "theme"
|
tab: "theme"
|
||||||
@@ -1809,7 +1703,7 @@ Item {
|
|||||||
tags: ["elevation", "shadow", "opacity", "transparency", "m3"]
|
tags: ["elevation", "shadow", "opacity", "transparency", "m3"]
|
||||||
settingKey: "m3ElevationOpacity"
|
settingKey: "m3ElevationOpacity"
|
||||||
text: I18n.tr("Shadow Opacity")
|
text: I18n.tr("Shadow Opacity")
|
||||||
description: I18n.tr("Controls the opacity of the shadow")
|
description: I18n.tr("Controls the transparency of the shadow")
|
||||||
value: SettingsData.m3ElevationOpacity ?? 30
|
value: SettingsData.m3ElevationOpacity ?? 30
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 100
|
maximum: 100
|
||||||
@@ -1963,6 +1857,105 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "background", "transparency", "glass", "frosted"]
|
||||||
|
title: I18n.tr("Background Blur")
|
||||||
|
settingKey: "blurEnabled"
|
||||||
|
iconName: "blur_on"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "background", "transparency", "glass", "frosted"]
|
||||||
|
settingKey: "blurEnabled"
|
||||||
|
text: I18n.tr("Background Blur")
|
||||||
|
description: !BlurService.available ? I18n.tr("Your compositor does not support background blur (ext-background-effect-v1)") : I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.")
|
||||||
|
checked: SettingsData.blurEnabled ?? false
|
||||||
|
enabled: BlurService.available
|
||||||
|
onToggled: checked => SettingsData.set("blurEnabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "foreground", "layers", "contrast", "glass", "frosted"]
|
||||||
|
settingKey: "blurForegroundLayers"
|
||||||
|
text: I18n.tr("Foreground Layers")
|
||||||
|
description: I18n.tr("Show foreground surfaces on blurred panels for stronger contrast")
|
||||||
|
checked: SettingsData.blurForegroundLayers ?? true
|
||||||
|
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
|
||||||
|
enabled: BlurService.available
|
||||||
|
onToggled: checked => SettingsData.set("blurForegroundLayers", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "foreground", "layers", "outline", "border", "cards", "widgets", "notifications", "control center"]
|
||||||
|
settingKey: "blurLayerOutlineOpacity"
|
||||||
|
text: I18n.tr("Layer Outline Opacity")
|
||||||
|
description: I18n.tr("Controls outlines around blurred foreground cards, pills, and notification cards")
|
||||||
|
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
|
||||||
|
value: Math.round((SettingsData.blurLayerOutlineOpacity ?? 0.12) * 100)
|
||||||
|
minimum: 0
|
||||||
|
maximum: 40
|
||||||
|
unit: "%"
|
||||||
|
defaultValue: 12
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("blurLayerOutlineOpacity", newValue / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDropdownRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "border", "outline", "edge"]
|
||||||
|
settingKey: "blurBorderColor"
|
||||||
|
text: I18n.tr("Blur Border Color")
|
||||||
|
description: I18n.tr("Border color around blurred surfaces")
|
||||||
|
visible: SettingsData.blurEnabled
|
||||||
|
options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")]
|
||||||
|
currentValue: {
|
||||||
|
switch (SettingsData.blurBorderColor) {
|
||||||
|
case "primary":
|
||||||
|
return I18n.tr("Primary", "blur border color");
|
||||||
|
case "secondary":
|
||||||
|
return I18n.tr("Secondary", "blur border color");
|
||||||
|
case "surfaceText":
|
||||||
|
return I18n.tr("Text Color", "blur border color");
|
||||||
|
case "custom":
|
||||||
|
return I18n.tr("Custom", "blur border color");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Outline", "blur border color");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
if (value === I18n.tr("Primary", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "primary");
|
||||||
|
} else if (value === I18n.tr("Secondary", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "secondary");
|
||||||
|
} else if (value === I18n.tr("Text Color", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "surfaceText");
|
||||||
|
} else if (value === I18n.tr("Custom", "blur border color")) {
|
||||||
|
SettingsData.set("blurBorderColor", "custom");
|
||||||
|
openBlurBorderColorPicker();
|
||||||
|
} else {
|
||||||
|
SettingsData.set("blurBorderColor", "outline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["blur", "border", "opacity"]
|
||||||
|
settingKey: "blurBorderOpacity"
|
||||||
|
text: I18n.tr("Blur Border Opacity")
|
||||||
|
description: I18n.tr("Controls the outer edge of protocol-blurred windows")
|
||||||
|
visible: SettingsData.blurEnabled
|
||||||
|
value: Math.round((SettingsData.blurBorderOpacity ?? 0.35) * 100)
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
unit: "%"
|
||||||
|
defaultValue: 35
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "theme"
|
tab: "theme"
|
||||||
tags: ["modal", "darken", "background", "overlay"]
|
tags: ["modal", "darken", "background", "overlay"]
|
||||||
@@ -2023,7 +2016,7 @@ Item {
|
|||||||
title: I18n.tr("Cursor Theme")
|
title: I18n.tr("Cursor Theme")
|
||||||
settingKey: "cursorTheme"
|
settingKey: "cursorTheme"
|
||||||
iconName: "mouse"
|
iconName: "mouse"
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -2188,6 +2181,8 @@ Item {
|
|||||||
return SettingsData.cursorSettings.niri?.hideAfterInactiveMs || 0;
|
return SettingsData.cursorSettings.niri?.hideAfterInactiveMs || 0;
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return SettingsData.cursorSettings.hyprland?.inactiveTimeout || 0;
|
return SettingsData.cursorSettings.hyprland?.inactiveTimeout || 0;
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
return SettingsData.cursorSettings.dwl?.cursorHideTimeout || 0;
|
||||||
if (CompositorService.isMango)
|
if (CompositorService.isMango)
|
||||||
return SettingsData.cursorSettings.mango?.cursorHideTimeout || 0;
|
return SettingsData.cursorSettings.mango?.cursorHideTimeout || 0;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -2206,6 +2201,10 @@ Item {
|
|||||||
if (!updated.hyprland)
|
if (!updated.hyprland)
|
||||||
updated.hyprland = {};
|
updated.hyprland = {};
|
||||||
updated.hyprland.inactiveTimeout = newValue;
|
updated.hyprland.inactiveTimeout = newValue;
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
if (!updated.dwl)
|
||||||
|
updated.dwl = {};
|
||||||
|
updated.dwl.cursorHideTimeout = newValue;
|
||||||
} else if (CompositorService.isMango) {
|
} else if (CompositorService.isMango) {
|
||||||
if (!updated.mango)
|
if (!updated.mango)
|
||||||
updated.mango = {};
|
updated.mango = {};
|
||||||
@@ -2689,7 +2688,7 @@ Item {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "settings"
|
name: "folder"
|
||||||
size: 16
|
size: 16
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: UsersService.refreshing ? I18n.tr("Refreshing...") : ""
|
text: UsersService.refreshing ? I18n.tr("Refreshing…") : ""
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -474,7 +474,7 @@ Item {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankButton {
|
DankButton {
|
||||||
text: root.operationPending ? I18n.tr("Working...") : I18n.tr("Create User")
|
text: root.operationPending ? I18n.tr("Working…") : I18n.tr("Create User")
|
||||||
iconName: "person_add"
|
iconName: "person_add"
|
||||||
backgroundColor: Theme.primary
|
backgroundColor: Theme.primary
|
||||||
textColor: Theme.primaryText
|
textColor: Theme.primaryText
|
||||||
|
|||||||
@@ -1271,7 +1271,6 @@ Item {
|
|||||||
tags: ["blur", "layer", "niri", "compositor"]
|
tags: ["blur", "layer", "niri", "compositor"]
|
||||||
title: I18n.tr("Blur Wallpaper Layer")
|
title: I18n.tr("Blur Wallpaper Layer")
|
||||||
settingKey: "blurWallpaper"
|
settingKey: "blurWallpaper"
|
||||||
iconName: "blur_on"
|
|
||||||
visible: CompositorService.isNiri
|
visible: CompositorService.isNiri
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ FloatingWindow {
|
|||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
width: widgetList.width
|
width: widgetList.width
|
||||||
height: Math.max(60, textColumn.implicitHeight + 24)
|
height: 60
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
|
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
|
||||||
color: isSelected ? Theme.withAlpha(Theme.primary, root.blurActive ? 0.22 : 0.16) : widgetArea.containsMouse ? Theme.withAlpha(Theme.primary, root.blurActive ? 0.14 : 0.08) : Theme.withAlpha(Theme.surfaceVariant, root.rowAlpha)
|
color: isSelected ? Theme.withAlpha(Theme.primary, root.blurActive ? 0.22 : 0.16) : widgetArea.containsMouse ? Theme.withAlpha(Theme.primary, root.blurActive ? 0.14 : 0.08) : Theme.withAlpha(Theme.surfaceVariant, root.rowAlpha)
|
||||||
@@ -351,10 +351,9 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: textColumn
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 2
|
spacing: 2
|
||||||
width: parent.width - Theme.iconSize * 2 - Theme.spacingM * 4 + 4
|
width: parent.width - Theme.iconSize - Theme.spacingM * 3
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: modelData.text
|
text: modelData.text
|
||||||
@@ -363,7 +362,6 @@ FloatingWindow {
|
|||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ Item {
|
|||||||
|
|
||||||
property alias model: buttonGroup.model
|
property alias model: buttonGroup.model
|
||||||
property alias currentIndex: buttonGroup.currentIndex
|
property alias currentIndex: buttonGroup.currentIndex
|
||||||
property alias initialSelection: buttonGroup.initialSelection
|
|
||||||
property alias currentSelection: buttonGroup.currentSelection
|
|
||||||
property alias selectionMode: buttonGroup.selectionMode
|
property alias selectionMode: buttonGroup.selectionMode
|
||||||
property alias buttonHeight: buttonGroup.buttonHeight
|
property alias buttonHeight: buttonGroup.buttonHeight
|
||||||
property alias minButtonWidth: buttonGroup.minButtonWidth
|
property alias minButtonWidth: buttonGroup.minButtonWidth
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ Item {
|
|||||||
{
|
{
|
||||||
"id": "layout",
|
"id": "layout",
|
||||||
"text": I18n.tr("Layout"),
|
"text": I18n.tr("Layout"),
|
||||||
"description": I18n.tr("Display and switch MangoWC layouts"),
|
"description": I18n.tr("Display and switch DWL layouts"),
|
||||||
"icon": "view_quilt",
|
"icon": "view_quilt",
|
||||||
"enabled": CompositorService.isMango && MangoService.available,
|
"enabled": (CompositorService.isDwl && DwlService.dwlAvailable) || (CompositorService.isMango && MangoService.available),
|
||||||
"warning": !CompositorService.isMango ? I18n.tr("Requires MangoWC compositor") : (!MangoService.available ? I18n.tr("Mango service not available") : undefined)
|
"warning": CompositorService.isMango ? (!MangoService.available ? I18n.tr("DWL service not available") : undefined) : (!CompositorService.isDwl ? I18n.tr("Requires DWL compositor") : (!DwlService.dwlAvailable ? I18n.tr("DWL service not available") : undefined))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "launcherButton",
|
"id": "launcherButton",
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Column {
|
|||||||
property real originalY: y
|
property real originalY: y
|
||||||
|
|
||||||
width: itemsList.width
|
width: itemsList.width
|
||||||
height: Math.max(70, textColumn.implicitHeight + 32)
|
height: 70
|
||||||
z: held ? 2 : 1
|
z: held ? 2 : 1
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -123,7 +123,6 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: textColumn
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingM * 3 + 40 + Theme.iconSize
|
anchors.leftMargin: Theme.spacingM * 3 + 40 + Theme.iconSize
|
||||||
anchors.right: actionButtons.left
|
anchors.right: actionButtons.left
|
||||||
@@ -138,7 +137,6 @@ Column {
|
|||||||
color: modelData.enabled ? Theme.surfaceText : Theme.outline
|
color: modelData.enabled ? Theme.surfaceText : Theme.outline
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ SettingsCard {
|
|||||||
SettingsButtonGroupRow {
|
SettingsButtonGroupRow {
|
||||||
text: I18n.tr("Occupied Color")
|
text: I18n.tr("Occupied Color")
|
||||||
model: ["none", "sec", "s", "sc", "sch", "schh"]
|
model: ["none", "sec", "s", "sc", "sch", "schh"]
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
|
||||||
buttonHeight: 22
|
buttonHeight: 22
|
||||||
minButtonWidth: 36
|
minButtonWidth: 36
|
||||||
buttonPadding: Theme.spacingS
|
buttonPadding: Theme.spacingS
|
||||||
@@ -87,7 +87,7 @@ SettingsCard {
|
|||||||
height: 1
|
height: 1
|
||||||
color: Theme.outline
|
color: Theme.outline
|
||||||
opacity: 0.15
|
opacity: 0.15
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsButtonGroupRow {
|
SettingsButtonGroupRow {
|
||||||
@@ -124,12 +124,12 @@ SettingsCard {
|
|||||||
height: 1
|
height: 1
|
||||||
color: Theme.outline
|
color: Theme.outline
|
||||||
opacity: 0.15
|
opacity: 0.15
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsButtonGroupRow {
|
SettingsButtonGroupRow {
|
||||||
text: I18n.tr("Urgent Color")
|
text: I18n.tr("Urgent Color")
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
||||||
model: ["err", "pri", "sec", "s", "sc"]
|
model: ["err", "pri", "sec", "s", "sc"]
|
||||||
buttonHeight: 22
|
buttonHeight: 22
|
||||||
minButtonWidth: 36
|
minButtonWidth: 36
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ Item {
|
|||||||
text: I18n.tr("Follow Monitor Focus")
|
text: I18n.tr("Follow Monitor Focus")
|
||||||
description: I18n.tr("Show workspaces of the currently focused monitor")
|
description: I18n.tr("Show workspaces of the currently focused monitor")
|
||||||
checked: SettingsData.workspaceFollowFocus
|
checked: SettingsData.workspaceFollowFocus
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
||||||
onToggled: checked => SettingsData.set("workspaceFollowFocus", checked)
|
onToggled: checked => SettingsData.set("workspaceFollowFocus", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ Item {
|
|||||||
text: I18n.tr("Show All Tags")
|
text: I18n.tr("Show All Tags")
|
||||||
description: I18n.tr("Show all 9 tags instead of only occupied tags")
|
description: I18n.tr("Show all 9 tags instead of only occupied tags")
|
||||||
checked: SettingsData.dwlShowAllTags
|
checked: SettingsData.dwlShowAllTags
|
||||||
visible: CompositorService.isMango
|
visible: CompositorService.isDwl || CompositorService.isMango
|
||||||
onToggled: checked => SettingsData.set("dwlShowAllTags", checked)
|
onToggled: checked => SettingsData.set("dwlShowAllTags", checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ Variants {
|
|||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
updatesEnabled: root.renderActive || root._settleFrames > 0
|
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: Item {}
|
item: Item {}
|
||||||
}
|
}
|
||||||
@@ -86,59 +84,20 @@ Variants {
|
|||||||
|
|
||||||
readonly property bool transitioning: transitionAnimation.running
|
readonly property bool transitioning: transitionAnimation.running
|
||||||
property bool effectActive: false
|
property bool effectActive: false
|
||||||
|
property bool _renderSettling: true
|
||||||
|
property bool _overviewBlurSettling: false
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
property string pendingWallpaper: ""
|
property string pendingWallpaper: ""
|
||||||
property string _deferredSource: ""
|
property string _deferredSource: ""
|
||||||
readonly property bool overviewBlurActive: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
|
readonly property bool overviewBlurActive: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
|
||||||
readonly property var backingWindow: Window.window
|
|
||||||
readonly property bool renderActive: !source || effectActive || overviewBlurActive || pendingWallpaper !== "" || _deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading
|
|
||||||
property int _settleFrames: 3
|
|
||||||
|
|
||||||
function invalidate() {
|
|
||||||
_settleFrames = 3;
|
|
||||||
backingWindow?.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
onRenderActiveChanged: invalidate()
|
|
||||||
onBackingWindowChanged: invalidate()
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.backingWindow
|
target: currentWallpaper
|
||||||
function onFrameSwapped() {
|
function onStatusChanged() {
|
||||||
if (root._settleFrames > 0)
|
if (currentWallpaper.status !== Image.Ready && currentWallpaper.status !== Image.Error)
|
||||||
root._settleFrames--;
|
|
||||||
}
|
|
||||||
function onVisibleChanged() {
|
|
||||||
root.invalidate();
|
|
||||||
}
|
|
||||||
function onWidthChanged() {
|
|
||||||
root.invalidate();
|
|
||||||
}
|
|
||||||
function onHeightChanged() {
|
|
||||||
root.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Quickshell
|
|
||||||
function onScreensChanged() {
|
|
||||||
root.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onWallpaperFillModeChanged() {
|
|
||||||
root.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: IdleService
|
|
||||||
function onIsShellLockedChanged() {
|
|
||||||
if (IdleService.isShellLocked)
|
|
||||||
return;
|
return;
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +109,32 @@ Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: wallpaperWindow
|
||||||
|
function onWidthChanged() {
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
}
|
||||||
|
function onHeightChanged() {
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Quickshell
|
||||||
|
function onScreensChanged() {
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: NiriService
|
target: NiriService
|
||||||
function onDisplayScalesChanged() {
|
function onDisplayScalesChanged() {
|
||||||
root._recheckScreenScale();
|
root._recheckScreenScale();
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +142,29 @@ Variants {
|
|||||||
target: WlrOutputService
|
target: WlrOutputService
|
||||||
function onWlrOutputAvailableChanged() {
|
function onWlrOutputAvailableChanged() {
|
||||||
root._recheckScreenScale();
|
root._recheckScreenScale();
|
||||||
root.invalidate();
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NiriService
|
||||||
|
function onInOverviewChanged() {
|
||||||
|
root._overviewBlurSettling = true;
|
||||||
|
overviewBlurSettleTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBlurWallpaperOnOverviewChanged() {
|
||||||
|
root._overviewBlurSettling = true;
|
||||||
|
overviewBlurSettleTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWallpaperFillModeChanged() {
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,22 +181,26 @@ Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTransitionLoadError(failedSource) {
|
Connections {
|
||||||
log.warn("failed to load candidate wallpaper for", modelData.name + ":", failedSource);
|
target: IdleService
|
||||||
transitionDelayTimer.stop();
|
function onIsShellLockedChanged() {
|
||||||
transitionAnimation.stop();
|
if (!IdleService.isShellLocked) {
|
||||||
root.useNextForEffect = false;
|
root._renderSettling = true;
|
||||||
root.effectActive = false;
|
renderSettleTimer.restart();
|
||||||
root.transitionProgress = 0.0;
|
}
|
||||||
currentWallpaper.layer.enabled = false;
|
}
|
||||||
nextWallpaper.layer.enabled = false;
|
}
|
||||||
nextWallpaper.source = "";
|
|
||||||
|
|
||||||
if (!root.pendingWallpaper)
|
Timer {
|
||||||
return;
|
id: renderSettleTimer
|
||||||
const pending = root.pendingWallpaper;
|
interval: 1000
|
||||||
root.pendingWallpaper = "";
|
onTriggered: root._renderSettling = false
|
||||||
Qt.callLater(() => root.changeWallpaper(pending, true));
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: overviewBlurSettleTimer
|
||||||
|
interval: 150
|
||||||
|
onTriggered: root._overviewBlurSettling = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFillMode(modeName) {
|
function getFillMode(modeName) {
|
||||||
@@ -221,6 +227,11 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
wallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || root.overviewBlurActive || root._overviewBlurSettling || root.pendingWallpaper !== "" || root._deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
root._renderSettling = false;
|
||||||
|
}
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +262,8 @@ Variants {
|
|||||||
transitionAnimation.stop();
|
transitionAnimation.stop();
|
||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
root.screenScale = CompositorService.getScreenScale(modelData);
|
root.screenScale = CompositorService.getScreenScale(modelData);
|
||||||
currentWallpaper.source = newSource;
|
currentWallpaper.source = newSource;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
@@ -315,6 +328,9 @@ Variants {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
|
||||||
nextWallpaper.source = newPath;
|
nextWallpaper.source = newPath;
|
||||||
|
|
||||||
if (nextWallpaper.status === Image.Ready)
|
if (nextWallpaper.status === Image.Ready)
|
||||||
@@ -323,7 +339,7 @@ Variants {
|
|||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: !root.source || root.isColorSource || currentWallpaper.status === Image.Error
|
active: !root.source || root.isColorSource
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: DankBackdrop {
|
sourceComponent: DankBackdrop {
|
||||||
@@ -348,12 +364,6 @@ Variants {
|
|||||||
cache: true
|
cache: true
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
||||||
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
|
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
|
||||||
|
|
||||||
onStatusChanged: {
|
|
||||||
if (status === Image.Error) {
|
|
||||||
log.warn("failed to load active wallpaper for", modelData.name + ":", source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
@@ -370,13 +380,11 @@ Variants {
|
|||||||
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
|
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status === Image.Error) {
|
|
||||||
root.handleTransitionLoadError(source);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (status !== Image.Ready)
|
if (status !== Image.Ready)
|
||||||
return;
|
return;
|
||||||
if (root.actualTransitionType === "none") {
|
if (root.actualTransitionType === "none") {
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
currentWallpaper.source = source;
|
currentWallpaper.source = source;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
@@ -624,6 +632,8 @@ Variants {
|
|||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
currentWallpaper.layer.enabled = false;
|
currentWallpaper.layer.enabled = false;
|
||||||
nextWallpaper.layer.enabled = false;
|
nextWallpaper.layer.enabled = false;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
|
||||||
if (!root.pendingWallpaper)
|
if (!root.pendingWallpaper)
|
||||||
|
|||||||
@@ -388,15 +388,11 @@ Singleton {
|
|||||||
return "text";
|
return "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPinnedEntryByHash(entryHash) {
|
|
||||||
if (!entryHash) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return internalEntries.find(entry => entry.pinned && entry.hash === entryHash) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashedPinnedEntry(entryHash) {
|
function hashedPinnedEntry(entryHash) {
|
||||||
return getPinnedEntryByHash(entryHash) !== null;
|
if (!entryHash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClipboardAvailableChanged: {
|
onClipboardAvailableChanged: {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Singleton {
|
|||||||
|
|
||||||
property bool isHyprland: false
|
property bool isHyprland: false
|
||||||
property bool isNiri: false
|
property bool isNiri: false
|
||||||
|
property bool isDwl: false
|
||||||
property bool isMango: false
|
property bool isMango: false
|
||||||
property bool isSway: false
|
property bool isSway: false
|
||||||
property bool isScroll: false
|
property bool isScroll: false
|
||||||
@@ -96,6 +97,12 @@ Singleton {
|
|||||||
return hyprlandMonitor.scale;
|
return hyprlandMonitor.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDwl && screen) {
|
||||||
|
const dwlScale = DwlService.getOutputScale(screen.name);
|
||||||
|
if (dwlScale !== undefined && dwlScale > 0)
|
||||||
|
return dwlScale;
|
||||||
|
}
|
||||||
|
|
||||||
if (isMango && screen) {
|
if (isMango && screen) {
|
||||||
const mangoScale = MangoService.getOutputScale(screen.name);
|
const mangoScale = MangoService.getOutputScale(screen.name);
|
||||||
if (mangoScale !== undefined && mangoScale > 0)
|
if (mangoScale !== undefined && mangoScale > 0)
|
||||||
@@ -114,7 +121,9 @@ Singleton {
|
|||||||
else if (isSway || isScroll || isMiracle) {
|
else if (isSway || isScroll || isMiracle) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
screenName = focusedWs?.monitor?.name || "";
|
screenName = focusedWs?.monitor?.name || "";
|
||||||
} else if (isMango && MangoService.activeOutput)
|
} else if (isDwl && DwlService.activeOutput)
|
||||||
|
screenName = DwlService.activeOutput;
|
||||||
|
else if (isMango && MangoService.activeOutput)
|
||||||
screenName = MangoService.activeOutput;
|
screenName = MangoService.activeOutput;
|
||||||
|
|
||||||
if (!screenName)
|
if (!screenName)
|
||||||
@@ -183,9 +192,19 @@ Singleton {
|
|||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
NiriService.generateNiriLayoutConfig();
|
NiriService.generateNiriLayoutConfig();
|
||||||
HyprlandService.generateLayoutConfig();
|
HyprlandService.generateLayoutConfig();
|
||||||
|
DwlService.generateLayoutConfig();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DwlService
|
||||||
|
function onStateChanged() {
|
||||||
|
if (isDwl && !isHyprland && !isNiri) {
|
||||||
|
scheduleSort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: MangoService
|
target: MangoService
|
||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
@@ -252,7 +271,13 @@ Singleton {
|
|||||||
function _specialWorkspaceNameFromMonitor(monitor) {
|
function _specialWorkspaceNameFromMonitor(monitor) {
|
||||||
if (!monitor)
|
if (!monitor)
|
||||||
return "";
|
return "";
|
||||||
const candidates = [monitor.activeSpecialWorkspace?.name, monitor.specialWorkspace?.name, monitor.lastIpcObject?.specialWorkspace?.name, monitor.lastIpcObject?.specialWorkspace, monitor.lastIpcObject?.activeSpecialWorkspace?.name];
|
const candidates = [
|
||||||
|
monitor.activeSpecialWorkspace?.name,
|
||||||
|
monitor.specialWorkspace?.name,
|
||||||
|
monitor.lastIpcObject?.specialWorkspace?.name,
|
||||||
|
monitor.lastIpcObject?.specialWorkspace,
|
||||||
|
monitor.lastIpcObject?.activeSpecialWorkspace?.name
|
||||||
|
];
|
||||||
for (let i = 0; i < candidates.length; i++) {
|
for (let i = 0; i < candidates.length; i++) {
|
||||||
const normalized = _normalizeSpecialWorkspaceName(candidates[i]);
|
const normalized = _normalizeSpecialWorkspaceName(candidates[i]);
|
||||||
if (normalized)
|
if (normalized)
|
||||||
@@ -835,6 +860,7 @@ Singleton {
|
|||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
NiriService.generateNiriLayoutConfig();
|
NiriService.generateNiriLayoutConfig();
|
||||||
HyprlandService.generateLayoutConfig();
|
HyprlandService.generateLayoutConfig();
|
||||||
|
DwlService.generateLayoutConfig();
|
||||||
MangoService.generateLayoutConfig();
|
MangoService.generateLayoutConfig();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -844,6 +870,7 @@ Singleton {
|
|||||||
if (mangoSignature && mangoSignature.length > 0) {
|
if (mangoSignature && mangoSignature.length > 0) {
|
||||||
isHyprland = false;
|
isHyprland = false;
|
||||||
isNiri = false;
|
isNiri = false;
|
||||||
|
isDwl = false;
|
||||||
isMango = true;
|
isMango = true;
|
||||||
isSway = false;
|
isSway = false;
|
||||||
isScroll = false;
|
isScroll = false;
|
||||||
@@ -857,6 +884,7 @@ Singleton {
|
|||||||
if (hyprlandSignature && hyprlandSignature.length > 0 && !niriSocket && !swaySocket && !scrollSocket && !miracleSocket && !labwcPid) {
|
if (hyprlandSignature && hyprlandSignature.length > 0 && !niriSocket && !swaySocket && !scrollSocket && !miracleSocket && !labwcPid) {
|
||||||
isHyprland = true;
|
isHyprland = true;
|
||||||
isNiri = false;
|
isNiri = false;
|
||||||
|
isDwl = false;
|
||||||
isMango = false;
|
isMango = false;
|
||||||
isSway = false;
|
isSway = false;
|
||||||
isScroll = false;
|
isScroll = false;
|
||||||
@@ -872,6 +900,7 @@ Singleton {
|
|||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
isNiri = true;
|
isNiri = true;
|
||||||
isHyprland = false;
|
isHyprland = false;
|
||||||
|
isDwl = false;
|
||||||
isMango = false;
|
isMango = false;
|
||||||
isSway = false;
|
isSway = false;
|
||||||
isScroll = false;
|
isScroll = false;
|
||||||
@@ -890,6 +919,7 @@ Singleton {
|
|||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
isNiri = false;
|
isNiri = false;
|
||||||
isHyprland = false;
|
isHyprland = false;
|
||||||
|
isDwl = false;
|
||||||
isSway = true;
|
isSway = true;
|
||||||
isScroll = false;
|
isScroll = false;
|
||||||
isMiracle = false;
|
isMiracle = false;
|
||||||
@@ -906,6 +936,7 @@ Singleton {
|
|||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
isNiri = false;
|
isNiri = false;
|
||||||
isHyprland = false;
|
isHyprland = false;
|
||||||
|
isDwl = false;
|
||||||
isMango = false;
|
isMango = false;
|
||||||
isSway = false;
|
isSway = false;
|
||||||
isScroll = false;
|
isScroll = false;
|
||||||
@@ -923,6 +954,7 @@ Singleton {
|
|||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
isNiri = false;
|
isNiri = false;
|
||||||
isHyprland = false;
|
isHyprland = false;
|
||||||
|
isDwl = false;
|
||||||
isMango = false;
|
isMango = false;
|
||||||
isSway = false;
|
isSway = false;
|
||||||
isScroll = true;
|
isScroll = true;
|
||||||
@@ -938,6 +970,7 @@ Singleton {
|
|||||||
if (labwcPid && labwcPid.length > 0) {
|
if (labwcPid && labwcPid.length > 0) {
|
||||||
isHyprland = false;
|
isHyprland = false;
|
||||||
isNiri = false;
|
isNiri = false;
|
||||||
|
isDwl = false;
|
||||||
isMango = false;
|
isMango = false;
|
||||||
isSway = false;
|
isSway = false;
|
||||||
isScroll = false;
|
isScroll = false;
|
||||||
@@ -948,15 +981,45 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isHyprland = false;
|
if (DMSService.dmsAvailable) {
|
||||||
isNiri = false;
|
Qt.callLater(checkForDwl);
|
||||||
isMango = false;
|
} else {
|
||||||
isSway = false;
|
isHyprland = false;
|
||||||
isScroll = false;
|
isNiri = false;
|
||||||
isMiracle = false;
|
isDwl = false;
|
||||||
isLabwc = false;
|
isMango = false;
|
||||||
compositor = "unknown";
|
isSway = false;
|
||||||
log.warn("No compositor detected");
|
isScroll = false;
|
||||||
|
isMiracle = false;
|
||||||
|
isLabwc = false;
|
||||||
|
compositor = "unknown";
|
||||||
|
log.warn("No compositor detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DMSService
|
||||||
|
function onCapabilitiesReceived() {
|
||||||
|
if (!isHyprland && !isNiri && !isDwl && !isMango && !isLabwc) {
|
||||||
|
checkForDwl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForDwl() {
|
||||||
|
if (isMango)
|
||||||
|
return;
|
||||||
|
if (DMSService.apiVersion >= 12 && DMSService.capabilities.includes("dwl")) {
|
||||||
|
isHyprland = false;
|
||||||
|
isNiri = false;
|
||||||
|
isDwl = true;
|
||||||
|
isSway = false;
|
||||||
|
isScroll = false;
|
||||||
|
isMiracle = false;
|
||||||
|
isLabwc = false;
|
||||||
|
compositor = "dwl";
|
||||||
|
log.info("Detected DWL via DMS capability");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function powerOffMonitors() {
|
function powerOffMonitors() {
|
||||||
@@ -964,6 +1027,8 @@ Singleton {
|
|||||||
return NiriService.powerOffMonitors();
|
return NiriService.powerOffMonitors();
|
||||||
if (isHyprland)
|
if (isHyprland)
|
||||||
return HyprlandService.dpmsOff();
|
return HyprlandService.dpmsOff();
|
||||||
|
if (isDwl)
|
||||||
|
return _dwlPowerOffMonitors();
|
||||||
if (isMango)
|
if (isMango)
|
||||||
return MangoService.powerOffMonitors();
|
return MangoService.powerOffMonitors();
|
||||||
if (isSway || isScroll || isMiracle) {
|
if (isSway || isScroll || isMiracle) {
|
||||||
@@ -983,6 +1048,8 @@ Singleton {
|
|||||||
return NiriService.powerOnMonitors();
|
return NiriService.powerOnMonitors();
|
||||||
if (isHyprland)
|
if (isHyprland)
|
||||||
return HyprlandService.dpmsOn();
|
return HyprlandService.dpmsOn();
|
||||||
|
if (isDwl)
|
||||||
|
return _dwlPowerOnMonitors();
|
||||||
if (isMango)
|
if (isMango)
|
||||||
return MangoService.powerOnMonitors();
|
return MangoService.powerOnMonitors();
|
||||||
if (isSway || isScroll || isMiracle) {
|
if (isSway || isScroll || isMiracle) {
|
||||||
@@ -996,4 +1063,32 @@ Singleton {
|
|||||||
}
|
}
|
||||||
log.warn("Cannot power on monitors, unknown compositor");
|
log.warn("Cannot power on monitors, unknown compositor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _dwlPowerOffMonitors() {
|
||||||
|
if (!Quickshell.screens || Quickshell.screens.length === 0) {
|
||||||
|
log.warn("No screens available for DWL power off");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
const screen = Quickshell.screens[i];
|
||||||
|
if (screen && screen.name) {
|
||||||
|
Quickshell.execDetached(["mmsg", "dispatch", "disable_monitor," + screen.name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dwlPowerOnMonitors() {
|
||||||
|
if (!Quickshell.screens || Quickshell.screens.length === 0) {
|
||||||
|
log.warn("No screens available for DWL power on");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
const screen = Quickshell.screens[i];
|
||||||
|
if (screen && screen.name) {
|
||||||
|
Quickshell.execDetached(["mmsg", "dispatch", "enable_monitor," + screen.name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Singleton {
|
|||||||
signal capabilitiesReceived
|
signal capabilitiesReceived
|
||||||
signal credentialsRequest(var data)
|
signal credentialsRequest(var data)
|
||||||
signal bluetoothPairingRequest(var data)
|
signal bluetoothPairingRequest(var data)
|
||||||
|
signal dwlStateUpdate(var data)
|
||||||
signal brightnessStateUpdate(var data)
|
signal brightnessStateUpdate(var data)
|
||||||
signal brightnessDeviceUpdate(var device)
|
signal brightnessDeviceUpdate(var device)
|
||||||
signal wlrOutputStateUpdate(var data)
|
signal wlrOutputStateUpdate(var data)
|
||||||
@@ -67,7 +68,7 @@ Singleton {
|
|||||||
property bool screensaverInhibited: false
|
property bool screensaverInhibited: false
|
||||||
property var screensaverInhibitors: []
|
property var screensaverInhibitors: []
|
||||||
|
|
||||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
|
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -285,7 +286,7 @@ Singleton {
|
|||||||
|
|
||||||
function removeSubscription(service) {
|
function removeSubscription(service) {
|
||||||
if (activeSubscriptions.includes("all")) {
|
if (activeSubscriptions.includes("all")) {
|
||||||
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "brightness", "browser", "location"];
|
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "browser", "location"];
|
||||||
const filtered = allServices.filter(s => s !== service);
|
const filtered = allServices.filter(s => s !== service);
|
||||||
subscribe(filtered);
|
subscribe(filtered);
|
||||||
} else {
|
} else {
|
||||||
@@ -307,7 +308,7 @@ Singleton {
|
|||||||
excludeServices = [excludeServices];
|
excludeServices = [excludeServices];
|
||||||
}
|
}
|
||||||
|
|
||||||
const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "brightness", "browser", "dbus", "location"];
|
const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "browser", "dbus", "location"];
|
||||||
const filtered = allServices.filter(s => !excludeServices.includes(s));
|
const filtered = allServices.filter(s => !excludeServices.includes(s));
|
||||||
subscribe(filtered);
|
subscribe(filtered);
|
||||||
}
|
}
|
||||||
@@ -353,6 +354,8 @@ Singleton {
|
|||||||
bluetoothPairingRequest(data);
|
bluetoothPairingRequest(data);
|
||||||
} else if (service === "cups") {
|
} else if (service === "cups") {
|
||||||
cupsStateUpdate(data);
|
cupsStateUpdate(data);
|
||||||
|
} else if (service === "dwl") {
|
||||||
|
dwlStateUpdate(data);
|
||||||
} else if (service === "brightness") {
|
} else if (service === "brightness") {
|
||||||
brightnessStateUpdate(data);
|
brightnessStateUpdate(data);
|
||||||
} else if (service === "brightness.update") {
|
} else if (service === "brightness.update") {
|
||||||
|
|||||||
@@ -0,0 +1,461 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
readonly property var log: Log.scoped("DwlService")
|
||||||
|
|
||||||
|
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
readonly property string mangoDmsDir: configDir + "/mango/dms"
|
||||||
|
readonly property string outputsPath: mangoDmsDir + "/outputs.conf"
|
||||||
|
readonly property string layoutPath: mangoDmsDir + "/layout.conf"
|
||||||
|
readonly property string cursorPath: mangoDmsDir + "/cursor.conf"
|
||||||
|
|
||||||
|
property int _lastGapValue: -1
|
||||||
|
|
||||||
|
property bool dwlAvailable: false
|
||||||
|
// Alias so consumers can treat DwlService/MangoService uniformly via `.available`.
|
||||||
|
readonly property bool available: dwlAvailable
|
||||||
|
property var outputs: ({})
|
||||||
|
property var tagCount: 9
|
||||||
|
property var layouts: []
|
||||||
|
property string activeOutput: ""
|
||||||
|
property var outputScales: ({})
|
||||||
|
property string currentKeyboardLayout: {
|
||||||
|
if (!outputs || !activeOutput)
|
||||||
|
return "";
|
||||||
|
const output = outputs[activeOutput];
|
||||||
|
return (output && output.kbLayout) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
signal stateChanged
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
if (!CompositorService.isDwl)
|
||||||
|
return;
|
||||||
|
const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
|
||||||
|
if (newGaps === root._lastGapValue)
|
||||||
|
return;
|
||||||
|
root._lastGapValue = newGaps;
|
||||||
|
generateLayoutConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: CompositorService
|
||||||
|
function onIsDwlChanged() {
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
generateLayoutConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DMSService
|
||||||
|
function onCapabilitiesReceived() {
|
||||||
|
checkCapabilities();
|
||||||
|
}
|
||||||
|
function onConnectionStateChanged() {
|
||||||
|
if (DMSService.isConnected) {
|
||||||
|
checkCapabilities();
|
||||||
|
} else {
|
||||||
|
dwlAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onDwlStateUpdate(data) {
|
||||||
|
if (dwlAvailable) {
|
||||||
|
handleStateUpdate(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (DMSService.dmsAvailable)
|
||||||
|
checkCapabilities();
|
||||||
|
if (dwlAvailable)
|
||||||
|
refreshOutputScales();
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
Qt.callLater(generateLayoutConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCapabilities() {
|
||||||
|
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
|
||||||
|
dwlAvailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasDwl = DMSService.capabilities.includes("dwl");
|
||||||
|
if (hasDwl && !dwlAvailable) {
|
||||||
|
dwlAvailable = true;
|
||||||
|
log.info("DWL capability detected");
|
||||||
|
requestState();
|
||||||
|
refreshOutputScales();
|
||||||
|
} else if (!hasDwl) {
|
||||||
|
dwlAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestState() {
|
||||||
|
if (!DMSService.isConnected || !dwlAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("dwl.getState", null, response => {
|
||||||
|
if (response.result) {
|
||||||
|
handleStateUpdate(response.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStateUpdate(state) {
|
||||||
|
outputs = state.outputs || {};
|
||||||
|
tagCount = state.tagCount || 9;
|
||||||
|
layouts = state.layouts || [];
|
||||||
|
activeOutput = state.activeOutput || "";
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTags(outputName, tagmask, toggleTagset) {
|
||||||
|
if (!DMSService.isConnected || !dwlAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("dwl.setTags", {
|
||||||
|
"output": outputName,
|
||||||
|
"tagmask": tagmask,
|
||||||
|
"toggleTagset": toggleTagset
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
log.warn("setTags error:", response.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setClientTags(outputName, andTags, xorTags) {
|
||||||
|
if (!DMSService.isConnected || !dwlAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("dwl.setClientTags", {
|
||||||
|
"output": outputName,
|
||||||
|
"andTags": andTags,
|
||||||
|
"xorTags": xorTags
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
log.warn("setClientTags error:", response.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLayout(outputName, index) {
|
||||||
|
if (!DMSService.isConnected || !dwlAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("dwl.setLayout", {
|
||||||
|
"output": outputName,
|
||||||
|
"index": index
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
log.warn("setLayout error:", response.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputState(outputName) {
|
||||||
|
if (!outputs || !outputs[outputName]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return outputs[outputName];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveTags(outputName) {
|
||||||
|
const output = getOutputState(outputName);
|
||||||
|
if (!output || !output.tags) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return output.tags.filter(tag => tag.state === 1).map(tag => tag.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTagsWithClients(outputName) {
|
||||||
|
const output = getOutputState(outputName);
|
||||||
|
if (!output || !output.tags) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return output.tags.filter(tag => tag.clients > 0).map(tag => tag.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrgentTags(outputName) {
|
||||||
|
const output = getOutputState(outputName);
|
||||||
|
if (!output || !output.tags) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return output.tags.filter(tag => tag.state === 2).map(tag => tag.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToTag(outputName, tagIndex) {
|
||||||
|
const tagmask = 1 << tagIndex;
|
||||||
|
setTags(outputName, tagmask, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTag(outputName, tagIndex) {
|
||||||
|
const output = getOutputState(outputName);
|
||||||
|
if (!output || !output.tags) {
|
||||||
|
log.debug("toggleTag: no output or tags for", outputName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentMask = 0;
|
||||||
|
output.tags.forEach(tag => {
|
||||||
|
if (tag.state === 1) {
|
||||||
|
currentMask |= (1 << tag.tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const clickedMask = 1 << tagIndex;
|
||||||
|
const newMask = currentMask ^ clickedMask;
|
||||||
|
|
||||||
|
log.debug("toggleTag:", outputName, "tag:", tagIndex, "currentMask:", currentMask.toString(2), "clickedMask:", clickedMask.toString(2), "newMask:", newMask.toString(2));
|
||||||
|
|
||||||
|
if (newMask === 0) {
|
||||||
|
log.debug("toggleTag: newMask is 0, switching to tag", tagIndex);
|
||||||
|
setTags(outputName, 1 << tagIndex, 0);
|
||||||
|
} else {
|
||||||
|
log.debug("toggleTag: setting combined mask", newMask);
|
||||||
|
setTags(outputName, newMask, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function quit() {
|
||||||
|
Quickshell.execDetached(["mmsg", "dispatch", "quit"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: scaleQueryProcess
|
||||||
|
command: ["mmsg", "get", "all-monitors"]
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
const newScales = {};
|
||||||
|
const data = JSON.parse(text.trim());
|
||||||
|
const monitors = data.monitors || [];
|
||||||
|
for (const mon of monitors) {
|
||||||
|
if (mon.name && typeof mon.scale === "number" && mon.scale > 0) {
|
||||||
|
newScales[mon.name] = mon.scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputScales = newScales;
|
||||||
|
} catch (e) {
|
||||||
|
log.warn("Failed to parse mmsg output:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
log.warn("mmsg failed with exit code:", exitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshOutputScales() {
|
||||||
|
if (!dwlAvailable)
|
||||||
|
return;
|
||||||
|
scaleQueryProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputScale(outputName) {
|
||||||
|
return outputScales[outputName];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisibleTags(outputName) {
|
||||||
|
const output = getOutputState(outputName);
|
||||||
|
if (!output || !output.tags) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleTags = new Set();
|
||||||
|
|
||||||
|
output.tags.forEach(tag => {
|
||||||
|
if (tag.state === 1 || tag.clients > 0) {
|
||||||
|
visibleTags.add(tag.tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(visibleTags).sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOutputsConfig(outputsData, callback) {
|
||||||
|
if (!outputsData || Object.keys(outputsData).length === 0) {
|
||||||
|
if (callback)
|
||||||
|
callback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let lines = ["# Auto-generated by DMS - do not edit manually", ""];
|
||||||
|
|
||||||
|
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 vrr = output.vrr_enabled ? 1 : 0;
|
||||||
|
|
||||||
|
const rule = ["name:^" + outputName + "$", "width:" + width, "height:" + height, "refresh:" + refreshRate, "x:" + x, "y:" + y, "scale:" + scale, "rr:" + transform, "vrr:" + vrr].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) {
|
||||||
|
log.warn("Failed to write outputs config:", output);
|
||||||
|
if (callback)
|
||||||
|
callback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("Generated outputs config at", outputsPath);
|
||||||
|
if (CompositorService.isDwl)
|
||||||
|
reloadConfig();
|
||||||
|
if (callback)
|
||||||
|
callback(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadConfig() {
|
||||||
|
Proc.runCommand("mango-reload", ["mmsg", "dispatch", "reload_config"], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
log.warn("mmsg reload_config failed:", output);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLayoutConfig() {
|
||||||
|
if (!CompositorService.isDwl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const defaultRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12;
|
||||||
|
const defaultGaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4;
|
||||||
|
const defaultBorderSize = 2;
|
||||||
|
|
||||||
|
const cornerRadius = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutRadiusOverride >= 0) ? SettingsData.mangoLayoutRadiusOverride : defaultRadius;
|
||||||
|
const gaps = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutGapsOverride >= 0) ? SettingsData.mangoLayoutGapsOverride : defaultGaps;
|
||||||
|
const borderSize = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutBorderSize >= 0) ? SettingsData.mangoLayoutBorderSize : defaultBorderSize;
|
||||||
|
|
||||||
|
let content = `# Auto-generated by DMS - do not edit manually
|
||||||
|
border_radius=${cornerRadius}
|
||||||
|
gappih=${gaps}
|
||||||
|
gappiv=${gaps}
|
||||||
|
gappoh=${gaps}
|
||||||
|
gappov=${gaps}
|
||||||
|
borderpx=${borderSize}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Proc.runCommand("mango-write-layout", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${layoutPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
log.warn("Failed to write layout config:", output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("Generated layout config at", layoutPath);
|
||||||
|
reloadConfig();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCursorConfig() {
|
||||||
|
if (!CompositorService.isDwl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
log.debug("Generating cursor config...");
|
||||||
|
|
||||||
|
const settings = typeof SettingsData !== "undefined" ? SettingsData.cursorSettings : null;
|
||||||
|
if (!settings) {
|
||||||
|
Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
log.warn("Failed to write cursor config:", output);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme;
|
||||||
|
const size = settings.size || 24;
|
||||||
|
const hideTimeout = settings.dwl?.cursorHideTimeout || 0;
|
||||||
|
|
||||||
|
const isDefaultConfig = !themeName && size === 24 && hideTimeout === 0;
|
||||||
|
if (isDefaultConfig) {
|
||||||
|
Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0)
|
||||||
|
log.warn("Failed to write cursor config:", output);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = `# Auto-generated by DMS - do not edit manually
|
||||||
|
cursor_size=${size}`;
|
||||||
|
|
||||||
|
if (themeName)
|
||||||
|
content += `\ncursor_theme=${themeName}`;
|
||||||
|
|
||||||
|
if (hideTimeout > 0)
|
||||||
|
content += `\ncursor_hide_timeout=${hideTimeout}`;
|
||||||
|
|
||||||
|
content += `\n`;
|
||||||
|
|
||||||
|
Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${cursorPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
log.warn("Failed to write cursor config:", output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("Generated cursor config at", cursorPath);
|
||||||
|
reloadConfig();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,13 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("KeybindsService")
|
readonly property var log: Log.scoped("KeybindsService")
|
||||||
|
|
||||||
property bool available: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
property bool available: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
|
||||||
property string currentProvider: {
|
property string currentProvider: {
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri)
|
||||||
return "niri";
|
return "niri";
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return "hyprland";
|
return "hyprland";
|
||||||
if (CompositorService.isMango)
|
if (CompositorService.isDwl || CompositorService.isMango)
|
||||||
return "mangowc";
|
return "mangowc";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ Singleton {
|
|||||||
return "niri";
|
return "niri";
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return "hyprland";
|
return "hyprland";
|
||||||
if (CompositorService.isMango)
|
if (CompositorService.isDwl || CompositorService.isMango)
|
||||||
return "mangowc";
|
return "mangowc";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -290,16 +290,13 @@ Singleton {
|
|||||||
configFile: mainConfigPath,
|
configFile: mainConfigPath,
|
||||||
backupFile: backupPath,
|
backupFile: backupPath,
|
||||||
fragmentFiles: [compositorConfigDir + "/dms/binds.lua", compositorConfigDir + "/dms/binds-user.lua"],
|
fragmentFiles: [compositorConfigDir + "/dms/binds.lua", compositorConfigDir + "/dms/binds-user.lua"],
|
||||||
includes: [
|
includes: [{
|
||||||
{
|
|
||||||
grepPattern: "dms.binds",
|
grepPattern: "dms.binds",
|
||||||
includeLine: "require(\"dms.binds\")"
|
includeLine: "require(\"dms.binds\")"
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
grepPattern: "dms.binds-user",
|
grepPattern: "dms.binds-user",
|
||||||
includeLine: "require(\"dms.binds-user\")"
|
includeLine: "require(\"dms.binds-user\")"
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "mangowc":
|
case "mangowc":
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import qs.Services
|
|||||||
|
|
||||||
// Native MangoWM IPC client. mango advertises a JSON-over-Unix-socket protocol
|
// Native MangoWM IPC client. mango advertises a JSON-over-Unix-socket protocol
|
||||||
// via MANGO_INSTANCE_SIGNATURE; each connection issues one `watch <target>` verb
|
// via MANGO_INSTANCE_SIGNATURE; each connection issues one `watch <target>` verb
|
||||||
// and gets a full JSON snapshot followed by newline-delimited updates. Exposes
|
// and gets a full JSON snapshot followed by newline-delimited updates. Replaces
|
||||||
// a dwl-style tag API plus a per-client window list.
|
// the legacy dwl-ipc-v2 path (DwlService) for mango, exposing a
|
||||||
|
// DwlService-compatible tag API plus a per-client window list.
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("MangoService")
|
readonly property var log: Log.scoped("MangoService")
|
||||||
@@ -218,7 +219,7 @@ Singleton {
|
|||||||
root.windows = data.clients;
|
root.windows = data.clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tag API (dwl-style tag model) ──────────────────────────────────────
|
// ── DwlService-compatible tag API ──────────────────────────────────────
|
||||||
|
|
||||||
function getOutputState(outputName) {
|
function getOutputState(outputName) {
|
||||||
return (outputs && outputs[outputName]) ? outputs[outputName] : null;
|
return (outputs && outputs[outputName]) ? outputs[outputName] : null;
|
||||||
|
|||||||
@@ -966,67 +966,4 @@ Singleton {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string _ipcIdPattern: "^[a-zA-Z0-9_\\-:]{1,64}$";
|
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
target: "plugin-scan"
|
|
||||||
|
|
||||||
function scan(): string {
|
|
||||||
root.scanPlugins();
|
|
||||||
return `SCAN_TRIGGERED: ${Object.keys(root.availablePlugins).length} known before debounce`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rescan(pluginId: string): string {
|
|
||||||
if (!pluginId)
|
|
||||||
return "ERROR: rescan requires a pluginId";
|
|
||||||
if (!new RegExp(root._ipcIdPattern).test(pluginId))
|
|
||||||
return `ERROR: invalid pluginId '${pluginId}' (allowed: [a-zA-Z0-9_\\-:]{1,64})`;
|
|
||||||
if (!(pluginId in root.availablePlugins))
|
|
||||||
return `ERROR: unknown pluginId '${pluginId}' (try 'list' first)`;
|
|
||||||
root.forceRescanPlugin(pluginId);
|
|
||||||
return `RESCAN_TRIGGERED: ${pluginId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reload(pluginId: string): string {
|
|
||||||
if (!pluginId)
|
|
||||||
return "ERROR: reload requires a pluginId";
|
|
||||||
if (!new RegExp(root._ipcIdPattern).test(pluginId))
|
|
||||||
return `ERROR: invalid pluginId '${pluginId}' (allowed: [a-zA-Z0-9_\\-:]{1,64})`;
|
|
||||||
if (!(pluginId in root.availablePlugins))
|
|
||||||
return `ERROR: unknown pluginId '${pluginId}'`;
|
|
||||||
root.reloadPlugin(pluginId);
|
|
||||||
return `RELOAD_TRIGGERED: ${pluginId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function list(): string {
|
|
||||||
const ids = Object.keys(root.availablePlugins);
|
|
||||||
const cap = 256;
|
|
||||||
const n = Math.min(ids.length, cap);
|
|
||||||
const lines = [];
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
const id = ids[i];
|
|
||||||
if (!new RegExp(root._ipcIdPattern).test(id))
|
|
||||||
continue;
|
|
||||||
const p = root.availablePlugins[id];
|
|
||||||
const safeName = String(p.name || "").replace(/[\t\n\r]/g, " ");
|
|
||||||
lines.push(`${id}\t${p.loaded ? "loaded" : "unloaded"}\t${p.type || "unknown"}\t${safeName}`);
|
|
||||||
}
|
|
||||||
const header = `# count=${ids.length} returned=${n}${ids.length > n ? " (truncated, see cap)" : ""}`;
|
|
||||||
return header + "\n" + lines.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function status(pluginId: string): string {
|
|
||||||
if (!pluginId)
|
|
||||||
return "ERROR: status requires a pluginId";
|
|
||||||
if (!new RegExp(root._ipcIdPattern).test(pluginId))
|
|
||||||
return `ERROR: invalid pluginId '${pluginId}'`;
|
|
||||||
const plugin = root.availablePlugins[pluginId];
|
|
||||||
if (!plugin)
|
|
||||||
return `ERROR: unknown pluginId '${pluginId}'`;
|
|
||||||
const err = root.pluginLoadErrors[pluginId] || "";
|
|
||||||
const safeErr = String(err).replace(/[\t\n\r]/g, " ");
|
|
||||||
return `${plugin.loaded ? "loaded" : "unloaded"}\t${plugin.type || ""}\t${safeErr}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import Quickshell.Hyprland
|
||||||
import Quickshell.I3
|
import Quickshell.I3
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -313,6 +314,11 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CompositorService.isDwl) {
|
||||||
|
DwlService.quit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (CompositorService.isMango) {
|
if (CompositorService.isMango) {
|
||||||
MangoService.quit();
|
MangoService.quit();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ Singleton {
|
|||||||
readonly property var conditionMap: ({
|
readonly property var conditionMap: ({
|
||||||
"isNiri": () => CompositorService.isNiri,
|
"isNiri": () => CompositorService.isNiri,
|
||||||
"isHyprland": () => CompositorService.isHyprland,
|
"isHyprland": () => CompositorService.isHyprland,
|
||||||
|
"isDwl": () => CompositorService.isDwl,
|
||||||
"isMango": () => CompositorService.isMango,
|
"isMango": () => CompositorService.isMango,
|
||||||
"isHyprlandOrNiri": () => CompositorService.isHyprland || CompositorService.isNiri,
|
|
||||||
"windowRulesCapable": () => CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango,
|
|
||||||
"layoutCapable": () => CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango,
|
|
||||||
"keybindsAvailable": () => KeybindsService.available,
|
"keybindsAvailable": () => KeybindsService.available,
|
||||||
"soundsAvailable": () => AudioService.soundsAvailable,
|
"soundsAvailable": () => AudioService.soundsAvailable,
|
||||||
"cupsAvailable": () => CupsService.cupsAvailable,
|
"cupsAvailable": () => CupsService.cupsAvailable,
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// High-level apply matching the generateOutputsConfig() pattern used by
|
// High-level apply matching the generateOutputsConfig() pattern used by
|
||||||
// NiriService, HyprlandService and MangoService. Instead of writing a
|
// NiriService, HyprlandService and DwlService. Instead of writing a
|
||||||
// config file, the changes are applied directly via the
|
// config file, the changes are applied directly via the
|
||||||
// wlr-output-management protocol.
|
// wlr-output-management protocol.
|
||||||
function applyOutputsConfig(outputsData, connectedOutputs) {
|
function applyOutputsConfig(outputsData, connectedOutputs) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user