1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

Compare commits

...

32 Commits

Author SHA1 Message Date
github-actions[bot]
5ddea836a1 chore: bump version to v0.6.0 2025-11-18 23:52:39 +00:00
bbedward
208d92aa06 launcher: re-create grid on open 2025-11-18 18:50:42 -05:00
bbedward
6ef9ddd4f3 hyprland: fix right click overview 2025-11-18 17:53:00 -05:00
bbedward
1c92d39185 i18n: update translations 2025-11-18 17:21:45 -05:00
bbedward
c0f072217c dankbar: split up monolithic file 2025-11-18 16:18:24 -05:00
bbedward
542562f988 dankbar: missing background click handler for plugin popout 2025-11-18 16:03:30 -05:00
bbedward
4e6f0d5e87 bluez: fix disappearing popouts with modal maanger 2025-11-18 14:36:10 -05:00
bbedward
10639a5ead re-add bound lost my qmlfmt 2025-11-17 20:53:55 -05:00
bbedward
06d668e710 launcher: new search algo
- replace fzf.js with custom levenshtein distance matching
- tweak scoring system
- more graceful fuzzy, more weight to prefixes
- basic tokenization
2025-11-17 20:52:04 -05:00
bbedward
d1472dfcba osd: also have left center and right center options 2025-11-17 14:05:04 -05:00
bbedward
ccb4da3cd8 extws: fix force option 2025-11-17 10:08:06 -05:00
bbedward
46e96b49f0 extws: fix capability check & don't show names 2025-11-17 09:50:06 -05:00
bbedward
984cfe7f98 labwc: use dms dpms off/on for idle service 2025-11-17 09:12:38 -05:00
bbedward
d769300137 core/cli: add dpms off/on via wlr-output-power-management 2025-11-17 00:31:00 -05:00
Hikiru
d175d66828 Add NixOS module (#734)
* default.nix: fix "wavelength" typo

* Add nixos module

typo

fix

* nix: refactor and fix nix modules

* nix: fix NixOS module import

* nix: revert quickshell option change

* nix: fix nixosModules dmsPkgs definition

---------

Co-authored-by: LuckShiba <luckshiba@protonmail.com>
2025-11-16 21:12:01 -05:00
bbedward
c1a314332e wallpaper: rename blur layer option 2025-11-16 19:50:19 -05:00
bbedward
046ac59d21 core/extworkspace: only register outputs on name received 2025-11-16 19:40:46 -05:00
bbedward
00c06f07d0 workspace: fix ext-ws hiding 2025-11-16 18:52:12 -05:00
bbedward
3e2ab40c6a ws: 0 width when 0 workspaces, restore labwc to README 2025-11-16 17:53:50 -05:00
bbedward
350ffd0052 i18n: update terms 2025-11-16 16:33:55 -05:00
bbedward
ecd1a622d2 display: fix wallpaper when using monitor model 2025-11-16 16:33:21 -05:00
bbedward
f13968aa61 osd: configurable position 2025-11-16 16:27:01 -05:00
bbedward
4d1ffde54c launcher: allow launch prefix to run in shell 2025-11-16 16:14:19 -05:00
bbedward
d69017a706 also update per-monitor wallpaper to accout for display setting 2025-11-16 16:01:11 -05:00
bbedward
f2deaeccdb scaling: snap value reported by wlr-output 2025-11-16 15:56:59 -05:00
bbedward
ea9b0d2a79 powermenu: use consistent new-style on locker + greeter
fixes #739
2025-11-16 15:05:06 -05:00
bbedward
2e6dbedb8b dwl/mango: support keyboard layout 2025-11-16 14:24:56 -05:00
bbedward
6f359df8f9 displays: allow filtering by model over name 2025-11-16 13:58:53 -05:00
claymorwan
f6db20cd06 confirm-modal:add layer namespace (#743) 2025-11-16 13:09:44 -05:00
bbedward
6287fae065 running apps: don't wrap on scroll wheel
fixes #740
2025-11-16 13:06:40 -05:00
bbedward
e441607ce3 colorpicker: don't include line break in copy
fixes #741
2025-11-16 13:00:13 -05:00
bbedward
b5379a95fa qs/dankbar/meta: add a mask region to the bar
- Allows bar items to be clickable evn when popouts open
- Add state machines to manage state across monitors
- change focuses to ondemand on hyprland
2025-11-16 12:52:13 -05:00
92 changed files with 6796 additions and 3536 deletions

View File

@@ -19,7 +19,7 @@
</div>
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
## Repository Structure
@@ -105,7 +105,7 @@ Extend functionality with the [plugin registry](https://plugins.danklinux.com).
## Supported Compositors
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), and [MangoWC](https://github.com/DreamMaoMao/mangowc) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), and [labwc](https://labwc.github.io/) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
[Compositor configuration guide](https://danklinux.com/docs/dankmaterialshell/compositors)
@@ -183,6 +183,10 @@ For documentation contributions, see [DankLinux-Docs](https://github.com/AvengeM
- [soramanew](https://github.com/soramanew) - [Caelestia](https://github.com/caelestia-dots/shell) inspiration
- [end-4](https://github.com/end-4) - [dots-hyprland](https://github.com/end-4/dots-hyprland) inspiration
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=AvengeMedia/DankMaterialShell&type=date&legend=top-left)](https://www.star-history.com/#AvengeMedia/DankMaterialShell&type=date&legend=top-left)
## License
MIT License - See [LICENSE](LICENSE) for details.

View File

@@ -368,6 +368,7 @@ func getCommonCommands() []*cobra.Command {
pluginsCmd,
dank16Cmd,
brightnessCmd,
dpmsCmd,
keybindsCmd,
greeterCmd,
setupCmd,

View File

@@ -0,0 +1,84 @@
package main
import (
"fmt"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/spf13/cobra"
)
var dpmsCmd = &cobra.Command{
Use: "dpms",
Short: "Control display power management",
}
var dpmsOnCmd = &cobra.Command{
Use: "on [output]",
Short: "Turn display(s) on",
Args: cobra.MaximumNArgs(1),
Run: runDPMSOn,
}
var dpmsOffCmd = &cobra.Command{
Use: "off [output]",
Short: "Turn display(s) off",
Args: cobra.MaximumNArgs(1),
Run: runDPMSOff,
}
var dpmsListCmd = &cobra.Command{
Use: "list",
Short: "List outputs",
Args: cobra.NoArgs,
Run: runDPMSList,
}
func init() {
dpmsCmd.AddCommand(dpmsOnCmd, dpmsOffCmd, dpmsListCmd)
}
func runDPMSOn(cmd *cobra.Command, args []string) {
outputName := ""
if len(args) > 0 {
outputName = args[0]
}
client, err := newDPMSClient()
if err != nil {
log.Fatalf("%v", err)
}
defer client.Close()
if err := client.SetDPMS(outputName, true); err != nil {
log.Fatalf("%v", err)
}
}
func runDPMSOff(cmd *cobra.Command, args []string) {
outputName := ""
if len(args) > 0 {
outputName = args[0]
}
client, err := newDPMSClient()
if err != nil {
log.Fatalf("%v", err)
}
defer client.Close()
if err := client.SetDPMS(outputName, false); err != nil {
log.Fatalf("%v", err)
}
}
func runDPMSList(cmd *cobra.Command, args []string) {
client, err := newDPMSClient()
if err != nil {
log.Fatalf("%v", err)
}
defer client.Close()
for _, output := range client.ListOutputs() {
fmt.Println(output)
}
}

345
core/cmd/dms/dpms_client.go Normal file
View File

@@ -0,0 +1,345 @@
package main
import (
"fmt"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_power"
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
type cmd struct {
fn func()
done chan error
}
type dpmsClient struct {
display *wlclient.Display
ctx *wlclient.Context
powerMgr *wlr_output_power.ZwlrOutputPowerManagerV1
outputs map[string]*outputState
mu sync.Mutex
syncRound int
done bool
err error
cmdq chan cmd
stopChan chan struct{}
wg sync.WaitGroup
}
type outputState struct {
wlOutput *wlclient.Output
powerCtrl *wlr_output_power.ZwlrOutputPowerV1
name string
mode uint32
failed bool
waitCh chan struct{}
wantMode *uint32
}
func (c *dpmsClient) post(fn func()) {
done := make(chan error, 1)
select {
case c.cmdq <- cmd{fn: fn, done: done}:
<-done
case <-c.stopChan:
}
}
func (c *dpmsClient) waylandActor() {
defer c.wg.Done()
for {
select {
case <-c.stopChan:
return
case cmd := <-c.cmdq:
cmd.fn()
close(cmd.done)
}
}
}
func newDPMSClient() (*dpmsClient, error) {
display, err := wlclient.Connect("")
if err != nil {
return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
}
c := &dpmsClient{
display: display,
ctx: display.Context(),
outputs: make(map[string]*outputState),
cmdq: make(chan cmd, 128),
stopChan: make(chan struct{}),
}
c.wg.Add(1)
go c.waylandActor()
registry, err := display.GetRegistry()
if err != nil {
display.Context().Close()
return nil, fmt.Errorf("failed to get registry: %w", err)
}
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
switch e.Interface {
case wlr_output_power.ZwlrOutputPowerManagerV1InterfaceName:
powerMgr := wlr_output_power.NewZwlrOutputPowerManagerV1(c.ctx)
version := e.Version
if version > 1 {
version = 1
}
if err := registry.Bind(e.Name, e.Interface, version, powerMgr); err == nil {
c.powerMgr = powerMgr
}
case "wl_output":
output := wlclient.NewOutput(c.ctx)
version := e.Version
if version > 4 {
version = 4
}
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
outputID := fmt.Sprintf("output-%d", output.ID())
state := &outputState{
wlOutput: output,
name: outputID,
}
c.mu.Lock()
c.outputs[outputID] = state
c.mu.Unlock()
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
c.mu.Lock()
delete(c.outputs, state.name)
state.name = ev.Name
c.outputs[ev.Name] = state
c.mu.Unlock()
})
}
}
})
syncCallback, err := display.Sync()
if err != nil {
c.Close()
return nil, fmt.Errorf("failed to sync display: %w", err)
}
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
c.handleSync()
})
for !c.done {
if err := c.ctx.Dispatch(); err != nil {
c.Close()
return nil, fmt.Errorf("dispatch error: %w", err)
}
}
if c.err != nil {
c.Close()
return nil, c.err
}
return c, nil
}
func (c *dpmsClient) handleSync() {
c.syncRound++
switch c.syncRound {
case 1:
if c.powerMgr == nil {
c.err = fmt.Errorf("wlr-output-power-management protocol not supported by compositor")
c.done = true
return
}
c.mu.Lock()
for _, state := range c.outputs {
powerCtrl, err := c.powerMgr.GetOutputPower(state.wlOutput)
if err != nil {
continue
}
state.powerCtrl = powerCtrl
powerCtrl.SetModeHandler(func(e wlr_output_power.ZwlrOutputPowerV1ModeEvent) {
c.mu.Lock()
defer c.mu.Unlock()
if state.powerCtrl == nil {
return
}
state.mode = e.Mode
if state.wantMode != nil && e.Mode == *state.wantMode && state.waitCh != nil {
close(state.waitCh)
state.wantMode = nil
}
})
powerCtrl.SetFailedHandler(func(e wlr_output_power.ZwlrOutputPowerV1FailedEvent) {
c.mu.Lock()
defer c.mu.Unlock()
if state.powerCtrl == nil {
return
}
state.failed = true
if state.waitCh != nil {
close(state.waitCh)
state.wantMode = nil
}
})
}
c.mu.Unlock()
syncCallback, err := c.display.Sync()
if err != nil {
c.err = fmt.Errorf("failed to sync display: %w", err)
c.done = true
return
}
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
c.handleSync()
})
default:
c.done = true
}
}
func (c *dpmsClient) ListOutputs() []string {
c.mu.Lock()
defer c.mu.Unlock()
names := make([]string, 0, len(c.outputs))
for name := range c.outputs {
names = append(names, name)
}
return names
}
func (c *dpmsClient) SetDPMS(outputName string, on bool) error {
var mode uint32
if on {
mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOn)
} else {
mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOff)
}
var setErr error
c.post(func() {
c.mu.Lock()
var waitStates []*outputState
if outputName == "" || outputName == "all" {
if len(c.outputs) == 0 {
c.mu.Unlock()
setErr = fmt.Errorf("no outputs found")
return
}
for _, state := range c.outputs {
if state.powerCtrl == nil {
continue
}
state.wantMode = &mode
state.waitCh = make(chan struct{})
state.failed = false
waitStates = append(waitStates, state)
state.powerCtrl.SetMode(mode)
}
} else {
state, ok := c.outputs[outputName]
if !ok {
c.mu.Unlock()
setErr = fmt.Errorf("output not found: %s", outputName)
return
}
if state.powerCtrl == nil {
c.mu.Unlock()
setErr = fmt.Errorf("output %s has nil powerCtrl", outputName)
return
}
state.wantMode = &mode
state.waitCh = make(chan struct{})
state.failed = false
waitStates = append(waitStates, state)
state.powerCtrl.SetMode(mode)
}
c.mu.Unlock()
deadline := time.Now().Add(10 * time.Second)
for _, state := range waitStates {
c.mu.Lock()
ch := state.waitCh
c.mu.Unlock()
done := false
for !done {
if err := c.ctx.Dispatch(); err != nil {
setErr = fmt.Errorf("dispatch error: %w", err)
return
}
select {
case <-ch:
c.mu.Lock()
if state.failed {
setErr = fmt.Errorf("compositor reported failed for %s", state.name)
c.mu.Unlock()
return
}
c.mu.Unlock()
done = true
default:
if time.Now().After(deadline) {
setErr = fmt.Errorf("timeout waiting for mode change on %s", state.name)
return
}
time.Sleep(10 * time.Millisecond)
}
}
}
c.mu.Lock()
for _, state := range waitStates {
if state.powerCtrl != nil {
state.powerCtrl.Destroy()
state.powerCtrl = nil
}
}
c.mu.Unlock()
c.display.Roundtrip()
})
return setErr
}
func (c *dpmsClient) Close() {
close(c.stopChan)
c.wg.Wait()
c.mu.Lock()
defer c.mu.Unlock()
for _, state := range c.outputs {
if state.powerCtrl != nil {
state.powerCtrl.Destroy()
}
}
c.outputs = nil
if c.powerMgr != nil {
c.powerMgr.Destroy()
c.powerMgr = nil
}
if c.display != nil {
c.ctx.Close()
c.display = nil
}
}

View File

@@ -157,6 +157,16 @@ type ZdwlIpcOutputV2 struct {
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
@@ -251,6 +261,60 @@ func (i *ZdwlIpcOutputV2) SetLayout(index uint32) error {
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 :
@@ -399,6 +463,136 @@ 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:
@@ -487,5 +681,111 @@ func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
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)
}
}

View File

@@ -0,0 +1,283 @@
// Generated by go-wayland-scanner
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
// XML file : internal/proto/xml/wlr-output-power-management-unstable-v1.xml
//
// wlr_output_power_management_unstable_v1 Protocol Copyright:
//
// Copyright © 2019 Purism SPC
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice (including the next
// paragraph) shall be included in all copies or substantial portions of the
// Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
package wlr_output_power
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
// ZwlrOutputPowerManagerV1InterfaceName 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 ZwlrOutputPowerManagerV1InterfaceName = "zwlr_output_power_manager_v1"
// ZwlrOutputPowerManagerV1 : manager to create per-output power management
//
// This interface is a manager that allows creating per-output power
// management mode controls.
type ZwlrOutputPowerManagerV1 struct {
client.BaseProxy
}
// NewZwlrOutputPowerManagerV1 : manager to create per-output power management
//
// This interface is a manager that allows creating per-output power
// management mode controls.
func NewZwlrOutputPowerManagerV1(ctx *client.Context) *ZwlrOutputPowerManagerV1 {
zwlrOutputPowerManagerV1 := &ZwlrOutputPowerManagerV1{}
ctx.Register(zwlrOutputPowerManagerV1)
return zwlrOutputPowerManagerV1
}
// GetOutputPower : get a power management for an output
//
// Create an output power management mode control that can be used to
// adjust the power management mode for a given output.
func (i *ZwlrOutputPowerManagerV1) GetOutputPower(output *client.Output) (*ZwlrOutputPowerV1, error) {
id := NewZwlrOutputPowerV1(i.Context())
const opcode = 0
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
}
// Destroy : destroy the manager
//
// All objects created by the manager will still remain valid, until their
// appropriate destroy request has been called.
func (i *ZwlrOutputPowerManagerV1) Destroy() error {
defer i.Context().Unregister(i)
const opcode = 1
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
}
// ZwlrOutputPowerV1InterfaceName 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 ZwlrOutputPowerV1InterfaceName = "zwlr_output_power_v1"
// ZwlrOutputPowerV1 : adjust power management mode for an output
//
// This object offers requests to set the power management mode of
// an output.
type ZwlrOutputPowerV1 struct {
client.BaseProxy
modeHandler ZwlrOutputPowerV1ModeHandlerFunc
failedHandler ZwlrOutputPowerV1FailedHandlerFunc
}
// NewZwlrOutputPowerV1 : adjust power management mode for an output
//
// This object offers requests to set the power management mode of
// an output.
func NewZwlrOutputPowerV1(ctx *client.Context) *ZwlrOutputPowerV1 {
zwlrOutputPowerV1 := &ZwlrOutputPowerV1{}
ctx.Register(zwlrOutputPowerV1)
return zwlrOutputPowerV1
}
// SetMode : Set an outputs power save mode
//
// Set an output's power save mode to the given mode. The mode change
// is effective immediately. If the output does not support the given
// mode a failed event is sent.
//
// mode: the power save mode to set
func (i *ZwlrOutputPowerV1) SetMode(mode uint32) error {
const opcode = 0
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(mode))
l += 4
err := i.Context().WriteMsg(_reqBuf[:], nil)
return err
}
// Destroy : destroy this power management
//
// Destroys the output power management mode control object.
func (i *ZwlrOutputPowerV1) Destroy() error {
defer i.Context().Unregister(i)
const opcode = 1
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
}
type ZwlrOutputPowerV1Mode uint32
// ZwlrOutputPowerV1Mode :
const (
// ZwlrOutputPowerV1ModeOff : Output is turned off.
ZwlrOutputPowerV1ModeOff ZwlrOutputPowerV1Mode = 0
// ZwlrOutputPowerV1ModeOn : Output is turned on, no power saving
ZwlrOutputPowerV1ModeOn ZwlrOutputPowerV1Mode = 1
)
func (e ZwlrOutputPowerV1Mode) Name() string {
switch e {
case ZwlrOutputPowerV1ModeOff:
return "off"
case ZwlrOutputPowerV1ModeOn:
return "on"
default:
return ""
}
}
func (e ZwlrOutputPowerV1Mode) Value() string {
switch e {
case ZwlrOutputPowerV1ModeOff:
return "0"
case ZwlrOutputPowerV1ModeOn:
return "1"
default:
return ""
}
}
func (e ZwlrOutputPowerV1Mode) String() string {
return e.Name() + "=" + e.Value()
}
type ZwlrOutputPowerV1Error uint32
// ZwlrOutputPowerV1Error :
const (
// ZwlrOutputPowerV1ErrorInvalidMode : nonexistent power save mode
ZwlrOutputPowerV1ErrorInvalidMode ZwlrOutputPowerV1Error = 1
)
func (e ZwlrOutputPowerV1Error) Name() string {
switch e {
case ZwlrOutputPowerV1ErrorInvalidMode:
return "invalid_mode"
default:
return ""
}
}
func (e ZwlrOutputPowerV1Error) Value() string {
switch e {
case ZwlrOutputPowerV1ErrorInvalidMode:
return "1"
default:
return ""
}
}
func (e ZwlrOutputPowerV1Error) String() string {
return e.Name() + "=" + e.Value()
}
// ZwlrOutputPowerV1ModeEvent : Report a power management mode change
//
// Report the power management mode change of an output.
//
// The mode event is sent after an output changed its power
// management mode. The reason can be a client using set_mode or the
// compositor deciding to change an output's mode.
// This event is also sent immediately when the object is created
// so the client is informed about the current power management mode.
type ZwlrOutputPowerV1ModeEvent struct {
Mode uint32
}
type ZwlrOutputPowerV1ModeHandlerFunc func(ZwlrOutputPowerV1ModeEvent)
// SetModeHandler : sets handler for ZwlrOutputPowerV1ModeEvent
func (i *ZwlrOutputPowerV1) SetModeHandler(f ZwlrOutputPowerV1ModeHandlerFunc) {
i.modeHandler = f
}
// ZwlrOutputPowerV1FailedEvent : object no longer valid
//
// This event indicates that the output power management mode control
// is no longer valid. This can happen for a number of reasons,
// including:
// - The output doesn't support power management
// - Another client already has exclusive power management mode control
// for this output
// - The output disappeared
//
// Upon receiving this event, the client should destroy this object.
type ZwlrOutputPowerV1FailedEvent struct{}
type ZwlrOutputPowerV1FailedHandlerFunc func(ZwlrOutputPowerV1FailedEvent)
// SetFailedHandler : sets handler for ZwlrOutputPowerV1FailedEvent
func (i *ZwlrOutputPowerV1) SetFailedHandler(f ZwlrOutputPowerV1FailedHandlerFunc) {
i.failedHandler = f
}
func (i *ZwlrOutputPowerV1) Dispatch(opcode uint32, fd int, data []byte) {
switch opcode {
case 0:
if i.modeHandler == nil {
return
}
var e ZwlrOutputPowerV1ModeEvent
l := 0
e.Mode = client.Uint32(data[l : l+4])
l += 4
i.modeHandler(e)
case 1:
if i.failedHandler == nil {
return
}
var e ZwlrOutputPowerV1FailedEvent
i.failedHandler(e)
}
}

View File

@@ -19,7 +19,7 @@ I would probably just submit raphi's patchset but I don't think that would be po
reset.
</description>
<interface name="zdwl_ipc_manager_v2" version="1">
<interface name="zdwl_ipc_manager_v2" version="2">
<description summary="manage dwl state">
This interface is exposed as a global in wl_registry.
@@ -60,7 +60,7 @@ I would probably just submit raphi's patchset but I don't think that would be po
</event>
</interface>
<interface name="zdwl_ipc_output_v2" version="1">
<interface name="zdwl_ipc_output_v2" version="2">
<description summary="control dwl output">
Observe and control a dwl output.
@@ -162,5 +162,91 @@ I would probably just submit raphi's patchset but I don't think that would be po
<description summary="Set the layout of this output"/>
<arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/>
</request>
<request name="quit" since="2">
<description summary="Quit mango">This request allows clients to instruct the compositor to quit mango.</description>
</request>
<request name="dispatch" since="2">
<description summary="Set the active tags of this output"/>
<arg name="dispatch" type="string" summary="dispatch name."/>
<arg name="arg1" type="string" summary="arg1."/>
<arg name="arg2" type="string" summary="arg2."/>
<arg name="arg3" type="string" summary="arg3."/>
<arg name="arg4" type="string" summary="arg4."/>
<arg name="arg5" type="string" summary="arg5."/>
</request>
<!-- Version 2 -->
<event name="fullscreen" since="2">
<description summary="Update fullscreen status">
Indicates if the selected client on this output is fullscreen.
</description>
<arg name="is_fullscreen" type="uint" summary="If the selected client is fullscreen. Nonzero is valid, zero invalid"/>
</event>
<event name="floating" since="2">
<description summary="Update the floating status">
Indicates if the selected client on this output is floating.
</description>
<arg name="is_floating" type="uint" summary="If the selected client is floating. Nonzero is valid, zero invalid"/>
</event>
<event name="x" since="2">
<description summary="Update the x coordinates">
Indicates if x coordinates of the selected client.
</description>
<arg name="x" type="int" summary="x coordinate of the selected client"/>
</event>
<event name="y" since="2">
<description summary="Update the y coordinates">
Indicates if y coordinates of the selected client.
</description>
<arg name="y" type="int" summary="y coordinate of the selected client"/>
</event>
<event name="width" since="2">
<description summary="Update the width">
Indicates if width of the selected client.
</description>
<arg name="width" type="int" summary="width of the selected client"/>
</event>
<event name="height" since="2">
<description summary="Update the height">
Indicates if height of the selected client.
</description>
<arg name="height" type="int" summary="height of the selected client"/>
</event>
<event name="last_layer" since="2">
<description summary="last map layer.">
last map layer.
</description>
<arg name="last_layer" type="string" summary="last map layer."/>
</event>
<event name="kb_layout" since="2">
<description summary="current keyboard layout.">
current keyboard layout.
</description>
<arg name="kb_layout" type="string" summary="current keyboard layout."/>
</event>
<event name="keymode" since="2">
<description summary="current keybind mode.">
current keybind mode.
</description>
<arg name="keymode" type="string" summary="current keybind mode."/>
</event>
<event name="scalefactor" since="2">
<description summary="scale factor of monitor.">
scale factor of monitor.
</description>
<arg name="scalefactor" type="uint" summary="scale factor of monitor."/>
</event>
</interface>
</protocol>

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_output_power_management_unstable_v1">
<copyright>
Copyright © 2019 Purism SPC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Control power management modes of outputs">
This protocol allows clients to control power management modes
of outputs that are currently part of the compositor space. The
intent is to allow special clients like desktop shells to power
down outputs when the system is idle.
To modify outputs not currently part of the compositor space see
wlr-output-management.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<interface name="zwlr_output_power_manager_v1" version="1">
<description summary="manager to create per-output power management">
This interface is a manager that allows creating per-output power
management mode controls.
</description>
<request name="get_output_power">
<description summary="get a power management for an output">
Create an output power management mode control that can be used to
adjust the power management mode for a given output.
</description>
<arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_output_power_v1" version="1">
<description summary="adjust power management mode for an output">
This object offers requests to set the power management mode of
an output.
</description>
<enum name="mode">
<entry name="off" value="0"
summary="Output is turned off."/>
<entry name="on" value="1"
summary="Output is turned on, no power saving"/>
</enum>
<enum name="error">
<entry name="invalid_mode" value="1" summary="nonexistent power save mode"/>
</enum>
<request name="set_mode">
<description summary="Set an outputs power save mode">
Set an output's power save mode to the given mode. The mode change
is effective immediately. If the output does not support the given
mode a failed event is sent.
</description>
<arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
</request>
<event name="mode">
<description summary="Report a power management mode change">
Report the power management mode change of an output.
The mode event is sent after an output changed its power
management mode. The reason can be a client using set_mode or the
compositor deciding to change an output's mode.
This event is also sent immediately when the object is created
so the client is informed about the current power management mode.
</description>
<arg name="mode" type="uint" enum="mode"
summary="the output's new power management mode"/>
</event>
<event name="failed">
<description summary="object no longer valid">
This event indicates that the output power management mode control
is no longer valid. This can happen for a number of reasons,
including:
- The output doesn't support power management
- Another client already has exclusive power management mode control
for this output
- The output disappeared
Upon receiving this event, the client should destroy this object.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy this power management">
Destroys the output power management mode control object.
</description>
</request>
</interface>
</protocol>

View File

@@ -165,12 +165,11 @@ func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, ente
log.Infof("[BluezAgent] DisplayPasskey: device=%s, passkey=%06d, entered=%d", device, passkey, entered)
if entered == 0 {
pk := passkey
_, err := a.promptFor(device, "display-passkey", []string{}, nil)
passkeyStr := strconv.FormatUint(uint64(passkey), 10)
_, err := a.promptFor(device, "display-passkey", []string{}, &passkeyStr)
if err != nil {
log.Warnf("[BluezAgent] DisplayPasskey acknowledgment failed: %v", err)
}
_ = pk
}
return nil
@@ -179,7 +178,8 @@ func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, ente
func (a *BluezAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
log.Infof("[BluezAgent] RequestConfirmation: device=%s, passkey=%06d", device, passkey)
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, nil)
passkeyStr := strconv.FormatUint(uint64(passkey), 10)
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, &passkeyStr)
if err != nil {
log.Warnf("[BluezAgent] RequestConfirmation failed: %v", err)
return a.errorFrom(err)

View File

@@ -354,21 +354,25 @@ func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed ma
_, hasTrusted := changed["Trusted"]
if hasPaired {
if paired, ok := pairedVar.Value().(bool); ok && paired {
devicePath := string(path)
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
devicePath := string(path)
if paired, ok := pairedVar.Value().(bool); ok {
if paired {
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
if wasPending {
select {
case m.eventQueue <- func() {
time.Sleep(300 * time.Millisecond)
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
if err := m.ConnectDevice(devicePath); err != nil {
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
if wasPending {
select {
case m.eventQueue <- func() {
time.Sleep(300 * time.Millisecond)
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
if err := m.ConnectDevice(devicePath); err != nil {
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
}
}:
default:
}
}:
default:
}
} else {
m.pendingPairings.Delete(devicePath)
}
}
}

View File

@@ -100,8 +100,8 @@ func (m *Manager) setupRegistry() error {
log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
manager := dwl_ipc.NewZdwlIpcManagerV2(m.ctx)
version := e.Version
if version > 1 {
version = 1
if version > 2 {
version = 2
}
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
dwlMgr = manager
@@ -282,6 +282,14 @@ func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclien
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()
})
@@ -310,6 +318,8 @@ func (m *Manager) updateState() {
LayoutSymbol: out.layoutSymbol,
Title: out.title,
AppID: out.appID,
KbLayout: out.kbLayout,
Keymode: out.keymode,
}
if out.active != 0 {

View File

@@ -22,6 +22,8 @@ type OutputState struct {
LayoutSymbol string `json:"layoutSymbol"`
Title string `json:"title"`
AppID string `json:"appId"`
KbLayout string `json:"kbLayout"`
Keymode string `json:"keymode"`
}
type State struct {
@@ -73,6 +75,8 @@ type outputState struct {
layoutSymbol string
title string
appID string
kbLayout string
keymode string
}
func (m *Manager) GetState() State {
@@ -147,6 +151,12 @@ func stateChanged(old, new *State) bool {
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
}

View File

@@ -9,6 +9,35 @@ import (
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
func CheckCapability() bool {
display, err := wlclient.Connect("")
if err != nil {
return false
}
defer display.Destroy()
registry, err := display.GetRegistry()
if err != nil {
return false
}
defer registry.Destroy()
found := false
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
found = true
}
})
// Roundtrip to ensure all registry events are processed
if err := display.Roundtrip(); err != nil {
return false
}
return found
}
func NewManager(display *wlclient.Display) (*Manager, error) {
m := &Manager{
display: display,
@@ -75,6 +104,9 @@ func (m *Manager) setupRegistry() error {
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
m.outputNames.Store(outputID, ev.Name)
log.Debugf("ExtWorkspace: Output %d (%s) name received", outputID, ev.Name)
m.post(func() {
m.updateState()
})
})
}
return
@@ -295,14 +327,8 @@ func (m *Manager) updateState() {
outputs := make([]string, 0)
for outputID := range group.outputIDs {
if name, ok := m.outputNames.Load(outputID); ok {
if name != "" {
outputs = append(outputs, name)
} else {
outputs = append(outputs, fmt.Sprintf("output-%d", outputID))
}
} else {
outputs = append(outputs, fmt.Sprintf("output-%d", outputID))
if name, ok := m.outputNames.Load(outputID); ok && name != "" {
outputs = append(outputs, name)
}
}

View File

@@ -140,8 +140,20 @@ func RouteRequest(conn net.Conn, req models.Request) {
if strings.HasPrefix(req.Method, "extworkspace.") {
if extWorkspaceManager == nil {
models.RespondError(conn, req.ID, "extworkspace manager not initialized")
return
if extWorkspaceAvailable.Load() {
extWorkspaceInitMutex.Lock()
if extWorkspaceManager == nil {
if err := InitializeExtWorkspaceManager(); err != nil {
extWorkspaceInitMutex.Unlock()
models.RespondError(conn, req.ID, "extworkspace manager not available")
return
}
}
extWorkspaceInitMutex.Unlock()
} else {
models.RespondError(conn, req.ID, "extworkspace manager not initialized")
return
}
}
extWorkspaceReq := extworkspace.Request{
ID: req.ID,

View File

@@ -31,7 +31,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
)
const APIVersion = 18
const APIVersion = 19
type Capabilities struct {
Capabilities []string `json:"capabilities"`
@@ -63,6 +63,8 @@ var wlContext *wlcontext.SharedContext
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
var cupsSubscribers syncmap.Map[string, bool]
var cupsSubscriberCount atomic.Int32
var extWorkspaceAvailable atomic.Bool
var extWorkspaceInitMutex sync.Mutex
func getSocketDir() string {
if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
@@ -361,7 +363,7 @@ func getCapabilities() Capabilities {
caps = append(caps, "dwl")
}
if extWorkspaceManager != nil {
if extWorkspaceAvailable.Load() {
caps = append(caps, "extworkspace")
}
@@ -411,7 +413,7 @@ func getServerInfo() ServerInfo {
caps = append(caps, "dwl")
}
if extWorkspaceManager != nil {
if extWorkspaceAvailable.Load() {
caps = append(caps, "extworkspace")
}
@@ -810,12 +812,14 @@ func handleSubscribe(conn net.Conn, req models.Request) {
}
if shouldSubscribe("extworkspace") {
if extWorkspaceManager == nil {
if err := InitializeExtWorkspaceManager(); err != nil {
log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err)
} else {
notifyCapabilityChange()
if extWorkspaceManager == nil && extWorkspaceAvailable.Load() {
extWorkspaceInitMutex.Lock()
if extWorkspaceManager == nil {
if err := InitializeExtWorkspaceManager(); err != nil {
log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err)
}
}
extWorkspaceInitMutex.Unlock()
}
if extWorkspaceManager != nil {
@@ -1141,11 +1145,18 @@ func Start(printDocs bool) error {
log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)")
log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)")
log.Info("DWL:")
log.Info(" dwl.getState - Get current dwl state (tags, windows, layouts)")
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("ExtWorkspace:")
log.Info(" extworkspace.getState - Get current workspace state (groups, workspaces)")
log.Info(" extworkspace.activateWorkspace - Activate workspace (params: groupID, workspaceID)")
@@ -1241,6 +1252,14 @@ func Start(printDocs bool) error {
log.Debugf("DWL manager unavailable: %v", err)
}
if extworkspace.CheckCapability() {
extWorkspaceAvailable.Store(true)
log.Info("ExtWorkspace capability detected and will be available on subscription")
} else {
log.Debug("ExtWorkspace capability not available")
extWorkspaceAvailable.Store(false)
}
if err := InitializeWlrOutputManager(); err != nil {
log.Debugf("WlrOutput manager unavailable: %v", err)
}

33
distro/nix/common.nix Normal file
View File

@@ -0,0 +1,33 @@
{
config,
lib,
pkgs,
dmsPkgs,
...
}: let
cfg = config.programs.dankMaterialShell;
in {
qmlPath = "${dmsPkgs.dankMaterialShell}/etc/xdg/quickshell/dms";
packages =
[
pkgs.material-symbols
pkgs.inter
pkgs.fira-code
pkgs.ddcutil
pkgs.libsForQt5.qt5ct
pkgs.kdePackages.qt6ct
dmsPkgs.dmsCli
]
++ lib.optional cfg.enableSystemMonitoring dmsPkgs.dgop
++ lib.optionals cfg.enableClipboard [pkgs.cliphist pkgs.wl-clipboard]
++ lib.optionals cfg.enableVPN [pkgs.glib pkgs.networkmanager]
++ lib.optional cfg.enableBrightnessControl pkgs.brightnessctl
++ lib.optional cfg.enableColorPicker pkgs.hyprpicker
++ lib.optional cfg.enableDynamicTheming pkgs.matugen
++ lib.optional cfg.enableAudioWavelength pkgs.cava
++ lib.optional cfg.enableCalendarEvents pkgs.khal
++ lib.optional cfg.enableSystemSound pkgs.kdePackages.qtmultimedia;
}

View File

@@ -1,170 +0,0 @@
{
config,
pkgs,
lib,
dmsPkgs,
...
}: let
cfg = config.programs.dankMaterialShell;
jsonFormat = pkgs.formats.json { };
in {
imports = [
(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "enableNightMode"] "Night mode is now always available.")
(lib.mkRenamedOptionModule ["programs" "dankMaterialShell" "enableSystemd"] ["programs" "dankMaterialShell" "systemd" "enable"])
];
options.programs.dankMaterialShell = with lib.types; {
enable = lib.mkEnableOption "DankMaterialShell";
systemd = {
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
restartIfChanged = lib.mkOption {
type = bool;
default = true;
description = "Auto-restart dms.service when dankMaterialShell changes";
};
};
enableSystemMonitoring = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to use system monitoring widgets";
};
enableClipboard = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to use the clipboard widget";
};
enableVPN = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to use the VPN widget";
};
enableBrightnessControl = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to have brightness/backlight support";
};
enableColorPicker = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to have color picking support";
};
enableDynamicTheming = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to have dynamic theming support";
};
enableAudioWavelength = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to have audio waveleng support";
};
enableCalendarEvents = lib.mkOption {
type = bool;
default = true;
description = "Add calendar events support via khal";
};
enableSystemSound = lib.mkOption {
type = bool;
default = true;
description = "Add needed dependencies to have system sound support";
};
quickshell = {
package = lib.mkPackageOption pkgs "quickshell" {};
};
default = {
settings = lib.mkOption {
type = jsonFormat.type;
default = { };
description = "The default settings are only read if the settings.json file don't exist";
};
session = lib.mkOption {
type = jsonFormat.type;
default = { };
description = "The default session are only read if the session.json file don't exist";
};
};
plugins = lib.mkOption {
type = attrsOf (types.submodule ({ config, ... }: {
options = {
enable = lib.mkOption {
type = types.bool;
default = true;
description = "Whether to link this plugin";
};
src = lib.mkOption {
type = types.path;
description = "Source to link to DMS plugins directory";
};
};
}));
default = {};
description = "DMS Plugins to install";
};
};
config = lib.mkIf cfg.enable
{
programs.quickshell = {
enable = true;
package = cfg.quickshell.package;
configs.dms = "${dmsPkgs.dankMaterialShell}/etc/xdg/quickshell/dms";
};
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
Unit = {
Description = "DankMaterialShell";
PartOf = [ config.wayland.systemd.target ];
After = [ config.wayland.systemd.target ];
X-Restart-Triggers = lib.optional cfg.systemd.restartIfChanged config.programs.quickshell.configs.dms;
};
Service = {
ExecStart = lib.getExe dmsPkgs.dmsCli + " run --session";
Restart = "on-failure";
};
Install.WantedBy = [ config.wayland.systemd.target ];
};
xdg.stateFile."DankMaterialShell/default-session.json" = lib.mkIf (cfg.default.session != { }) {
source = jsonFormat.generate "default-session.json" cfg.default.session;
};
xdg.configFile = lib.mkMerge [
(lib.mapAttrs' (name: plugin: {
name = "DankMaterialShell/plugins/${name}";
value.source = plugin.src;
}) (lib.filterAttrs (n: v: v.enable) cfg.plugins))
{
"DankMaterialShell/default-settings.json" = lib.mkIf (cfg.default.settings != { }) {
source = jsonFormat.generate "default-settings.json" cfg.default.settings;
};
}
];
home.packages =
[
pkgs.material-symbols
pkgs.inter
pkgs.fira-code
pkgs.ddcutil
pkgs.libsForQt5.qt5ct
pkgs.kdePackages.qt6ct
dmsPkgs.dmsCli
]
++ lib.optional cfg.enableSystemMonitoring dmsPkgs.dgop
++ lib.optionals cfg.enableClipboard [pkgs.cliphist pkgs.wl-clipboard]
++ lib.optionals cfg.enableVPN [pkgs.glib pkgs.networkmanager]
++ lib.optional cfg.enableBrightnessControl pkgs.brightnessctl
++ lib.optional cfg.enableColorPicker pkgs.hyprpicker
++ lib.optional cfg.enableDynamicTheming pkgs.matugen
++ lib.optional cfg.enableAudioWavelength pkgs.cava
++ lib.optional cfg.enableCalendarEvents pkgs.khal
++ lib.optional cfg.enableSystemSound pkgs.kdePackages.qtmultimedia;
};
}

View File

@@ -11,7 +11,7 @@
user = config.services.greetd.settings.default_session.user;
greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
export PATH=$PATH:${lib.makeBinPath [ cfg.quickshell.package config.programs.${cfg.compositor.name}.package ]}
export PATH=$PATH:${lib.makeBinPath [cfg.quickshell.package config.programs.${cfg.compositor.name}.package]}
${lib.escapeShellArgs ([
"sh"
"${../../quickshell/Modules/Greetd/assets/dms-greeter}"
@@ -28,11 +28,9 @@
])} ${lib.optionalString cfg.logs.save "> ${cfg.logs.path} 2>&1"}
'';
in {
imports =
let
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
in
[ (lib.mkRemovedOptionModule [ "programs" "dankMaterialShell" "greeter" "compositor" "extraConfig" ] msg) ];
imports = let
msg = "The option 'programs.dankMaterialShell.greeter.compositor.extraConfig' is deprecated. Please use 'programs.dankMaterialShell.greeter.compositor.customConfig' instead.";
in [(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "greeter" "compositor" "extraConfig"] msg)];
options.programs.dankMaterialShell.greeter = {
enable = lib.mkEnableOption "DankMaterialShell greeter";
@@ -77,7 +75,7 @@ in {
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = (config.users.users.${user} or { }) != { };
assertion = (config.users.users.${user} or {}) != {};
message = ''
dmsgreeter: user set for greetd default_session ${user} does not exist. Please create it before referencing it.
'';
@@ -95,8 +93,10 @@ in {
systemd.tmpfiles.settings."10-dmsgreeter" = {
"/var/lib/dmsgreeter".d = {
user = user;
group = if config.users.users.${user}.group != ""
then config.users.users.${user}.group else "greeter";
group =
if config.users.users.${user}.group != ""
then config.users.users.${user}.group
else "greeter";
mode = "0755";
};
};
@@ -106,7 +106,8 @@ in {
if [ -f "${f}" ]; then
cp "${f}" .
fi
'') cfg.configFiles)}
'')
cfg.configFiles)}
if [ -f session.json ]; then
if cp "$(${lib.getExe pkgs.jq} -r '.wallpaperPath' session.json)" wallpaper.jpg; then

94
distro/nix/home.nix Normal file
View File

@@ -0,0 +1,94 @@
{
config,
pkgs,
lib,
dmsPkgs,
...
}: let
cfg = config.programs.dankMaterialShell;
jsonFormat = pkgs.formats.json {};
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
in {
imports = [
./options.nix
(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "enableNightMode"] "Night mode is now always available.")
(lib.mkRenamedOptionModule ["programs" "dankMaterialShell" "enableSystemd"] ["programs" "dankMaterialShell" "systemd" "enable"])
];
options.programs.dankMaterialShell = with lib.types; {
default = {
settings = lib.mkOption {
type = jsonFormat.type;
default = {};
description = "The default settings are only read if the settings.json file don't exist";
};
session = lib.mkOption {
type = jsonFormat.type;
default = {};
description = "The default session are only read if the session.json file don't exist";
};
};
plugins = lib.mkOption {
type = attrsOf (types.submodule ({config, ...}: {
options = {
enable = lib.mkOption {
type = types.bool;
default = true;
description = "Whether to link this plugin";
};
src = lib.mkOption {
type = types.path;
description = "Source to link to DMS plugins directory";
};
};
}));
default = {};
description = "DMS Plugins to install";
};
};
config = lib.mkIf cfg.enable
{
programs.quickshell = {
enable = true;
package = cfg.quickshell.package;
configs.dms = common.qmlPath;
};
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
Unit = {
Description = "DankMaterialShell";
PartOf = [config.wayland.systemd.target];
After = [config.wayland.systemd.target];
X-Restart-Triggers = lib.optional cfg.systemd.restartIfChanged common.qmlPath;
};
Service = {
ExecStart = lib.getExe dmsPkgs.dmsCli + " run --session";
Restart = "on-failure";
};
Install.WantedBy = [config.wayland.systemd.target];
};
xdg.stateFile."DankMaterialShell/default-session.json" = lib.mkIf (cfg.default.session != {}) {
source = jsonFormat.generate "default-session.json" cfg.default.session;
};
xdg.configFile = lib.mkMerge [
(lib.mapAttrs' (name: plugin: {
name = "DankMaterialShell/plugins/${name}";
value.source = plugin.src;
}) (lib.filterAttrs (n: v: v.enable) cfg.plugins))
{
"DankMaterialShell/default-settings.json" = lib.mkIf (cfg.default.settings != {}) {
source = jsonFormat.generate "default-settings.json" cfg.default.settings;
};
}
];
home.packages = common.packages;
};
}

36
distro/nix/nixos.nix Normal file
View File

@@ -0,0 +1,36 @@
{
config,
pkgs,
lib,
dmsPkgs,
...
}: let
cfg = config.programs.dankMaterialShell;
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
in {
imports = [
./options.nix
];
config = lib.mkIf cfg.enable
{
environment.etc."xdg/quickshell/dms".source = "${dmsPkgs.dankMaterialShell}/etc/xdg/quickshell/dms";
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
description = "DankMaterialShell";
path = [cfg.quickshell.package];
partOf = ["graphical-session.target"];
after = ["graphical-session.target"];
wantedBy = ["graphical-session.target"];
restartTriggers = lib.optional cfg.systemd.restartIfChanged common.qmlPath;
serviceConfig = {
ExecStart = lib.getExe dmsPkgs.dmsCli + " run --session";
Restart = "on-failure";
};
};
environment.systemPackages = [cfg.quickshell.package] ++ common.packages;
};
}

68
distro/nix/options.nix Normal file
View File

@@ -0,0 +1,68 @@
{
pkgs,
lib,
...
}: let
inherit (lib) types;
in {
options.programs.dankMaterialShell = {
enable = lib.mkEnableOption "DankMaterialShell";
systemd = {
enable = lib.mkEnableOption "DankMaterialShell systemd startup";
restartIfChanged = lib.mkOption {
type = types.bool;
default = true;
description = "Auto-restart dms.service when dankMaterialShell changes";
};
};
enableSystemMonitoring = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to use system monitoring widgets";
};
enableClipboard = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to use the clipboard widget";
};
enableVPN = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to use the VPN widget";
};
enableBrightnessControl = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have brightness/backlight support";
};
enableColorPicker = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have color picking support";
};
enableDynamicTheming = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have dynamic theming support";
};
enableAudioWavelength = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have audio wavelength support";
};
enableCalendarEvents = lib.mkOption {
type = types.bool;
default = true;
description = "Add calendar events support via khal";
};
enableSystemSound = lib.mkOption {
type = types.bool;
default = true;
description = "Add needed dependencies to have system sound support";
};
quickshell = {
package = lib.mkPackageOption pkgs "quickshell" {};
};
};
}

View File

@@ -24,6 +24,11 @@
dgop = dgop.packages.${pkgs.stdenv.hostPlatform.system}.dgop;
dankMaterialShell = self.packages.${pkgs.stdenv.hostPlatform.system}.dankMaterialShell;
};
mkModuleWithDmsPkgs = path: args @ {pkgs, ...}: {
imports = [
(import path (args // {dmsPkgs = buildDmsPkgs pkgs;}))
];
};
in {
formatter = forEachSystem (_: pkgs: pkgs.alejandra);
@@ -81,20 +86,12 @@
}
);
homeModules.dankMaterialShell.default = {pkgs, ...}: let
dmsPkgs = buildDmsPkgs pkgs;
in {
imports = [./distro/nix/default.nix];
_module.args.dmsPkgs = dmsPkgs;
};
homeModules.dankMaterialShell.default = mkModuleWithDmsPkgs ./distro/nix/home.nix;
homeModules.dankMaterialShell.niri = import ./distro/nix/niri.nix;
nixosModules.greeter = {pkgs, ...}: let
dmsPkgs = buildDmsPkgs pkgs;
in {
imports = [./distro/nix/greeter.nix];
_module.args.dmsPkgs = dmsPkgs;
};
nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
nixosModules.greeter = mkModuleWithDmsPkgs ./distro/nix/greeter.nix;
};
}

View File

@@ -12,5 +12,9 @@ Singleton {
if (!modal.allowStacking) {
closeAllModalsExcept(modal)
}
if (!modal.keepPopoutsOpen) {
PopoutManager.closeAllPopouts()
}
TrayMenuManager.closeAllMenus()
}
}

View File

@@ -0,0 +1,164 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property var currentPopoutsByScreen: ({})
property var currentPopoutTriggers: ({})
function showPopout(popout) {
if (!popout || !popout.screen) return
const screenName = popout.screen.name
for (const otherScreenName in currentPopoutsByScreen) {
const otherPopout = currentPopoutsByScreen[otherScreenName]
if (!otherPopout || otherPopout === popout) continue
if (otherPopout.dashVisible !== undefined) {
otherPopout.dashVisible = false
} else if (otherPopout.notificationHistoryVisible !== undefined) {
otherPopout.notificationHistoryVisible = false
} else {
otherPopout.close()
}
}
currentPopoutsByScreen[screenName] = popout
ModalManager.closeAllModalsExcept(null)
TrayMenuManager.closeAllMenus()
}
function hidePopout(popout) {
if (!popout || !popout.screen) return
const screenName = popout.screen.name
if (currentPopoutsByScreen[screenName] === popout) {
currentPopoutsByScreen[screenName] = null
currentPopoutTriggers[screenName] = null
}
}
function closeAllPopouts() {
for (const screenName in currentPopoutsByScreen) {
const popout = currentPopoutsByScreen[screenName]
if (!popout) continue
if (popout.dashVisible !== undefined) {
popout.dashVisible = false
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = false
} else {
popout.close()
}
}
currentPopoutsByScreen = {}
}
function getActivePopout(screen) {
if (!screen) return null
return currentPopoutsByScreen[screen.name] || null
}
function requestPopout(popout, tabIndex, triggerSource) {
if (!popout || !popout.screen) return
const screenName = popout.screen.name
const currentPopout = currentPopoutsByScreen[screenName]
const triggerId = triggerSource !== undefined ? triggerSource : tabIndex
let justClosedSamePopout = false
for (const otherScreenName in currentPopoutsByScreen) {
if (otherScreenName === screenName) continue
const otherPopout = currentPopoutsByScreen[otherScreenName]
if (!otherPopout) continue
if (otherPopout === popout) {
justClosedSamePopout = true
}
if (otherPopout.dashVisible !== undefined) {
otherPopout.dashVisible = false
} else if (otherPopout.notificationHistoryVisible !== undefined) {
otherPopout.notificationHistoryVisible = false
} else {
otherPopout.close()
}
}
if (currentPopout && currentPopout !== popout) {
if (currentPopout.dashVisible !== undefined) {
currentPopout.dashVisible = false
} else if (currentPopout.notificationHistoryVisible !== undefined) {
currentPopout.notificationHistoryVisible = false
} else {
currentPopout.close()
}
}
if (currentPopout === popout && popout.shouldBeVisible) {
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
if (popout.dashVisible !== undefined) {
popout.dashVisible = false
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = false
} else {
popout.close()
}
return
}
if (triggerId === undefined) {
if (popout.dashVisible !== undefined) {
popout.dashVisible = false
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = false
} else {
popout.close()
}
return
}
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
popout.currentTabIndex = tabIndex
}
currentPopoutTriggers[screenName] = triggerId
return
}
currentPopoutTriggers[screenName] = triggerId
currentPopoutsByScreen[screenName] = popout
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
popout.currentTabIndex = tabIndex
}
if (currentPopout !== popout) {
ModalManager.closeAllModalsExcept(null)
}
TrayMenuManager.closeAllMenus()
if (justClosedSamePopout) {
Qt.callLater(() => {
if (popout.dashVisible !== undefined) {
popout.dashVisible = true
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = true
} else {
popout.open()
}
})
} else {
if (popout.dashVisible !== undefined) {
popout.dashVisible = true
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = true
} else {
popout.open()
}
}
}
}

View File

@@ -422,29 +422,59 @@ Singleton {
}
function setMonitorWallpaper(screenName, path) {
var newMonitorWallpapers = Object.assign({}, monitorWallpapers)
if (path && path !== "") {
newMonitorWallpapers[screenName] = path
} else {
delete newMonitorWallpapers[screenName]
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName)
return
}
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name
var newMonitorWallpapers = {}
for (var key in monitorWallpapers) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newMonitorWallpapers[key] = monitorWallpapers[key]
}
}
if (path && path !== "") {
newMonitorWallpapers[identifier] = path
}
monitorWallpapers = newMonitorWallpapers
if (perModeWallpaper) {
if (isLightMode) {
var newLight = Object.assign({}, monitorWallpapersLight)
var newLight = {}
for (var key in monitorWallpapersLight) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newLight[key] = monitorWallpapersLight[key]
}
}
if (path && path !== "") {
newLight[screenName] = path
} else {
delete newLight[screenName]
newLight[identifier] = path
}
monitorWallpapersLight = newLight
} else {
var newDark = Object.assign({}, monitorWallpapersDark)
var newDark = {}
for (var key in monitorWallpapersDark) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newDark[key] = monitorWallpapersDark[key]
}
}
if (path && path !== "") {
newDark[screenName] = path
} else {
delete newDark[screenName]
newDark[identifier] = path
}
monitorWallpapersDark = newDark
}
@@ -489,61 +519,153 @@ Singleton {
}
function setMonitorCyclingEnabled(screenName, enabled) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = {
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName)
return
}
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name
var newSettings = {}
for (var key in monitorCyclingSettings) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newSettings[key] = monitorCyclingSettings[key]
}
}
if (!newSettings[identifier]) {
newSettings[identifier] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].enabled = enabled
newSettings[identifier].enabled = enabled
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingMode(screenName, mode) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = {
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName)
return
}
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name
var newSettings = {}
for (var key in monitorCyclingSettings) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newSettings[key] = monitorCyclingSettings[key]
}
}
if (!newSettings[identifier]) {
newSettings[identifier] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].mode = mode
newSettings[identifier].mode = mode
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingInterval(screenName, interval) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = {
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName)
return
}
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name
var newSettings = {}
for (var key in monitorCyclingSettings) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newSettings[key] = monitorCyclingSettings[key]
}
}
if (!newSettings[identifier]) {
newSettings[identifier] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].interval = interval
newSettings[identifier].interval = interval
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingTime(screenName, time) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = {
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName)
return
}
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name
var newSettings = {}
for (var key in monitorCyclingSettings) {
var isThisScreen = key === screen.name || (screen.model && key === screen.model)
if (!isThisScreen) {
newSettings[key] = monitorCyclingSettings[key]
}
}
if (!newSettings[identifier]) {
newSettings[identifier] = {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
newSettings[screenName].time = time
newSettings[identifier].time = time
monitorCyclingSettings = newSettings
saveSettings()
}
@@ -770,11 +892,57 @@ Singleton {
if (!perMonitorWallpaper) {
return wallpaperPath
}
return monitorWallpapers[screenName] || wallpaperPath
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
return monitorWallpapers[screenName] || wallpaperPath
}
if (monitorWallpapers[screen.name]) {
return monitorWallpapers[screen.name]
}
if (screen.model && monitorWallpapers[screen.model]) {
return monitorWallpapers[screen.model]
}
return wallpaperPath
}
function getMonitorCyclingSettings(screenName) {
return monitorCyclingSettings[screenName] || {
var screen = null
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === screenName) {
screen = screens[i]
break
}
}
if (!screen) {
return monitorCyclingSettings[screenName] || {
"enabled": false,
"mode": "interval",
"interval": 300,
"time": "06:00"
}
}
if (monitorCyclingSettings[screen.name]) {
return monitorCyclingSettings[screen.name]
}
if (screen.model && monitorCyclingSettings[screen.model]) {
return monitorCyclingSettings[screen.model]
}
return {
"enabled": false,
"mode": "interval",
"interval": 300,

View File

@@ -23,7 +23,11 @@ Singleton {
Top,
Bottom,
Left,
Right
Right,
TopCenter,
BottomCenter,
LeftCenter,
RightCenter
}
enum AnimationSpeed {
@@ -305,6 +309,7 @@ Singleton {
property int notificationPopupPosition: SettingsData.Position.Top
property bool osdAlwaysShowValue: false
property int osdPosition: SettingsData.Position.BottomCenter
property bool osdVolumeEnabled: true
property bool osdBrightnessEnabled: true
property bool osdIdleInhibitorEnabled: true
@@ -327,6 +332,7 @@ Singleton {
property string updaterCustomCommand: ""
property string updaterTerminalAdditionalParams: ""
property string displayNameMode: "system"
property var screenPreferences: ({})
property var showOnLastDisplay: ({})
@@ -590,12 +596,82 @@ rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || tr
}
}
function getBarBounds(screen, barThickness) {
if (!screen) {
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
}
const wingRadius = dankBarGothCornerRadiusOverride ? dankBarGothCornerRadiusValue : Theme.cornerRadius
const wingSize = dankBarGothCornersEnabled ? Math.max(0, wingRadius) : 0
const screenWidth = screen.width
const screenHeight = screen.height
if (dankBarPosition === SettingsData.Position.Top) {
return {
"x": 0,
"y": 0,
"width": screenWidth,
"height": barThickness + dankBarSpacing + wingSize,
"wingSize": wingSize
}
} else if (dankBarPosition === SettingsData.Position.Bottom) {
return {
"x": 0,
"y": screenHeight - barThickness - dankBarSpacing - wingSize,
"width": screenWidth,
"height": barThickness + dankBarSpacing + wingSize,
"wingSize": wingSize
}
} else if (dankBarPosition === SettingsData.Position.Left) {
return {
"x": 0,
"y": 0,
"width": barThickness + dankBarSpacing + wingSize,
"height": screenHeight,
"wingSize": wingSize
}
} else if (dankBarPosition === SettingsData.Position.Right) {
return {
"x": screenWidth - barThickness - dankBarSpacing - wingSize,
"y": 0,
"width": barThickness + dankBarSpacing + wingSize,
"height": screenHeight,
"wingSize": wingSize
}
}
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
}
function getScreenDisplayName(screen) {
if (!screen) return ""
if (displayNameMode === "model" && screen.model) {
return screen.model
}
return screen.name
}
function isScreenInPreferences(screen, prefs) {
if (!screen) return false
return prefs.some(pref => {
if (typeof pref === "string") {
return pref === "all" || pref === screen.name || pref === screen.model
}
if (displayNameMode === "model") {
return pref.model && screen.model && pref.model === screen.model
}
return pref.name === screen.name
})
}
function getFilteredScreens(componentId) {
var prefs = screenPreferences && screenPreferences[componentId] || ["all"]
if (prefs.includes("all")) {
if (prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
return Quickshell.screens
}
var filtered = Quickshell.screens.filter(screen => prefs.includes(screen.name))
var filtered = Quickshell.screens.filter(screen => isScreenInPreferences(screen, prefs))
if (filtered.length === 0 && showOnLastDisplay && showOnLastDisplay[componentId] && Quickshell.screens.length === 1) {
return Quickshell.screens
}

View File

@@ -421,15 +421,44 @@ Singleton {
}
return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
}
property string fontFamily: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.fontFamily
}
return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable"
}
property string monoFontFamily: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.monoFontFamily
}
return typeof SettingsData !== "undefined" ? SettingsData.monoFontFamily : "Fira Code"
}
property int fontWeight: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.fontWeight
}
return typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal
}
property real fontScale: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.fontScale
}
return typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0
}
property real spacingXS: 4
property real spacingS: 8
property real spacingM: 12
property real spacingL: 16
property real spacingXL: 24
property real fontSizeSmall: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 12
property real fontSizeMedium: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 14
property real fontSizeLarge: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 16
property real fontSizeXLarge: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 20
property real fontSizeSmall: Math.round(fontScale * 12)
property real fontSizeMedium: Math.round(fontScale * 14)
property real fontSizeLarge: Math.round(fontScale * 16)
property real fontSizeXLarge: Math.round(fontScale * 20)
property real barHeight: 48
property real iconSize: 24
property real iconSizeSmall: 16
@@ -650,10 +679,10 @@ Singleton {
const scale = barThickness / 48
const dankBarScale = (typeof SettingsData !== "undefined" ? SettingsData.dankBarFontScale : 1.0)
if (scale <= 0.75)
return fontSizeSmall * 0.9 * dankBarScale
return Math.round(fontSizeSmall * 0.9 * dankBarScale)
if (scale >= 1.25)
return fontSizeMedium * dankBarScale
return fontSizeSmall * dankBarScale
return Math.round(fontSizeMedium * dankBarScale)
return Math.round(fontSizeSmall * dankBarScale)
}
function getBatteryIcon(level, isCharging, batteryAvailable) {

View File

@@ -0,0 +1,32 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property var activeTrayBars: ({})
function register(screenName, trayBar) {
if (!screenName || !trayBar) return
activeTrayBars[screenName] = trayBar
}
function unregister(screenName) {
if (!screenName) return
delete activeTrayBars[screenName]
}
function closeAllMenus() {
for (const screenName in activeTrayBars) {
const trayBar = activeTrayBars[screenName]
if (!trayBar) continue
trayBar.menuOpen = false
if (trayBar.currentTrayMenu) {
trayBar.currentTrayMenu.showMenu = false
}
}
}
}

View File

@@ -215,6 +215,7 @@ var SPEC = {
notificationPopupPosition: { def: 0 },
osdAlwaysShowValue: { def: false },
osdPosition: { def: 5 },
osdVolumeEnabled: { def: true },
osdBrightnessEnabled: { def: true },
osdIdleInhibitorEnabled: { def: true },
@@ -237,6 +238,7 @@ var SPEC = {
updaterCustomCommand: { def: "" },
updaterTerminalAdditionalParams: { def: "" },
displayNameMode: { def: "system" },
screenPreferences: { def: {} },
showOnLastDisplay: { def: {} }
};

View File

@@ -217,6 +217,14 @@ Item {
id: polkitAuthModal
}
BluetoothPairingModal {
id: bluetoothPairingModal
Component.onCompleted: {
PopoutService.bluetoothPairingModal = bluetoothPairingModal
}
}
property string lastCredentialsToken: ""
property var lastCredentialsTime: 0
@@ -297,48 +305,6 @@ Item {
}
}
LazyLoader {
id: powerMenuLoader
active: false
PowerMenu {
id: powerMenu
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {})
}
} else {
actionApply(action)
}
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
}
}
LazyLoader {
id: powerConfirmModalLoader

View File

@@ -18,6 +18,7 @@ DankModal {
property string passkeyInput: ""
function show(pairingData) {
console.log("BluetoothPairingModal.show() called:", JSON.stringify(pairingData))
token = pairingData.token || ""
deviceName = pairingData.deviceName || ""
deviceAddress = pairingData.deviceAddr || ""
@@ -26,6 +27,7 @@ DankModal {
pinInput = ""
passkeyInput = ""
console.log("BluetoothPairingModal: Calling open()")
open()
Qt.callLater(() => {
if (contentLoader.item) {
@@ -39,6 +41,8 @@ DankModal {
}
shouldBeVisible: false
allowStacking: true
keepPopoutsOpen: true
width: 420
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240
@@ -62,8 +66,11 @@ DankModal {
}
onBackgroundClicked: () => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
}
@@ -80,8 +87,11 @@ DankModal {
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
event.accepted = true
@@ -110,17 +120,22 @@ DankModal {
StyledText {
text: {
if (requestType === "confirm")
switch (requestType) {
case "confirm":
return I18n.tr("Confirm passkey for ") + deviceName
if (requestType === "authorize")
case "display-passkey":
return I18n.tr("Enter this passkey on ") + deviceName
case "authorize":
return I18n.tr("Authorize pairing with ") + deviceName
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
if (requestType === "pin")
case "pin":
return I18n.tr("Enter PIN for ") + deviceName
if (requestType === "passkey")
case "passkey":
return I18n.tr("Enter passkey for ") + deviceName
return deviceName
default:
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize service for ") + deviceName
return deviceName
}
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
@@ -204,7 +219,7 @@ DankModal {
height: 56
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
visible: requestType === "confirm"
visible: requestType === "confirm" || requestType === "display-passkey"
Column {
anchors.centerIn: parent
@@ -261,8 +276,11 @@ DankModal {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
}
@@ -288,11 +306,17 @@ DankModal {
anchors.centerIn: parent
text: {
if (requestType === "confirm")
switch (requestType) {
case "confirm":
case "display-passkey":
return I18n.tr("Confirm")
if (requestType === "authorize" || requestType.startsWith("authorize-service"))
case "authorize":
return I18n.tr("Authorize")
return I18n.tr("Pair")
default:
if (requestType.startsWith("authorize-service"))
return I18n.tr("Authorize")
return I18n.tr("Pair")
}
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
@@ -331,8 +355,11 @@ DankModal {
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
DMSService.bluetoothCancelPairing(token)
if (token) {
DMSService.bluetoothCancelPairing(token)
}
close()
token = ""
pinInput = ""
passkeyInput = ""
}
@@ -343,12 +370,23 @@ DankModal {
function submitPairing() {
const secrets = {}
if (requestType === "pin") {
switch (requestType) {
case "pin":
secrets["pin"] = pinInput
} else if (requestType === "passkey") {
break
case "passkey":
secrets["passkey"] = passkeyInput
} else if (requestType === "confirm" || requestType === "authorize" || requestType.startsWith("authorize-service")) {
break
case "confirm":
case "display-passkey":
case "authorize":
secrets["decision"] = "yes"
break
default:
if (requestType.startsWith("authorize-service")) {
secrets["decision"] = "yes"
}
break
}
DMSService.bluetoothSubmitPairing(token, secrets, true, response => {
@@ -358,6 +396,7 @@ DankModal {
})
close()
token = ""
pinInput = ""
passkeyInput = ""
}

View File

@@ -6,6 +6,8 @@ import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:confirm-modal"
property string confirmTitle: ""
property string confirmMessage: ""
property string confirmButtonText: "Confirm"

View File

@@ -43,6 +43,7 @@ PanelWindow {
property bool allowFocusOverride: false
property bool allowStacking: false
property bool keepContentLoaded: false
property bool keepPopoutsOpen: false
signal opened
signal dialogClosed
@@ -88,7 +89,17 @@ PanelWindow {
}
}
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: shouldHaveFocus ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: {
if (!shouldHaveFocus) return WlrKeyboardFocus.None
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand
return WlrKeyboardFocus.Exclusive
}
HyprlandFocusGrab {
windows: [root]
active: CompositorService.isHyprland && shouldHaveFocus
}
onVisibleChanged: {
if (root.visible) {
opened()

View File

@@ -60,7 +60,7 @@ DankModal {
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
Quickshell.execDetached(["sh", "-c", `echo -n "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied`)
SessionData.addRecentColor(currentColor)
}
@@ -571,7 +571,7 @@ DankModal {
} else {
rgbString = `rgb(${r}, ${g}, ${b})`
}
Quickshell.execDetached(["sh", "-c", `echo "${rgbString}" | wl-copy`])
Quickshell.execDetached(["sh", "-c", `echo -n "${rgbString}" | wl-copy`])
ToastService.showInfo(`${rgbString} copied`)
}
}
@@ -635,7 +635,7 @@ DankModal {
} else {
hsvString = `${h}, ${s}, ${v}`
}
Quickshell.execDetached(["sh", "-c", `echo "${hsvString}" | wl-copy`])
Quickshell.execDetached(["sh", "-c", `echo -n "${hsvString}" | wl-copy`])
ToastService.showInfo(`HSV ${hsvString} copied`)
}
}

View File

@@ -33,7 +33,9 @@ DankModal {
parentBounds = bounds
parentScreen = targetScreen
backgroundOpacity = 0
keepPopoutsOpen = true
open()
keepPopoutsOpen = false
}
function updateVisibleActions() {

View File

@@ -139,7 +139,7 @@ Rectangle {
}
StyledText {
text: UserInfoService.hostname || "Linux"
text: DgopService.hostname || "DMS"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideRight

View File

@@ -12,7 +12,9 @@ Rectangle {
function resetScroll() {
resultsList.contentY = 0
resultsGrid.contentY = 0
if (gridLoader.item) {
gridLoader.item.contentY = 0
}
}
radius: Theme.cornerRadius
@@ -92,88 +94,106 @@ Rectangle {
}
}
DankGridView {
id: resultsGrid
Loader {
id: gridLoader
property int currentIndex: appLauncher ? appLauncher.selectedIndex : -1
property int columns: appLauncher ? appLauncher.gridColumns : 4
property bool adaptiveColumns: false
property int minCellWidth: 120
property int maxCellWidth: 160
property int cellPadding: 8
property real iconSizeRatio: 0.55
property int maxIconSize: 48
property int minIconSize: 32
property bool hoverUpdatesSelection: false
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset
signal itemClicked(int index, var modelData)
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
function ensureVisible(index) {
if (index < 0 || index >= count)
return
const itemY = Math.floor(index / actualColumns) * cellHeight
const itemBottom = itemY + cellHeight
if (itemY < contentY)
contentY = itemY
else if (itemBottom > contentY + height)
contentY = itemBottom - height
}
property real _lastWidth: 0
anchors.fill: parent
anchors.margins: Theme.spacingS
visible: appLauncher && appLauncher.viewMode === "grid"
model: appLauncher ? appLauncher.model : null
clip: true
cellWidth: baseCellWidth
cellHeight: baseCellHeight
leftMargin: Math.max(Theme.spacingS, remainingSpace / 2)
rightMargin: leftMargin
focus: true
interactive: true
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
reuseItems: true
onCurrentIndexChanged: {
if (keyboardNavigationActive)
ensureVisible(currentIndex)
}
onItemClicked: (index, modelData) => {
if (appLauncher)
appLauncher.launchApp(modelData)
}
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
if (contextMenu)
contextMenu.show(mouseX, mouseY, modelData)
}
onKeyboardNavigationReset: () => {
if (appLauncher)
appLauncher.keyboardNavigationActive = false
}
delegate: AppLauncherGridDelegate {
gridView: resultsGrid
cellWidth: resultsGrid.cellWidth
cellHeight: resultsGrid.cellHeight
cellPadding: resultsGrid.cellPadding
minIconSize: resultsGrid.minIconSize
maxIconSize: resultsGrid.maxIconSize
iconSizeRatio: resultsGrid.iconSizeRatio
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
currentIndex: resultsGrid.currentIndex
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
resultsGrid.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
active: appLauncher && appLauncher.viewMode === "grid"
onWidthChanged: {
if (visible && Math.abs(width - _lastWidth) > 1) {
_lastWidth = width
active = false
Qt.callLater(() => {
active = true
})
}
}
sourceComponent: Component {
DankGridView {
id: resultsGrid
property int currentIndex: appLauncher ? appLauncher.selectedIndex : -1
property int columns: appLauncher ? appLauncher.gridColumns : 4
property bool adaptiveColumns: false
property int minCellWidth: 120
property int maxCellWidth: 160
property int cellPadding: 8
property real iconSizeRatio: 0.55
property int maxIconSize: 48
property int minIconSize: 32
property bool hoverUpdatesSelection: false
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset
signal itemClicked(int index, var modelData)
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
function ensureVisible(index) {
if (index < 0 || index >= count)
return
const itemY = Math.floor(index / actualColumns) * cellHeight
const itemBottom = itemY + cellHeight
if (itemY < contentY)
contentY = itemY
else if (itemBottom > contentY + height)
contentY = itemBottom - height
}
model: appLauncher ? appLauncher.model : null
clip: true
cellWidth: baseCellWidth
cellHeight: baseCellHeight
leftMargin: Math.max(Theme.spacingS, remainingSpace / 2)
rightMargin: leftMargin
focus: true
interactive: true
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
reuseItems: true
onCurrentIndexChanged: {
if (keyboardNavigationActive)
ensureVisible(currentIndex)
}
onItemClicked: (index, modelData) => {
if (appLauncher)
appLauncher.launchApp(modelData)
}
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
if (contextMenu)
contextMenu.show(mouseX, mouseY, modelData)
}
onKeyboardNavigationReset: () => {
if (appLauncher)
appLauncher.keyboardNavigationActive = false
}
delegate: AppLauncherGridDelegate {
gridView: resultsGrid
cellWidth: resultsGrid.cellWidth
cellHeight: resultsGrid.cellHeight
cellPadding: resultsGrid.cellPadding
minIconSize: resultsGrid.minIconSize
maxIconSize: resultsGrid.maxIconSize
iconSizeRatio: resultsGrid.iconSizeRatio
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
currentIndex: resultsGrid.currentIndex
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
resultsGrid.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
}
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
}
}
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
}
}
}

View File

@@ -17,9 +17,6 @@ DankPopout {
property var triggerScreen: null
// Setting to Exclusive, so virtual keyboards can send input to app drawer
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
function show() {
open()
}
@@ -40,6 +37,8 @@ DankPopout {
positioning: ""
screen: triggerScreen
onBackgroundClicked: close()
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
appLauncher.searchQuery = ""

View File

@@ -71,7 +71,15 @@ DankPopout {
positioning: ""
screen: triggerScreen
shouldBeVisible: false
visible: shouldBeVisible
WlrLayershell.keyboardFocus: {
if (!shouldBeVisible) return WlrKeyboardFocus.None
if (powerMenuOpen) return WlrKeyboardFocus.None
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand
return WlrKeyboardFocus.Exclusive
}
onBackgroundClicked: close()
onShouldBeVisibleChanged: {
if (shouldBeVisible) {

View File

@@ -29,11 +29,13 @@ Rectangle {
if (!device) return
const deviceAddr = device.address
devicesBeingPaired.add(deviceAddr)
const pairingSet = devicesBeingPaired
pairingSet.add(deviceAddr)
devicesBeingPairedChanged()
BluetoothService.pairDevice(device, function(response) {
devicesBeingPaired.delete(deviceAddr)
pairingSet.delete(deviceAddr)
devicesBeingPairedChanged()
if (response.error) {
@@ -625,15 +627,14 @@ Rectangle {
}
}
BluetoothPairingModal {
id: bluetoothPairingModal
}
Connections {
target: DMSService
function onBluetoothPairingRequest(data) {
bluetoothPairingModal.show(data)
const modal = PopoutService.bluetoothPairingModal
if (modal && modal.token !== data.token) {
modal.show(data)
}
}
}
}

View File

@@ -1,316 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Widgets
PanelWindow {
id: root
readonly property string powerOptionsText: I18n.tr("Power Options")
readonly property string logOutText: I18n.tr("Log Out")
readonly property string suspendText: I18n.tr("Suspend")
readonly property string rebootText: I18n.tr("Reboot")
readonly property string powerOffText: I18n.tr("Power Off")
property bool powerMenuVisible: false
signal powerActionRequested(string action, string title, string message)
visible: powerMenuVisible
implicitWidth: 400
implicitHeight: 320
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
MouseArea {
anchors.fill: parent
onClicked: {
powerMenuVisible = false
}
}
Rectangle {
width: Math.min(320, parent.width - Theme.spacingL * 2)
height: 320 // Fixed height to prevent cropping
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 0
opacity: powerMenuVisible ? 1 : 0
scale: powerMenuVisible ? 1 : 0.85
MouseArea {
anchors.fill: parent
onClicked: {
}
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
StyledText {
text: root.powerOptionsText
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - 150
height: 1
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: {
powerMenuVisible = false
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.08) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.08)
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "logout"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.logOutText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: logoutArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerMenuVisible = false
root.powerActionRequested(
"logout", "Log Out",
"Are you sure you want to log out?")
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.08) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.08)
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "bedtime"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.suspendText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: suspendArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerMenuVisible = false
root.powerActionRequested(
"suspend", "Suspend",
"Are you sure you want to suspend the system?")
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r,
Theme.warning.g,
Theme.warning.b,
0.08) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.08)
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "restart_alt"
size: Theme.iconSize
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.rebootText
font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: rebootArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerMenuVisible = false
root.powerActionRequested(
"reboot", "Reboot",
"Are you sure you want to reboot the system?")
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r,
Theme.error.g,
Theme.error.b,
0.08) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.08)
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "power_settings_new"
size: Theme.iconSize
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.powerOffText
font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: powerOffArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerMenuVisible = false
root.powerActionRequested(
"poweroff", "Power Off",
"Are you sure you want to power off the system?")
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}

View File

@@ -1,183 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
Item {
id: root
required property var barWindow
required property var axis
required property var appDrawerLoader
required property var dankDashPopoutLoader
required property var processListPopoutLoader
required property var notificationCenterLoader
required property var batteryPopoutLoader
required property var layoutPopoutLoader
required property var vpnPopoutLoader
required property var controlCenterLoader
required property var clipboardHistoryModalPopup
required property var systemUpdateLoader
required property var notepadInstance
property alias reveal: core.reveal
property alias autoHide: core.autoHide
property alias backgroundTransparency: core.backgroundTransparency
property alias hasActivePopout: core.hasActivePopout
property alias mouseArea: topBarMouseArea
Item {
id: inputMask
readonly property int barThickness: barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing)
readonly property bool showing: SettingsData.dankBarVisible && (core.reveal
|| (CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview)
|| !core.autoHide)
readonly property int maskThickness: showing ? barThickness : 1
x: {
if (!axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left: return 0
case SettingsData.Position.Right: return parent.width - maskThickness
default: return 0
}
}
}
y: {
if (axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top: return 0
case SettingsData.Position.Bottom: return parent.height - maskThickness
default: return 0
}
}
}
width: axis.isVertical ? maskThickness : parent.width
height: axis.isVertical ? parent.height : maskThickness
}
Region {
id: mask
item: inputMask
}
property alias maskRegion: mask
QtObject {
id: core
property real backgroundTransparency: SettingsData.dankBarTransparency
property bool autoHide: SettingsData.dankBarAutoHide
property bool revealSticky: false
property bool notepadInstanceVisible: notepadInstance?.isVisible ?? false
readonly property bool hasActivePopout: {
const loaders = [{
"loader": appDrawerLoader,
"prop": "shouldBeVisible"
}, {
"loader": dankDashPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": processListPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": notificationCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": batteryPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": layoutPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": vpnPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": controlCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": clipboardHistoryModalPopup,
"prop": "visible"
}, {
"loader": systemUpdateLoader,
"prop": "shouldBeVisible"
}]
return notepadInstanceVisible || loaders.some(item => {
if (item.loader) {
return item.loader?.item?.[item.prop]
}
return false
})
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dankBarOpenOnOverview
}
return SettingsData.dankBarVisible && (!autoHide || topBarMouseArea.containsMouse || hasActivePopout || revealSticky)
}
onHasActivePopoutChanged: {
if (!hasActivePopout && autoHide && !topBarMouseArea.containsMouse) {
revealSticky = true
revealHold.restart()
}
}
}
Timer {
id: revealHold
interval: 250
repeat: false
onTriggered: core.revealSticky = false
}
Connections {
function onDankBarTransparencyChanged() {
core.backgroundTransparency = SettingsData.dankBarTransparency
}
target: SettingsData
}
Connections {
target: topBarMouseArea
function onContainsMouseChanged() {
if (topBarMouseArea.containsMouse) {
core.revealSticky = true
revealHold.stop()
} else {
if (core.autoHide && !core.hasActivePopout) {
revealHold.restart()
}
}
}
}
MouseArea {
id: topBarMouseArea
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
width: barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
anchors {
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
top: barWindow.isVertical ? parent.top : undefined
bottom: barWindow.isVertical ? parent.bottom : undefined
}
hoverEnabled: SettingsData.dankBarAutoHide && !core.reveal
acceptedButtons: Qt.NoButton
enabled: SettingsData.dankBarAutoHide && !core.reveal
}
}

View File

@@ -24,6 +24,25 @@ Item {
debounceTimer.restart()
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
z: -999
onClicked: {
const activePopout = PopoutManager.getActivePopout(barWindow.screen)
if (activePopout) {
if (activePopout.dashVisible !== undefined) {
activePopout.dashVisible = false
} else if (activePopout.notificationHistoryVisible !== undefined) {
activePopout.notificationHistoryVisible = false
} else {
activePopout.close()
}
}
TrayMenuManager.closeAllMenus()
}
}
Timer {
id: debounceTimer
interval: 50

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,539 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Shapes
import Quickshell
import Quickshell.Hyprland
import Quickshell.I3
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Services.Notifications
import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modules
import qs.Modules.DankBar.Widgets
import qs.Modules.DankBar.Popouts
import qs.Services
import qs.Widgets
PanelWindow {
id: barWindow
required property var rootWindow
property var modelData: item
property var hyprlandOverviewLoader: rootWindow ? rootWindow.hyprlandOverviewLoader : null
property var controlCenterButtonRef: null
property var clockButtonRef: null
function triggerControlCenter() {
controlCenterLoader.active = true
if (!controlCenterLoader.item) {
return
}
if (controlCenterButtonRef && controlCenterLoader.item.setTriggerPosition) {
const globalPos = controlCenterButtonRef.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, controlCenterButtonRef.width)
const section = controlCenterButtonRef.section || "right"
controlCenterLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen)
} else {
controlCenterLoader.item.triggerScreen = barWindow.screen
}
controlCenterLoader.item.toggle()
if (controlCenterLoader.item.shouldBeVisible && NetworkService.wifiEnabled) {
NetworkService.scanWifi()
}
}
function triggerWallpaperBrowser() {
dankDashPopoutLoader.active = true
if (!dankDashPopoutLoader.item) {
return
}
if (clockButtonRef && clockButtonRef.visualContent && dankDashPopoutLoader.item.setTriggerPosition) {
const globalPos = clockButtonRef.visualContent.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.visualWidth)
const section = clockButtonRef.section || "center"
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen)
} else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen
}
PopoutManager.requestPopout(dankDashPopoutLoader.item, 2)
}
readonly property var dBarLayer: {
switch (Quickshell.env("DMS_DANKBAR_LAYER")) {
case "bottom":
return WlrLayer.Bottom
case "overlay":
return WlrLayer.Overlay
case "background":
return WlrLayer.background
default:
return WlrLayer.Top
}
}
WlrLayershell.layer: dBarLayer
WlrLayershell.namespace: "dms:bar"
signal colorPickerRequested
onColorPickerRequested: rootWindow.colorPickerRequested()
property alias axis: axis
AxisContext {
id: axis
edge: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return "top"
case SettingsData.Position.Bottom:
return "bottom"
case SettingsData.Position.Left:
return "left"
case SettingsData.Position.Right:
return "right"
default:
return "top"
}
}
}
readonly property bool isVertical: axis.isVertical
property bool gothCornersEnabled: SettingsData.dankBarGothCornersEnabled
property real wingtipsRadius: SettingsData.dankBarGothCornerRadiusOverride ? SettingsData.dankBarGothCornerRadiusValue : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property real _backgroundAlpha: topBarCore?.backgroundTransparency ?? SettingsData.dankBarTransparency
readonly property color _bgColor: Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
property string screenName: modelData.name
readonly property int notificationCount: NotificationService.notifications.length
readonly property real effectiveBarThickness: Math.max(barWindow.widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
readonly property real widgetThickness: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
screen: modelData
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
color: "transparent"
property var nativeInhibitor: null
Component.onCompleted: {
if (SettingsData.forceStatusBarLayoutRefresh) {
SettingsData.forceStatusBarLayoutRefresh.connect(() => {
Qt.callLater(() => {
stackContainer.visible = false
Qt.callLater(() => {
stackContainer.visible = true
})
})
})
}
updateGpuTempConfig()
inhibitorInitTimer.start()
}
Timer {
id: inhibitorInitTimer
interval: 300
repeat: false
onTriggered: {
if (SessionService.nativeInhibitorAvailable) {
createNativeInhibitor()
}
}
}
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
console.info("DankBar: Plugin loaded:", pluginId)
SettingsData.widgetDataChanged()
}
function onPluginUnloaded(pluginId) {
console.info("DankBar: Plugin unloaded:", pluginId)
SettingsData.widgetDataChanged()
}
}
function updateGpuTempConfig() {
const allWidgets = [...(SettingsData.dankBarLeftWidgets || []), ...(SettingsData.dankBarCenterWidgets || []), ...(SettingsData.dankBarRightWidgets || [])]
const hasGpuTempWidget = allWidgets.some(widget => {
const widgetId = typeof widget === "string" ? widget : widget.id
const widgetEnabled = typeof widget === "string" ? true : (widget.enabled !== false)
return widgetId === "gpuTemp" && widgetEnabled
})
DgopService.gpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled || SessionData.nonNvidiaGpuTempEnabled
DgopService.nvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled
}
function createNativeInhibitor() {
if (!SessionService.nativeInhibitorAvailable) {
return
}
try {
const qmlString = `
import QtQuick
import Quickshell.Wayland
IdleInhibitor {
enabled: false
}
`
nativeInhibitor = Qt.createQmlObject(qmlString, barWindow, "DankBar.NativeInhibitor")
nativeInhibitor.window = barWindow
nativeInhibitor.enabled = Qt.binding(() => SessionService.idleInhibited)
nativeInhibitor.enabledChanged.connect(function () {
console.log("DankBar: Native inhibitor enabled changed to:", nativeInhibitor.enabled)
if (SessionService.idleInhibited !== nativeInhibitor.enabled) {
SessionService.idleInhibited = nativeInhibitor.enabled
SessionService.inhibitorChanged()
}
})
console.log("DankBar: Created native Wayland IdleInhibitor for", barWindow.screenName)
} catch (e) {
console.warn("DankBar: Failed to create native IdleInhibitor:", e)
nativeInhibitor = null
}
}
Connections {
function onDankBarLeftWidgetsChanged() {
barWindow.updateGpuTempConfig()
}
function onDankBarCenterWidgetsChanged() {
barWindow.updateGpuTempConfig()
}
function onDankBarRightWidgetsChanged() {
barWindow.updateGpuTempConfig()
}
target: SettingsData
}
Connections {
function onNvidiaGpuTempEnabledChanged() {
barWindow.updateGpuTempConfig()
}
function onNonNvidiaGpuTempEnabledChanged() {
barWindow.updateGpuTempConfig()
}
target: SessionData
}
Connections {
target: barWindow.screen
function onGeometryChanged() {
Qt.callLater(forceWidgetRefresh)
}
}
Timer {
id: refreshTimer
interval: 0
running: false
repeat: false
onTriggered: {
forceWidgetRefresh()
}
}
Connections {
target: axis
function onChanged() {
Qt.application.active
refreshTimer.restart()
}
}
anchors.top: !isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Top) : true
anchors.bottom: !isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom) : true
anchors.left: !isVertical ? true : (SettingsData.dankBarPosition === SettingsData.Position.Left)
anchors.right: !isVertical ? true : (SettingsData.dankBarPosition === SettingsData.Position.Right)
exclusiveZone: (!SettingsData.dankBarVisible || topBarCore.autoHide) ? -1 : (barWindow.effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap)
Item {
id: inputMask
readonly property int barThickness: Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr)
readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
readonly property bool effectiveVisible: SettingsData.dankBarVisible || inOverviewWithShow
readonly property bool showing: effectiveVisible && (topBarCore.reveal || inOverviewWithShow || !topBarCore.autoHide)
readonly property int maskThickness: showing ? barThickness : 1
x: {
if (!axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return 0
case SettingsData.Position.Right:
return parent.width - maskThickness
default:
return 0
}
}
}
y: {
if (axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return 0
case SettingsData.Position.Bottom:
return parent.height - maskThickness
default:
return 0
}
}
}
width: axis.isVertical ? maskThickness : parent.width
height: axis.isVertical ? parent.height : maskThickness
}
mask: Region {
item: inputMask
}
Item {
id: topBarCore
anchors.fill: parent
layer.enabled: true
property real backgroundTransparency: SettingsData.dankBarTransparency
property bool autoHide: SettingsData.dankBarAutoHide
property bool revealSticky: false
Timer {
id: revealHold
interval: SettingsData.dankBarAutoHideDelay
repeat: false
onTriggered: topBarCore.revealSticky = false
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dankBarOpenOnOverview || topBarMouseArea.containsMouse || hasActivePopout || revealSticky
}
return SettingsData.dankBarVisible && (!autoHide || topBarMouseArea.containsMouse || hasActivePopout || revealSticky)
}
readonly property bool hasActivePopout: {
const loaders = [{
"loader": appDrawerLoader,
"prop": "shouldBeVisible"
}, {
"loader": dankDashPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": processListPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": notificationCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": batteryPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": layoutPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": vpnPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": controlCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": clipboardHistoryModalPopup,
"prop": "visible"
}, {
"loader": systemUpdateLoader,
"prop": "shouldBeVisible"
}]
return loaders.some(item => {
if (item.loader && item.loader.item) {
return item.loader.item[item.prop]
}
return false
}) || rootWindow.systemTrayMenuOpen
}
Connections {
function onDankBarTransparencyChanged() {
topBarCore.backgroundTransparency = SettingsData.dankBarTransparency
}
target: SettingsData
}
Connections {
target: topBarMouseArea
function onContainsMouseChanged() {
if (topBarMouseArea.containsMouse) {
topBarCore.revealSticky = true
revealHold.stop()
} else {
if (topBarCore.autoHide && !topBarCore.hasActivePopout) {
revealHold.restart()
}
}
}
}
onHasActivePopoutChanged: {
if (hasActivePopout) {
revealSticky = true
revealHold.stop()
} else if (autoHide && !topBarMouseArea.containsMouse) {
revealSticky = true
revealHold.restart()
}
}
MouseArea {
id: topBarMouseArea
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
width: barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
anchors {
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
top: barWindow.isVertical ? parent.top : undefined
bottom: barWindow.isVertical ? parent.bottom : undefined
}
readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
hoverEnabled: SettingsData.dankBarAutoHide && !topBarCore.reveal && !inOverview
acceptedButtons: Qt.NoButton
enabled: SettingsData.dankBarAutoHide && !topBarCore.reveal && !inOverview
Item {
id: topBarContainer
anchors.fill: parent
transform: Translate {
id: topBarSlide
x: barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Right ? barWindow.implicitWidth : -barWindow.implicitWidth), barWindow._dpr) : 0
y: !barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barWindow.implicitHeight : -barWindow.implicitHeight), barWindow._dpr) : 0
Behavior on x {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
}
Item {
id: barUnitInset
anchors.fill: parent
anchors.leftMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "left" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
anchors.rightMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "right" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
anchors.topMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? 0 : Theme.px(SettingsData.dankBarSpacing, barWindow._dpr))
anchors.bottomMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
BarCanvas {
id: barBackground
barWindow: barWindow
axis: axis
}
MouseArea {
id: scrollArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
property real scrollAccumulator: 0
property real touchpadThreshold: 500
property bool actionInProgress: false
Timer {
id: cooldownTimer
interval: 100
onTriggered: parent.actionInProgress = false
}
onWheel: wheel => {
if (actionInProgress) {
wheel.accepted = false
return
}
const deltaY = wheel.angleDelta.y
const deltaX = wheel.angleDelta.x
if (CompositorService.isNiri && Math.abs(deltaX) > Math.abs(deltaY)) {
topBarContent.switchApp(deltaX)
wheel.accepted = false
return
}
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
const direction = deltaY < 0 ? 1 : -1
if (isMouseWheel) {
topBarContent.switchWorkspace(direction)
actionInProgress = true
cooldownTimer.restart()
} else {
scrollAccumulator += deltaY
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
const touchDirection = scrollAccumulator < 0 ? 1 : -1
topBarContent.switchWorkspace(touchDirection)
scrollAccumulator = 0
actionInProgress = true
cooldownTimer.restart()
}
}
wheel.accepted = false
}
}
DankBarContent {
id: topBarContent
barWindow: barWindow
rootWindow: rootWindow
}
}
}
}
}
}

View File

@@ -50,8 +50,8 @@ DankPopout {
triggerWidth: 70
positioning: ""
screen: triggerScreen
shouldBeVisible: false
visible: shouldBeVisible
onBackgroundClicked: close()
content: Component {
Rectangle {

View File

@@ -95,7 +95,6 @@ DankPopout {
positioning: ""
screen: triggerScreen
shouldBeVisible: false
visible: shouldBeVisible
content: Component {
Rectangle {

View File

@@ -46,7 +46,8 @@ DankPopout {
positioning: ""
screen: triggerScreen
shouldBeVisible: false
visible: shouldBeVisible
onBackgroundClicked: close()
content: Component {
Rectangle {

View File

@@ -19,7 +19,7 @@ BasePill {
id: clockColumn
visible: root.isVerticalOrientation
anchors.centerIn: parent
spacing: -2
spacing: 0
Row {
spacing: 0
@@ -37,7 +37,6 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -54,7 +53,6 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -68,7 +66,6 @@ BasePill {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -77,7 +74,6 @@ BasePill {
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -92,7 +88,6 @@ BasePill {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -101,19 +96,18 @@ BasePill {
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.surfaceText
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
}
Item {
width: 12
width: parent.width
height: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 12
width: parent.width * 0.6
height: 1
color: Theme.outlineButton
anchors.centerIn: parent
@@ -134,7 +128,6 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -149,7 +142,6 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -169,7 +161,6 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}
@@ -184,7 +175,6 @@ BasePill {
}
font.pixelSize: Theme.barTextSize(root.barThickness)
color: Theme.primary
width: 9
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom
}

View File

@@ -132,8 +132,8 @@ BasePill {
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("cpu");
if (root.toggleProcessList) {
root.toggleProcessList();
if (popoutTarget) {
PopoutManager.requestPopout(popoutTarget, undefined, "cpu");
}
}
}

View File

@@ -132,8 +132,8 @@ BasePill {
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("cpu");
if (root.toggleProcessList) {
root.toggleProcessList();
if (popoutTarget) {
PopoutManager.requestPopout(popoutTarget, undefined, "cpu_temp");
}
}
}

View File

@@ -196,8 +196,8 @@ BasePill {
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("cpu");
if (root.toggleProcessList) {
root.toggleProcessList();
if (popoutTarget) {
PopoutManager.requestPopout(popoutTarget, undefined, "gpu_temp");
}
}
}

View File

@@ -13,7 +13,14 @@ BasePill {
id: root
property bool compactMode: SettingsData.keyboardLayoutNameCompactMode
property string currentLayout: CompositorService.isNiri ? NiriService.getCurrentKeyboardLayoutName() : ""
property string currentLayout: {
if (CompositorService.isNiri) {
return NiriService.getCurrentKeyboardLayoutName()
} else if (CompositorService.isDwl) {
return DwlService.currentKeyboardLayout
}
return ""
}
property string hyprlandKeyboard: ""
content: Component {
@@ -79,6 +86,8 @@ BasePill {
root.hyprlandKeyboard,
"next"
])
} else if (CompositorService.isDwl) {
Quickshell.execDetached(["mmsg", "-d", "switch_keyboard_layout"])
}
}
}

View File

@@ -155,8 +155,8 @@ BasePill {
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
DgopService.setSortBy("memory");
if (root.toggleProcessList) {
root.toggleProcessList();
if (popoutTarget) {
PopoutManager.requestPopout(popoutTarget, undefined, "memory");
}
}
}

View File

@@ -149,13 +149,13 @@ Item {
if (currentIndex === -1) {
nextIndex = 0
} else {
nextIndex = (currentIndex + 1) % windows.length
nextIndex = Math.min(currentIndex + 1, windows.length - 1)
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1
} else {
nextIndex = (currentIndex - 1 + windows.length) % windows.length
nextIndex = Math.max(currentIndex - 1, 0)
}
}
@@ -181,13 +181,13 @@ Item {
if (currentIndex === -1) {
nextIndex = 0
} else {
nextIndex = (currentIndex + 1) % windows.length
nextIndex = Math.min(currentIndex + 1, windows.length - 1)
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1
} else {
nextIndex = (currentIndex - 1 + windows.length) % windows.length
nextIndex = Math.max(currentIndex - 1, 0)
}
}

View File

@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
@@ -49,7 +50,17 @@ Item {
visible: allTrayItems.length > 0
property bool menuOpen: false
property bool overflowWasOpenBeforeTrayMenu: false
property var currentTrayMenu: null
Component.onCompleted: {
if (!parentScreen) return
TrayMenuManager.register(parentScreen.name, root)
}
Component.onDestruction: {
if (!parentScreen) return
TrayMenuManager.unregister(parentScreen.name)
}
Rectangle {
id: visualBackground
@@ -171,7 +182,7 @@ Item {
if (!delegateRoot.trayItem.hasMenu) return
root.overflowWasOpenBeforeTrayMenu = root.menuOpen
root.menuOpen = false
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
}
}
@@ -304,7 +315,7 @@ Item {
if (!delegateRoot.trayItem.hasMenu) return
root.overflowWasOpenBeforeTrayMenu = root.menuOpen
root.menuOpen = false
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
}
}
@@ -356,10 +367,19 @@ Item {
screen: root.parentScreen
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: {
if (!root.menuOpen) return WlrKeyboardFocus.None
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand
return WlrKeyboardFocus.Exclusive
}
WlrLayershell.namespace: "dms:tray-overflow-menu"
color: "transparent"
HyprlandFocusGrab {
windows: [overflowMenu]
active: CompositorService.isHyprland && root.menuOpen
}
anchors {
top: true
left: true
@@ -372,12 +392,108 @@ Item {
: (screen?.devicePixelRatio || 1)
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
readonly property var barBounds: {
if (!overflowMenu.screen) {
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
}
return SettingsData.getBarBounds(overflowMenu.screen, root.barThickness + SettingsData.dankBarSpacing)
}
readonly property real barX: barBounds.x
readonly property real barY: barBounds.y
readonly property real barWidth: barBounds.width
readonly property real barHeight: barBounds.height
readonly property real maskX: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return barWidth > 0 ? barWidth : 0
case SettingsData.Position.Right:
case SettingsData.Position.Top:
case SettingsData.Position.Bottom:
default:
return 0
}
}
readonly property real maskY: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return barHeight > 0 ? barHeight : 0
case SettingsData.Position.Bottom:
case SettingsData.Position.Left:
case SettingsData.Position.Right:
default:
return 0
}
}
readonly property real maskWidth: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return barWidth > 0 ? width - barWidth : width
case SettingsData.Position.Right:
return barWidth > 0 ? width - barWidth : width
case SettingsData.Position.Top:
case SettingsData.Position.Bottom:
default:
return width
}
}
readonly property real maskHeight: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return barHeight > 0 ? height - barHeight : height
case SettingsData.Position.Bottom:
return barHeight > 0 ? height - barHeight : height
case SettingsData.Position.Left:
case SettingsData.Position.Right:
default:
return height
}
}
mask: Region {
item: Rectangle {
x: overflowMenu.maskX
y: overflowMenu.maskY
width: overflowMenu.maskWidth
height: overflowMenu.maskHeight
}
}
onVisibleChanged: {
if (visible) {
if (currentTrayMenu) {
currentTrayMenu.showMenu = false
}
PopoutManager.closeAllPopouts()
ModalManager.closeAllModalsExcept(null)
updatePosition()
}
}
MouseArea {
x: overflowMenu.maskX
y: overflowMenu.maskY
width: overflowMenu.maskWidth
height: overflowMenu.maskHeight
z: -1
enabled: root.menuOpen
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
const clickX = mouse.x + overflowMenu.maskX
const clickY = mouse.y + overflowMenu.maskY
const outsideContent = clickX < menuContainer.x || clickX > menuContainer.x + menuContainer.width ||
clickY < menuContainer.y || clickY > menuContainer.y + menuContainer.height
if (!outsideContent) return
root.menuOpen = false
}
}
FocusScope {
id: overflowFocusScope
anchors.fill: parent
@@ -389,11 +505,6 @@ Item {
}
function updatePosition() {
if (!root.parentWindow) {
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
return
}
const globalPos = root.mapToGlobal(0, 0)
const screenX = screen.x || 0
const screenY = screen.y || 0
@@ -613,7 +724,6 @@ Item {
if (!trayItem.hasMenu) return
root.overflowWasOpenBeforeTrayMenu = true
root.menuOpen = false
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
}
@@ -622,12 +732,6 @@ Item {
}
}
}
MouseArea {
anchors.fill: parent
z: -1
onClicked: root.menuOpen = false
}
}
Component {
@@ -674,14 +778,9 @@ Item {
function close() {
showMenu = false
if (root.overflowWasOpenBeforeTrayMenu) {
root.menuOpen = true
}
root.overflowWasOpenBeforeTrayMenu = false
}
function closeWithAction() {
root.overflowWasOpenBeforeTrayMenu = false
close()
}
@@ -715,9 +814,18 @@ Item {
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: {
if (!menuRoot.showMenu) return WlrKeyboardFocus.None
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand
return WlrKeyboardFocus.Exclusive
}
color: "transparent"
HyprlandFocusGrab {
windows: [menuWindow]
active: CompositorService.isHyprland && menuRoot.showMenu
}
anchors {
top: true
left: true
@@ -730,12 +838,106 @@ Item {
: (screen?.devicePixelRatio || 1)
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
readonly property var barBounds: {
if (!menuWindow.screen) {
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
}
return SettingsData.getBarBounds(menuWindow.screen, root.barThickness + SettingsData.dankBarSpacing)
}
readonly property real barX: barBounds.x
readonly property real barY: barBounds.y
readonly property real barWidth: barBounds.width
readonly property real barHeight: barBounds.height
readonly property real maskX: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return barWidth > 0 ? barWidth : 0
case SettingsData.Position.Right:
case SettingsData.Position.Top:
case SettingsData.Position.Bottom:
default:
return 0
}
}
readonly property real maskY: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return barHeight > 0 ? barHeight : 0
case SettingsData.Position.Bottom:
case SettingsData.Position.Left:
case SettingsData.Position.Right:
default:
return 0
}
}
readonly property real maskWidth: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return barWidth > 0 ? width - barWidth : width
case SettingsData.Position.Right:
return barWidth > 0 ? width - barWidth : width
case SettingsData.Position.Top:
case SettingsData.Position.Bottom:
default:
return width
}
}
readonly property real maskHeight: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return barHeight > 0 ? height - barHeight : height
case SettingsData.Position.Bottom:
return barHeight > 0 ? height - barHeight : height
case SettingsData.Position.Left:
case SettingsData.Position.Right:
default:
return height
}
}
mask: Region {
item: Rectangle {
x: menuWindow.maskX
y: menuWindow.maskY
width: menuWindow.maskWidth
height: menuWindow.maskHeight
}
}
onVisibleChanged: {
if (visible) {
root.menuOpen = false
PopoutManager.closeAllPopouts()
ModalManager.closeAllModalsExcept(null)
updatePosition()
}
}
MouseArea {
x: menuWindow.maskX
y: menuWindow.maskY
width: menuWindow.maskWidth
height: menuWindow.maskHeight
z: -1
enabled: menuRoot.showMenu
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
const clickX = mouse.x + menuWindow.maskX
const clickY = mouse.y + menuWindow.maskY
const outsideContent = clickX < menuContainer.x || clickX > menuContainer.x + menuContainer.width ||
clickY < menuContainer.y || clickY > menuContainer.y + menuContainer.height
if (!outsideContent) return
menuRoot.close()
}
}
FocusScope {
id: menuFocusScope
anchors.fill: parent
@@ -751,12 +953,8 @@ Item {
}
function updatePosition() {
if (!root.parentWindow) {
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
return
}
const globalPos = root.mapToGlobal(0, 0)
const targetItem = (typeof menuRoot !== "undefined" && menuRoot.anchorItem) ? menuRoot.anchorItem : root
const globalPos = targetItem.mapToGlobal(0, 0)
const screenX = screen.x || 0
const screenY = screen.y || 0
const relativeX = globalPos.x - screenX
@@ -770,12 +968,12 @@ Item {
let targetX = edge === "left"
? effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
: screen.width - (effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance)
anchorPos = Qt.point(targetX, relativeY + root.height / 2)
anchorPos = Qt.point(targetX, relativeY + targetItem.height / 2)
} else {
let targetY = root.isAtBottom
? screen.height - (effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance)
: effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
anchorPos = Qt.point(relativeX + root.width / 2, targetY)
anchorPos = Qt.point(relativeX + targetItem.width / 2, targetY)
}
}
@@ -1128,40 +1326,23 @@ Item {
}
}
}
MouseArea {
anchors.fill: parent
z: -1
onClicked: menuRoot.close()
}
}
}
}
property var currentTrayMenu: null
Connections {
target: currentTrayMenu
enabled: currentTrayMenu !== null
function onShowMenuChanged() {
if (parentWindow && typeof parentWindow.systemTrayMenuOpen !== "undefined") {
parentWindow.systemTrayMenuOpen = currentTrayMenu.showMenu
}
}
}
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
if (parentWindow && typeof parentWindow.systemTrayMenuOpen !== "undefined") {
parentWindow.systemTrayMenuOpen = true
}
if (!screen) return
if (currentTrayMenu) {
currentTrayMenu.showMenu = false
currentTrayMenu.destroy()
currentTrayMenu = null
}
currentTrayMenu = trayMenuComponent.createObject(null)
if (currentTrayMenu) {
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj)
}
if (!currentTrayMenu) return
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj)
}
}

View File

@@ -316,12 +316,19 @@ Item {
return [{"id": "1", "name": "1", "active": false}]
}
const visible = group.workspaces.filter(ws => !ws.hidden).sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
}).map(ws => ({
let visible = group.workspaces.filter(ws => !ws.hidden)
const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0)
if (hasValidCoordinates) {
visible = visible.sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
})
}
visible = visible.map(ws => ({
id: ws.id,
name: ws.name,
coordinates: ws.coordinates,
@@ -350,7 +357,7 @@ Item {
function getRealWorkspaces() {
return root.workspaceList.filter(ws => {
if (useExtWorkspace) return ws && ws.id !== "" && !ws.hidden
if (useExtWorkspace) return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden
if (CompositorService.isHyprland) return ws && ws.id !== -1
if (CompositorService.isDwl) return ws && ws.tag !== -1
if (CompositorService.isSway) return ws && ws.num !== -1
@@ -438,9 +445,13 @@ Item {
}
}
width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || useExtWorkspace
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
width: shouldShow ? (isVertical ? barThickness : visualWidth) : 0
height: shouldShow ? (isVertical ? visualHeight : barThickness) : 0
visible: shouldShow
Rectangle {
id: visualBackground
@@ -889,7 +900,7 @@ Item {
if (isPlaceholder) return index + 1
if (root.useExtWorkspace) return modelData?.name || modelData?.id || ""
if (root.useExtWorkspace) return index + 1
if (CompositorService.isHyprland) return modelData?.id || ""
if (CompositorService.isDwl) return (modelData?.tag !== undefined) ? (modelData.tag + 1) : ""
if (CompositorService.isSway) return modelData?.num || ""

View File

@@ -18,7 +18,6 @@ DankPopout {
property var triggerScreen: null
property int currentTabIndex: 0
keyboardFocusMode: WlrKeyboardFocus.Exclusive
function setTriggerPosition(x, y, width, section, screen) {
triggerSection = section
@@ -44,8 +43,8 @@ DankPopout {
triggerX: Screen.width - 620 - Theme.spacingL
triggerY: Math.max(26 + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap - 2
triggerWidth: 80
screen: triggerScreen
shouldBeVisible: dashVisible
visible: shouldBeVisible
property bool __focusArmed: false
property bool __contentReady: false

View File

@@ -35,7 +35,7 @@ Scope {
WlrLayershell.namespace: "dms:workspace-overview"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.keyboardFocus: overviewScope.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
anchors {
top: true

View File

@@ -11,25 +11,123 @@ Rectangle {
property bool isVisible: false
property bool showLogout: true
property int selectedIndex: 0
property int optionCount: {
let count = 0
if (showLogout) count++
count++
if (SessionService.hibernateSupported) count++
count += 2
return count
}
property int selectedRow: 0
property int selectedCol: 0
property var visibleActions: []
property int gridColumns: 3
property int gridRows: 2
property bool useGridLayout: false
signal closed()
function show() {
isVisible = true
selectedIndex = 0
Qt.callLater(() => {
if (powerMenuFocusScope && powerMenuFocusScope.forceActiveFocus) {
powerMenuFocusScope.forceActiveFocus()
}
function updateVisibleActions() {
const allActions = (typeof SettingsData !== "undefined" && SettingsData.powerMenuActions)
? SettingsData.powerMenuActions
: ["logout", "suspend", "hibernate", "reboot", "poweroff"]
const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false
let filtered = allActions.filter(action => {
if (action === "hibernate" && !hibernateSupported) return false
if (action === "lock") return false
if (action === "restart") return false
if (action === "logout" && !showLogout) return false
return true
})
visibleActions = filtered
useGridLayout = (typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined)
? SettingsData.powerMenuGridLayout
: false
if (!useGridLayout) return
const count = visibleActions.length
if (count === 0) {
gridColumns = 1
gridRows = 1
return
}
if (count <= 3) {
gridColumns = 1
gridRows = count
return
}
if (count === 4) {
gridColumns = 2
gridRows = 2
return
}
gridColumns = 3
gridRows = Math.ceil(count / 3)
}
function getDefaultActionIndex() {
const defaultAction = (typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction)
? SettingsData.powerMenuDefaultAction
: "suspend"
const index = visibleActions.indexOf(defaultAction)
return index >= 0 ? index : 0
}
function getActionAtIndex(index) {
if (index < 0 || index >= visibleActions.length) return ""
return visibleActions[index]
}
function getActionData(action) {
switch (action) {
case "reboot":
return { "icon": "restart_alt", "label": I18n.tr("Reboot"), "key": "R" }
case "logout":
return { "icon": "logout", "label": I18n.tr("Log Out"), "key": "X" }
case "poweroff":
return { "icon": "power_settings_new", "label": I18n.tr("Power Off"), "key": "P" }
case "suspend":
return { "icon": "bedtime", "label": I18n.tr("Suspend"), "key": "S" }
case "hibernate":
return { "icon": "ac_unit", "label": I18n.tr("Hibernate"), "key": "H" }
default:
return { "icon": "help", "label": action, "key": "?" }
}
}
function selectOption(action) {
if (!action) return
if (typeof SessionService === "undefined") return
hide()
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
function show() {
updateVisibleActions()
const defaultIndex = getDefaultActionIndex()
if (useGridLayout) {
selectedRow = Math.floor(defaultIndex / gridColumns)
selectedCol = defaultIndex % gridColumns
selectedIndex = defaultIndex
} else {
selectedIndex = defaultIndex
}
isVisible = true
Qt.callLater(() => powerMenuFocusScope.forceActiveFocus())
}
function hide() {
@@ -37,6 +135,148 @@ Rectangle {
closed()
}
function handleListNavigation(event) {
switch (event.key) {
case Qt.Key_Up:
case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length
event.accepted = true
break
case Qt.Key_Down:
case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % visibleActions.length
event.accepted = true
break
case Qt.Key_Return:
case Qt.Key_Enter:
selectOption(getActionAtIndex(selectedIndex))
event.accepted = true
break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % visibleActions.length
event.accepted = true
}
break
case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) {
selectOption("poweroff")
event.accepted = true
} else {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % visibleActions.length
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length
event.accepted = true
}
break
case Qt.Key_R:
selectOption("reboot")
event.accepted = true
break
case Qt.Key_X:
selectOption("logout")
event.accepted = true
break
case Qt.Key_S:
selectOption("suspend")
event.accepted = true
break
case Qt.Key_H:
selectOption("hibernate")
event.accepted = true
break
}
}
function handleGridNavigation(event) {
switch (event.key) {
case Qt.Key_Left:
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
break
case Qt.Key_Right:
selectedCol = (selectedCol + 1) % gridColumns
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
break
case Qt.Key_Up:
case Qt.Key_Backtab:
selectedRow = (selectedRow - 1 + gridRows) % gridRows
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
break
case Qt.Key_Down:
case Qt.Key_Tab:
selectedRow = (selectedRow + 1) % gridRows
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
break
case Qt.Key_Return:
case Qt.Key_Enter:
selectOption(getActionAtIndex(selectedIndex))
event.accepted = true
break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedCol = (selectedCol + 1) % gridColumns
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
}
break
case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) {
selectOption("poweroff")
event.accepted = true
} else {
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedRow = (selectedRow + 1) % gridRows
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedRow = (selectedRow - 1 + gridRows) % gridRows
selectedIndex = selectedRow * gridColumns + selectedCol
event.accepted = true
}
break
case Qt.Key_R:
selectOption("reboot")
event.accepted = true
break
case Qt.Key_X:
selectOption("logout")
event.accepted = true
break
case Qt.Key_S:
selectOption("suspend")
event.accepted = true
break
case Qt.Key_H:
selectOption("hibernate")
event.accepted = true
break
}
}
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
visible: isVisible
@@ -53,102 +293,39 @@ Rectangle {
focus: root.isVisible
onVisibleChanged: {
if (visible) {
Qt.callLater(() => forceActiveFocus())
}
}
Keys.onEscapePressed: {
root.hide()
if (visible) Qt.callLater(() => forceActiveFocus())
}
Keys.onEscapePressed: root.hide()
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Up:
case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount
event.accepted = true
break
case Qt.Key_Down:
case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % optionCount
event.accepted = true
break
case Qt.Key_Return:
case Qt.Key_Enter:
const actions = []
if (showLogout) actions.push("logout")
actions.push("suspend")
if (SessionService.hibernateSupported) actions.push("hibernate")
actions.push("reboot", "poweroff")
if (selectedIndex < actions.length) {
const action = actions[selectedIndex]
hide()
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
event.accepted = true
break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % optionCount
event.accepted = true
}
break
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % optionCount
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount
event.accepted = true
}
break
if (useGridLayout) {
handleGridNavigation(event)
} else {
handleListNavigation(event)
}
}
Rectangle {
anchors.centerIn: parent
width: 320
implicitHeight: mainColumn.implicitHeight + Theme.spacingL * 2
height: implicitHeight
width: useGridLayout
? Math.min(550, gridColumns * 180 + Theme.spacingS * (gridColumns - 1) + Theme.spacingL * 2)
: 320
height: contentItem.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outlineMedium
border.width: 1
Column {
id: mainColumn
Item {
id: contentItem
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
implicitHeight: headerRow.height + Theme.spacingM + (useGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight)
Row {
id: headerRow
width: parent.width
height: 30
StyledText {
text: I18n.tr("Power Options")
@@ -171,289 +348,199 @@ Rectangle {
}
}
Column {
Grid {
id: buttonGrid
visible: useGridLayout
anchors.top: headerRow.bottom
anchors.topMargin: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
columns: root.gridColumns
columnSpacing: Theme.spacingS
rowSpacing: Theme.spacingS
width: parent.width
spacing: Theme.spacingS
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
visible: showLogout
color: {
if (selectedIndex === 0) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
} else if (logoutArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
} else {
Repeater {
model: root.visibleActions
Rectangle {
required property int index
required property string modelData
readonly property var actionData: root.getActionData(modelData)
readonly property bool isSelected: root.selectedIndex === index
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
width: (contentItem.width - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
height: 100
radius: Theme.cornerRadius
color: {
if (isSelected) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
if (mouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
}
}
border.color: selectedIndex === 0 ? Theme.primary : "transparent"
border.width: selectedIndex === 0 ? 1 : 0
border.color: isSelected ? Theme.primary : "transparent"
border.width: isSelected ? 2 : 0
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "logout"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: parent.parent.actionData.icon
size: Theme.iconSize + 8
color: {
if (parent.parent.showWarning && mouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: parent.parent.actionData.label
font.pixelSize: Theme.fontSizeMedium
color: {
if (parent.parent.showWarning && mouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning
}
return Theme.surfaceText
}
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
width: 20
height: 16
radius: 4
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: parent.parent.parent.actionData.key
font.pixelSize: Theme.fontSizeSmall - 1
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium
anchors.centerIn: parent
}
}
}
StyledText {
text: I18n.tr("Log Out")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: logoutArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hide()
SessionService.logout()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: {
const suspendIdx = showLogout ? 1 : 0
if (selectedIndex === suspendIdx) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
} else if (suspendArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
}
}
border.color: selectedIndex === (showLogout ? 1 : 0) ? Theme.primary : "transparent"
border.width: selectedIndex === (showLogout ? 1 : 0) ? 1 : 0
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "bedtime"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Suspend")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: suspendArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hide()
SessionService.suspend()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: {
const hibernateIdx = showLogout ? 2 : 1
if (selectedIndex === hibernateIdx) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
} else if (hibernateArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
}
}
border.color: selectedIndex === (showLogout ? 2 : 1) ? Theme.primary : "transparent"
border.width: selectedIndex === (showLogout ? 2 : 1) ? 1 : 0
visible: SessionService.hibernateSupported
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "ac_unit"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Hibernate")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: hibernateArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hide()
SessionService.hibernate()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: {
let rebootIdx = showLogout ? 3 : 2
if (!SessionService.hibernateSupported) rebootIdx--
if (selectedIndex === rebootIdx) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
} else if (rebootArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
}
}
border.color: {
let rebootIdx = showLogout ? 3 : 2
if (!SessionService.hibernateSupported) rebootIdx--
return selectedIndex === rebootIdx ? Theme.primary : "transparent"
}
border.width: {
let rebootIdx = showLogout ? 3 : 2
if (!SessionService.hibernateSupported) rebootIdx--
return selectedIndex === rebootIdx ? 1 : 0
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "restart_alt"
size: Theme.iconSize
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Reboot")
font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: rebootArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hide()
SessionService.reboot()
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: {
let powerOffIdx = showLogout ? 4 : 3
if (!SessionService.hibernateSupported) powerOffIdx--
if (selectedIndex === powerOffIdx) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
} else if (powerOffArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
}
}
border.color: {
let powerOffIdx = showLogout ? 4 : 3
if (!SessionService.hibernateSupported) powerOffIdx--
return selectedIndex === powerOffIdx ? Theme.primary : "transparent"
}
border.width: {
let powerOffIdx = showLogout ? 4 : 3
if (!SessionService.hibernateSupported) powerOffIdx--
return selectedIndex === powerOffIdx ? 1 : 0
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "power_settings_new"
size: Theme.iconSize
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Power Off")
font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: powerOffArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hide()
SessionService.poweroff()
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.selectedRow = Math.floor(index / root.gridColumns)
root.selectedCol = index % root.gridColumns
root.selectOption(modelData)
}
}
}
}
}
Item {
height: Theme.spacingS
Column {
id: buttonColumn
visible: !useGridLayout
anchors.top: headerRow.bottom
anchors.topMargin: Theme.spacingM
anchors.left: parent.left
anchors.right: parent.right
spacing: Theme.spacingS
Repeater {
model: root.visibleActions
Rectangle {
required property int index
required property string modelData
readonly property var actionData: root.getActionData(modelData)
readonly property bool isSelected: root.selectedIndex === index
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
width: parent.width
height: 50
radius: Theme.cornerRadius
color: {
if (isSelected) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
if (listMouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
}
border.color: isSelected ? Theme.primary : "transparent"
border.width: isSelected ? 2 : 0
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: parent.parent.actionData.icon
size: Theme.iconSize + 4
color: {
if (parent.parent.showWarning && listMouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning
}
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: parent.parent.actionData.label
font.pixelSize: Theme.fontSizeMedium
color: {
if (parent.parent.showWarning && listMouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning
}
return Theme.surfaceText
}
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
width: 28
height: 20
radius: 4
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.1)
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: parent.parent.actionData.key
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium
anchors.centerIn: parent
}
}
MouseArea {
id: listMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.selectedIndex = index
root.selectOption(modelData)
}
}
}
}
}
}
}
}
Component.onCompleted: updateVisibleActions()
}

View File

@@ -40,7 +40,8 @@ DankListView {
NotificationEmptyState {
visible: listView.count === 0
anchors.centerIn: parent
y: 20
anchors.horizontalCenter: parent.horizontalCenter
}
onModelChanged: {

View File

@@ -42,7 +42,14 @@ DankPopout {
positioning: ""
screen: triggerScreen
shouldBeVisible: notificationHistoryVisible
visible: shouldBeVisible
function toggle() {
notificationHistoryVisible = !notificationHistoryVisible
}
onBackgroundClicked: {
notificationHistoryVisible = false
}
onNotificationHistoryVisibleChanged: {
if (notificationHistoryVisible) {
@@ -104,13 +111,20 @@ DankPopout {
implicitHeight: {
let baseHeight = Theme.spacingL * 2
baseHeight += cachedHeaderHeight
baseHeight += (notificationSettings.expanded ? notificationSettings.contentHeight : 0)
baseHeight += Theme.spacingM * 2
const settingsHeight = notificationSettings.expanded ? notificationSettings.contentHeight : 0
let listHeight = notificationList.listContentHeight
if (NotificationService.groupedNotifications.length === 0) {
listHeight = 200
}
baseHeight += Math.min(listHeight, 600)
const maxContentArea = 600
const availableListSpace = Math.max(200, maxContentArea - settingsHeight)
baseHeight += settingsHeight
baseHeight += Math.min(listHeight, availableListSpace)
const maxHeight = root.screen ? root.screen.height * 0.8 : Screen.height * 0.8
return Math.max(300, Math.min(baseHeight, maxHeight))
}
@@ -191,13 +205,6 @@ DankPopout {
showHints: (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false
z: 200
}
Behavior on implicitHeight {
NumberAnimation {
duration: 180
easing.type: Easing.OutQuart
}
}
}
}
}

View File

@@ -6,8 +6,10 @@ import qs.Widgets
DankOSD {
id: root
osdWidth: Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: 40 + Theme.spacingS * 2
readonly property bool useVertical: isVerticalLayout
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
autoHideInterval: 3000
enableMouseInteraction: true
@@ -20,8 +22,13 @@ DankOSD {
}
}
content: Item {
content: Loader {
anchors.fill: parent
sourceComponent: useVertical ? verticalContent : horizontalContent
}
Component {
id: horizontalContent
Item {
property int gap: Theme.spacingS
@@ -135,4 +142,175 @@ DankOSD {
}
}
}
Component {
id: verticalContent
Item {
anchors.fill: parent
property int gap: Theme.spacingS
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.iconSize / 2
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
y: gap
DankIcon {
anchors.centerIn: parent
name: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
return "brightness_medium"
} else if (deviceInfo.name.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
}
size: Theme.iconSize
color: Theme.primary
}
}
Item {
id: vertSlider
width: 12
height: parent.height - Theme.iconSize - gap * 3 - 24
anchors.horizontalCenter: parent.horizontalCenter
y: gap * 2 + Theme.iconSize
property bool dragging: false
property int value: DisplayService.brightnessAvailable ? DisplayService.brightnessLevel : 0
readonly property int minimum: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo) return 1
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
if (isExponential) return 1
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0
}
readonly property int maximum: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo) return 100
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
if (isExponential) return 100
return deviceInfo.displayMax || 100
}
Rectangle {
id: vertTrack
width: parent.width
height: parent.height
anchors.centerIn: parent
color: Theme.outline
radius: Theme.cornerRadius
}
Rectangle {
id: vertFill
width: parent.width
height: {
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
return ratio * parent.height
}
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
radius: Theme.cornerRadius
}
Rectangle {
id: vertHandle
width: 24
height: 8
radius: Theme.cornerRadius
y: {
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
const travel = parent.height - height
return Math.max(0, Math.min(travel, travel * (1 - ratio)))
}
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
border.width: 3
border.color: Theme.surfaceContainer
}
MouseArea {
id: vertSliderArea
anchors.fill: parent
anchors.margins: -12
enabled: DisplayService.brightnessAvailable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: {
setChildHovered(containsMouse)
}
onPressed: mouse => {
vertSlider.dragging = true
updateBrightness(mouse)
}
onReleased: {
vertSlider.dragging = false
}
onPositionChanged: mouse => {
if (pressed) {
updateBrightness(mouse)
}
}
onClicked: mouse => {
updateBrightness(mouse)
}
function updateBrightness(mouse) {
if (DisplayService.brightnessAvailable) {
const ratio = 1.0 - (mouse.y / height)
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum))
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true)
resetHideTimer()
}
}
}
Connections {
target: DisplayService
function onBrightnessChanged(showOsd) {
if (!vertSlider.dragging && vertSlider.value !== DisplayService.brightnessLevel) {
vertSlider.value = DisplayService.brightnessLevel
}
}
function onDeviceSwitched() {
if (!vertSlider.dragging && vertSlider.value !== DisplayService.brightnessLevel) {
vertSlider.value = DisplayService.brightnessLevel
}
}
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: gap
text: {
const deviceInfo = DisplayService.getCurrentDeviceInfo()
const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false
const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%"
return vertSlider.value + unit
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
visible: SettingsData.osdAlwaysShowValue
}
}
}
}

View File

@@ -6,8 +6,10 @@ import qs.Widgets
DankOSD {
id: root
osdWidth: Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: 40 + Theme.spacingS * 2
readonly property bool useVertical: isVerticalLayout
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
autoHideInterval: 3000
enableMouseInteraction: true
@@ -37,8 +39,13 @@ DankOSD {
}
}
content: Item {
content: Loader {
anchors.fill: parent
sourceComponent: useVertical ? verticalContent : horizontalContent
}
Component {
id: horizontalContent
Item {
property int gap: Theme.spacingS
@@ -128,11 +135,161 @@ DankOSD {
}
}
Component {
id: verticalContent
Item {
anchors.fill: parent
property int gap: Theme.spacingS
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.iconSize / 2
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
y: gap
DankIcon {
anchors.centerIn: parent
name: AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted ? "volume_off" : "volume_up"
size: Theme.iconSize
color: muteButtonVert.containsMouse ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: muteButtonVert
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
AudioService.toggleMute()
}
onContainsMouseChanged: {
setChildHovered(containsMouse || vertSliderArea.containsMouse)
}
}
}
Item {
id: vertSlider
width: 12
height: parent.height - Theme.iconSize - gap * 3 - 24
anchors.horizontalCenter: parent.horizontalCenter
y: gap * 2 + Theme.iconSize
property bool dragging: false
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
Rectangle {
id: vertTrack
width: parent.width
height: parent.height
anchors.centerIn: parent
color: Theme.outline
radius: Theme.cornerRadius
}
Rectangle {
id: vertFill
width: parent.width
height: (vertSlider.value / 100) * parent.height
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
radius: Theme.cornerRadius
}
Rectangle {
id: vertHandle
width: 24
height: 8
radius: Theme.cornerRadius
y: {
const ratio = vertSlider.value / 100
const travel = parent.height - height
return Math.max(0, Math.min(travel, travel * (1 - ratio)))
}
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary
border.width: 3
border.color: Theme.surfaceContainer
}
MouseArea {
id: vertSliderArea
anchors.fill: parent
anchors.margins: -12
enabled: AudioService.sink && AudioService.sink.audio
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: {
setChildHovered(containsMouse || muteButtonVert.containsMouse)
}
onPressed: mouse => {
vertSlider.dragging = true
updateVolume(mouse)
}
onReleased: {
vertSlider.dragging = false
}
onPositionChanged: mouse => {
if (pressed) {
updateVolume(mouse)
}
}
onClicked: mouse => {
updateVolume(mouse)
}
function updateVolume(mouse) {
if (AudioService.sink && AudioService.sink.audio) {
const ratio = 1.0 - (mouse.y / height)
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)))
AudioService.suppressOSD = true
AudioService.sink.audio.volume = volume / 100
AudioService.suppressOSD = false
resetHideTimer()
}
}
}
Connections {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
function onVolumeChanged() {
if (!vertSlider.dragging) {
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
}
}
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: gap
text: vertSlider.value + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
visible: SettingsData.osdAlwaysShowValue
}
}
}
onOsdShown: {
if (AudioService.sink && AudioService.sink.audio && contentLoader.item) {
const slider = contentLoader.item.children[0].children[1]
if (slider) {
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
if (AudioService.sink && AudioService.sink.audio && contentLoader.item && contentLoader.item.item) {
if (!useVertical) {
const slider = contentLoader.item.item.children[0].children[1]
if (slider && slider.value !== undefined) {
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100))
}
}
}
}

View File

@@ -8,8 +8,6 @@ DankPopout {
layerNamespace: "dms-plugin:" + layerNamespacePlugin
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
property var triggerScreen: null
property Component pluginContent: null
property real contentWidth: 400
@@ -27,7 +25,8 @@ DankPopout {
popupHeight: contentHeight
screen: triggerScreen
shouldBeVisible: false
visible: shouldBeVisible
onBackgroundClicked: close()
content: Component {
Rectangle {

View File

@@ -45,9 +45,10 @@ DankPopout {
triggerWidth: 55
positioning: ""
screen: triggerScreen
visible: shouldBeVisible
shouldBeVisible: false
onBackgroundClicked: close()
Ref {
service: DgopService
}

View File

@@ -542,11 +542,57 @@ Item {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Available Screens (") + Quickshell.screens.length + ")"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
Column {
width: parent.width
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Available Screens (") + Quickshell.screens.length + ")"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Item {
width: 1
height: 1
Layout.fillWidth: true
}
Column {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Display Name Format")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
DankButtonGroup {
id: displayModeGroup
model: [I18n.tr("Name"), I18n.tr("Model")]
currentIndex: SettingsData.displayNameMode === "model" ? 1 : 0
onSelectionChanged: (index, selected) => {
if (!selected) return
SettingsData.displayNameMode = index === 1 ? "model" : "system"
SettingsData.saveSettings()
}
Connections {
target: SettingsData
function onDisplayNameModeChanged() {
displayModeGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0
}
}
}
}
}
}
Repeater {
@@ -580,7 +626,7 @@ Item {
spacing: Theme.spacingXS / 2
StyledText {
text: modelData.name
text: SettingsData.getScreenDisplayName(modelData)
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
@@ -602,7 +648,7 @@ Item {
}
StyledText {
text: modelData.model || "Unknown Model"
text: SettingsData.displayNameMode === "system" ? (modelData.model || "Unknown Model") : modelData.name
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
@@ -701,14 +747,17 @@ Item {
width: parent.width
text: I18n.tr("All displays")
description: I18n.tr("Show on all connected displays")
checked: displaysTab.getScreenPreferences(parent.componentId).includes("all")
checked: {
var prefs = displaysTab.getScreenPreferences(parent.componentId)
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")
}
onToggled: (checked) => {
if (checked) {
displaysTab.setScreenPreferences(parent.componentId, ["all"]);
displaysTab.setScreenPreferences(parent.componentId, ["all"])
} else {
displaysTab.setScreenPreferences(parent.componentId, []);
displaysTab.setScreenPreferences(parent.componentId, [])
if (["dankBar", "dock", "notifications", "osd", "toast"].includes(parent.componentId)) {
displaysTab.setShowOnLastDisplay(parent.componentId, true);
displaysTab.setShowOnLastDisplay(parent.componentId, true)
}
}
}
@@ -719,9 +768,13 @@ Item {
text: I18n.tr("Show on Last Display")
description: I18n.tr("Always show when there's only one connected display")
checked: displaysTab.getShowOnLastDisplay(parent.componentId)
visible: !displaysTab.getScreenPreferences(parent.componentId).includes("all") && ["dankBar", "dock", "notifications", "osd", "toast", "notepad", "systemTray"].includes(parent.componentId)
visible: {
var prefs = displaysTab.getScreenPreferences(parent.componentId)
var isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")
return !isAll && ["dankBar", "dock", "notifications", "osd", "toast", "notepad", "systemTray"].includes(parent.componentId)
}
onToggled: (checked) => {
displaysTab.setShowOnLastDisplay(parent.componentId, checked);
displaysTab.setShowOnLastDisplay(parent.componentId, checked)
}
}
@@ -730,45 +783,54 @@ Item {
height: 1
color: Theme.outline
opacity: 0.2
visible: !displaysTab.getScreenPreferences(parent.componentId).includes("all")
visible: {
var prefs = displaysTab.getScreenPreferences(parent.componentId)
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all")
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
visible: !displaysTab.getScreenPreferences(parent.componentId).includes("all")
visible: {
var prefs = displaysTab.getScreenPreferences(parent.componentId)
return !prefs.includes("all") && !(typeof prefs[0] === "string" && prefs[0] === "all")
}
Repeater {
model: Quickshell.screens
delegate: DankToggle {
property string screenName: modelData.name
property var screenData: modelData
property string componentId: parent.parent.componentId
width: parent.width
text: screenName
description: modelData.width + "×" + modelData.height + " • " + (modelData.model || "Unknown Model")
text: SettingsData.getScreenDisplayName(screenData)
description: screenData.width + "×" + screenData.height + " • " + (SettingsData.displayNameMode === "system" ? (screenData.model || "Unknown Model") : screenData.name)
checked: {
var prefs = displaysTab.getScreenPreferences(componentId);
return !prefs.includes("all") && prefs.includes(screenName);
var prefs = displaysTab.getScreenPreferences(componentId)
if (typeof prefs[0] === "string" && prefs[0] === "all") return false
return SettingsData.isScreenInPreferences(screenData, prefs)
}
onToggled: (checked) => {
var currentPrefs = displaysTab.getScreenPreferences(componentId);
if (currentPrefs.includes("all"))
currentPrefs = [];
var newPrefs = currentPrefs.slice();
if (checked) {
if (!newPrefs.includes(screenName))
newPrefs.push(screenName);
} else {
var index = newPrefs.indexOf(screenName);
if (index > -1)
newPrefs.splice(index, 1);
var currentPrefs = displaysTab.getScreenPreferences(componentId)
if (typeof currentPrefs[0] === "string" && currentPrefs[0] === "all") {
currentPrefs = []
}
displaysTab.setScreenPreferences(componentId, newPrefs);
var newPrefs = currentPrefs.filter(pref => {
if (typeof pref === "string") return false
return pref.name !== screenData.name || pref.model !== screenData.model
})
if (checked) {
newPrefs.push({
name: screenData.name,
model: screenData.model || ""
})
}
displaysTab.setScreenPreferences(componentId, newPrefs)
}
}

View File

@@ -1007,18 +1007,32 @@ Item {
text: I18n.tr("Wallpaper Monitor")
description: I18n.tr("Select monitor to configure wallpaper")
currentValue: selectedMonitorName || "No monitors"
currentValue: {
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === selectedMonitorName) {
return SettingsData.getScreenDisplayName(screens[i])
}
}
return "No monitors"
}
options: {
var screenNames = []
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
screenNames.push(screens[i].name)
screenNames.push(SettingsData.getScreenDisplayName(screens[i]))
}
return screenNames
}
onValueChanged: value => {
selectedMonitorName = value
}
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (SettingsData.getScreenDisplayName(screens[i]) === value) {
selectedMonitorName = screens[i].name
return
}
}
}
}
DankDropdown {
@@ -1027,9 +1041,14 @@ Item {
text: I18n.tr("Matugen Target Monitor")
description: I18n.tr("Monitor whose wallpaper drives dynamic theming colors")
currentValue: {
var screens = Quickshell.screens
if (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "") {
var screens = Quickshell.screens
return screens.length > 0 ? screens[0].name + " (Default)" : "No monitors"
return screens.length > 0 ? SettingsData.getScreenDisplayName(screens[0]) + " (Default)" : "No monitors"
}
for (var i = 0; i < screens.length; i++) {
if (screens[i].name === SettingsData.matugenTargetMonitor) {
return SettingsData.getScreenDisplayName(screens[i])
}
}
return SettingsData.matugenTargetMonitor
}
@@ -1037,7 +1056,7 @@ Item {
var screenNames = []
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
var label = screens[i].name
var label = SettingsData.getScreenDisplayName(screens[i])
if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) {
label += " (Default)"
}
@@ -1046,9 +1065,15 @@ Item {
return screenNames
}
onValueChanged: value => {
var cleanValue = value.replace(" (Default)", "")
SettingsData.setMatugenTargetMonitor(cleanValue)
}
var cleanValue = value.replace(" (Default)", "")
var screens = Quickshell.screens
for (var i = 0; i < screens.length; i++) {
if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) {
SettingsData.setMatugenTargetMonitor(screens[i].name)
return
}
}
}
}
}
}
@@ -1439,7 +1464,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Blur Layer")
text: I18n.tr("Duplicate Wallpaper with Blur")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText

View File

@@ -736,6 +736,70 @@ Item {
}
}
Column {
width: parent.width
spacing: 0
leftPadding: Theme.spacingM
rightPadding: Theme.spacingM
DankDropdown {
width: parent.width - parent.leftPadding - parent.rightPadding
text: I18n.tr("OSD Position")
description: I18n.tr("Choose where on-screen displays appear on screen")
currentValue: {
switch (SettingsData.osdPosition) {
case SettingsData.Position.Top:
return "Top Right"
case SettingsData.Position.Left:
return "Top Left"
case SettingsData.Position.TopCenter:
return "Top Center"
case SettingsData.Position.Right:
return "Bottom Right"
case SettingsData.Position.Bottom:
return "Bottom Left"
case SettingsData.Position.BottomCenter:
return "Bottom Center"
case SettingsData.Position.LeftCenter:
return "Left Center"
case SettingsData.Position.RightCenter:
return "Right Center"
default:
return "Bottom Center"
}
}
options: ["Top Right", "Top Left", "Top Center", "Bottom Right", "Bottom Left", "Bottom Center", "Left Center", "Right Center"]
onValueChanged: value => {
switch (value) {
case "Top Right":
SettingsData.set("osdPosition", SettingsData.Position.Top)
break
case "Top Left":
SettingsData.set("osdPosition", SettingsData.Position.Left)
break
case "Top Center":
SettingsData.set("osdPosition", SettingsData.Position.TopCenter)
break
case "Bottom Right":
SettingsData.set("osdPosition", SettingsData.Position.Right)
break
case "Bottom Left":
SettingsData.set("osdPosition", SettingsData.Position.Bottom)
break
case "Bottom Center":
SettingsData.set("osdPosition", SettingsData.Position.BottomCenter)
break
case "Left Center":
SettingsData.set("osdPosition", SettingsData.Position.LeftCenter)
break
case "Right Center":
SettingsData.set("osdPosition", SettingsData.Position.RightCenter)
break
}
}
}
}
DankToggle {
width: parent.width
text: I18n.tr("Volume OSD")

View File

@@ -35,9 +35,10 @@ DankPopout {
triggerWidth: 55
positioning: ""
screen: triggerScreen
visible: shouldBeVisible
shouldBeVisible: false
onBackgroundClicked: close()
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
if (SystemUpdateService.updateCount === 0 && !SystemUpdateService.isChecking) {

View File

@@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import "../Common/fzf.js" as Fzf
import qs.Common
Singleton {
@@ -12,7 +11,141 @@ Singleton {
property var applications: DesktopEntries.applications.values.filter(app => !app.noDisplay && !app.runInTerminal)
readonly property int maxResults: 10
readonly property int frecencySampleSize: 10
readonly property var timeBuckets: [{
"maxDays": 4,
"weight": 100
}, {
"maxDays": 14,
"weight": 70
}, {
"maxDays": 31,
"weight": 50
}, {
"maxDays": 90,
"weight": 30
}, {
"maxDays": 99999,
"weight": 10
}]
function tokenize(text) {
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(w => w.length > 0)
}
function wordBoundaryMatch(text, query) {
const textWords = tokenize(text)
const queryWords = tokenize(query)
if (queryWords.length === 0)
return false
if (queryWords.length > textWords.length)
return false
for (var i = 0; i <= textWords.length - queryWords.length; i++) {
let allMatch = true
for (var j = 0; j < queryWords.length; j++) {
if (!textWords[i + j].startsWith(queryWords[j])) {
allMatch = false
break
}
}
if (allMatch)
return true
}
return false
}
function levenshteinDistance(s1, s2) {
const len1 = s1.length
const len2 = s2.length
const matrix = []
for (var i = 0; i <= len1; i++) {
matrix[i] = [i]
}
for (var j = 0; j <= len2; j++) {
matrix[0][j] = j
}
for (var i = 1; i <= len1; i++) {
for (var j = 1; j <= len2; j++) {
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
}
}
return matrix[len1][len2]
}
function fuzzyMatchScore(text, query) {
const queryLower = query.toLowerCase()
const maxDistance = query.length <= 2 ? 0 : query.length === 3 ? 1 : query.length <= 6 ? 2 : 3
let bestScore = 0
const distance = levenshteinDistance(text.toLowerCase(), queryLower)
if (distance <= maxDistance) {
const maxLen = Math.max(text.length, query.length)
bestScore = 1 - (distance / maxLen)
}
const words = tokenize(text)
for (const word of words) {
const wordDistance = levenshteinDistance(word, queryLower)
if (wordDistance <= maxDistance) {
const maxLen = Math.max(word.length, query.length)
const score = 1 - (wordDistance / maxLen)
bestScore = Math.max(bestScore, score)
}
}
return bestScore
}
function calculateFrecency(app) {
const usageRanking = AppUsageHistoryData.appUsageRanking || {}
const appId = app.id || (app.execString || app.exec || "")
const idVariants = [appId, appId.replace(".desktop", ""), app.id, app.id ? app.id.replace(".desktop", "") : null].filter(id => id)
let usageData = null
for (const variant of idVariants) {
if (usageRanking[variant]) {
usageData = usageRanking[variant]
break
}
}
if (!usageData || !usageData.usageCount) {
return {
"frecency": 0,
"daysSinceUsed": 999999
}
}
const usageCount = usageData.usageCount || 0
const lastUsed = usageData.lastUsed || 0
const now = Date.now()
const daysSinceUsed = (now - lastUsed) / (1000 * 60 * 60 * 24)
let timeBucketWeight = 10
for (const bucket of timeBuckets) {
if (daysSinceUsed <= bucket.maxDays) {
timeBucketWeight = bucket.weight
break
}
}
const contextBonus = 100
const sampleSize = Math.min(usageCount, frecencySampleSize)
const frecency = (timeBucketWeight * contextBonus * sampleSize) / 100
return {
"frecency": frecency,
"daysSinceUsed": daysSinceUsed
}
}
function searchApplications(query) {
if (!query || query.length === 0) {
@@ -23,7 +156,7 @@ Singleton {
const queryLower = query.toLowerCase().trim()
const scoredApps = []
const usageRanking = AppUsageHistoryData.appUsageRanking || {}
const results = []
for (const app of applications) {
const name = (app.name || "").toLowerCase()
@@ -31,123 +164,83 @@ Singleton {
const comment = (app.comment || "").toLowerCase()
const keywords = app.keywords ? app.keywords.map(k => k.toLowerCase()) : []
let score = 0
let matched = false
const nameWords = name.trim().split(/\s+/).filter(w => w.length > 0)
const containsAsWord = nameWords.includes(queryLower)
const startsWithAsWord = nameWords.some(word => word.startsWith(queryLower))
let textScore = 0
let matchType = "none"
if (name === queryLower) {
score = 10000
matched = true
} else if (containsAsWord) {
score = 9500 + (100 - Math.min(name.length, 100))
matched = true
textScore = 10000
matchType = "exact"
} else if (name.startsWith(queryLower)) {
score = 9000 + (100 - Math.min(name.length, 100))
matched = true
} else if (startsWithAsWord) {
score = 8500 + (100 - Math.min(name.length, 100))
matched = true
textScore = 5000
matchType = "prefix"
} else if (wordBoundaryMatch(name, queryLower)) {
textScore = 1000
matchType = "word_boundary"
} else if (name.includes(queryLower)) {
score = 8000 + (100 - Math.min(name.length, 100))
matched = true
} else if (keywords.length > 0) {
textScore = 500
matchType = "substring"
} else if (genericName && genericName.startsWith(queryLower)) {
textScore = 800
matchType = "generic_prefix"
} else if (genericName && genericName.includes(queryLower)) {
textScore = 400
matchType = "generic"
}
if (matchType === "none" && keywords.length > 0) {
for (const keyword of keywords) {
if (keyword === queryLower) {
score = 6000
matched = true
break
} else if (keyword.startsWith(queryLower)) {
score = 5500
matched = true
if (keyword.startsWith(queryLower)) {
textScore = 300
matchType = "keyword_prefix"
break
} else if (keyword.includes(queryLower)) {
score = 5000
matched = true
textScore = 150
matchType = "keyword"
break
}
}
}
if (!matched && genericName.includes(queryLower)) {
if (genericName === queryLower) {
score = 9000
} else if (genericName.startsWith(queryLower)) {
score = 8500
} else {
const genericWords = genericName.trim().split(/\s+/).filter(w => w.length > 0)
if (genericWords.includes(queryLower)) {
score = 8000
} else if (genericWords.some(word => word.startsWith(queryLower))) {
score = 7500
} else {
score = 7000
}
}
matched = true
} else if (!matched && comment.includes(queryLower)) {
score = 3000
matched = true
} else if (!matched) {
const nameFinder = new Fzf.Finder([app], {
"selector": a => a.name || "",
"casing": "case-insensitive",
"fuzzy": "v2"
})
const fuzzyResults = nameFinder.find(query)
if (fuzzyResults.length > 0 && fuzzyResults[0].score > 0) {
score = Math.min(fuzzyResults[0].score, 2000)
matched = true
if (matchType === "none" && comment && comment.includes(queryLower)) {
textScore = 50
matchType = "comment"
}
if (matchType === "none") {
const fuzzyScore = fuzzyMatchScore(name, queryLower)
if (fuzzyScore > 0) {
textScore = fuzzyScore * 100
matchType = "fuzzy"
}
}
if (matched) {
const appId = app.id || (app.execString || app.exec || "")
const idVariants = [
appId,
appId.replace(".desktop", ""),
app.id,
app.id ? app.id.replace(".desktop", "") : null
].filter(id => id)
if (matchType !== "none") {
const frecencyData = calculateFrecency(app)
let usageData = null
for (const variant of idVariants) {
if (usageRanking[variant]) {
usageData = usageRanking[variant]
break
}
}
if (usageData) {
const usageCount = usageData.usageCount || 0
const lastUsed = usageData.lastUsed || 0
const now = Date.now()
const daysSinceUsed = (now - lastUsed) / (1000 * 60 * 60 * 24)
let usageBonus = 0
usageBonus += Math.min(usageCount * 100, 2000)
if (daysSinceUsed < 1) {
usageBonus += 1500
} else if (daysSinceUsed < 7) {
usageBonus += 1000
} else if (daysSinceUsed < 30) {
usageBonus += 500
}
score += usageBonus
}
scoredApps.push({
"app": app,
"score": score
})
results.push({
"app": app,
"textScore": textScore,
"frecency": frecencyData.frecency,
"daysSinceUsed": frecencyData.daysSinceUsed,
"matchType": matchType
})
}
}
for (const result of results) {
const frecencyBonus = result.frecency > 0 ? Math.min(result.frecency / 10, 2000) : 0
const recencyBonus = result.daysSinceUsed < 1 ? 1500 : result.daysSinceUsed < 7 ? 1000 : result.daysSinceUsed < 30 ? 500 : 0
const finalScore = result.textScore + frecencyBonus + recencyBonus
scoredApps.push({
"app": result.app,
"score": finalScore
})
}
scoredApps.sort((a, b) => b.score - a.score)
return scoredApps.slice(0, 50).map(item => item.app)
return scoredApps.slice(0, maxResults).map(item => item.app)
}
function getCategoriesForApp(app) {
@@ -265,7 +358,8 @@ Singleton {
}
function getPluginCategoryIcon(category) {
if (typeof PluginService === "undefined") return null
if (typeof PluginService === "undefined")
return null
const launchers = PluginService.getLauncherPlugins()
for (const pluginId in launchers) {
@@ -295,7 +389,8 @@ Singleton {
}
function getPluginItems(category, query) {
if (typeof PluginService === "undefined") return []
if (typeof PluginService === "undefined")
return []
const launchers = PluginService.getLauncherPlugins()
for (const pluginId in launchers) {
@@ -313,12 +408,13 @@ Singleton {
}
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component) return []
if (!component)
return []
try {
const instance = component.createObject(root, {
"pluginService": PluginService
})
"pluginService": PluginService
})
if (instance && typeof instance.getItems === "function") {
const items = instance.getItems(query || "")
@@ -337,15 +433,17 @@ Singleton {
}
function executePluginItem(item, pluginId) {
if (typeof PluginService === "undefined") return false
if (typeof PluginService === "undefined")
return false
const component = PluginService.pluginLauncherComponents[pluginId]
if (!component) return false
if (!component)
return false
try {
const instance = component.createObject(root, {
"pluginService": PluginService
})
"pluginService": PluginService
})
if (instance && typeof instance.executeItem === "function") {
instance.executeItem(item)
@@ -364,7 +462,8 @@ Singleton {
}
function searchPluginItems(query) {
if (typeof PluginService === "undefined") return []
if (typeof PluginService === "undefined")
return []
let allItems = []
const launchers = PluginService.getLauncherPlugins()

View File

@@ -49,20 +49,20 @@ Singleton {
return devices.sort((a, b) => {
const aName = a.name || a.deviceName || ""
const bName = b.name || b.deviceName || ""
const aAddr = a.address || ""
const bAddr = b.address || ""
const aHasRealName = aName.includes(" ") && aName.length > 3
const bHasRealName = bName.includes(" ") && bName.length > 3
if (aHasRealName && !bHasRealName) {
return -1
}
if (!aHasRealName && bHasRealName) {
return 1
if (aHasRealName && !bHasRealName) return -1
if (!aHasRealName && bHasRealName) return 1
if (aHasRealName && bHasRealName) {
return aName.localeCompare(bName)
}
const aSignal = (a.signalStrength !== undefined && a.signalStrength > 0) ? a.signalStrength : 0
const bSignal = (b.signalStrength !== undefined && b.signalStrength > 0) ? b.signalStrength : 0
return bSignal - aSignal
return aAddr.localeCompare(bAddr)
})
}

View File

@@ -38,7 +38,7 @@ Singleton {
if (WlrOutputService.wlrOutputAvailable && screen) {
const wlrOutput = WlrOutputService.getOutput(screen.name)
if (wlrOutput?.enabled && wlrOutput.scale !== undefined && wlrOutput.scale > 0) {
return wlrOutput.scale
return Math.round(wlrOutput.scale * 20) / 20
}
}
@@ -441,6 +441,7 @@ Singleton {
if (isHyprland) return Hyprland.dispatch("dpms off")
if (isDwl) return _dwlPowerOffMonitors()
if (isSway) { try { I3.dispatch("output * dpms off") } catch(_){} return }
if (isLabwc) { Quickshell.execDetached(["dms", "dpms", "off"]) }
console.warn("CompositorService: Cannot power off monitors, unknown compositor")
}
@@ -449,6 +450,7 @@ Singleton {
if (isHyprland) return Hyprland.dispatch("dpms on")
if (isDwl) return _dwlPowerOnMonitors()
if (isSway) { try { I3.dispatch("output * dpms on") } catch(_){} return }
if (isLabwc) { Quickshell.execDetached(["dms", "dpms", "on"]) }
console.warn("CompositorService: Cannot power on monitors, unknown compositor")
}

View File

@@ -78,10 +78,15 @@ Singleton {
}
}
const oldValue = deviceBrightness[device.id]
const newBrightness = Object.assign({}, deviceBrightness)
newBrightness[device.id] = displayValue
deviceBrightness = newBrightness
brightnessVersion++
if (oldValue !== undefined && oldValue !== displayValue && brightnessInitialized) {
brightnessChanged(true)
}
}
function updateFromBrightnessState(state) {
@@ -110,9 +115,12 @@ Singleton {
deviceMaxCache = newMaxCache
const newBrightness = {}
let anyDeviceBrightnessChanged = false
for (const device of state.devices) {
const isExponential = SessionData.getBrightnessExponential(device.id)
const userSetValue = deviceBrightnessUserSet[device.id]
const oldValue = deviceBrightness[device.id]
if (isExponential) {
if (userSetValue !== undefined) {
@@ -123,6 +131,11 @@ Singleton {
} else {
newBrightness[device.id] = device.currentPercent
}
const newValue = newBrightness[device.id]
if (oldValue !== undefined && oldValue !== newValue) {
anyDeviceBrightnessChanged = true
}
}
deviceBrightness = newBrightness
brightnessVersion++
@@ -142,9 +155,15 @@ Singleton {
}
}
const shouldShowOsd = brightnessInitialized && anyDeviceBrightnessChanged
if (!brightnessInitialized) {
brightnessInitialized = true
}
if (shouldShowOsd) {
brightnessChanged(true)
}
}
function setBrightness(percentage, device, suppressOsd) {

View File

@@ -14,6 +14,11 @@ Singleton {
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()

View File

@@ -46,8 +46,17 @@ Singleton {
const hasExtWorkspace = DMSService.capabilities.includes("extworkspace")
if (hasExtWorkspace && !extWorkspaceAvailable) {
if (typeof CompositorService !== "undefined") {
const useExtWorkspace = DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway)
if (!useExtWorkspace) {
console.info("ExtWorkspaceService: ext-workspace available but compositor has native support")
extWorkspaceAvailable = false
return
}
}
extWorkspaceAvailable = true
console.info("ExtWorkspaceService: ext-workspace capability detected")
DMSService.addSubscription("extworkspace")
requestState()
} else if (!hasExtWorkspace) {
extWorkspaceAvailable = false
@@ -181,12 +190,17 @@ Singleton {
function getVisibleWorkspaces(outputName) {
const workspaces = getWorkspacesForOutput(outputName)
const visible = workspaces.filter(ws => !ws.hidden).sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
})
let visible = workspaces.filter(ws => !ws.hidden)
const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0)
if (hasValidCoordinates) {
visible = visible.sort((a, b) => {
const coordsA = a.coordinates || [0, 0]
const coordsB = b.coordinates || [0, 0]
if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0]
return coordsA[1] - coordsB[1]
})
}
const cacheKey = outputName
if (!_cachedWorkspaces[cacheKey]) {

View File

@@ -23,6 +23,7 @@ Singleton {
property var colorPickerModal: null
property var notificationModal: null
property var wifiPasswordModal: null
property var bluetoothPairingModal: null
property var networkInfoModal: null
property var notepadSlideouts: []

View File

@@ -137,21 +137,42 @@ Singleton {
}
}
// * Apps
function escapeShellArg(arg) {
return "'" + arg.replace(/'/g, "'\\''") + "'"
}
function needsShellExecution(prefix) {
if (!prefix || prefix.length === 0) return false
return /[;&|<>()$`\\"']/.test(prefix)
}
function launchDesktopEntry(desktopEntry, usePrimeRun) {
let cmd = desktopEntry.command
if (usePrimeRun && hasPrimeRun) {
cmd = ["prime-run"].concat(cmd)
}
if (SettingsData.launchPrefix && SettingsData.launchPrefix.length > 0) {
const launchPrefix = SettingsData.launchPrefix.trim().split(" ")
cmd = launchPrefix.concat(cmd)
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
});
const prefix = SettingsData.launchPrefix?.trim() || ""
if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ")
const shellCmd = `${prefix} ${escapedCmd}`
Quickshell.execDetached({
command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
} else {
if (prefix.length > 0) {
const launchPrefix = prefix.split(" ")
cmd = launchPrefix.concat(cmd)
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
}
}
function launchDesktopAction(desktopEntry, action, usePrimeRun) {
@@ -159,15 +180,28 @@ Singleton {
if (usePrimeRun && hasPrimeRun) {
cmd = ["prime-run"].concat(cmd)
}
if (SettingsData.launchPrefix && SettingsData.launchPrefix.length > 0) {
const launchPrefix = SettingsData.launchPrefix.trim().split(" ")
cmd = launchPrefix.concat(cmd)
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
});
const prefix = SettingsData.launchPrefix?.trim() || ""
if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ")
const shellCmd = `${prefix} ${escapedCmd}`
Quickshell.execDetached({
command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
} else {
if (prefix.length > 0) {
const launchPrefix = prefix.split(" ")
cmd = launchPrefix.concat(cmd)
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"),
})
}
}
// * Session management

View File

@@ -1 +1 @@
v0.5.2
v0.6.0

View File

@@ -74,16 +74,83 @@ PanelWindow {
readonly property real screenHeight: screen.height
readonly property real alignedWidth: Theme.px(osdWidth, dpr)
readonly property real alignedHeight: Theme.px(osdHeight, dpr)
readonly property real alignedX: Theme.snap((screenWidth - alignedWidth) / 2, dpr)
readonly property real barOffsetWhenBottom: {
if (SettingsData.dankBarPosition === SettingsData.Position.Bottom && SettingsData.dankBarVisible) {
const widgetThickness = Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
const effectiveBarThickness = Math.max(widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
return effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap
}
return 0
readonly property bool isVerticalLayout: SettingsData.osdPosition === SettingsData.Position.LeftCenter || SettingsData.osdPosition === SettingsData.Position.RightCenter
readonly property real barThickness: {
if (!SettingsData.dankBarVisible) return 0
const widgetThickness = Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
return Math.max(widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
}
readonly property real barOffset: {
if (!SettingsData.dankBarVisible) return 0
return barThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap
}
readonly property real dockThickness: {
if (!SettingsData.showDock) return 0
return SettingsData.dockIconSize + SettingsData.dockSpacing * 2 + 10
}
readonly property real dockOffset: {
if (!SettingsData.showDock || SettingsData.dockAutoHide) return 0
return dockThickness + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin
}
readonly property real alignedX: {
const margin = Theme.spacingM
const centerX = (screenWidth - alignedWidth) / 2
switch (SettingsData.osdPosition) {
case SettingsData.Position.Left:
case SettingsData.Position.Bottom:
const leftBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Left ? barOffset : 0
const leftDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0
return Theme.snap(margin + Math.max(leftBarOffset, leftDockOffset), dpr)
case SettingsData.Position.Top:
case SettingsData.Position.Right:
const rightBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Right ? barOffset : 0
const rightDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0
return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightBarOffset, rightDockOffset), dpr)
case SettingsData.Position.LeftCenter:
const leftCenterBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Left ? barOffset : 0
const leftCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0
return Theme.snap(margin + Math.max(leftCenterBarOffset, leftCenterDockOffset), dpr)
case SettingsData.Position.RightCenter:
const rightCenterBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Right ? barOffset : 0
const rightCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0
return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightCenterBarOffset, rightCenterDockOffset), dpr)
case SettingsData.Position.TopCenter:
case SettingsData.Position.BottomCenter:
default:
return Theme.snap(centerX, dpr)
}
}
readonly property real alignedY: {
const margin = Theme.spacingM
const centerY = (screenHeight - alignedHeight) / 2
switch (SettingsData.osdPosition) {
case SettingsData.Position.Top:
case SettingsData.Position.Left:
case SettingsData.Position.TopCenter:
const topBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Top ? barOffset : 0
const topDockOffset = SettingsData.dockPosition === SettingsData.Position.Top ? dockOffset : 0
return Theme.snap(margin + Math.max(topBarOffset, topDockOffset), dpr)
case SettingsData.Position.Right:
case SettingsData.Position.Bottom:
case SettingsData.Position.BottomCenter:
const bottomBarOffset = SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barOffset : 0
const bottomDockOffset = SettingsData.dockPosition === SettingsData.Position.Bottom ? dockOffset : 0
return Theme.snap(screenHeight - alignedHeight - margin - Math.max(bottomBarOffset, bottomDockOffset), dpr)
case SettingsData.Position.LeftCenter:
case SettingsData.Position.RightCenter:
default:
return Theme.snap(centerY, dpr)
}
}
readonly property real alignedY: Theme.snap(screenHeight - alignedHeight - Theme.spacingM - barOffsetWhenBottom, dpr)
anchors {
top: true

View File

@@ -28,7 +28,23 @@ PanelWindow {
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property bool shouldBeVisible: false
property int keyboardFocusMode: WlrKeyboardFocus.OnDemand
visible: false
readonly property real effectiveBarThickness: Math.max(26 + SettingsData.dankBarInnerPadding * 0.6, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing
readonly property var barBounds: {
if (!root.screen) {
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
}
return SettingsData.getBarBounds(root.screen, effectiveBarThickness)
}
readonly property real barX: barBounds.x
readonly property real barY: barBounds.y
readonly property real barWidth: barBounds.width
readonly property real barHeight: barBounds.height
readonly property real barWingSize: barBounds.wingSize
signal opened
signal popoutClosed
@@ -38,6 +54,7 @@ PanelWindow {
closeTimer.stop()
shouldBeVisible = true
visible = true
PopoutManager.showPopout(root)
opened()
}
@@ -59,6 +76,7 @@ PanelWindow {
onTriggered: {
if (!shouldBeVisible) {
visible = false
PopoutManager.hidePopout(root)
popoutClosed()
}
}
@@ -78,7 +96,11 @@ PanelWindow {
}
}
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: shouldBeVisible ? keyboardFocusMode : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: {
if (!shouldBeVisible) return WlrKeyboardFocus.None
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand
return WlrKeyboardFocus.Exclusive
}
anchors {
top: true
@@ -114,16 +136,82 @@ PanelWindow {
}
})(), dpr)
readonly property real maskX: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return root.barWidth > 0 ? root.barWidth : 0
case SettingsData.Position.Right:
case SettingsData.Position.Top:
case SettingsData.Position.Bottom:
default:
return 0
}
}
readonly property real maskY: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return root.barHeight > 0 ? root.barHeight : 0
case SettingsData.Position.Bottom:
case SettingsData.Position.Left:
case SettingsData.Position.Right:
default:
return 0
}
}
readonly property real maskWidth: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left:
return root.barWidth > 0 ? root.width - root.barWidth : root.width
case SettingsData.Position.Right:
return root.barWidth > 0 ? root.width - root.barWidth : root.width
case SettingsData.Position.Top:
case SettingsData.Position.Bottom:
default:
return root.width
}
}
readonly property real maskHeight: {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top:
return root.barHeight > 0 ? root.height - root.barHeight : root.height
case SettingsData.Position.Bottom:
return root.barHeight > 0 ? root.height - root.barHeight : root.height
case SettingsData.Position.Left:
case SettingsData.Position.Right:
default:
return root.height
}
}
mask: Region {
item: Rectangle {
x: root.maskX
y: root.maskY
width: root.maskWidth
height: root.maskHeight
}
}
MouseArea {
anchors.fill: parent
enabled: shouldBeVisible && contentLoader.opacity > 0.1
x: maskX
y: maskY
width: maskWidth
height: maskHeight
z: -1
enabled: shouldBeVisible
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (mouse.x < alignedX || mouse.x > alignedX + alignedWidth ||
mouse.y < alignedY || mouse.y > alignedY + alignedHeight) {
backgroundClicked()
close()
}
const clickX = mouse.x + maskX
const clickY = mouse.y + maskY
const outsideContent = clickX < alignedX || clickX > alignedX + alignedWidth ||
clickY < alignedY || clickY > alignedY + alignedHeight
if (!outsideContent) return
backgroundClicked()
}
}
@@ -253,6 +341,7 @@ PanelWindow {
}
Item {
id: focusHelper
parent: contentContainer
anchors.fill: parent
focus: true
@@ -262,7 +351,5 @@ PanelWindow {
event.accepted = true
}
}
Component.onCompleted: forceActiveFocus()
onVisibleChanged: if (visible) forceActiveFocus()
}
}

View File

@@ -38,7 +38,7 @@ Text {
wrapMode: Text.WordWrap
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
antialiasing: true
//renderType: Text.NativeRendering
Behavior on opacity {
NumberAnimation {

File diff suppressed because it is too large Load Diff

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "Aggiungi una VPN in NetworkManager"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "Regola il numero di colonne nella modalità di visualizzazione a griglia."
},
"All": {
"All": "Tutto"
@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "Luminosità"
},
"Brightness OSD": {
"Brightness OSD": ""
},
"Browse": {
"Browse": "Sfoglia"
},
@@ -303,7 +306,7 @@
"CUPS Missing Filter Warning": "Avviso Filtro Mancante CUPS"
},
"Camera": {
"Camera": ""
"Camera": "Fotocamera"
},
"Cancel": {
"Cancel": "Annulla"
@@ -314,6 +317,9 @@
"Caps Lock Indicator": {
"Caps Lock Indicator": "Indicatore Maiuscolo"
},
"Caps Lock OSD": {
"Caps Lock OSD": ""
},
"Center Section": {
"Center Section": "Sezione Centrale"
},
@@ -341,6 +347,9 @@
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Scegli dove i popup delle notifiche appaiono sullo schermo"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
},
"Clear": {
"Clear": "Pulisci"
},
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "Dismetti"
},
"Display Name Format": {
"Display Name Format": ""
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Visualizza una dock con applicazioni pinnate ed in esecuzione che possono essere posizionate nell'angolo superiore, inferiore, sinistro o destro dello schermo"
},
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Trascina i widget per riordinarli nelle sezioni. Usa l'icona occhio per nascondere/mostrare i widget (mantenendo la spaziatura), o X per rimuoverli completamente."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "Duplica Sfondo con Sfocatura"
},
"Duration": {
"Duration": "Durata"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Inserisci password per"
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "Errore"
},
@@ -900,7 +918,7 @@
"Grid": "Griglia"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "Colonne Griglia"
},
"Group by App": {
"Group by App": "Raggruppa per App"
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "Inibitore Riposo"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": ""
},
"Idle Settings": {
"Idle Settings": "Impostazioni Riposo"
},
@@ -1173,7 +1194,10 @@
"Memory usage indicator": "Indicatore uso memoria"
},
"Microphone": {
"Microphone": ""
"Microphone": "Microfono"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": ""
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Tavolozza minima costruita attorno a una singola tonalità."
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": "Modalità:"
},
"Model": {
"Model": "Modello"
},
"Monitor Selection:": {
"Monitor Selection:": "Selezione Monitor:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "NM non supportato"
},
"Name": {
"Name": "Nome"
},
"Named Workspace Icons": {
"Named Workspace Icons": "Icone Workspace con Nome"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "Logo OS"
},
"OSD Position": {
"OSD Position": ""
},
"Office": {
"Office": "Ufficio"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "Visualizzazione A Schermo"
},
"On-screen Displays": {
"On-screen Displays": ""
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "Regolare gamma solo in base alle regole di tempo o di posizione."
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "Degradamento profilo energetico"
},
"Power Profile OSD": {
"Power Profile OSD": ""
},
"Pressure": {
"Pressure": "Pressione"
},
@@ -1647,7 +1686,7 @@
"Science": "Scienza"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "Condivisione schermo"
},
"Scrolling": {
"Scrolling": "Scorrimento"
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "Mostra sullo schermo:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": ""
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": ""
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": ""
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": ""
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": ""
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": ""
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "Mostra solo apps eseguite nel workspace attuale"
},
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "Volume Cambiato"
},
"Volume OSD": {
"Volume OSD": ""
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Volume, luminosità, e altri OSD di sistema"
},

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "NetworkManagerでVPNを追加"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "グリッド表示モードでの列数を調整します。"
},
"All": {
"All": "全て"
@@ -75,7 +75,7 @@
"Always Show OSD Percentage": "常に OSD パーセンテージを表示"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "常時表示アイコン"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "使用可能なワークスペースが少ない場合でも、常に最低 3 つを表示"
@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "明るさ"
},
"Brightness OSD": {
"Brightness OSD": "明るさOSD"
},
"Browse": {
"Browse": "ブラウズ"
},
@@ -303,7 +306,7 @@
"CUPS Missing Filter Warning": "CUPS の欠落フィルターの警告"
},
"Camera": {
"Camera": ""
"Camera": "カメラ"
},
"Cancel": {
"Cancel": "キャンセル"
@@ -314,6 +317,9 @@
"Caps Lock Indicator": {
"Caps Lock Indicator": "Caps Lock インジケーター"
},
"Caps Lock OSD": {
"Caps Lock OSD": "Caps Lock OSD"
},
"Center Section": {
"Center Section": "センターセクション"
},
@@ -341,6 +347,9 @@
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "通知ポップアップが画面に表示される場所を選ぶ"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "OSDの表示する場所を選んでください"
},
"Clear": {
"Clear": "クリア"
},
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "解除"
},
"Display Name Format": {
"Display Name Format": "名称形式を表示"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "画面の上、下、左、右の端に配置できる、ピン留めされた実行中のアプリケーションを含むドックを表示します"
},
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示にスペースは維持、Xで完全に削除できます。"
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "ぼかしで壁紙を複製"
},
"Duration": {
"Duration": "期間"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "パスワードを入力"
},
"Enter this passkey on ": {
"Enter this passkey on ": "ここでパスキーを入力してください "
},
"Error": {
"Error": "エラー"
},
@@ -900,7 +918,7 @@
"Grid": "グリッド"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "グリッド列"
},
"Group by App": {
"Group by App": "アプリ別にグループ化"
@@ -924,7 +942,7 @@
"Hibernate": "休止状態"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "遅延を隠す (ms)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "使用していないときはドックを非表示にし、ドックエリアの近くにホバーすると表示されます"
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "アイドルインヒビター"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": "アイドルインヒビターOSD"
},
"Idle Settings": {
"Idle Settings": "アイドル設定"
},
@@ -1173,7 +1194,10 @@
"Memory usage indicator": "メモリ使用率インジケーター"
},
"Microphone": {
"Microphone": ""
"Microphone": "マイク"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": "マイクミュートOSD"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "単一の色相を中心に構築された最小限のパレット。"
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": "モード: "
},
"Model": {
"Model": "モデル"
},
"Monitor Selection:": {
"Monitor Selection:": "モニターの選択:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "NMが利用できません"
},
"Name": {
"Name": "名称"
},
"Named Workspace Icons": {
"Named Workspace Icons": "名前付きワークスペースアイコン"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "OSロゴ"
},
"OSD Position": {
"OSD Position": "OSD位置"
},
"Office": {
"Office": "オフィス"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "オンスクリーンディスプレイ"
},
"On-screen Displays": {
"On-screen Displays": "オンスクリーンディスプレイ"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "ガンマは、時間または場所のルールに基づいてのみ調整します。"
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "電源プロファイルの劣化"
},
"Power Profile OSD": {
"Power Profile OSD": "電源プロファイルOSD"
},
"Pressure": {
"Pressure": "プレッシャー"
},
@@ -1647,7 +1686,7 @@
"Science": "科学"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "画面共有"
},
"Scrolling": {
"Scrolling": "スクロール"
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "画面に表示:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": "明るさが変化した時にOSDを表示"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": "Caps Lock の状態が変化した時にOSDを表示"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": "アイドルインヒビターの状態が変化した時にOSDを表示"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "マイクがミュート/ミュート解除された時にOSDを表示"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": "電源プロファイルが変化した時にOSDを表示"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": "音量が変化したときにOSDを表示"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "現在のワークスペースで実行されているアプリのみを表示"
},
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "音量変更"
},
"Volume OSD": {
"Volume OSD": "音量OSD"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、明るさ、その他のシステム OSD"
},

View File

@@ -30,7 +30,7 @@
"A file with this name already exists. Do you want to overwrite it?": "Plik o takiej nazwie już istnieje. Czy chcesz go nadpisać?"
},
"About": {
"About": "O..."
"About": "O programie"
},
"Access clipboard history": {
"Access clipboard history": "Dostęp do historii schowka"
@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "Dodaj sieć VPN w NetworkManager"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "Dostosuj liczbę kolumn w widoku siatki."
},
"All": {
"All": "Wszystkie"
@@ -66,16 +66,16 @@
"All day": "Cały dzień"
},
"All displays": {
"All displays": "Wszystkie wyświetlacze"
"All displays": "Wszystkie ekrany"
},
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": {
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/Backspace: Wstecz • F1/I: Informacje o pliku • F10: Pomoc • Esc: Zamknij"
},
"Always Show OSD Percentage": {
"Always Show OSD Percentage": "Zawsze pokazuj procent OSD"
"Always Show OSD Percentage": "Zawsze pokazuj procent w OSD"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "Ikony zawsze włączone"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "Zawsze pokazuj co najmniej 3 obszary robocze, nawet jeśli jest ich mniej."
@@ -87,7 +87,7 @@
"Anonymous Identity (optional)": "Tożsamość anonimowa (opcjonalnie)"
},
"App Launcher": {
"App Launcher": "App Launcher"
"App Launcher": "Program Uruchamiający"
},
"Application Dock": {
"Application Dock": "Aplikacja Dok"
@@ -171,7 +171,7 @@
"Auto-hide Dock": "Automatyczne ukrywanie doku"
},
"Auto-saving...": {
"Auto-saving...": "Automatyczne zapisywanie..."
"Auto-saving...": "Automatyczny zapis..."
},
"Autoconnect disabled": {
"Autoconnect disabled": "Automatyczne łączenie wyłączone"
@@ -183,13 +183,13 @@
"Automatic Control": "Automatyczna kontrola"
},
"Automatic Cycling": {
"Automatic Cycling": "Automatyczne przełączanie"
"Automatic Cycling": "Automatyczne cykl"
},
"Automatically calculate popup distance from bar edge.": {
"Automatically calculate popup distance from bar edge.": "Automatycznie obliczaj odległość wyskakującego okienka od krawędzi paska."
},
"Automatically cycle through wallpapers in the same folder": {
"Automatically cycle through wallpapers in the same folder": "Automatycznie zmieniaj tapety z tego samego folderu"
"Automatically cycle through wallpapers in the same folder": "Automatycznie zmieniaj tapety z tego samego katalogu"
},
"Automatically detect location based on IP address": {
"Automatically detect location based on IP address": "Automatycznie wykrywaj lokalizację na podstawie adresu IP"
@@ -198,7 +198,7 @@
"Automatically determine your location using your IP address": "Automatycznie określ swoją lokalizację na podstawie adresu IP"
},
"Automatically extract colors from wallpaper": {
"Automatically extract colors from wallpaper": "Automatycznie dopasuj kolory z tapety"
"Automatically extract colors from wallpaper": "Automatycznie wyciągnij kolory z tapety"
},
"Automatically hide the top bar to expand screen real estate": {
"Automatically hide the top bar to expand screen real estate": "Automatycznie ukryj górny pasek, aby zwiększyć powierzchnię ekranu"
@@ -252,7 +252,7 @@
"Blur wallpaper when niri overview is open": "Rozmyj tapetę, gdy podgląd niri jest otwarty"
},
"Border": {
"Border": "Obramowanie"
"Border": "Ramka"
},
"Border Color": {
"Border Color": "Kolor obramowania"
@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "Jasność"
},
"Brightness OSD": {
"Brightness OSD": "OSD Jasności"
},
"Browse": {
"Browse": "Przeglądaj"
},
@@ -291,7 +294,7 @@
"CPU Usage": "Użycie CPU"
},
"CPU temperature display": {
"CPU temperature display": "Temperatura CPU"
"CPU temperature display": "Wskaźnik temperatury procesora"
},
"CPU usage indicator": {
"CPU usage indicator": "Wskaźnik użycia procesora"
@@ -303,7 +306,7 @@
"CUPS Missing Filter Warning": "CUPS: Ostrzeżenie o brakującym filtrze"
},
"Camera": {
"Camera": ""
"Camera": "Kamera"
},
"Cancel": {
"Cancel": "Anuluj"
@@ -312,7 +315,10 @@
"Capacity": "Pojemność"
},
"Caps Lock Indicator": {
"Caps Lock Indicator": ""
"Caps Lock Indicator": "Wskaźnik caps locka"
},
"Caps Lock OSD": {
"Caps Lock OSD": "OSD Caps Lock"
},
"Center Section": {
"Center Section": "Sekcja środkowa"
@@ -339,7 +345,10 @@
"Choose the logo displayed on the launcher button in DankBar": "Wybierz logo wyświetlane na przycisku launchera w DankBar"
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Wybierz, gdzie na ekranie mają pojawiać się wyskakujące powiadomienia"
"Choose where notification popups appear on screen": "Wybierz, gdzie na ekranie mają pojawiać się powiadomienia"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "Wybierz miejsce wyświetlania informacji na ekranie"
},
"Clear": {
"Clear": "Wyczyść"
@@ -435,7 +444,7 @@
"Connect to Wi-Fi": "Połącz z siecią Wi-Fi"
},
"Connected Displays": {
"Connected Displays": "Podłączone wyświetlacze"
"Connected Displays": "Podłączone ekrany"
},
"Connecting to Device": {
"Connecting to Device": "Łączenie z urządzeniem"
@@ -447,7 +456,7 @@
"Control Center": "Centrum sterowania"
},
"Control currently playing media": {
"Control currently playing media": "Steruj aktualnie odtwarzanymi mediami"
"Control currently playing media": "Steruj aktualnie odtwarzanymi multimediami"
},
"Controls opacity of all popouts, modals, and their content layers (DankDash, Settings, App Drawer, Control Center, etc.)": {
"Controls opacity of all popouts, modals, and their content layers (DankDash, Settings, App Drawer, Control Center, etc.)": "Kontroluje przezroczystość wszystkich wyskakujących okienek, okien modalnych i ich warstw zawartości (DankDash, Ustawienia, Szuflada aplikacji, Centrum sterowania itp.)"
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "Odrzuć"
},
"Display Name Format": {
"Display Name Format": "Format nazwy wyświetlanej"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Wyświetla dok z przypiętymi i uruchomionymi aplikacjami, który można umieścić na górze, na dole, po lewej lub po prawej stronie ekranu."
},
@@ -633,7 +645,7 @@
"Display currently focused application title": "Wyświetlaj tytuł aktywnej aplikacji"
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": ""
"Display power menu actions in a grid instead of a list": "Wyświetl elementy menu zasilania w siatce zamiast listy"
},
"Display settings for ": {
"Display settings for ": "Ustawienia wyświetlania dla "
@@ -642,7 +654,7 @@
"Display volume and brightness percentage values by default in OSD popups": "Domyślnie wyświetlaj wartości procentowe głośności i jasności w wyskakujących okienkach OSD"
},
"Displays": {
"Displays": "Wyświetlacze"
"Displays": "Ekrany"
},
"Displays the active keyboard layout and allows switching": {
"Displays the active keyboard layout and allows switching": "Wyświetla aktywny układ klawiatury i umożliwia przełączanie"
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Przeciągnij widżety, aby zmienić kolejność w sekcjach. Użyj ikony oka, aby ukryć/pokazać widżety (zachowując odstępy), lub X, aby je całkowicie usunąć."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "Powiel tapetę z rozmyciem"
},
"Duration": {
"Duration": "Czas trwania"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Wprowadź hasło dla "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "Błąd"
},
@@ -828,13 +846,13 @@
"Focused Window": "Aktywne okno"
},
"Font Family": {
"Font Family": "Rodzina czcionek"
"Font Family": "Rodzina czcionki"
},
"Font Scale": {
"Font Scale": "Skala czcionki"
},
"Font Settings": {
"Font Settings": "Ustawienia czcionek"
"Font Settings": "Ustawienia czcionki"
},
"Font Size": {
"Font Size": "Rozmiar czcionki"
@@ -846,7 +864,7 @@
"Force Kill Process": "Wymuś zamknięcie procesu"
},
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": ""
"Force terminal applications to always use dark color schemes": "Wymuś ciemny motyw na aplikacjach terminala"
},
"Forget Device": {
"Forget Device": "Zapomnij urządzenie"
@@ -870,7 +888,7 @@
"GPU Temperature": "Temperatura GPU"
},
"GPU temperature display": {
"GPU temperature display": "Wyświetlanie temperatury GPU"
"GPU temperature display": "Wskaźnik temperatury GPU"
},
"Games": {
"Games": "Gry"
@@ -900,7 +918,7 @@
"Grid": "Siatka"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "Kolumny siatki"
},
"Group by App": {
"Group by App": "Grupuj według aplikacji"
@@ -924,7 +942,7 @@
"Hibernate": "Hibernacja"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "Opóźnienie ukrycia (ms)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "Ukryj dok, gdy nie jest używany, i odkryj go po najechaniu kursorem w jego pobliże"
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "Inhibitor bezczynności"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": "OSD Inhibitora Bezczynności"
},
"Idle Settings": {
"Idle Settings": "Ustawienia bezczynności"
},
@@ -1074,7 +1095,7 @@
"Location Search": "Wyszukiwanie lokalizacji"
},
"Lock": {
"Lock": "Zamek"
"Lock": "Zablokuj"
},
"Lock Screen": {
"Lock Screen": "Ekran blokady"
@@ -1173,7 +1194,10 @@
"Memory usage indicator": "Wskaźnik zużycia pamięci"
},
"Microphone": {
"Microphone": ""
"Microphone": "Mikrofon"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": "OSD Wyciszenia Mikrofonu"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Minimalna paleta zbudowana wokół jednego odcienia."
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": "Tryb: "
},
"Model": {
"Model": "Model"
},
"Monitor Selection:": {
"Monitor Selection:": "Wybór monitora:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "NM nie jest obsługiwany"
},
"Name": {
"Name": "Nazwa"
},
"Named Workspace Icons": {
"Named Workspace Icons": "Ikony nazwanych obszarów roboczych"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "Logo OS"
},
"OSD Position": {
"OSD Position": "Pozycja OSD"
},
"Office": {
"Office": "Biuro"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "Wyświetlacze ekranowe"
},
"On-screen Displays": {
"On-screen Displays": "Powiadomienia ekranowe"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "Dostosuj gamma tylko na podstawie reguł czasu lub lokalizacji."
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "Pogorszenie profilu zasilania"
},
"Power Profile OSD": {
"Power Profile OSD": "OSD Profilu Zasilania"
},
"Pressure": {
"Pressure": "Ciśnienie"
},
@@ -1647,7 +1686,7 @@
"Science": "Nauka"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "Udostępnianie ekranu"
},
"Scrolling": {
"Scrolling": "Przewijanie"
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "Pokaż na ekranach:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": "Wyświetlaj powiadomienie ekranowe przy zmianie jasności"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": "Wyświetlaj powiadomienie ekranowe przy zmianie stanu Caps Lock"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": "Wyświetlaj powiadomienie ekranowe przy zmianie stanu inhibitora bezczynności"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "Wyświetlaj powiadomienie ekranowe przy wyciszaniu/włączaniu mikrofonu"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": "Wyświetlaj powiadomienie ekranowe przy zmianie profilu zasilania"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": "Wyświetlaj powiadomienie ekranowe przy zmianie głośności"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "Pokaż tylko aplikacje uruchomione w bieżącym obszarze roboczym"
},
@@ -1809,7 +1866,7 @@
"Shows current workspace and allows switching": "Pokazuje bieżący obszar roboczy i pozwala na przełączanie"
},
"Shows when caps lock is active": {
"Shows when caps lock is active": ""
"Shows when caps lock is active": "Pojawia się gdy 'Caps Lock' jest aktywny"
},
"Shows when microphone, camera, or screen sharing is active": {
"Shows when microphone, camera, or screen sharing is active": "Pokazuje, gdy mikrofon, kamera lub udostępnianie ekranu są aktywne"
@@ -1938,7 +1995,7 @@
"Terminal custom additional parameters": "Niestandardowe dodatkowe parametry terminala"
},
"Terminals - Always use Dark Theme": {
"Terminals - Always use Dark Theme": ""
"Terminals - Always use Dark Theme": "Terminal - Zawsze używaj ciemnego motywu"
},
"Text": {
"Text": "Tekst"
@@ -2055,16 +2112,16 @@
"Use Fahrenheit instead of Celsius for temperature": "Użyj Fahrenheita zamiast Celsjusza dla temperatury"
},
"Use Grid Layout": {
"Use Grid Layout": ""
"Use Grid Layout": "Użyj układu siatki"
},
"Use IP Location": {
"Use IP Location": "Użyj lokalizacji IP"
},
"Use Imperial Units": {
"Use Imperial Units": ""
"Use Imperial Units": "Użyj imperialnych jednostek"
},
"Use Imperial units (°F, mph, inHg) instead of Metric (°C, km/h, hPa)": {
"Use Imperial units (°F, mph, inHg) instead of Metric (°C, km/h, hPa)": ""
"Use Imperial units (°F, mph, inHg) instead of Metric (°C, km/h, hPa)": "Użyj imperialnych jednostek (°F, mph, inHg) zamiast metrycznych (°C, km/h, hPa)"
},
"Use Monospace Font": {
"Use Monospace Font": "Użyj czcionki o stałej szerokości"
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "Zmieniony wolumen"
},
"Volume OSD": {
"Volume OSD": "OSD Głośności"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Głośność, jasność i inne systemowe menu ekranowe"
},

View File

@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "Brilho"
},
"Brightness OSD": {
"Brightness OSD": ""
},
"Browse": {
"Browse": "Navegar"
},
@@ -314,6 +317,9 @@
"Caps Lock Indicator": {
"Caps Lock Indicator": ""
},
"Caps Lock OSD": {
"Caps Lock OSD": ""
},
"Center Section": {
"Center Section": "Seção Central"
},
@@ -341,6 +347,9 @@
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Escolher onde as notificações irão aparecer na tela"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
},
"Clear": {
"Clear": "Limpar"
},
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "Descartar"
},
"Display Name Format": {
"Display Name Format": ""
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Exibir um dock com aplicativos que estão sendo utilizados, e que pode ser posicionada no superior, inferior, esquerda ou direita dos cantos da tela"
},
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Arraste Widgets para reordená-los nas seções. Use o ícone de olho para esconder ou mostrá-los (manter o espaçamento), ou clique no X para removê-los por completo."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
},
"Duration": {
"Duration": "Duração"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Insira senha para "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": ""
},
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "Inibitor de inatividade"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": ""
},
"Idle Settings": {
"Idle Settings": "Configurações de inatividade"
},
@@ -1175,6 +1196,9 @@
"Microphone": {
"Microphone": ""
},
"Microphone Mute OSD": {
"Microphone Mute OSD": ""
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Paleta mínima construída ao redor de um único tom."
},
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": ""
},
"Model": {
"Model": ""
},
"Monitor Selection:": {
"Monitor Selection:": "Seleção de Monitor:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "NM não suportado"
},
"Name": {
"Name": ""
},
"Named Workspace Icons": {
"Named Workspace Icons": "Ícones de Áreas de Trabalho Nomeadas"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "Logo do SO"
},
"OSD Position": {
"OSD Position": ""
},
"Office": {
"Office": "Escritório"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "Displays na Tela (OSDs)"
},
"On-screen Displays": {
"On-screen Displays": ""
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "Apenas ajustar gama baseada em regras de tempo ou localização."
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "Degradação do Perfil de Energia"
},
"Power Profile OSD": {
"Power Profile OSD": ""
},
"Pressure": {
"Pressure": "Pressão"
},
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "Mostrar nas telas:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": ""
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": ""
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": ""
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": ""
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": ""
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": ""
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "Mostrar apenas aplicativos rodando na área de trabalho atual"
},
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "Volume Alterado"
},
"Volume OSD": {
"Volume OSD": ""
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Volume, brilho, e outros OSDs do sistema"
},

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "NetworkManager'da VPN ekle"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "Izgara görünümü modunda sütun sayısını ayarla"
},
"All": {
"All": "Tümü"
@@ -75,7 +75,7 @@
"Always Show OSD Percentage": "OSD Yüzdesini Her Zaman Göster"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "Her zaman açık simgeler"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "Her zaman en az 3 çalışma alanı göster, daha azı kullanılabilir olsa da"
@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "Parlaklık"
},
"Brightness OSD": {
"Brightness OSD": "Parlaklık OSD"
},
"Browse": {
"Browse": "Göz at"
},
@@ -303,7 +306,7 @@
"CUPS Missing Filter Warning": "CUPS Eksik Filtre Uyarısı"
},
"Camera": {
"Camera": ""
"Camera": "Kamera"
},
"Cancel": {
"Cancel": "İptal"
@@ -312,7 +315,10 @@
"Capacity": "Kapasite"
},
"Caps Lock Indicator": {
"Caps Lock Indicator": ""
"Caps Lock Indicator": "Caps Lock Göstergesi"
},
"Caps Lock OSD": {
"Caps Lock OSD": "Caps Lock OSD"
},
"Center Section": {
"Center Section": "Orta Bölüm"
@@ -341,6 +347,9 @@
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Bildirim açılır pencerelerinin ekranda nerede görüneceğini seçin"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "Ekran gösterimlerinin ekranda nerede gösterileceğini seç"
},
"Clear": {
"Clear": "Temizle"
},
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "Reddet"
},
"Display Name Format": {
"Display Name Format": "Ekran İsim Formatı"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Ekranın üst, alt, sol veya sağ kenarına yerleştirilebilen, sabitlenmiş ve çalışan uygulamaları içeren bir dock görüntüleyin."
},
@@ -633,7 +645,7 @@
"Display currently focused application title": "Şu anda odaklanmış uygulamanın başlığını göster"
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": ""
"Display power menu actions in a grid instead of a list": "Güç menüsü eylemlerini liste yerine ızgara şeklinde göster"
},
"Display settings for ": {
"Display settings for ": "Ekran ayarları: "
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Widget'ları sürükleyerek bölümler içinde yeniden sıralayın. Göz simgesini kullanarak widget'ları gizleyin/gösterin (aralıkları korur) veya X simgesini kullanarak tamamen kaldırın."
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "Duvar kağıdını bulanıklık ile çoğalt"
},
"Duration": {
"Duration": "Süre"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "Parolayı girin "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "Hata"
},
@@ -846,7 +864,7 @@
"Force Kill Process": "Süreci Zorla Kapat"
},
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": ""
"Force terminal applications to always use dark color schemes": "Terminal uygulamalarının her zaman koyu renk şemalarını kullanmasını zorla"
},
"Forget Device": {
"Forget Device": "Aygıtı Unut"
@@ -900,7 +918,7 @@
"Grid": "Izgara"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "Izgara Sütunları"
},
"Group by App": {
"Group by App": "Uygulamaya Göre Gruplandır"
@@ -924,7 +942,7 @@
"Hibernate": "Hazırda Beklet"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "Gizleme Gecikmesi (ms)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "Kullanılmadığında dock'u gizle ve dock alanının yakınına geldiğinizde göster"
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "Boşta Kalma Engelleyici"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": "Boşta Kalma Engelleyici OSD"
},
"Idle Settings": {
"Idle Settings": "Boşta Kalma Ayarları"
},
@@ -1173,7 +1194,10 @@
"Memory usage indicator": "Bellek kullanım göstergesi"
},
"Microphone": {
"Microphone": ""
"Microphone": "Mikrofon"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": "Mikrofon Sessiz OSD"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Tek bir renk tonu etrafında oluşturulmuş minimal palet."
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": "Mod: "
},
"Model": {
"Model": "Model"
},
"Monitor Selection:": {
"Monitor Selection:": "Monitör Seçimi:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "NM desteklenmiyor"
},
"Name": {
"Name": "İsim"
},
"Named Workspace Icons": {
"Named Workspace Icons": "Adlandırılmış Çalışma Alanı Simgeleri"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "OS Logo"
},
"OSD Position": {
"OSD Position": "OSD Pozisyonu"
},
"Office": {
"Office": "Ofis"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "Ekran Üstü Gösterimler"
},
"On-screen Displays": {
"On-screen Displays": "Ekran Gösterimleri"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "Gamayı yalnızca zaman veya konum kurallarına göre ayarlayın."
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "Güç Profili Dejenerasyonu"
},
"Power Profile OSD": {
"Power Profile OSD": "Güç Profili OSD"
},
"Pressure": {
"Pressure": "Basınç"
},
@@ -1647,7 +1686,7 @@
"Science": "Bilim"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "Ekran paylaşımı"
},
"Scrolling": {
"Scrolling": "Kaydırma"
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "Şu ekranda göster:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": "Parlaklık değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": "Caps lock durumu değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": "Boşta kalma engelleyici durumu değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "Mikrofon sessize alındığında/sessizden çıkarıldığında ekran gösterimi göster"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": "Güç profili değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": "Ses değiştiğinde ekran gösterimi göster"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "Yalnızca mevcut çalışma alanında çalışan uygulamaları göster"
},
@@ -1809,7 +1866,7 @@
"Shows current workspace and allows switching": "Mevcut çalışma alanı gösterir ve değiştirmeye izin verir"
},
"Shows when caps lock is active": {
"Shows when caps lock is active": ""
"Shows when caps lock is active": "Caps Lock tuşunun etkin olduğunu gösterir"
},
"Shows when microphone, camera, or screen sharing is active": {
"Shows when microphone, camera, or screen sharing is active": "Mikrofon, kamera veya ekran paylaşımı aktif olduğunda gösterir"
@@ -1938,7 +1995,7 @@
"Terminal custom additional parameters": "Terminal özel ek parametreleri"
},
"Terminals - Always use Dark Theme": {
"Terminals - Always use Dark Theme": ""
"Terminals - Always use Dark Theme": "Terminaller - Her zaman Karanlı Tema kullan"
},
"Text": {
"Text": "Metin"
@@ -2055,16 +2112,16 @@
"Use Fahrenheit instead of Celsius for temperature": "Sıcaklık için Celsius yerine Fahrenhayt kullan"
},
"Use Grid Layout": {
"Use Grid Layout": ""
"Use Grid Layout": "Izgara Düzeni Kullan"
},
"Use IP Location": {
"Use IP Location": "IP Konumunu Kullan"
},
"Use Imperial Units": {
"Use Imperial Units": ""
"Use Imperial Units": "İngiliz Ölçü Birimini Kullan"
},
"Use Imperial units (°F, mph, inHg) instead of Metric (°C, km/h, hPa)": {
"Use Imperial units (°F, mph, inHg) instead of Metric (°C, km/h, hPa)": ""
"Use Imperial units (°F, mph, inHg) instead of Metric (°C, km/h, hPa)": "Metrik birimler (°C, km/h, hPa) yerine İngiliz birimleri (°F, mph, inHg) kullan"
},
"Use Monospace Font": {
"Use Monospace Font": "Sabit Aralıklı Yazı Tipi Kullan"
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "Ses Seviyesi Değişti"
},
"Volume OSD": {
"Volume OSD": "Ses OSD"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Ses, parlaklık ve diğer sistem ekran üstü gösterimleri"
},

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "在 NetworkManager 中添加 VPN"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "在网格模式中按需调整列的数量。"
},
"All": {
"All": "全部"
@@ -75,7 +75,7 @@
"Always Show OSD Percentage": "始终显示 OSD 百分比"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "总显示的图标"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "即使不足也总是显示至少三个工作区"
@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "亮度"
},
"Brightness OSD": {
"Brightness OSD": "OSD亮度"
},
"Browse": {
"Browse": "浏览"
},
@@ -303,7 +306,7 @@
"CUPS Missing Filter Warning": "CUPS 警告:缺少打印过滤器"
},
"Camera": {
"Camera": ""
"Camera": "摄像头"
},
"Cancel": {
"Cancel": "取消"
@@ -314,6 +317,9 @@
"Caps Lock Indicator": {
"Caps Lock Indicator": "大小写指示灯"
},
"Caps Lock OSD": {
"Caps Lock OSD": "OSD大小写"
},
"Center Section": {
"Center Section": "中间区域"
},
@@ -341,6 +347,9 @@
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "设置通知弹窗的出现位置"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "选择OSD在屏幕上出现的位置"
},
"Clear": {
"Clear": "清除"
},
@@ -531,7 +540,7 @@
"DMS_SOCKET not available": "DMS_SOCKET 不可用"
},
"DWL service not available": {
"DWL service not available": "DWL服务不可用"
"DWL service not available": "DWL 服务不可用"
},
"Daily at:": {
"Daily at:": "在每日:"
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "忽略"
},
"Display Name Format": {
"Display Name Format": "显示名称格式"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "显示一个包含固定和运行中应用的程序坞,可放置在屏幕四边任意位置"
},
@@ -624,7 +636,7 @@
"Display all priorities over fullscreen apps": "应用全屏时仍显示所有优先级的通知"
},
"Display and switch DWL layouts": {
"Display and switch DWL layouts": "显示与切换DWL布局"
"Display and switch DWL layouts": "显示与切换 DWL 布局"
},
"Display application icons in workspace indicators": {
"Display application icons in workspace indicators": "在工作区指示器中显示应用程序图标"
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖动组件以在各区块内重新排序。点击眼睛图标可隐藏/显示组件(保留间距),点击 X 可彻底移除。"
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "带模糊效果的壁纸复本"
},
"Duration": {
"Duration": "持续时间"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "输入密码"
},
"Enter this passkey on ": {
"Enter this passkey on ": "在该处输入此通行密钥 "
},
"Error": {
"Error": "错误"
},
@@ -846,7 +864,7 @@
"Force Kill Process": "强制结束进程"
},
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "强制终端应用使用暗色"
"Force terminal applications to always use dark color schemes": "强制终端应用使用暗色"
},
"Forget Device": {
"Forget Device": "取消配对"
@@ -900,7 +918,7 @@
"Grid": "网格"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "网格列"
},
"Group by App": {
"Group by App": "按应用分组"
@@ -924,7 +942,7 @@
"Hibernate": "休眠"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "隐藏延迟 (ms)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "在未使用时隐藏程序坞,鼠标悬停到程序坞区域时显示"
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "待机抑制器"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": "OSD空闲抑制"
},
"Idle Settings": {
"Idle Settings": "待机设置"
},
@@ -1020,13 +1041,13 @@
"Last launched %1": "上次启动于 %1"
},
"Last launched %1 day%2 ago": {
"Last launched %1 day%2 ago": "上次启动于 %1 天%2 前"
"Last launched %1 day%2 ago": "上次启动于 %1 天 前"
},
"Last launched %1 hour%2 ago": {
"Last launched %1 hour%2 ago": "上次启动于 %1 小时%2 前"
"Last launched %1 hour%2 ago": "上次启动于 %1 小时 前"
},
"Last launched %1 minute%2 ago": {
"Last launched %1 minute%2 ago": "上次启动于 %1 分钟%2 前"
"Last launched %1 minute%2 ago": "上次启动于 %1 分钟 前"
},
"Last launched just now": {
"Last launched just now": "刚刚启动"
@@ -1173,7 +1194,10 @@
"Memory usage indicator": "内存占用情况"
},
"Microphone": {
"Microphone": ""
"Microphone": "麦克风"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": "OSD麦克风静音"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "围绕单一色调构建的简约配色。"
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": "模式: "
},
"Model": {
"Model": "模型"
},
"Monitor Selection:": {
"Monitor Selection:": "选择显示器:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "不支持 NM"
},
"Name": {
"Name": "名称"
},
"Named Workspace Icons": {
"Named Workspace Icons": "已命名工作区图标"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "系统 Logo"
},
"OSD Position": {
"OSD Position": "OSD位置"
},
"Office": {
"Office": "办公"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "屏幕显示"
},
"On-screen Displays": {
"On-screen Displays": "屏上显示"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "根据时间或位置调节伽马值。"
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "电源配置性能下降"
},
"Power Profile OSD": {
"Power Profile OSD": "OSD电源配置"
},
"Pressure": {
"Pressure": "气压"
},
@@ -1575,7 +1614,7 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "关机、重启、挂起、休眠和注销前请求确认"
},
"Requires DWL compositor": {
"Requires DWL compositor": "需要DWL合成器"
"Requires DWL compositor": "需要 DWL 合成器"
},
"Reset": {
"Reset": "重置"
@@ -1587,10 +1626,10 @@
"Restart": "重启"
},
"Restart DMS": {
"Restart DMS": "重启DMS"
"Restart DMS": "重启 DMS"
},
"Restart the DankMaterialShell": {
"Restart the DankMaterialShell": "重启DankMaterialShell"
"Restart the DankMaterialShell": "重启 DankMaterialShell"
},
"Resume": {
"Resume": "恢复"
@@ -1611,7 +1650,7 @@
"Right Tiling": "右侧平铺"
},
"Right-click bar widget to cycle": {
"Right-click bar widget to cycle": "点击bar上小部件以轮换"
"Right-click bar widget to cycle": "点击 bar 上小部件以轮换"
},
"Run User Templates": {
"Run User Templates": "运行用户模板"
@@ -1647,7 +1686,7 @@
"Science": "科学"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "屏幕分享"
},
"Scrolling": {
"Scrolling": "滚动"
@@ -1755,7 +1794,7 @@
"Show Reboot": "显示重启"
},
"Show Restart DMS": {
"Show Restart DMS": "显示重启DMS"
"Show Restart DMS": "显示重启 DMS"
},
"Show Suspend": {
"Show Suspend": "显示挂起"
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "选择显示的屏幕:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": "当亮度改变时显示OSD"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": "当大小写状态变化时显示OSD"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": "当空闲抑制状态改变时显示OSD"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "当麦克风静音状态切换时显示OSD"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": "当电源配置改变时显示OSD"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": "当音量变化时显示OSD"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "仅显示当前工作区中的活动应用"
},
@@ -2115,7 +2172,7 @@
"VRR: ": "可变刷新率: "
},
"Vertical Deck": {
"Vertical Deck": "垂直Deck"
"Vertical Deck": "垂直 Deck"
},
"Vertical Grid": {
"Vertical Grid": "垂直网格"
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "音量变化"
},
"Volume OSD": {
"Volume OSD": "OSD音量"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、亮度和其他系统屏幕显示"
},

View File

@@ -57,7 +57,7 @@
"Add a VPN in NetworkManager": "新增VPN至網路管理器"
},
"Adjust the number of columns in grid view mode.": {
"Adjust the number of columns in grid view mode.": ""
"Adjust the number of columns in grid view mode.": "調整網格檢視模式中的欄數。"
},
"All": {
"All": "所有"
@@ -75,7 +75,7 @@
"Always Show OSD Percentage": "OSD 始終顯示百分比"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "始終顯示圖示"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "始終顯示至少 3 個工作區,即使可用的工作區較少"
@@ -275,6 +275,9 @@
"Brightness": {
"Brightness": "亮度"
},
"Brightness OSD": {
"Brightness OSD": "亮度 OSD"
},
"Browse": {
"Browse": "瀏覽"
},
@@ -303,7 +306,7 @@
"CUPS Missing Filter Warning": "CUPS 缺少過濾器警告"
},
"Camera": {
"Camera": ""
"Camera": "相機"
},
"Cancel": {
"Cancel": "取消"
@@ -312,7 +315,10 @@
"Capacity": "容量"
},
"Caps Lock Indicator": {
"Caps Lock Indicator": ""
"Caps Lock Indicator": "大小寫鎖定指示器"
},
"Caps Lock OSD": {
"Caps Lock OSD": "大小寫鎖定 OSD"
},
"Center Section": {
"Center Section": "中間區塊"
@@ -341,6 +347,9 @@
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "選擇通知彈出視窗在螢幕上出現的位置"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "選擇螢幕顯示出現在螢幕上的位置"
},
"Clear": {
"Clear": "清除"
},
@@ -617,6 +626,9 @@
"Dismiss": {
"Dismiss": "忽略"
},
"Display Name Format": {
"Display Name Format": "顯示名稱格式"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "顯示一個帶有固定和正在運行的應用程式的 Dock這些應用程式可以放置在螢幕的頂部、底部、左側或右側邊緣"
},
@@ -633,7 +645,7 @@
"Display currently focused application title": "顯示目前焦點應用程式的標題"
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": ""
"Display power menu actions in a grid instead of a list": "以網格而非列表顯示電源選單操作"
},
"Display settings for ": {
"Display settings for ": "顯示設定適用於"
@@ -674,6 +686,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖曳部件即可在版塊內重新排序。使用眼睛圖示隱藏/顯示部件 (會保持間距),或使用 X 將其完全移除。"
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "模糊化重複桌布"
},
"Duration": {
"Duration": "持續時間"
},
@@ -749,6 +764,9 @@
"Enter password for ": {
"Enter password for ": "輸入密碼 "
},
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Error": {
"Error": "錯誤"
},
@@ -900,7 +918,7 @@
"Grid": "網格"
},
"Grid Columns": {
"Grid Columns": ""
"Grid Columns": "網格欄數"
},
"Group by App": {
"Group by App": "App 分組"
@@ -924,7 +942,7 @@
"Hibernate": "休眠"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "隱藏延遲 (毫秒)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "不使用時隱藏 Dock並在 Dock 區域附近懸停時顯示 Dock"
@@ -959,6 +977,9 @@
"Idle Inhibitor": {
"Idle Inhibitor": "空閒抑制器"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": "閒置抑制器 OSD"
},
"Idle Settings": {
"Idle Settings": "閒置設定"
},
@@ -1173,7 +1194,10 @@
"Memory usage indicator": "記憶體使用率指示器"
},
"Microphone": {
"Microphone": ""
"Microphone": "麥克風"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": "麥克風靜音 OSD"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "圍繞單一色調構建的最小調色板。"
@@ -1187,6 +1211,9 @@
"Mode: ": {
"Mode: ": "模式:"
},
"Model": {
"Model": "型號"
},
"Monitor Selection:": {
"Monitor Selection:": "螢幕選擇:"
},
@@ -1211,6 +1238,9 @@
"NM not supported": {
"NM not supported": "NM 不支援"
},
"Name": {
"Name": "名稱"
},
"Named Workspace Icons": {
"Named Workspace Icons": "命名工作區圖示"
},
@@ -1337,6 +1367,9 @@
"OS Logo": {
"OS Logo": "發行版 Logo"
},
"OSD Position": {
"OSD Position": "OSD 位置"
},
"Office": {
"Office": "辦公"
},
@@ -1346,6 +1379,9 @@
"On-Screen Displays": {
"On-Screen Displays": "螢幕顯示"
},
"On-screen Displays": {
"On-screen Displays": "螢幕顯示"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "僅根據時間或位置規則調整 gamma。"
},
@@ -1493,6 +1529,9 @@
"Power Profile Degradation": {
"Power Profile Degradation": "電源配置降級"
},
"Power Profile OSD": {
"Power Profile OSD": "電源設定檔 OSD"
},
"Pressure": {
"Pressure": "氣壓"
},
@@ -1647,7 +1686,7 @@
"Science": "科學"
},
"Screen sharing": {
"Screen sharing": ""
"Screen sharing": "螢幕分享"
},
"Scrolling": {
"Scrolling": "滾動"
@@ -1781,6 +1820,24 @@
"Show on screens:": {
"Show on screens:": "在螢幕上顯示:"
},
"Show on-screen display when brightness changes": {
"Show on-screen display when brightness changes": "亮度改變時顯示螢幕顯示"
},
"Show on-screen display when caps lock state changes": {
"Show on-screen display when caps lock state changes": "大小寫鎖定狀態改變時顯示螢幕顯示"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": "閒置抑制器狀態改變時顯示螢幕顯示"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "麥克風靜音/取消靜音時顯示螢幕顯示"
},
"Show on-screen display when power profile changes": {
"Show on-screen display when power profile changes": "電源設定檔改變時顯示螢幕顯示"
},
"Show on-screen display when volume changes": {
"Show on-screen display when volume changes": "音量改變時顯示螢幕顯示"
},
"Show only apps running in current workspace": {
"Show only apps running in current workspace": "僅顯示目前工作區中正在執行的應用程式"
},
@@ -1809,7 +1866,7 @@
"Shows current workspace and allows switching": "顯示目前工作區並允許切換"
},
"Shows when caps lock is active": {
"Shows when caps lock is active": ""
"Shows when caps lock is active": "顯示大小寫鎖定啟用時"
},
"Shows when microphone, camera, or screen sharing is active": {
"Shows when microphone, camera, or screen sharing is active": "顯示麥克風、攝影機或螢幕共用處於活動狀態"
@@ -2055,7 +2112,7 @@
"Use Fahrenheit instead of Celsius for temperature": "使用華氏度代替攝氏度來表示溫度"
},
"Use Grid Layout": {
"Use Grid Layout": ""
"Use Grid Layout": "使用網格佈局"
},
"Use IP Location": {
"Use IP Location": "使用 IP 位置"
@@ -2141,6 +2198,9 @@
"Volume Changed": {
"Volume Changed": "音量改變"
},
"Volume OSD": {
"Volume OSD": "音量 OSD"
},
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、亮度及其他系統OSD"
},

View File

@@ -566,13 +566,6 @@
"reference": "",
"comment": ""
},
{
"term": "Blur Layer",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur on Overview",
"translation": "",
@@ -643,6 +636,13 @@
"reference": "",
"comment": ""
},
{
"term": "Brightness OSD",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Browse",
"translation": "",
@@ -734,6 +734,13 @@
"reference": "",
"comment": ""
},
{
"term": "Caps Lock OSD",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Center Section",
"translation": "",
@@ -797,6 +804,13 @@
"reference": "",
"comment": ""
},
{
"term": "Choose where on-screen displays appear on screen",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Clear",
"translation": "",
@@ -1441,6 +1455,13 @@
"reference": "",
"comment": ""
},
{
"term": "Display Name Format",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen",
"translation": "",
@@ -1574,6 +1595,13 @@
"reference": "",
"comment": ""
},
{
"term": "Duplicate Wallpaper with Blur",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Duration",
"translation": "",
@@ -1749,6 +1777,13 @@
"reference": "",
"comment": ""
},
{
"term": "Enter this passkey on ",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Error",
"translation": "",
@@ -2232,6 +2267,13 @@
"reference": "",
"comment": ""
},
{
"term": "Idle Inhibitor OSD",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Idle Settings",
"translation": "",
@@ -2736,6 +2778,13 @@
"reference": "",
"comment": ""
},
{
"term": "Microphone Mute OSD",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Minimal palette built around a single hue.",
"translation": "",
@@ -2764,6 +2813,13 @@
"reference": "",
"comment": ""
},
{
"term": "Model",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Monitor Selection:",
"translation": "",
@@ -2820,6 +2876,13 @@
"reference": "",
"comment": ""
},
{
"term": "Name",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Named Workspace Icons",
"translation": "",
@@ -3114,6 +3177,13 @@
"reference": "",
"comment": ""
},
{
"term": "OSD Position",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Office",
"translation": "",
@@ -3135,6 +3205,13 @@
"reference": "",
"comment": ""
},
{
"term": "On-screen Displays",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Only adjust gamma based on time or location rules.",
"translation": "",
@@ -3478,6 +3555,13 @@
"reference": "",
"comment": ""
},
{
"term": "Power Profile OSD",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Pressure",
"translation": "",
@@ -4143,6 +4227,48 @@
"reference": "",
"comment": ""
},
{
"term": "Show on-screen display when brightness changes",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Show on-screen display when caps lock state changes",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Show on-screen display when idle inhibitor state changes",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Show on-screen display when microphone is muted/unmuted",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Show on-screen display when power profile changes",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Show on-screen display when volume changes",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Show only apps running in current workspace",
"translation": "",
@@ -4969,6 +5095,13 @@
"reference": "",
"comment": ""
},
{
"term": "Volume OSD",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Volume, brightness, and other system OSDs",
"translation": "",