From fd99558ce509ae31f55b8c82a4dfa6a16d1bbcae Mon Sep 17 00:00:00 2001 From: bbedward Date: Sat, 4 Jul 2026 16:23:50 -0400 Subject: [PATCH] clipboard: implement virtual-keyboard-unstable-v1 to replace wtype for pasting entries --- core/cmd/dms/commands_clipboard.go | 19 +- core/internal/clipboard/clipboard.go | 466 ++++++------------ core/internal/clipboard/pastekey.go | 206 ++++++++ core/internal/clipboard/pastekey_live_test.go | 65 +++ core/internal/clipboard/pastekey_test.go | 105 ++++ core/internal/clipboard/wl.go | 93 ++++ .../virtual_keyboard/virtual_keyboard.go | 289 +++++++++++ .../xml/virtual-keyboard-unstable-v1.xml | 113 +++++ core/internal/server/clipboard/handlers.go | 16 + core/internal/server/clipboard/manager.go | 107 ++-- .../internal/server/clipboard/manager_test.go | 41 -- core/internal/server/clipboard/types.go | 12 +- distro/nix/common.nix | 3 +- distro/nix/options.nix | 2 +- distro/void/README.md | 1 - .../Modals/Clipboard/ClipboardContent.qml | 2 +- .../Modals/Clipboard/ClipboardEditor.qml | 4 +- .../Clipboard/ClipboardHistoryContent.qml | 2 +- .../Clipboard/ClipboardKeyboardHints.qml | 4 +- .../Modals/DankLauncherV2/Controller.qml | 12 +- quickshell/Services/ClipboardService.qml | 75 ++- quickshell/Services/SessionService.qml | 11 - quickshell/translations/en.json | 386 ++++++++------- quickshell/translations/template.json | 64 ++- 24 files changed, 1383 insertions(+), 715 deletions(-) create mode 100644 core/internal/clipboard/pastekey.go create mode 100644 core/internal/clipboard/pastekey_live_test.go create mode 100644 core/internal/clipboard/pastekey_test.go create mode 100644 core/internal/clipboard/wl.go create mode 100644 core/internal/proto/virtual_keyboard/virtual_keyboard.go create mode 100644 core/internal/proto/xml/virtual-keyboard-unstable-v1.xml diff --git a/core/cmd/dms/commands_clipboard.go b/core/cmd/dms/commands_clipboard.go index 8a9b8585c..6fef781fd 100644 --- a/core/cmd/dms/commands_clipboard.go +++ b/core/cmd/dms/commands_clipboard.go @@ -63,6 +63,15 @@ var clipPasteCmd = &cobra.Command{ Run: runClipPaste, } +var clipSendPasteCmd = &cobra.Command{ + Use: "send-paste", + Short: "Send a paste keystroke to the focused window", + Long: "Emulate ctrl+v (or ctrl+shift+v with --shift) via a virtual keyboard. Works without server.", + Run: runClipSendPaste, +} + +var clipSendPasteShift bool + var clipWatchCmd = &cobra.Command{ Use: "watch [command]", Short: "Watch clipboard for changes", @@ -216,8 +225,10 @@ func init() { clipMigrateCmd.Flags().BoolVar(&clipMigrateDelete, "delete", false, "Delete cliphist db after successful migration") + clipSendPasteCmd.Flags().BoolVarP(&clipSendPasteShift, "shift", "s", false, "Send ctrl+shift+v (terminal paste)") + clipConfigCmd.AddCommand(clipConfigGetCmd, clipConfigSetCmd) - clipboardCmd.AddCommand(clipCopyCmd, clipPasteCmd, clipWatchCmd, clipHistoryCmd, clipGetCmd, clipDeleteCmd, clipClearCmd, clipSearchCmd, clipConfigCmd, clipExportCmd, clipImportCmd, clipMigrateCmd) + clipboardCmd.AddCommand(clipCopyCmd, clipPasteCmd, clipSendPasteCmd, clipWatchCmd, clipHistoryCmd, clipGetCmd, clipDeleteCmd, clipClearCmd, clipSearchCmd, clipConfigCmd, clipExportCmd, clipImportCmd, clipMigrateCmd) } func runClipCopy(cmd *cobra.Command, args []string) { @@ -314,6 +325,12 @@ func runClipPaste(cmd *cobra.Command, args []string) { os.Stdout.Write(data) } +func runClipSendPaste(cmd *cobra.Command, args []string) { + if err := clipboard.SendPasteKeystroke(clipSendPasteShift); err != nil { + log.Fatalf("send-paste: %v", err) + } +} + func runClipWatch(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/core/internal/clipboard/clipboard.go b/core/internal/clipboard/clipboard.go index d27d6ff47..277271d10 100644 --- a/core/internal/clipboard/clipboard.go +++ b/core/internal/clipboard/clipboard.go @@ -9,7 +9,6 @@ import ( "syscall" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control" - wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" ) const envServe = "_DMS_CLIPBOARD_SERVE" @@ -17,6 +16,37 @@ const envMime = "_DMS_CLIPBOARD_MIME" const envPasteOnce = "_DMS_CLIPBOARD_PASTE_ONCE" const envCacheFile = "_DMS_CLIPBOARD_CACHE" +type Offer struct { + MimeType string + Data []byte +} + +// textMimeAliases are offered alongside plain-text content so legacy X11 +// clients bridged through XWayland find a target they can convert. +var textMimeAliases = []string{ + "text/plain", + "text/plain;charset=utf-8", + "UTF8_STRING", + "STRING", + "TEXT", +} + +// ExpandOffers turns raw clipboard data into the full offer list to serve, +// adding the standard alias set for text content. +func ExpandOffers(data []byte, mimeType string) []Offer { + offers := []Offer{{MimeType: mimeType, Data: data}} + if mimeType != "text/plain" && mimeType != "text/plain;charset=utf-8" { + return offers + } + for _, alias := range textMimeAliases { + if alias == mimeType { + continue + } + offers = append(offers, Offer{MimeType: alias, Data: data}) + } + return offers +} + // MaybeServeAndExit intercepts before cobra when re-exec'd as a clipboard // child. Reads source data into memory, deletes any cache file, then serves. func MaybeServeAndExit() { @@ -44,7 +74,7 @@ func MaybeServeAndExit() { os.Exit(1) } - if err := serveClipboard(data, mimeType, pasteOnce); err != nil { + if err := serveOffers(ExpandOffers(data, mimeType), pasteOnce); err != nil { fmt.Fprintf(os.Stderr, "clipboard: serve: %v\n", err) os.Exit(1) } @@ -55,22 +85,33 @@ func Copy(data []byte, mimeType string) error { return copyForkCached(data, mimeType, false) } +func CopyText(text string) error { + return Copy([]byte(text), "text/plain;charset=utf-8") +} + func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error { if foreground { - return serveClipboard(data, mimeType, pasteOnce) + return serveOffers(ExpandOffers(data, mimeType), pasteOnce) } return copyForkCached(data, mimeType, pasteOnce) } func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error { - if foreground { - buf, err := io.ReadAll(data) - if err != nil { - return fmt.Errorf("read source: %w", err) - } - return serveClipboard(buf, mimeType, pasteOnce) + if !foreground { + return copyFork(data, mimeType, pasteOnce) } - return copyFork(data, mimeType, pasteOnce) + buf, err := io.ReadAll(data) + if err != nil { + return fmt.Errorf("read source: %w", err) + } + return serveOffers(ExpandOffers(buf, mimeType), pasteOnce) +} + +func CopyMulti(offers []Offer, foreground, pasteOnce bool) error { + if foreground { + return serveOffers(offers, pasteOnce) + } + return copyMultiFork(offers, pasteOnce) } func newForkCmd(mimeType string, pasteOnce bool, extra ...string) *exec.Cmd { @@ -132,39 +173,70 @@ func copyForkCached(data []byte, mimeType string, pasteOnce bool) error { func copyFork(data io.Reader, mimeType string, pasteOnce bool) error { cmd := newForkCmd(mimeType, pasteOnce) - switch src := data.(type) { - case *os.File: + if src, ok := data.(*os.File); ok { cmd.Stdin = src return waitReady(cmd) - - default: - stdin, err := cmd.StdinPipe() - if err != nil { - return fmt.Errorf("stdin pipe: %w", err) - } - - stdout, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("stdout pipe: %w", err) - } - - if err := cmd.Start(); err != nil { - return fmt.Errorf("start: %w", err) - } - if _, err := io.Copy(stdin, data); err != nil { - stdin.Close() - return fmt.Errorf("write stdin: %w", err) - } - if err := stdin.Close(); err != nil { - return fmt.Errorf("close stdin: %w", err) - } - - var buf [1]byte - if _, err := stdout.Read(buf[:]); err != nil { - return fmt.Errorf("waiting for clipboard ready: %w", err) - } - return nil } + + stdin, err := cmd.StdinPipe() + if err != nil { + return fmt.Errorf("stdin pipe: %w", err) + } + + stdout, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("stdout pipe: %w", err) + } + + if err := cmd.Start(); err != nil { + return fmt.Errorf("start: %w", err) + } + if _, err := io.Copy(stdin, data); err != nil { + stdin.Close() + return fmt.Errorf("write stdin: %w", err) + } + if err := stdin.Close(); err != nil { + return fmt.Errorf("close stdin: %w", err) + } + + var buf [1]byte + if _, err := stdout.Read(buf[:]); err != nil { + return fmt.Errorf("waiting for clipboard ready: %w", err) + } + return nil +} + +func copyMultiFork(offers []Offer, pasteOnce bool) error { + args := []string{os.Args[0], "cl", "copy", "--foreground", "--type", "__multi__"} + if pasteOnce { + args = append(args, "--paste-once") + } + + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdin = nil + cmd.Stdout = nil + cmd.Stderr = nil + cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + + stdin, err := cmd.StdinPipe() + if err != nil { + return fmt.Errorf("stdin pipe: %w", err) + } + + if err := cmd.Start(); err != nil { + return fmt.Errorf("start: %w", err) + } + + for _, offer := range offers { + fmt.Fprintf(stdin, "%s\x00%d\x00", offer.MimeType, len(offer.Data)) + if _, err := stdin.Write(offer.Data); err != nil { + stdin.Close() + return fmt.Errorf("write offer data: %w", err) + } + } + stdin.Close() + + return nil } func signalReady() { @@ -194,57 +266,25 @@ func createClipboardCacheFile() (*os.File, error) { return os.CreateTemp("", "dms-clipboard-*") } -func serveClipboard(data []byte, mimeType string, pasteOnce bool) error { - display, err := wlclient.Connect("") +// serveOffers owns the Wayland selection until cancelled (or first paste when +// pasteOnce is set), answering every offered mime type with its data. +func serveOffers(offers []Offer, pasteOnce bool) error { + if len(offers) == 0 { + return fmt.Errorf("no offers to serve") + } + + s, err := connectSession() if err != nil { - return fmt.Errorf("wayland connect: %w", err) + return err } - defer display.Destroy() + defer s.Close() - ctx := display.Context() - registry, err := display.GetRegistry() + dataControlMgr, err := s.requireDataControl() if err != nil { - return fmt.Errorf("get registry: %w", err) - } - defer registry.Destroy() - - var dataControlMgr *ext_data_control.ExtDataControlManagerV1 - var seat *wlclient.Seat - var bindErr error - - registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { - switch e.Interface { - case "ext_data_control_manager_v1": - dataControlMgr = ext_data_control.NewExtDataControlManagerV1(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, dataControlMgr); err != nil { - bindErr = err - } - case "wl_seat": - if seat != nil { - return - } - seat = wlclient.NewSeat(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, seat); err != nil { - bindErr = err - } - } - }) - - display.Roundtrip() - display.Roundtrip() - - if bindErr != nil { - return fmt.Errorf("registry bind: %w", bindErr) - } - if dataControlMgr == nil { - return fmt.Errorf("compositor does not support ext_data_control_manager_v1") - } - defer dataControlMgr.Destroy() - if seat == nil { - return fmt.Errorf("no seat available") + return err } - device, err := dataControlMgr.GetDataDevice(seat) + device, err := dataControlMgr.GetDataDevice(s.seat) if err != nil { return fmt.Errorf("get data device: %w", err) } @@ -255,25 +295,12 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error { return fmt.Errorf("create data source: %w", err) } - if err := source.Offer(mimeType); err != nil { - return fmt.Errorf("offer mime type: %w", err) - } - if mimeType == "text/plain;charset=utf-8" || mimeType == "text/plain" { - if err := source.Offer("text/plain"); err != nil { - return fmt.Errorf("offer text/plain: %w", err) - } - if err := source.Offer("text/plain;charset=utf-8"); err != nil { - return fmt.Errorf("offer text/plain;charset=utf-8: %w", err) - } - if err := source.Offer("UTF8_STRING"); err != nil { - return fmt.Errorf("offer UTF8_STRING: %w", err) - } - if err := source.Offer("STRING"); err != nil { - return fmt.Errorf("offer STRING: %w", err) - } - if err := source.Offer("TEXT"); err != nil { - return fmt.Errorf("offer TEXT: %w", err) + offerData := make(map[string][]byte, len(offers)) + for _, offer := range offers { + if err := source.Offer(offer.MimeType); err != nil { + return fmt.Errorf("offer %s: %w", offer.MimeType, err) } + offerData[offer.MimeType] = offer.Data } cancelled := make(chan struct{}) @@ -283,7 +310,11 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error { _ = syscall.SetNonblock(e.Fd, false) file := os.NewFile(uintptr(e.Fd), "pipe") defer file.Close() - _, _ = file.Write(data) + + if data, ok := offerData[e.MimeType]; ok { + _, _ = file.Write(data) + } + select { case pasted <- struct{}{}: default: @@ -298,7 +329,7 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error { return fmt.Errorf("set selection: %w", err) } - display.Roundtrip() + s.display.Roundtrip() signalReady() for { @@ -310,70 +341,26 @@ func serveClipboard(data []byte, mimeType string, pasteOnce bool) error { return nil } default: - if err := ctx.Dispatch(); err != nil { + if err := s.ctx.Dispatch(); err != nil { return nil } } } } -func CopyText(text string) error { - return Copy([]byte(text), "text/plain;charset=utf-8") -} - func Paste() ([]byte, string, error) { - display, err := wlclient.Connect("") + s, err := connectSession() if err != nil { - return nil, "", fmt.Errorf("wayland connect: %w", err) + return nil, "", err } - defer display.Destroy() + defer s.Close() - ctx := display.Context() - registry, err := display.GetRegistry() + dataControlMgr, err := s.requireDataControl() if err != nil { - return nil, "", fmt.Errorf("get registry: %w", err) - } - defer registry.Destroy() - - var dataControlMgr *ext_data_control.ExtDataControlManagerV1 - var seat *wlclient.Seat - var bindErr error - - registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { - switch e.Interface { - case "ext_data_control_manager_v1": - dataControlMgr = ext_data_control.NewExtDataControlManagerV1(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, dataControlMgr); err != nil { - bindErr = err - } - case "wl_seat": - if seat != nil { - return - } - seat = wlclient.NewSeat(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, seat); err != nil { - bindErr = err - } - } - }) - - display.Roundtrip() - display.Roundtrip() - - if bindErr != nil { - return nil, "", fmt.Errorf("registry bind: %w", bindErr) + return nil, "", err } - if dataControlMgr == nil { - return nil, "", fmt.Errorf("compositor does not support ext_data_control_manager_v1") - } - defer dataControlMgr.Destroy() - - if seat == nil { - return nil, "", fmt.Errorf("no seat available") - } - - device, err := dataControlMgr.GetDataDevice(seat) + device, err := dataControlMgr.GetDataDevice(s.seat) if err != nil { return nil, "", fmt.Errorf("get data device: %w", err) } @@ -399,15 +386,14 @@ func Paste() ([]byte, string, error) { gotSelection = true }) - display.Roundtrip() - display.Roundtrip() + s.display.Roundtrip() + s.display.Roundtrip() if !gotSelection || selectionOffer == nil { return nil, "", fmt.Errorf("no clipboard data") } - mimeTypes := offerMimeTypes[selectionOffer] - selectedMime := selectPreferredMimeType(mimeTypes) + selectedMime := selectPreferredMimeType(offerMimeTypes[selectionOffer]) if selectedMime == "" { return nil, "", fmt.Errorf("no supported mime type") } @@ -424,7 +410,7 @@ func Paste() ([]byte, string, error) { } w.Close() - display.Roundtrip() + s.display.Roundtrip() data, err := io.ReadAll(r) if err != nil { @@ -470,161 +456,3 @@ func selectPreferredMimeType(mimes []string) string { func IsImageMimeType(mime string) bool { return len(mime) > 6 && mime[:6] == "image/" } - -type Offer struct { - MimeType string - Data []byte -} - -func CopyMulti(offers []Offer, foreground, pasteOnce bool) error { - if !foreground { - return copyMultiFork(offers, pasteOnce) - } - return copyMultiServe(offers, pasteOnce) -} - -func copyMultiFork(offers []Offer, pasteOnce bool) error { - args := []string{os.Args[0], "cl", "copy", "--foreground", "--type", "__multi__"} - if pasteOnce { - args = append(args, "--paste-once") - } - - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdin = nil - cmd.Stdout = nil - cmd.Stderr = nil - cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - - stdin, err := cmd.StdinPipe() - if err != nil { - return fmt.Errorf("stdin pipe: %w", err) - } - - if err := cmd.Start(); err != nil { - return fmt.Errorf("start: %w", err) - } - - for _, offer := range offers { - fmt.Fprintf(stdin, "%s\x00%d\x00", offer.MimeType, len(offer.Data)) - if _, err := stdin.Write(offer.Data); err != nil { - stdin.Close() - return fmt.Errorf("write offer data: %w", err) - } - } - stdin.Close() - - return nil -} - -func copyMultiServe(offers []Offer, pasteOnce bool) error { - display, err := wlclient.Connect("") - if err != nil { - return fmt.Errorf("wayland connect: %w", err) - } - defer display.Destroy() - - ctx := display.Context() - registry, err := display.GetRegistry() - if err != nil { - return fmt.Errorf("get registry: %w", err) - } - defer registry.Destroy() - - var dataControlMgr *ext_data_control.ExtDataControlManagerV1 - var seat *wlclient.Seat - var bindErr error - - registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { - switch e.Interface { - case "ext_data_control_manager_v1": - dataControlMgr = ext_data_control.NewExtDataControlManagerV1(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, dataControlMgr); err != nil { - bindErr = err - } - case "wl_seat": - if seat != nil { - return - } - seat = wlclient.NewSeat(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, seat); err != nil { - bindErr = err - } - } - }) - - display.Roundtrip() - display.Roundtrip() - - if bindErr != nil { - return fmt.Errorf("registry bind: %w", bindErr) - } - if dataControlMgr == nil { - return fmt.Errorf("compositor does not support ext_data_control_manager_v1") - } - defer dataControlMgr.Destroy() - if seat == nil { - return fmt.Errorf("no seat available") - } - - device, err := dataControlMgr.GetDataDevice(seat) - if err != nil { - return fmt.Errorf("get data device: %w", err) - } - defer device.Destroy() - - source, err := dataControlMgr.CreateDataSource() - if err != nil { - return fmt.Errorf("create data source: %w", err) - } - - offerMap := make(map[string][]byte) - for _, offer := range offers { - if err := source.Offer(offer.MimeType); err != nil { - return fmt.Errorf("offer %s: %w", offer.MimeType, err) - } - offerMap[offer.MimeType] = offer.Data - } - - cancelled := make(chan struct{}) - pasted := make(chan struct{}, 1) - - source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) { - _ = syscall.SetNonblock(e.Fd, false) - file := os.NewFile(uintptr(e.Fd), "pipe") - defer file.Close() - - if data, ok := offerMap[e.MimeType]; ok { - _, _ = file.Write(data) - } - - select { - case pasted <- struct{}{}: - default: - } - }) - - source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) { - close(cancelled) - }) - - if err := device.SetSelection(source); err != nil { - return fmt.Errorf("set selection: %w", err) - } - - display.Roundtrip() - - for { - select { - case <-cancelled: - return nil - case <-pasted: - if pasteOnce { - return nil - } - default: - if err := ctx.Dispatch(); err != nil { - return nil - } - } - } -} diff --git a/core/internal/clipboard/pastekey.go b/core/internal/clipboard/pastekey.go new file mode 100644 index 000000000..0927c1b9f --- /dev/null +++ b/core/internal/clipboard/pastekey.go @@ -0,0 +1,206 @@ +package clipboard + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + "golang.org/x/sys/unix" +) + +const ( + xkbKeymapFormatV1 = 1 + + keyStateReleased = 0 + keyStatePressed = 1 + + // xkb real modifier bit positions are fixed: Shift=0, Lock=1, Control=2 + shiftModMask = 1 << 0 + ctrlModMask = 1 << 2 + + // evdev fallbacks for a standard pc105 map + fallbackCtrlKey = 29 // KEY_LEFTCTRL + fallbackShiftKey = 42 // KEY_LEFTSHIFT + fallbackVKey = 47 // KEY_V +) + +// SendPasteKeystroke emulates a paste shortcut via zwp_virtual_keyboard_v1 +// using the seat's own keymap, so keycodes stay valid for XWayland clients +// (a synthetic wtype-style keymap breaks X11 apps like Steam). withShift +// selects ctrl+shift+v for terminal targets. +func SendPasteKeystroke(withShift bool) error { + s, err := connectSession() + if err != nil { + return err + } + defer s.Close() + + if s.virtualKeyboardMgr == nil { + return fmt.Errorf("compositor does not support zwp_virtual_keyboard_manager_v1") + } + if s.seat == nil { + return fmt.Errorf("no seat available") + } + + keyboard, err := s.seat.GetKeyboard() + if err != nil { + return fmt.Errorf("get keyboard: %w", err) + } + defer keyboard.Release() + + var keymap *wlclient.KeyboardKeymapEvent + keyboard.SetKeymapHandler(func(e wlclient.KeyboardKeymapEvent) { + if keymap == nil { + keymap = &e + } + }) + + s.display.Roundtrip() + + if keymap == nil || keymap.Format != xkbKeymapFormatV1 { + return fmt.Errorf("no xkb keymap from seat") + } + defer unix.Close(keymap.Fd) + + keymapText, err := readKeymap(keymap.Fd, keymap.Size) + if err != nil { + return fmt.Errorf("read keymap: %w", err) + } + keys := resolveKeycodes(keymapText) + + vk, err := s.virtualKeyboardMgr.CreateVirtualKeyboard(s.seat) + if err != nil { + return fmt.Errorf("create virtual keyboard: %w", err) + } + defer vk.Destroy() + + if err := vk.Keymap(xkbKeymapFormatV1, keymap.Fd, keymap.Size); err != nil { + return fmt.Errorf("set keymap: %w", err) + } + + mods := uint32(ctrlModMask) + held := []uint32{keys.ctrl} + if withShift { + mods |= shiftModMask + held = append(held, keys.shift) + } + + t := uint32(0) + press := func(key, state uint32) error { + t++ + return vk.Key(t, key, state) + } + + for _, key := range held { + if err := press(key, keyStatePressed); err != nil { + return fmt.Errorf("key press: %w", err) + } + } + if err := vk.Modifiers(mods, 0, 0, 0); err != nil { + return fmt.Errorf("set modifiers: %w", err) + } + if err := press(keys.v, keyStatePressed); err != nil { + return fmt.Errorf("key press: %w", err) + } + if err := press(keys.v, keyStateReleased); err != nil { + return fmt.Errorf("key release: %w", err) + } + for i := len(held) - 1; i >= 0; i-- { + if err := press(held[i], keyStateReleased); err != nil { + return fmt.Errorf("key release: %w", err) + } + } + if err := vk.Modifiers(0, 0, 0, 0); err != nil { + return fmt.Errorf("clear modifiers: %w", err) + } + + s.display.Roundtrip() + return nil +} + +func readKeymap(fd int, size uint32) (string, error) { + data, err := unix.Mmap(fd, 0, int(size), unix.PROT_READ, unix.MAP_PRIVATE) + if err != nil { + return "", err + } + text := strings.TrimRight(string(data), "\x00") + return text, unix.Munmap(data) +} + +type pasteKeycodes struct { + ctrl uint32 + shift uint32 + v uint32 +} + +var ( + keycodeDefRe = regexp.MustCompile(`<([A-Za-z0-9+_-]+)>\s*=\s*(\d+)`) + keySymbolsRe = regexp.MustCompile(`key\s*<([A-Za-z0-9+_-]+)>\s*\{([^}]*)\}`) + groupIndexRe = regexp.MustCompile(`\w+\[\d+\]\s*=`) + symbolListRe = regexp.MustCompile(`\[([^\]]*)\]`) +) + +// xkbcommon may serialize keysyms as hex escapes instead of names +// (e.g. "0x76" for v, "0xffe3" for Control_L). +var keysymNames = map[uint32]string{ + 0x76: "v", + 0xffe3: "Control_L", + 0xffe1: "Shift_L", +} + +func canonicalKeysym(sym string) string { + if !strings.HasPrefix(sym, "0x") && !strings.HasPrefix(sym, "0X") { + return sym + } + value, err := strconv.ParseUint(sym[2:], 16, 32) + if err != nil { + return sym + } + if name, ok := keysymNames[uint32(value)]; ok { + return name + } + return sym +} + +// resolveKeycodes finds the evdev keycodes producing the keysyms we need in +// the seat keymap's first group, falling back to pc105 positions. +func resolveKeycodes(keymap string) pasteKeycodes { + codes := map[string]uint32{} + for _, m := range keycodeDefRe.FindAllStringSubmatch(keymap, -1) { + if code, err := strconv.Atoi(m[2]); err == nil { + codes[m[1]] = uint32(code) + } + } + + keys := pasteKeycodes{ctrl: fallbackCtrlKey, shift: fallbackShiftKey, v: fallbackVKey} + want := map[string]*uint32{ + "Control_L": &keys.ctrl, + "Shift_L": &keys.shift, + "v": &keys.v, + } + + for _, m := range keySymbolsRe.FindAllStringSubmatch(keymap, -1) { + group := symbolListRe.FindStringSubmatch(groupIndexRe.ReplaceAllString(m[2], "")) + if group == nil { + continue + } + xkbCode, ok := codes[m[1]] + if !ok || xkbCode < 8 { + continue + } + level1 := canonicalKeysym(strings.TrimSpace(strings.Split(group[1], ",")[0])) + target, wanted := want[level1] + if !wanted { + continue + } + *target = xkbCode - 8 + delete(want, level1) + if len(want) == 0 { + break + } + } + + return keys +} diff --git a/core/internal/clipboard/pastekey_live_test.go b/core/internal/clipboard/pastekey_live_test.go new file mode 100644 index 000000000..4420441c5 --- /dev/null +++ b/core/internal/clipboard/pastekey_live_test.go @@ -0,0 +1,65 @@ +package clipboard + +import ( + "os" + "testing" + + wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + "golang.org/x/sys/unix" +) + +func TestLiveSeatKeymapResolution(t *testing.T) { + if os.Getenv("DMS_LIVE_TEST") == "" { + t.Skip("set DMS_LIVE_TEST=1 to run against the live compositor") + } + + s, err := connectSession() + if err != nil { + t.Fatalf("connect: %v", err) + } + defer s.Close() + + if s.virtualKeyboardMgr == nil { + t.Fatal("compositor does not advertise zwp_virtual_keyboard_manager_v1") + } + if s.seat == nil { + t.Fatal("no seat") + } + + keyboard, err := s.seat.GetKeyboard() + if err != nil { + t.Fatalf("get keyboard: %v", err) + } + defer keyboard.Release() + + var keymap *wlclient.KeyboardKeymapEvent + keyboard.SetKeymapHandler(func(e wlclient.KeyboardKeymapEvent) { + if keymap == nil { + keymap = &e + } + }) + s.display.Roundtrip() + + if keymap == nil { + t.Fatal("no keymap event") + } + defer unix.Close(keymap.Fd) + + text, err := readKeymap(keymap.Fd, keymap.Size) + if err != nil { + t.Fatalf("read keymap: %v", err) + } + + if dump := os.Getenv("DMS_LIVE_DUMP"); dump != "" { + if err := os.WriteFile(dump, []byte(text), 0o644); err != nil { + t.Fatalf("dump keymap: %v", err) + } + } + + keys := resolveKeycodes(text) + t.Logf("keymap size=%d resolved ctrl=%d shift=%d v=%d", keymap.Size, keys.ctrl, keys.shift, keys.v) + + if keys.ctrl == fallbackCtrlKey && keys.shift == fallbackShiftKey && keys.v == fallbackVKey { + t.Log("all keycodes are fallbacks - parsing may not have matched the live keymap") + } +} diff --git a/core/internal/clipboard/pastekey_test.go b/core/internal/clipboard/pastekey_test.go new file mode 100644 index 000000000..c568852b5 --- /dev/null +++ b/core/internal/clipboard/pastekey_test.go @@ -0,0 +1,105 @@ +package clipboard + +import "testing" + +const qwertyKeymap = `xkb_keymap { +xkb_keycodes "(unnamed)" { + minimum = 8; + maximum = 708; + = 9; + = 55; + = 37; + = 50; + alias = ; + indicator 1 = "Caps Lock"; +}; +xkb_types "(unnamed)" { + type "ALPHABETIC" { + modifiers = Shift+Lock; + map[Shift] = Level2; + level_name[Level1] = "Base"; + }; +}; +xkb_symbols "(unnamed)" { + key { [ Escape ] }; + key { type= "ALPHABETIC", [ v, V ] }; + key { [ Control_L ] }; + key { [ Shift_L ] }; +}; +};` + +const hexKeymap = `xkb_keymap { +xkb_keycodes "(unnamed)" { + = 56; + = 38; + = 51; +}; +xkb_symbols "(unnamed)" { + key { [ 0xffe3 ] }; + key { + type= "PC_ALT_LEVEL2", + symbols[1]= [ 0xffe1, 0xfe08 ] + }; + key { [ 0x76, 0x56 ] }; +}; +};` + +const dvorakKeymap = `xkb_keymap { +xkb_keycodes "(unnamed)" { + = 60; + = 37; + = 50; +}; +xkb_symbols "(unnamed)" { + key { [ v, V ] }; + key { [ Control_L ] }; + key { [ Shift_L ] }; +}; +};` + +func TestResolveKeycodes(t *testing.T) { + tests := []struct { + name string + keymap string + want pasteKeycodes + }{ + {"qwerty", qwertyKeymap, pasteKeycodes{ctrl: 29, shift: 42, v: 47}}, + {"dvorak", dvorakKeymap, pasteKeycodes{ctrl: 29, shift: 42, v: 52}}, + {"hex keysyms", hexKeymap, pasteKeycodes{ctrl: 30, shift: 43, v: 48}}, + {"empty falls back", "", pasteKeycodes{ctrl: fallbackCtrlKey, shift: fallbackShiftKey, v: fallbackVKey}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := resolveKeycodes(tt.keymap) + if got != tt.want { + t.Errorf("resolveKeycodes() = %+v, want %+v", got, tt.want) + } + }) + } +} + +func TestExpandOffers(t *testing.T) { + text := ExpandOffers([]byte("hi"), "text/plain;charset=utf-8") + if len(text) != 5 { + t.Fatalf("expected 5 text offers, got %d", len(text)) + } + seen := map[string]bool{} + for _, o := range text { + if string(o.Data) != "hi" { + t.Errorf("offer %s has wrong data", o.MimeType) + } + if seen[o.MimeType] { + t.Errorf("duplicate offer %s", o.MimeType) + } + seen[o.MimeType] = true + } + if !seen["UTF8_STRING"] || !seen["STRING"] || !seen["TEXT"] || !seen["text/plain"] { + t.Errorf("missing X11 alias offers: %v", seen) + } + + img := ExpandOffers([]byte{1}, "image/png") + if len(img) != 1 || img[0].MimeType != "image/png" { + t.Errorf("non-text mime should not expand, got %+v", img) + } +} diff --git a/core/internal/clipboard/wl.go b/core/internal/clipboard/wl.go new file mode 100644 index 000000000..684743061 --- /dev/null +++ b/core/internal/clipboard/wl.go @@ -0,0 +1,93 @@ +package clipboard + +import ( + "fmt" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control" + "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/virtual_keyboard" + wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" +) + +type session struct { + display *wlclient.Display + ctx *wlclient.Context + registry *wlclient.Registry + seat *wlclient.Seat + dataControlMgr *ext_data_control.ExtDataControlManagerV1 + virtualKeyboardMgr *virtual_keyboard.ZwpVirtualKeyboardManagerV1 +} + +// connectSession opens a short-lived Wayland connection and binds the seat +// plus whichever clipboard-related globals the compositor advertises. +func connectSession() (*session, error) { + display, err := wlclient.Connect("") + if err != nil { + return nil, fmt.Errorf("wayland connect: %w", err) + } + + s := &session{display: display, ctx: display.Context()} + + registry, err := display.GetRegistry() + if err != nil { + display.Destroy() + return nil, fmt.Errorf("get registry: %w", err) + } + s.registry = registry + + var bindErr error + bind := func(name uint32, iface string, version uint32, proxy wlclient.Proxy) { + if err := registry.Bind(name, iface, version, proxy); err != nil { + bindErr = fmt.Errorf("bind %s: %w", iface, err) + } + } + + registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { + switch e.Interface { + case ext_data_control.ExtDataControlManagerV1InterfaceName: + mgr := ext_data_control.NewExtDataControlManagerV1(s.ctx) + bind(e.Name, e.Interface, e.Version, mgr) + s.dataControlMgr = mgr + case virtual_keyboard.ZwpVirtualKeyboardManagerV1InterfaceName: + mgr := virtual_keyboard.NewZwpVirtualKeyboardManagerV1(s.ctx) + bind(e.Name, e.Interface, e.Version, mgr) + s.virtualKeyboardMgr = mgr + case "wl_seat": + if s.seat != nil { + return + } + seat := wlclient.NewSeat(s.ctx) + bind(e.Name, e.Interface, e.Version, seat) + s.seat = seat + } + }) + + display.Roundtrip() + display.Roundtrip() + + if bindErr != nil { + s.Close() + return nil, bindErr + } + return s, nil +} + +func (s *session) requireDataControl() (*ext_data_control.ExtDataControlManagerV1, error) { + switch { + case s.dataControlMgr == nil: + return nil, fmt.Errorf("compositor does not support ext_data_control_manager_v1") + case s.seat == nil: + return nil, fmt.Errorf("no seat available") + default: + return s.dataControlMgr, nil + } +} + +func (s *session) Close() { + if s.dataControlMgr != nil { + s.dataControlMgr.Destroy() + } + if s.registry != nil { + s.registry.Destroy() + } + s.display.Destroy() +} diff --git a/core/internal/proto/virtual_keyboard/virtual_keyboard.go b/core/internal/proto/virtual_keyboard/virtual_keyboard.go new file mode 100644 index 000000000..232d75474 --- /dev/null +++ b/core/internal/proto/virtual_keyboard/virtual_keyboard.go @@ -0,0 +1,289 @@ +// Generated by go-wayland-scanner +// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner +// XML file : internal/proto/xml/virtual-keyboard-unstable-v1.xml +// +// virtual_keyboard_unstable_v1 Protocol Copyright: +// +// Copyright © 2008-2011 Kristian Høgsberg +// Copyright © 2010-2013 Intel Corporation +// Copyright © 2012-2013 Collabora, Ltd. +// Copyright © 2018 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 virtual_keyboard + +import ( + "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + "golang.org/x/sys/unix" +) + +// ZwpVirtualKeyboardV1InterfaceName 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 ZwpVirtualKeyboardV1InterfaceName = "zwp_virtual_keyboard_v1" + +// ZwpVirtualKeyboardV1 : virtual keyboard +// +// The virtual keyboard provides an application with requests which emulate +// the behaviour of a physical keyboard. +// +// This interface can be used by clients on its own to provide raw input +// events, or it can accompany the input method protocol. +type ZwpVirtualKeyboardV1 struct { + client.BaseProxy +} + +// NewZwpVirtualKeyboardV1 : virtual keyboard +// +// The virtual keyboard provides an application with requests which emulate +// the behaviour of a physical keyboard. +// +// This interface can be used by clients on its own to provide raw input +// events, or it can accompany the input method protocol. +func NewZwpVirtualKeyboardV1(ctx *client.Context) *ZwpVirtualKeyboardV1 { + zwpVirtualKeyboardV1 := &ZwpVirtualKeyboardV1{} + ctx.Register(zwpVirtualKeyboardV1) + return zwpVirtualKeyboardV1 +} + +// Keymap : keyboard mapping +// +// Provide a file descriptor to the compositor which can be +// memory-mapped to provide a keyboard mapping description. +// +// Format carries a value from the keymap_format enumeration. +// +// format: keymap format +// fd: keymap file descriptor +// size: keymap size, in bytes +func (i *ZwpVirtualKeyboardV1) Keymap(format uint32, fd int, size uint32) error { + 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], uint32(format)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(size)) + l += 4 + oob := unix.UnixRights(int(fd)) + err := i.Context().WriteMsg(_reqBuf[:], oob) + return err +} + +// Key : key event +// +// A key was pressed or released. +// The time argument is a timestamp with millisecond granularity, with an +// undefined base. All requests regarding a single object must share the +// same clock. +// +// Keymap must be set before issuing this request. +// +// State carries a value from the key_state enumeration. +// +// time: timestamp with millisecond granularity +// key: key that produced the event +// state: physical state of the key +func (i *ZwpVirtualKeyboardV1) Key(time, key, state uint32) error { + const opcode = 1 + const _reqBufLen = 8 + 4 + 4 + 4 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(time)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(key)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(state)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// Modifiers : modifier and group state +// +// Notifies the compositor that the modifier and/or group state has +// changed, and it should update state. +// +// The client should use wl_keyboard.modifiers event to synchronize its +// internal state with seat state. +// +// Keymap must be set before issuing this request. +// +// modsDepressed: depressed modifiers +// modsLatched: latched modifiers +// modsLocked: locked modifiers +// group: keyboard layout +func (i *ZwpVirtualKeyboardV1) Modifiers(modsDepressed, modsLatched, modsLocked, group uint32) error { + const opcode = 2 + const _reqBufLen = 8 + 4 + 4 + 4 + 4 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(modsDepressed)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(modsLatched)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(modsLocked)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(group)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// Destroy : destroy the virtual keyboard keyboard object +func (i *ZwpVirtualKeyboardV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 3 + 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 ZwpVirtualKeyboardV1Error uint32 + +// ZwpVirtualKeyboardV1Error : +const ( + // ZwpVirtualKeyboardV1ErrorNoKeymap : No keymap was set + ZwpVirtualKeyboardV1ErrorNoKeymap ZwpVirtualKeyboardV1Error = 0 +) + +func (e ZwpVirtualKeyboardV1Error) Name() string { + switch e { + case ZwpVirtualKeyboardV1ErrorNoKeymap: + return "no_keymap" + default: + return "" + } +} + +func (e ZwpVirtualKeyboardV1Error) Value() string { + switch e { + case ZwpVirtualKeyboardV1ErrorNoKeymap: + return "0" + default: + return "" + } +} + +func (e ZwpVirtualKeyboardV1Error) String() string { + return e.Name() + "=" + e.Value() +} + +// ZwpVirtualKeyboardManagerV1InterfaceName 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 ZwpVirtualKeyboardManagerV1InterfaceName = "zwp_virtual_keyboard_manager_v1" + +// ZwpVirtualKeyboardManagerV1 : virtual keyboard manager +// +// A virtual keyboard manager allows an application to provide keyboard +// input events as if they came from a physical keyboard. +type ZwpVirtualKeyboardManagerV1 struct { + client.BaseProxy +} + +// NewZwpVirtualKeyboardManagerV1 : virtual keyboard manager +// +// A virtual keyboard manager allows an application to provide keyboard +// input events as if they came from a physical keyboard. +func NewZwpVirtualKeyboardManagerV1(ctx *client.Context) *ZwpVirtualKeyboardManagerV1 { + zwpVirtualKeyboardManagerV1 := &ZwpVirtualKeyboardManagerV1{} + ctx.Register(zwpVirtualKeyboardManagerV1) + return zwpVirtualKeyboardManagerV1 +} + +// CreateVirtualKeyboard : Create a new virtual keyboard +// +// Creates a new virtual keyboard associated to a seat. +// +// If the compositor enables a keyboard to perform arbitrary actions, it +// should present an error when an untrusted client requests a new +// keyboard. +func (i *ZwpVirtualKeyboardManagerV1) CreateVirtualKeyboard(seat *client.Seat) (*ZwpVirtualKeyboardV1, error) { + id := NewZwpVirtualKeyboardV1(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], seat.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], id.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return id, err +} + +func (i *ZwpVirtualKeyboardManagerV1) Destroy() error { + i.Context().Unregister(i) + return nil +} + +type ZwpVirtualKeyboardManagerV1Error uint32 + +// ZwpVirtualKeyboardManagerV1Error : +const ( + // ZwpVirtualKeyboardManagerV1ErrorUnauthorized : client not authorized to use the interface + ZwpVirtualKeyboardManagerV1ErrorUnauthorized ZwpVirtualKeyboardManagerV1Error = 0 +) + +func (e ZwpVirtualKeyboardManagerV1Error) Name() string { + switch e { + case ZwpVirtualKeyboardManagerV1ErrorUnauthorized: + return "unauthorized" + default: + return "" + } +} + +func (e ZwpVirtualKeyboardManagerV1Error) Value() string { + switch e { + case ZwpVirtualKeyboardManagerV1ErrorUnauthorized: + return "0" + default: + return "" + } +} + +func (e ZwpVirtualKeyboardManagerV1Error) String() string { + return e.Name() + "=" + e.Value() +} diff --git a/core/internal/proto/xml/virtual-keyboard-unstable-v1.xml b/core/internal/proto/xml/virtual-keyboard-unstable-v1.xml new file mode 100644 index 000000000..5095c91b8 --- /dev/null +++ b/core/internal/proto/xml/virtual-keyboard-unstable-v1.xml @@ -0,0 +1,113 @@ + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2013 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2018 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. + + + + + The virtual keyboard provides an application with requests which emulate + the behaviour of a physical keyboard. + + This interface can be used by clients on its own to provide raw input + events, or it can accompany the input method protocol. + + + + + Provide a file descriptor to the compositor which can be + memory-mapped to provide a keyboard mapping description. + + Format carries a value from the keymap_format enumeration. + + + + + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. All requests regarding a single object must share the + same clock. + + Keymap must be set before issuing this request. + + State carries a value from the key_state enumeration. + + + + + + + + + Notifies the compositor that the modifier and/or group state has + changed, and it should update state. + + The client should use wl_keyboard.modifiers event to synchronize its + internal state with seat state. + + Keymap must be set before issuing this request. + + + + + + + + + + + + + + + A virtual keyboard manager allows an application to provide keyboard + input events as if they came from a physical keyboard. + + + + + + + + + Creates a new virtual keyboard associated to a seat. + + If the compositor enables a keyboard to perform arbitrary actions, it + should present an error when an untrusted client requests a new + keyboard. + + + + + + diff --git a/core/internal/server/clipboard/handlers.go b/core/internal/server/clipboard/handlers.go index 1bfd59942..d9045e661 100644 --- a/core/internal/server/clipboard/handlers.go +++ b/core/internal/server/clipboard/handlers.go @@ -6,6 +6,7 @@ import ( "fmt" "net" + clipboardstore "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" ) @@ -28,6 +29,10 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager) { handleCopyEntry(conn, req, m) case "clipboard.paste": handlePaste(conn, req, m) + case "clipboard.sendPaste": + handleSendPaste(conn, req) + case "clipboard.pasteSupported": + models.Respond(conn, req.ID, map[string]bool{"supported": m.pasteSupported}) case "clipboard.subscribe": handleSubscribe(conn, req, m) case "clipboard.search": @@ -176,6 +181,17 @@ func handlePaste(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, map[string]string{"text": text}) } +func handleSendPaste(conn net.Conn, req models.Request) { + shift, _ := models.Get[bool](req, "shift") + + if err := clipboardstore.SendPasteKeystroke(shift); err != nil { + models.RespondError(conn, req.ID, err.Error()) + return + } + + models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "paste sent"}) +} + func handleSubscribe(conn net.Conn, req models.Request, m *Manager) { clientID := fmt.Sprintf("clipboard-%d", req.ID) diff --git a/core/internal/server/clipboard/manager.go b/core/internal/server/clipboard/manager.go index 63449cc7b..6486ecda5 100644 --- a/core/internal/server/clipboard/manager.go +++ b/core/internal/server/clipboard/manager.go @@ -31,6 +31,7 @@ import ( clipboardstore "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control" + "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/virtual_keyboard" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext" wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" ) @@ -164,6 +165,8 @@ func (m *Manager) setupRegistry() error { m.seat = seat m.seatName = e.Name log.Info("Bound wl_seat") + case virtual_keyboard.ZwpVirtualKeyboardManagerV1InterfaceName: + m.pasteSupported = true } }) @@ -1054,6 +1057,13 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error { dataCopy := make([]byte, len(data)) copy(dataCopy, data) + m.takeSelection(clipboardstore.ExpandOffers(dataCopy, mimeType)) + return nil +} + +// takeSelection makes the daemon the selection owner, serving the given +// offers until another client claims the clipboard. +func (m *Manager) takeSelection(offers []clipboardstore.Offer) { m.post(func() { if m.dataControlMgr == nil || m.dataDevice == nil { log.Error("Data control manager or device not initialized") @@ -1068,9 +1078,13 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error { return } - if err := source.Offer(mimeType); err != nil { - log.Errorf("Failed to offer mime type: %v", err) - return + offerData := make(map[string][]byte, len(offers)) + for _, offer := range offers { + if err := source.Offer(offer.MimeType); err != nil { + log.Errorf("Failed to offer %s: %v", offer.MimeType, err) + return + } + offerData[offer.MimeType] = offer.Data } source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) { @@ -1080,7 +1094,11 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error { file := os.NewFile(uintptr(fd), "clipboard-pipe") defer file.Close() - if _, err := file.Write(dataCopy); err != nil { + data, ok := offerData[e.MimeType] + if !ok { + return + } + if _, err := file.Write(data); err != nil { log.Errorf("Failed to write clipboard data: %v", err) } }) @@ -1093,9 +1111,6 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error { m.releaseCurrentSource() m.currentSource = source - m.sourceMutex.Lock() - m.sourceMimeTypes = []string{mimeType} - m.sourceMutex.Unlock() m.ownerLock.Lock() m.isOwner = true @@ -1106,8 +1121,6 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error { log.Errorf("Failed to set selection: %v", err) } }) - - return nil } func (m *Manager) CopyText(text string) error { @@ -1779,74 +1792,16 @@ func (m *Manager) CopyFile(filePath string) error { m.updateState() m.notifySubscribers() - _, imgMime, imgErr := image.DecodeConfig(bytes.NewReader(fileData)) - - m.post(func() { - if m.dataControlMgr == nil || m.dataDevice == nil { - log.Error("Data control manager or device not initialized") - return - } - - dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1) - source, err := dataMgr.CreateDataSource() - if err != nil { - log.Errorf("Failed to create data source: %v", err) - return - } - - type offer struct { - mime string - data []byte - } - offers := []offer{ - {"x-special/gnome-copied-files", []byte("copy\n" + fileURI)}, - {"text/uri-list", []byte(fileURI + "\r\n")}, - {"text/plain", []byte(filePath)}, - } - - if imgErr == nil { - imgMimeType := "image/" + imgMime - offers = append(offers, offer{imgMimeType, fileData}) - } - - offerData := make(map[string][]byte) - for _, o := range offers { - if err := source.Offer(o.mime); err != nil { - log.Errorf("Failed to offer %s: %v", o.mime, err) - return - } - offerData[o.mime] = o.data - } - - source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) { - fd := e.Fd - defer syscall.Close(fd) - file := os.NewFile(uintptr(fd), "clipboard-pipe") - defer file.Close() - if data, ok := offerData[e.MimeType]; ok { - file.Write(data) - } - }) - - source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) { - m.ownerLock.Lock() - m.isOwner = false - m.ownerLock.Unlock() - }) - - m.releaseCurrentSource() - m.currentSource = source - - m.ownerLock.Lock() - m.isOwner = true - m.ownerLock.Unlock() - - device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1) - if err := device.SetSelection(source); err != nil { - log.Errorf("Failed to set selection: %v", err) - } - }) + offers := []clipboardstore.Offer{ + {MimeType: "x-special/gnome-copied-files", Data: []byte("copy\n" + fileURI)}, + {MimeType: "text/uri-list", Data: []byte(fileURI + "\r\n")}, + {MimeType: "text/plain", Data: []byte(filePath)}, + } + if _, imgMime, err := image.DecodeConfig(bytes.NewReader(fileData)); err == nil { + offers = append(offers, clipboardstore.Offer{MimeType: "image/" + imgMime, Data: fileData}) + } + m.takeSelection(offers) return nil } diff --git a/core/internal/server/clipboard/manager_test.go b/core/internal/server/clipboard/manager_test.go index cc231f8ab..398f303dc 100644 --- a/core/internal/server/clipboard/manager_test.go +++ b/core/internal/server/clipboard/manager_test.go @@ -531,47 +531,6 @@ func TestManager_ConcurrentOfferAccess(t *testing.T) { wg.Wait() } -func TestManager_ConcurrentPersistAccess(t *testing.T) { - m := &Manager{ - persistData: make(map[string][]byte), - persistMimeTypes: []string{}, - } - - var wg sync.WaitGroup - const goroutines = 20 - const iterations = 50 - - for i := 0; i < goroutines/2; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < iterations; j++ { - m.persistMutex.RLock() - _ = m.persistData - _ = m.persistMimeTypes - m.persistMutex.RUnlock() - } - }() - } - - for i := 0; i < goroutines/2; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - for j := 0; j < iterations; j++ { - m.persistMutex.Lock() - m.persistMimeTypes = []string{"text/plain", "text/html"} - m.persistData = map[string][]byte{ - "text/plain": []byte("test"), - } - m.persistMutex.Unlock() - } - }(i) - } - - wg.Wait() -} - func TestManager_ConcurrentOwnerAccess(t *testing.T) { m := &Manager{} diff --git a/core/internal/server/clipboard/types.go b/core/internal/server/clipboard/types.go index c75456138..2427badec 100644 --- a/core/internal/server/clipboard/types.go +++ b/core/internal/server/clipboard/types.go @@ -132,15 +132,9 @@ type Manager struct { offerMutex sync.RWMutex offerRegistry map[uint32]any - sourceMimeTypes []string - sourceMutex sync.RWMutex - - persistData map[string][]byte - persistMimeTypes []string - persistMutex sync.RWMutex - - isOwner bool - ownerLock sync.Mutex + isOwner bool + ownerLock sync.Mutex + pasteSupported bool initialized bool diff --git a/distro/nix/common.nix b/distro/nix/common.nix index 1dcc2597a..2f99bdcec 100644 --- a/distro/nix/common.nix +++ b/distro/nix/common.nix @@ -18,8 +18,7 @@ in ] ++ lib.optional cfg.enableDynamicTheming pkgs.matugen ++ lib.optional cfg.enableAudioWavelength pkgs.cava - ++ lib.optional cfg.enableCalendarEvents pkgs.khal - ++ lib.optional cfg.enableClipboardPaste pkgs.wtype; + ++ lib.optional cfg.enableCalendarEvents pkgs.khal; plugins = lib.mapAttrs (name: plugin: { source = plugin.src; diff --git a/distro/nix/options.nix b/distro/nix/options.nix index e6f8bbae3..428b3fc32 100644 --- a/distro/nix/options.nix +++ b/distro/nix/options.nix @@ -76,7 +76,7 @@ in enableClipboardPaste = lib.mkOption { type = types.bool; default = true; - description = "Adds needed dependencies for directly pasting items from the clipboard history."; + description = "Deprecated: paste is built into dms; no extra dependencies needed. Kept as a no-op for compatibility."; }; quickshell = { diff --git a/distro/void/README.md b/distro/void/README.md index b4f0ecfdf..094ad52b2 100644 --- a/distro/void/README.md +++ b/distro/void/README.md @@ -61,7 +61,6 @@ install whichever features you want: | `cava` | audio visualiser widget | | `qt6-multimedia` | system sound feedback | | `qt6ct` | Qt app theming | -| `wtype` | virtual keyboard input | | `power-profiles-daemon` | power profile control | | `cups-pk-helper` | printer management | | `NetworkManager` | network control | diff --git a/quickshell/Modals/Clipboard/ClipboardContent.qml b/quickshell/Modals/Clipboard/ClipboardContent.qml index 62dfedb8c..8228a979a 100644 --- a/quickshell/Modals/Clipboard/ClipboardContent.qml +++ b/quickshell/Modals/Clipboard/ClipboardContent.qml @@ -396,7 +396,7 @@ Item { } sourceComponent: ClipboardKeyboardHints { - wtypeAvailable: modal.wtypeAvailable + pasteAvailable: modal.pasteAvailable enterToPaste: SettingsData.clipboardEnterToPaste } } diff --git a/quickshell/Modals/Clipboard/ClipboardEditor.qml b/quickshell/Modals/Clipboard/ClipboardEditor.qml index d32bb3ce4..36fd5a1c2 100644 --- a/quickshell/Modals/Clipboard/ClipboardEditor.qml +++ b/quickshell/Modals/Clipboard/ClipboardEditor.qml @@ -496,7 +496,7 @@ Item { implicitHeight: saveMenuPasteRow.implicitHeight + Theme.spacingS * 2 radius: Theme.cornerRadius color: saveMenuPasteArea.containsMouse ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0) - opacity: modal.wtypeAvailable ? 1 : 0.5 + opacity: modal.pasteAvailable ? 1 : 0.5 Row { id: saveMenuPasteRow @@ -522,7 +522,7 @@ Item { id: saveMenuPasteArea anchors.fill: parent hoverEnabled: true - enabled: modal.wtypeAvailable + enabled: modal.pasteAvailable cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor onClicked: { saveMenu.close(); diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml b/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml index 6a29c20de..f1f8e5c75 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml @@ -21,7 +21,7 @@ FocusScope { property string activeFilter: SettingsData.clipboardRememberTypeFilter ? SettingsData.clipboardTypeFilter : "all" readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable - readonly property bool wtypeAvailable: ClipboardService.wtypeAvailable + readonly property bool pasteAvailable: ClipboardService.pasteAvailable readonly property int totalCount: ClipboardService.totalCount readonly property var clipboardEntries: ClipboardService.clipboardEntries readonly property var pinnedEntries: ClipboardService.pinnedEntries diff --git a/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml b/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml index 001633adf..b0800c14a 100644 --- a/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml +++ b/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml @@ -5,10 +5,10 @@ import qs.Widgets Rectangle { id: keyboardHints - property bool wtypeAvailable: false + property bool pasteAvailable: false property bool enterToPaste: false readonly property string hintsText: { - if (!wtypeAvailable) + if (!pasteAvailable) return I18n.tr("Ctrl+Tab: Switch Tab • Ctrl+S: Pin/Unpin • Shift+Del: Clear All • Esc: Close"); return enterToPaste ? I18n.tr("Ctrl+Tab: Switch Tabs • Ctrl+S: Pin/Unpin • Shift+Enter: Copy • Shift+Del: Clear All • F10: Help • Esc: Close", "Keyboard hints when enter-to-paste is enabled") : I18n.tr("Ctrl+Tab: Switch Tabs • Ctrl+S: Pin/Unpin • Shift+Enter: Paste • Shift+Del: Clear All • F10: Help • Esc: Close"); } diff --git a/quickshell/Modals/DankLauncherV2/Controller.qml b/quickshell/Modals/DankLauncherV2/Controller.qml index 17aba5d17..0f926f7e0 100644 --- a/quickshell/Modals/DankLauncherV2/Controller.qml +++ b/quickshell/Modals/DankLauncherV2/Controller.qml @@ -145,12 +145,6 @@ Item { } } - Process { - id: wtypeProcess - command: ["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"] - running: false - } - Process { id: copyProcess running: false @@ -161,7 +155,7 @@ Item { id: pasteTimer interval: 200 repeat: false - onTriggered: wtypeProcess.running = true + onTriggered: ClipboardService.sendPasteKeystroke() } function pasteSelected() { @@ -179,10 +173,6 @@ Item { } return; } - if (!SessionService.wtypeAvailable) { - ToastService.showError(I18n.tr("wtype not available - install wtype for paste support")); - return; - } const pluginId = selectedItem.pluginId; if (!pluginId) diff --git a/quickshell/Services/ClipboardService.qml b/quickshell/Services/ClipboardService.qml index 28f90d22c..f5e64c5f8 100644 --- a/quickshell/Services/ClipboardService.qml +++ b/quickshell/Services/ClipboardService.qml @@ -3,7 +3,7 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell -import Quickshell.Io +import Quickshell.Wayland import qs.Common import qs.Services @@ -14,7 +14,10 @@ Singleton { readonly property int longTextThreshold: 200 readonly property bool clipboardAvailable: DMSService.isConnected && (DMSService.capabilities.length === 0 || DMSService.capabilities.includes("clipboard")) - readonly property bool wtypeAvailable: SessionService.wtypeAvailable + property bool pasteSupported: false + readonly property bool pasteAvailable: clipboardAvailable && pasteSupported + + readonly property var terminalAppIds: ["kitty", "foot", "footclient", "alacritty", "st", "org.wezfurlong.wezterm", "com.mitchellh.ghostty", "ghostty", "org.kde.konsole", "konsole", "org.gnome.terminal", "gnome-terminal-server", "org.gnome.console", "kgx", "com.gexperts.tilix", "tilix", "terminator", "xfce4-terminal", "lxterminal", "deepin-terminal", "io.elementary.terminal", "rio", "contour", "wayst", "urxvt", "rxvt"] property var internalEntries: [] property var clipboardEntries: [] @@ -37,38 +40,62 @@ Singleton { signal historyCleared signal launcherSearchReady(string query) - Process { - id: wtypeProcess - // TODO: This is only a paste shortcut fallback. It assumes the target - // application accepts Ctrl+V, which is false for many terminals. - // Replace with a more reliable target-aware paste strategy. - command: ["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"] - running: false - } - Timer { id: pasteTimer interval: 200 repeat: false - onTriggered: wtypeProcess.running = true + onTriggered: root.sendPasteKeystroke() + } + + Connections { + target: DMSService + function onIsConnectedChanged() { + root.refreshPasteSupport(); + } + } + + Component.onCompleted: refreshPasteSupport() + + function refreshPasteSupport() { + if (!DMSService.isConnected) { + pasteSupported = false; + return; + } + DMSService.sendRequest("clipboard.pasteSupported", null, function (response) { + root.pasteSupported = !response.error && response.result && response.result.supported === true; + }); + } + + function isTerminalFocused() { + const appId = (ToplevelManager.activeToplevel?.appId ?? "").toLowerCase(); + if (!appId) { + return false; + } + return terminalAppIds.includes(appId) || appId.endsWith("term") || appId.includes("terminal"); + } + + function sendPasteKeystroke() { + DMSService.sendRequest("clipboard.sendPaste", { + "shift": isTerminalFocused() + }, function (response) { + if (response.error) { + ToastService.showError(I18n.tr("Paste failed: %1").arg(response.error)); + } + }); } function updateFilteredModel() { let filtered = internalEntries; if (activeFilter !== "all") { - filtered = filtered.filter(entry => - getEntryType(entry) === activeFilter - ); + filtered = filtered.filter(entry => getEntryType(entry) === activeFilter); } const query = searchText.trim(); if (query.length > 0) { const lowerQuery = query.toLowerCase(); - filtered = filtered.filter(entry => - entry.preview.toLowerCase().includes(lowerQuery) - ); + filtered = filtered.filter(entry => entry.preview.toLowerCase().includes(lowerQuery)); } filtered.sort((a, b) => { @@ -256,19 +283,17 @@ Singleton { } function pasteClipboard(closeCallback) { - if (!wtypeAvailable) { - ToastService.showError(I18n.tr("wtype not available - install wtype for paste support")); - return; - } if (closeCallback) { closeCallback(); } - pasteTimer.start(); + if (pasteAvailable) { + pasteTimer.start(); + } } function pasteEntry(entry, closeCallback) { - if (!wtypeAvailable) { - ToastService.showError(I18n.tr("wtype not available - install wtype for paste support")); + if (!pasteAvailable) { + copyEntry(entry, closeCallback); return; } DMSService.sendRequest("clipboard.copyEntry", { diff --git a/quickshell/Services/SessionService.qml b/quickshell/Services/SessionService.qml index e21af8d6d..f924e0889 100644 --- a/quickshell/Services/SessionService.qml +++ b/quickshell/Services/SessionService.qml @@ -21,7 +21,6 @@ Singleton { property string nvidiaCommand: "" property bool loginctlAvailable: false - property bool wtypeAvailable: false property string sessionId: "" property string sessionPath: "" property bool locked: false @@ -55,7 +54,6 @@ Singleton { detectElogindProcess.running = true; detectHibernateProcess.running = true; detectPrimeRunProcess.running = true; - detectWtypeProcess.running = true; if (!SettingsData.loginctlLockIntegration) { log.debug("loginctl lock integration disabled by user"); return; @@ -120,15 +118,6 @@ Singleton { } } - Process { - id: detectWtypeProcess - running: false - command: ["sh", "-c", "command -v wtype"] - onExited: exitCode => { - wtypeAvailable = (exitCode === 0); - } - } - Process { id: detectPrimeRunProcess running: false diff --git a/quickshell/translations/en.json b/quickshell/translations/en.json index da3343b4f..3250b4b49 100644 --- a/quickshell/translations/en.json +++ b/quickshell/translations/en.json @@ -146,7 +146,7 @@ { "term": "%1 notifications", "context": "%1 notifications", - "reference": "Modules/Lock/LockScreenContent.qml:514", + "reference": "Modules/Lock/LockScreenContent.qml:521", "comment": "" }, { @@ -248,7 +248,7 @@ { "term": "+ %1 more", "context": "+ %1 more", - "reference": "Modules/Lock/LockScreenContent.qml:594, Modules/Lock/LockScreenContent.qml:709", + "reference": "Modules/Lock/LockScreenContent.qml:601, Modules/Lock/LockScreenContent.qml:716", "comment": "" }, { @@ -302,7 +302,7 @@ { "term": "1 notification", "context": "1 notification", - "reference": "Modules/Lock/LockScreenContent.qml:514", + "reference": "Modules/Lock/LockScreenContent.qml:521", "comment": "" }, { @@ -380,7 +380,7 @@ { "term": "180°", "context": "180°", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2601, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2622", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2612, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2633", "comment": "" }, { @@ -440,7 +440,7 @@ { "term": "270°", "context": "270°", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2603, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2624", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2614, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2635", "comment": "" }, { @@ -608,7 +608,7 @@ { "term": "90°", "context": "90°", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2599, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2620", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2610, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2631", "comment": "" }, { @@ -644,7 +644,7 @@ { "term": "AC Adapter (Plugged In)", "context": "AC Adapter (Plugged In)", - "reference": "Modules/Settings/BatteryTab.qml:67", + "reference": "Modules/Settings/BatteryTab.qml:71", "comment": "" }, { @@ -785,12 +785,6 @@ "reference": "Modules/Settings/WidgetsTabSection.qml:4071", "comment": "" }, - { - "term": "Active Lock Screen Monitor", - "context": "Active Lock Screen Monitor", - "reference": "Modules/Settings/LockScreenTab.qml:415", - "comment": "" - }, { "term": "Active VPN", "context": "Active VPN", @@ -1022,15 +1016,9 @@ { "term": "All", "context": "All", - "reference": "Modals/ProcessListModal.qml:383, Services/AppSearchService.qml:724, Services/AppSearchService.qml:745, dms-plugins/DankStickerSearch/DankStickerSearch.qml:101, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:101, Modals/DankLauncherV2/Controller.qml:755, Modals/DankLauncherV2/SpotlightLauncherContent.qml:440, Modals/DankLauncherV2/LauncherContent.qml:362, Modals/DankLauncherV2/LauncherContent.qml:651, Modals/Clipboard/ClipboardContent.qml:16, Modules/ProcessList/ProcessListPopout.qml:169, Modules/Settings/WidgetsTabSection.qml:3782, Modules/Settings/WidgetsTabSection.qml:3838, Modules/Settings/PluginBrowser.qml:194, Modules/Settings/KeybindsTab.qml:458, Modules/Settings/DisplayConfig/NiriOutputSettings.qml:103, Modules/Settings/DisplayConfig/NiriOutputSettings.qml:106, Modules/Settings/DisplayConfig/NiriOutputSettings.qml:118, Modules/ControlCenter/BuiltinPlugins/TailscaleWidget.qml:254, Modules/Notifications/Center/HistoryNotificationList.qml:87", + "reference": "Modals/ProcessListModal.qml:383, Services/AppSearchService.qml:724, Services/AppSearchService.qml:745, dms-plugins/DankStickerSearch/DankStickerSearch.qml:101, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:101, Modals/DankLauncherV2/Controller.qml:745, Modals/DankLauncherV2/SpotlightLauncherContent.qml:440, Modals/DankLauncherV2/LauncherContent.qml:362, Modals/DankLauncherV2/LauncherContent.qml:651, Modals/Clipboard/ClipboardContent.qml:16, Modules/ProcessList/ProcessListPopout.qml:169, Modules/Settings/WidgetsTabSection.qml:3782, Modules/Settings/WidgetsTabSection.qml:3838, Modules/Settings/PluginBrowser.qml:194, Modules/Settings/KeybindsTab.qml:458, Modules/Settings/DisplayConfig/NiriOutputSettings.qml:103, Modules/Settings/DisplayConfig/NiriOutputSettings.qml:106, Modules/Settings/DisplayConfig/NiriOutputSettings.qml:118, Modules/ControlCenter/BuiltinPlugins/TailscaleWidget.qml:254, Modules/Notifications/Center/HistoryNotificationList.qml:87", "comment": "Tailscale filter: all devices | notification history filter | plugin browser category filter" }, - { - "term": "All Monitors", - "context": "All Monitors", - "reference": "Modules/Settings/LockScreenTab.qml:417, Modules/Settings/LockScreenTab.qml:427, Modules/Settings/LockScreenTab.qml:437, Modules/Settings/LockScreenTab.qml:441", - "comment": "" - }, { "term": "All checks passed", "context": "All checks passed", @@ -1316,7 +1304,7 @@ { "term": "Applications", "context": "Applications", - "reference": "Modals/DankLauncherV2/Controller.qml:208, Modals/Settings/SettingsSidebar.qml:277, Modules/Settings/ThemeColorsTab.qml:2147, Modules/Dock/DockLauncherButton.qml:25", + "reference": "Modals/DankLauncherV2/Controller.qml:198, Modals/Settings/SettingsSidebar.qml:277, Modules/Settings/ThemeColorsTab.qml:2147, Modules/Dock/DockLauncherButton.qml:25", "comment": "" }, { @@ -1364,7 +1352,7 @@ { "term": "Apply to Hardware", "context": "Apply to Hardware", - "reference": "Modules/Settings/BatteryTab.qml:198", + "reference": "Modules/Settings/BatteryTab.qml:196", "comment": "" }, { @@ -1604,7 +1592,7 @@ { "term": "Authentication error - try again", "context": "Authentication error - try again", - "reference": "Modules/Lock/LockScreenContent.qml:69, Modules/Greetd/GreeterContent.qml:245", + "reference": "Modules/Lock/LockScreenContent.qml:77, Modules/Greetd/GreeterContent.qml:246", "comment": "" }, { @@ -1676,7 +1664,7 @@ { "term": "Auto Power Saver", "context": "Auto Power Saver", - "reference": "Modules/Settings/BatteryTab.qml:257", + "reference": "Modules/Settings/BatteryTab.qml:324", "comment": "" }, { @@ -1886,7 +1874,7 @@ { "term": "Automatically turn on Power Saver profile when battery is low.", "context": "Automatically turn on Power Saver profile when battery is low.", - "reference": "Modules/Settings/BatteryTab.qml:258", + "reference": "Modules/Settings/BatteryTab.qml:325", "comment": "" }, { @@ -2129,28 +2117,34 @@ "reference": "Modules/DankBar/Popouts/BatteryPopout.qml:395", "comment": "" }, + { + "term": "Battery Alerts", + "context": "Battery Alerts", + "reference": "Modules/Settings/BatteryTab.qml:233", + "comment": "" + }, { "term": "Battery Charge Limit", "context": "Battery Charge Limit", - "reference": "Modules/Settings/BatteryTab.qml:182", + "reference": "Modules/Settings/BatteryTab.qml:175", "comment": "" }, { "term": "Battery Health", "context": "Battery Health", - "reference": "Modules/Settings/BatteryTab.qml:157", + "reference": "Modules/Settings/BatteryTab.qml:148", "comment": "" }, { "term": "Battery Power", "context": "Battery Power", - "reference": "Modules/Settings/BatteryTab.qml:67", + "reference": "Modules/Settings/BatteryTab.qml:71", "comment": "" }, { - "term": "Battery Protection & Charging", - "context": "Battery Protection & Charging", - "reference": "Modules/Settings/BatteryTab.qml:177", + "term": "Battery Protection", + "context": "Battery Protection", + "reference": "Modules/Settings/BatteryTab.qml:170", "comment": "" }, { @@ -2192,7 +2186,7 @@ { "term": "Battery percentage to trigger a critical alert.", "context": "Battery percentage to trigger a critical alert.", - "reference": "Modules/Settings/BatteryTab.qml:281", + "reference": "Modules/Settings/BatteryTab.qml:284", "comment": "" }, { @@ -2228,7 +2222,7 @@ { "term": "Bit Depth", "context": "Bit Depth", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2053", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2064", "comment": "" }, { @@ -2474,7 +2468,7 @@ { "term": "Browse", "context": "Browse", - "reference": "Modals/DankLauncherV2/SpotlightResultRow.qml:62, Modals/DankLauncherV2/Controller.qml:229, Modals/DankLauncherV2/Controller.qml:1377, Modules/Settings/ThemeColorsTab.qml:406, Modules/Settings/AutoStartTab.qml:487, Modules/Settings/PluginsTab.qml:236, Modules/Settings/LockScreenTab.qml:378, Modules/Settings/Widgets/SettingsWallpaperPicker.qml:57", + "reference": "Modals/DankLauncherV2/SpotlightResultRow.qml:62, Modals/DankLauncherV2/Controller.qml:219, Modals/DankLauncherV2/Controller.qml:1367, Modules/Settings/ThemeColorsTab.qml:406, Modules/Settings/AutoStartTab.qml:487, Modules/Settings/PluginsTab.qml:236, Modules/Settings/LockScreenTab.qml:378, Modules/Settings/Widgets/SettingsWallpaperPicker.qml:57", "comment": "theme category option" }, { @@ -2684,7 +2678,7 @@ { "term": "Caps Lock is on", "context": "Caps Lock is on", - "reference": "Modules/Lock/LockScreenContent.qml:1301", + "reference": "Modules/Lock/LockScreenContent.qml:1296", "comment": "" }, { @@ -2886,9 +2880,21 @@ "comment": "" }, { - "term": "Choose how to be notified about battery alerts.", - "context": "Choose how to be notified about battery alerts.", - "reference": "Modules/Settings/BatteryTab.qml:245", + "term": "Choose how to be notified about critical battery alerts.", + "context": "Choose how to be notified about critical battery alerts.", + "reference": "Modules/Settings/BatteryTab.qml:303", + "comment": "" + }, + { + "term": "Choose how to be notified about low battery alerts.", + "context": "Choose how to be notified about low battery alerts.", + "reference": "Modules/Settings/BatteryTab.qml:258", + "comment": "" + }, + { + "term": "Choose how to be notified when charge limit is reached.", + "context": "Choose how to be notified when charge limit is reached.", + "reference": "Modules/Settings/BatteryTab.qml:217", "comment": "" }, { @@ -2964,9 +2970,9 @@ "comment": "" }, { - "term": "Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.", - "context": "Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.", - "reference": "Modules/Settings/LockScreenTab.qml:404", + "term": "Choose which monitors show the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.", + "context": "Choose which monitors show the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.", + "reference": "Modules/Settings/LockScreenTab.qml:403", "comment": "" }, { @@ -3116,7 +3122,7 @@ { "term": "Clipboard", "context": "Clipboard", - "reference": "Services/AppSearchService.qml:223, Modals/DankLauncherV2/ResultItem.qml:228, Modals/DankLauncherV2/SpotlightResultRow.qml:68, Modals/DankLauncherV2/Controller.qml:222, Modals/DankLauncherV2/Controller.qml:1225, Modals/Settings/SettingsSidebar.qml:330", + "reference": "Services/AppSearchService.qml:223, Modals/DankLauncherV2/ResultItem.qml:228, Modals/DankLauncherV2/SpotlightResultRow.qml:68, Modals/DankLauncherV2/Controller.qml:212, Modals/DankLauncherV2/Controller.qml:1215, Modals/Settings/SettingsSidebar.qml:330", "comment": "" }, { @@ -3212,7 +3218,7 @@ { "term": "Color Management", "context": "Color Management", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2055", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2066", "comment": "" }, { @@ -3242,7 +3248,7 @@ { "term": "Color displayed on monitors without the lock screen", "context": "Color displayed on monitors without the lock screen", - "reference": "Modules/Settings/LockScreenTab.qml:472", + "reference": "Modules/Settings/LockScreenTab.qml:441", "comment": "" }, { @@ -3350,7 +3356,7 @@ { "term": "Commands", "context": "Commands", - "reference": "Modals/DankLauncherV2/Controller.qml:243", + "reference": "Modals/DankLauncherV2/Controller.qml:233", "comment": "" }, { @@ -3398,7 +3404,7 @@ { "term": "Config Format", "context": "Config Format", - "reference": "Modules/Settings/DisplayConfigTab.qml:508, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2017", + "reference": "Modules/Settings/DisplayConfigTab.qml:508, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2028", "comment": "" }, { @@ -3410,7 +3416,7 @@ { "term": "Config validation failed", "context": "Config validation failed", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2100, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2108", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2111, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2119", "comment": "" }, { @@ -3794,7 +3800,7 @@ { "term": "Copied to clipboard", "context": "Copied to clipboard", - "reference": "Services/ClipboardService.qml:250, dms-plugins/DankStickerSearch/DankStickerSearch.qml:230, dms-plugins/DankLauncherKeys/DankLauncherKeys.qml:154, dms-plugins/DankGifSearch/DankGifSearch.qml:175, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:230, dms-plugins/dms-plugins-official/DankLauncherKeys/DankLauncherKeys.qml:154, dms-plugins/dms-plugins-official/DankGifSearch/DankGifSearch.qml:175, Modals/Settings/SettingsModal.qml:325, Modals/Settings/SettingsModal.qml:342, Modules/Notepad/NotepadTextEditor.qml:328, Modules/Settings/DesktopWidgetInstanceCard.qml:426", + "reference": "Services/ClipboardService.qml:281, dms-plugins/DankStickerSearch/DankStickerSearch.qml:230, dms-plugins/DankLauncherKeys/DankLauncherKeys.qml:154, dms-plugins/DankGifSearch/DankGifSearch.qml:175, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:230, dms-plugins/dms-plugins-official/DankLauncherKeys/DankLauncherKeys.qml:154, dms-plugins/dms-plugins-official/DankGifSearch/DankGifSearch.qml:175, Modals/Settings/SettingsModal.qml:325, Modals/Settings/SettingsModal.qml:342, Modules/Notepad/NotepadTextEditor.qml:328, Modules/Settings/DesktopWidgetInstanceCard.qml:426", "comment": "" }, { @@ -3806,7 +3812,7 @@ { "term": "Copy", "context": "Copy", - "reference": "dms-plugins/DankLauncherKeys/DankLauncherKeys.qml:170, dms-plugins/dms-plugins-official/DankLauncherKeys/DankLauncherKeys.qml:170, Modals/DankLauncherV2/Controller.qml:1221, Modals/Clipboard/ClipboardContextMenu.qml:40, Modules/Settings/ClipboardTab.qml:156, Modules/ControlCenter/BuiltinPlugins/TailscaleWidget.qml:395", + "reference": "dms-plugins/DankLauncherKeys/DankLauncherKeys.qml:170, dms-plugins/dms-plugins-official/DankLauncherKeys/DankLauncherKeys.qml:170, Modals/DankLauncherV2/Controller.qml:1211, Modals/Clipboard/ClipboardContextMenu.qml:40, Modules/Settings/ClipboardTab.qml:156, Modules/ControlCenter/BuiltinPlugins/TailscaleWidget.qml:395", "comment": "Copy to clipboard" }, { @@ -3848,7 +3854,7 @@ { "term": "Copy path", "context": "Copy path", - "reference": "Modals/DankLauncherV2/Controller.qml:1217, Modals/FileBrowser/FileBrowserItemContextMenu.qml:28", + "reference": "Modals/DankLauncherV2/Controller.qml:1207, Modals/FileBrowser/FileBrowserItemContextMenu.qml:28", "comment": "" }, { @@ -3956,13 +3962,13 @@ { "term": "Critical Battery Alert", "context": "Critical Battery Alert", - "reference": "Modules/Settings/BatteryTab.qml:271", + "reference": "Modules/Settings/BatteryTab.qml:272", "comment": "" }, { "term": "Critical Battery Notifications", "context": "Critical Battery Notifications", - "reference": "Modules/Settings/BatteryTab.qml:291", + "reference": "Modules/Settings/BatteryTab.qml:294", "comment": "" }, { @@ -3974,7 +3980,7 @@ { "term": "Critical Threshold", "context": "Critical Threshold", - "reference": "Modules/Settings/BatteryTab.qml:280", + "reference": "Modules/Settings/BatteryTab.qml:283", "comment": "" }, { @@ -4058,7 +4064,7 @@ { "term": "Current Weather", "context": "Current Weather", - "reference": "Modules/Settings/TimeWeatherTab.qml:661", + "reference": "Modules/Settings/TimeWeatherTab.qml:663", "comment": "" }, { @@ -4268,7 +4274,7 @@ { "term": "DEMO MODE - Click anywhere to exit", "context": "DEMO MODE - Click anywhere to exit", - "reference": "Modules/Lock/LockScreenContent.qml:1319", + "reference": "Modules/Lock/LockScreenContent.qml:1314", "comment": "" }, { @@ -4622,7 +4628,7 @@ { "term": "Delete Saved Item?", "context": "Delete Saved Item?", - "reference": "Services/ClipboardService.qml:320", + "reference": "Services/ClipboardService.qml:349", "comment": "" }, { @@ -4850,7 +4856,7 @@ { "term": "Disabled", "context": "Disabled", - "reference": "Modules/Settings/ThemeColorsTab.qml:1507, Modules/Settings/AutoStartTab.qml:721, Modules/Settings/LockScreenTab.qml:148, Modules/Settings/DankBarTab.qml:413, Modules/Settings/NotificationsTab.qml:774, Modules/Settings/NetworkWifiTab.qml:179, Modules/Settings/DisplayConfig/OutputCard.qml:128, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2031, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2037, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2039, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2051, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2065, Modules/ControlCenter/Components/DragDropGrid.qml:507", + "reference": "Modules/Settings/ThemeColorsTab.qml:1507, Modules/Settings/AutoStartTab.qml:721, Modules/Settings/LockScreenTab.qml:148, Modules/Settings/DankBarTab.qml:413, Modules/Settings/NotificationsTab.qml:774, Modules/Settings/NetworkWifiTab.qml:179, Modules/Settings/DisplayConfig/OutputCard.qml:128, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2042, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2048, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2050, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2062, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2076, Modules/ControlCenter/Components/DragDropGrid.qml:507", "comment": "bluetooth status | lock screen notification mode option" }, { @@ -5066,7 +5072,7 @@ { "term": "Display setup failed", "context": "Display setup failed", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:1461", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:1472", "comment": "" }, { @@ -5174,7 +5180,7 @@ { "term": "Don't Change", "context": "Don't Change", - "reference": "Modules/Settings/PowerSleepTab.qml:154, Modules/Settings/BatteryTab.qml:309, Modules/Settings/BatteryTab.qml:327", + "reference": "Modules/Settings/PowerSleepTab.qml:154, Modules/Settings/BatteryTab.qml:336, Modules/Settings/BatteryTab.qml:354", "comment": "" }, { @@ -5324,7 +5330,7 @@ { "term": "Dynamic colors parse error: %1", "context": "Dynamic colors parse error: %1", - "reference": "Common/Theme.qml:2237", + "reference": "Common/Theme.qml:2240", "comment": "" }, { @@ -5564,7 +5570,7 @@ { "term": "Enabled", "context": "Enabled", - "reference": "Modules/Settings/ThemeColorsTab.qml:1507, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2031, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2039, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2065, Modules/ControlCenter/Components/DragDropGrid.qml:508", + "reference": "Modules/Settings/ThemeColorsTab.qml:1507, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2042, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2050, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2076, Modules/ControlCenter/Components/DragDropGrid.qml:508", "comment": "bluetooth status" }, { @@ -5780,13 +5786,13 @@ { "term": "Entry pinned", "context": "Entry pinned", - "reference": "Services/ClipboardService.qml:355", + "reference": "Services/ClipboardService.qml:384", "comment": "" }, { "term": "Entry unpinned", "context": "Entry unpinned", - "reference": "Services/ClipboardService.qml:369", + "reference": "Services/ClipboardService.qml:398", "comment": "" }, { @@ -5810,7 +5816,7 @@ { "term": "Estimated Time", "context": "Estimated Time", - "reference": "Modules/Settings/BatteryTab.qml:133", + "reference": "Modules/Settings/BatteryTab.qml:127", "comment": "" }, { @@ -5996,13 +6002,13 @@ { "term": "Failed to apply GTK colors", "context": "Failed to apply GTK colors", - "reference": "Common/Theme.qml:1969", + "reference": "Common/Theme.qml:1972", "comment": "" }, { "term": "Failed to apply Qt colors", "context": "Failed to apply Qt colors", - "reference": "Common/Theme.qml:1990", + "reference": "Common/Theme.qml:1993", "comment": "" }, { @@ -6014,7 +6020,7 @@ { "term": "Failed to apply profile", "context": "Failed to apply profile", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:530", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:541", "comment": "" }, { @@ -6038,7 +6044,7 @@ { "term": "Failed to check pin limit", "context": "Failed to check pin limit", - "reference": "Services/ClipboardService.qml:338", + "reference": "Services/ClipboardService.qml:367", "comment": "" }, { @@ -6056,7 +6062,7 @@ { "term": "Failed to copy entry", "context": "Failed to copy entry", - "reference": "Services/ClipboardService.qml:247, Services/ClipboardService.qml:278", + "reference": "Services/ClipboardService.qml:278, Services/ClipboardService.qml:307", "comment": "" }, { @@ -6206,7 +6212,7 @@ { "term": "Failed to parse plugin_settings.json", "context": "Failed to parse plugin_settings.json", - "reference": "Common/SettingsData.qml:1876", + "reference": "Common/SettingsData.qml:1888", "comment": "" }, { @@ -6218,7 +6224,7 @@ { "term": "Failed to parse settings.json", "context": "Failed to parse settings.json", - "reference": "Common/SettingsData.qml:1778, Common/SettingsData.qml:3642", + "reference": "Common/SettingsData.qml:1790, Common/SettingsData.qml:3654", "comment": "" }, { @@ -6230,7 +6236,7 @@ { "term": "Failed to pin entry", "context": "Failed to pin entry", - "reference": "Services/ClipboardService.qml:352", + "reference": "Services/ClipboardService.qml:381", "comment": "" }, { @@ -6242,7 +6248,7 @@ { "term": "Failed to read theme file: %1", "context": "Failed to read theme file: %1", - "reference": "Common/Theme.qml:2192", + "reference": "Common/Theme.qml:2195", "comment": "" }, { @@ -6338,7 +6344,7 @@ { "term": "Failed to save profile", "context": "Failed to save profile", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:612", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:623", "comment": "" }, { @@ -6422,7 +6428,7 @@ { "term": "Failed to unpin entry", "context": "Failed to unpin entry", - "reference": "Services/ClipboardService.qml:366", + "reference": "Services/ClipboardService.qml:395", "comment": "" }, { @@ -6476,13 +6482,13 @@ { "term": "Failed to write outputs config.", "context": "Failed to write outputs config.", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:1461", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:1472", "comment": "" }, { "term": "Failed to write temp file for validation", "context": "Failed to write temp file for validation", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2099", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2110", "comment": "" }, { @@ -6506,13 +6512,13 @@ { "term": "Feels Like", "context": "Feels Like", - "reference": "Modules/Settings/TimeWeatherTab.qml:918", + "reference": "Modules/Settings/TimeWeatherTab.qml:920", "comment": "" }, { "term": "Feels Like %1°", "context": "Feels Like %1°", - "reference": "Modules/DankDash/WeatherTab.qml:314, Modules/Settings/TimeWeatherTab.qml:805", + "reference": "Modules/DankDash/WeatherTab.qml:314, Modules/Settings/TimeWeatherTab.qml:807", "comment": "weather feels like temperature" }, { @@ -6584,7 +6590,7 @@ { "term": "Files", "context": "Files", - "reference": "Modals/DankLauncherV2/Controller.qml:236, Modals/DankLauncherV2/Controller.qml:1125, Modals/DankLauncherV2/Controller.qml:1146, Modals/DankLauncherV2/SpotlightLauncherContent.qml:448, Modals/DankLauncherV2/LauncherContent.qml:372, Modals/DankLauncherV2/LauncherContent.qml:656, Modules/Settings/LauncherTab.qml:902", + "reference": "Modals/DankLauncherV2/Controller.qml:226, Modals/DankLauncherV2/Controller.qml:1115, Modals/DankLauncherV2/Controller.qml:1136, Modals/DankLauncherV2/SpotlightLauncherContent.qml:448, Modals/DankLauncherV2/LauncherContent.qml:372, Modals/DankLauncherV2/LauncherContent.qml:656, Modules/Settings/LauncherTab.qml:902", "comment": "" }, { @@ -6644,13 +6650,13 @@ { "term": "Fingerprint error", "context": "Fingerprint error", - "reference": "Modules/Lock/LockScreenContent.qml:75", + "reference": "Modules/Lock/LockScreenContent.qml:83", "comment": "" }, { "term": "Fingerprint not recognized (%1/%2). Please try again or use password.", "context": "Fingerprint not recognized (%1/%2). Please try again or use password.", - "reference": "Modules/Lock/LockScreenContent.qml:79", + "reference": "Modules/Lock/LockScreenContent.qml:87", "comment": "" }, { @@ -6698,25 +6704,25 @@ { "term": "Flipped", "context": "Flipped", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2605, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2626", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2616, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2637", "comment": "" }, { "term": "Flipped 180°", "context": "Flipped 180°", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2609, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2630", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2620, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2641", "comment": "" }, { "term": "Flipped 270°", "context": "Flipped 270°", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2611, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2632", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2622, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2643", "comment": "" }, { "term": "Flipped 90°", "context": "Flipped 90°", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2607, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2628", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2618, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2639", "comment": "" }, { @@ -6788,7 +6794,7 @@ { "term": "Focus at Startup", "context": "Focus at Startup", - "reference": "Modules/Settings/DisplayConfig/NiriOutputSettings.qml:77, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2041", + "reference": "Modules/Settings/DisplayConfig/NiriOutputSettings.qml:77, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2052", "comment": "" }, { @@ -6842,7 +6848,7 @@ { "term": "Folders", "context": "Folders", - "reference": "Modals/DankLauncherV2/Controller.qml:1136, Modals/DankLauncherV2/Controller.qml:1146, Modals/DankLauncherV2/LauncherContent.qml:661, Modules/Settings/LauncherTab.qml:913", + "reference": "Modals/DankLauncherV2/Controller.qml:1126, Modals/DankLauncherV2/Controller.qml:1136, Modals/DankLauncherV2/LauncherContent.qml:661, Modules/Settings/LauncherTab.qml:913", "comment": "" }, { @@ -6956,7 +6962,7 @@ { "term": "Force HDR", "context": "Force HDR", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2061", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2072", "comment": "" }, { @@ -6980,7 +6986,7 @@ { "term": "Force Wide Color", "context": "Force Wide Color", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2063", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2074", "comment": "" }, { @@ -7220,7 +7226,7 @@ { "term": "GTK colors applied successfully", "context": "GTK colors applied successfully", - "reference": "Common/Theme.qml:1965", + "reference": "Common/Theme.qml:1968", "comment": "" }, { @@ -7310,7 +7316,7 @@ { "term": "Good", "context": "Good", - "reference": "Modules/Settings/TimeWeatherTab.qml:1163", + "reference": "Modules/Settings/TimeWeatherTab.qml:1165", "comment": "" }, { @@ -7808,7 +7814,7 @@ { "term": "History cleared. %1 pinned entries kept.", "context": "History cleared. %1 pinned entries kept.", - "reference": "Services/ClipboardService.qml:385", + "reference": "Services/ClipboardService.qml:414", "comment": "" }, { @@ -7868,7 +7874,7 @@ { "term": "Hot Corners", "context": "Hot Corners", - "reference": "Modules/Settings/DisplayConfig/NiriOutputSettings.qml:85, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2043", + "reference": "Modules/Settings/DisplayConfig/NiriOutputSettings.qml:85, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2054", "comment": "" }, { @@ -7928,7 +7934,7 @@ { "term": "Humidity", "context": "Humidity", - "reference": "dms-plugins/DankDesktopWeather/DankDesktopWeather.qml:413, dms-plugins/dms-plugins-official/DankDesktopWeather/DankDesktopWeather.qml:413, Modules/DankDash/WeatherTab.qml:88, Modules/DankDash/WeatherForecastCard.qml:80, Modules/Settings/TimeWeatherTab.qml:962", + "reference": "dms-plugins/DankDesktopWeather/DankDesktopWeather.qml:413, dms-plugins/dms-plugins-official/DankDesktopWeather/DankDesktopWeather.qml:413, Modules/DankDash/WeatherTab.qml:88, Modules/DankDash/WeatherForecastCard.qml:80, Modules/Settings/TimeWeatherTab.qml:964", "comment": "" }, { @@ -7958,13 +7964,13 @@ { "term": "Hyprland conf mode", "context": "Hyprland conf mode", - "reference": "Services/KeybindsService.qml:550, Modules/Settings/ThemeColorsTab.qml:146, Modules/Settings/ThemeColorsTab.qml:2219, Modules/Settings/WindowRulesTab.qml:344, Modules/Settings/WindowRulesTab.qml:501, Modules/Settings/KeybindsTab.qml:383, Modules/Settings/CompositorLayoutTab.qml:97, Modules/Settings/CompositorLayoutTab.qml:196, Modules/Settings/DisplayConfig/IncludeWarningBox.qml:48, Modules/Settings/DisplayConfig/DisplayConfigState.qml:1475", + "reference": "Services/KeybindsService.qml:550, Modules/Settings/ThemeColorsTab.qml:146, Modules/Settings/ThemeColorsTab.qml:2219, Modules/Settings/WindowRulesTab.qml:344, Modules/Settings/WindowRulesTab.qml:501, Modules/Settings/KeybindsTab.qml:383, Modules/Settings/CompositorLayoutTab.qml:97, Modules/Settings/CompositorLayoutTab.qml:196, Modules/Settings/DisplayConfig/IncludeWarningBox.qml:48, Modules/Settings/DisplayConfig/DisplayConfigState.qml:1486", "comment": "" }, { "term": "Hyprland conf mode is read-only in Settings", "context": "Hyprland conf mode is read-only in Settings", - "reference": "Modules/Settings/KeybindsTab.qml:299, Modules/Settings/DisplayConfig/DisplayConfigState.qml:515", + "reference": "Modules/Settings/KeybindsTab.qml:299, Modules/Settings/DisplayConfig/DisplayConfigState.qml:526", "comment": "" }, { @@ -8036,7 +8042,7 @@ { "term": "Icon theme changed outside DMS; switched to System Default", "context": "Icon theme changed outside DMS; switched to System Default", - "reference": "Common/SettingsData.qml:1532", + "reference": "Common/SettingsData.qml:1533", "comment": "shown when an external tool overrides the icon theme DMS applied" }, { @@ -8102,7 +8108,7 @@ { "term": "Image", "context": "Image", - "reference": "Modals/DankLauncherV2/Controller.qml:1225, Modals/Clipboard/ClipboardEntry.qml:186, Modals/Clipboard/ClipboardContent.qml:16", + "reference": "Modals/DankLauncherV2/Controller.qml:1215, Modals/Clipboard/ClipboardEntry.qml:195, Modals/Clipboard/ClipboardContent.qml:16", "comment": "" }, { @@ -8114,7 +8120,7 @@ { "term": "Image copied to clipboard", "context": "Image copied to clipboard", - "reference": "Services/ClipboardService.qml:250", + "reference": "Services/ClipboardService.qml:281", "comment": "" }, { @@ -8132,7 +8138,7 @@ { "term": "Inactive Monitor Color", "context": "Inactive Monitor Color", - "reference": "Modules/Settings/LockScreenTab.qml:466, Modules/Settings/LockScreenTab.qml:497", + "reference": "Modules/Settings/LockScreenTab.qml:435, Modules/Settings/LockScreenTab.qml:466", "comment": "" }, { @@ -8180,25 +8186,25 @@ { "term": "Incorrect password", "context": "Incorrect password", - "reference": "Modals/WifiPasswordModal.qml:363, Modules/Greetd/GreeterContent.qml:257", + "reference": "Modals/WifiPasswordModal.qml:363, Modules/Greetd/GreeterContent.qml:258", "comment": "" }, { "term": "Incorrect password - attempt %1 of %2 (lockout may follow)", "context": "Incorrect password - attempt %1 of %2 (lockout may follow)", - "reference": "Modules/Greetd/GreeterContent.qml:253", + "reference": "Modules/Greetd/GreeterContent.qml:254", "comment": "" }, { "term": "Incorrect password - next failures may trigger account lockout", "context": "Incorrect password - next failures may trigger account lockout", - "reference": "Modules/Greetd/GreeterContent.qml:255", + "reference": "Modules/Greetd/GreeterContent.qml:256", "comment": "" }, { "term": "Incorrect password - try again", "context": "Incorrect password - try again", - "reference": "Modules/Lock/LockScreenContent.qml:73", + "reference": "Modules/Lock/LockScreenContent.qml:81", "comment": "" }, { @@ -8282,7 +8288,7 @@ { "term": "Insert your security key...", "context": "Insert your security key...", - "reference": "Modules/Lock/LockScreenContent.qml:63", + "reference": "Modules/Lock/LockScreenContent.qml:71", "comment": "" }, { @@ -8444,13 +8450,13 @@ { "term": "Invalid JSON format: %1", "context": "Invalid JSON format: %1", - "reference": "Common/Theme.qml:2178", + "reference": "Common/Theme.qml:2181", "comment": "" }, { "term": "Invalid configuration", "context": "Invalid configuration", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2107", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2118", "comment": "" }, { @@ -8726,7 +8732,7 @@ { "term": "Launch", "context": "Launch", - "reference": "Modals/DankLauncherV2/LauncherContextMenu.qml:230, Modals/DankLauncherV2/Controller.qml:1199, Modals/DankLauncherV2/Controller.qml:1657, Modules/DankDash/Overview/CalendarOverviewCard.qml:265", + "reference": "Modals/DankLauncherV2/LauncherContextMenu.qml:230, Modals/DankLauncherV2/Controller.qml:1189, Modals/DankLauncherV2/Controller.qml:1647, Modules/DankDash/Overview/CalendarOverviewCard.qml:265", "comment": "" }, { @@ -8774,7 +8780,7 @@ { "term": "Layout", "context": "Layout", - "reference": "Modules/Settings/WidgetsTab.qml:55, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2045, Modules/Settings/DesktopWidgetSettings/SystemMonitorSettings.qml:350, Modules/DankBar/Popouts/DWLLayoutPopout.qml:169", + "reference": "Modules/Settings/WidgetsTab.qml:55, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2056, Modules/Settings/DesktopWidgetSettings/SystemMonitorSettings.qml:350, Modules/DankBar/Popouts/DWLLayoutPopout.qml:169", "comment": "" }, { @@ -8864,7 +8870,7 @@ { "term": "Limit the maximum battery charge level to extend lifespan.", "context": "Limit the maximum battery charge level to extend lifespan.", - "reference": "Modules/Settings/BatteryTab.qml:183", + "reference": "Modules/Settings/BatteryTab.qml:176", "comment": "" }, { @@ -8966,7 +8972,7 @@ { "term": "Location Search", "context": "Location Search", - "reference": "Modules/Settings/TimeWeatherTab.qml:630", + "reference": "Modules/Settings/TimeWeatherTab.qml:632", "comment": "" }, { @@ -9080,7 +9086,7 @@ { "term": "Long Text", "context": "Long Text", - "reference": "Modals/Clipboard/ClipboardEntry.qml:188, Modals/Clipboard/ClipboardContent.qml:16", + "reference": "Modals/Clipboard/ClipboardEntry.qml:197, Modals/Clipboard/ClipboardContent.qml:16", "comment": "" }, { @@ -9092,7 +9098,7 @@ { "term": "Longitude", "context": "Longitude", - "reference": "Modules/Settings/ThemeColorsTab.qml:1428, Modules/Settings/GammaControlTab.qml:402, Modules/Settings/TimeWeatherTab.qml:579", + "reference": "Modules/Settings/ThemeColorsTab.qml:1428, Modules/Settings/GammaControlTab.qml:402, Modules/Settings/TimeWeatherTab.qml:580", "comment": "" }, { @@ -9110,13 +9116,13 @@ { "term": "Low Battery Notifications", "context": "Low Battery Notifications", - "reference": "Modules/Settings/BatteryTab.qml:236", + "reference": "Modules/Settings/BatteryTab.qml:249", "comment": "" }, { "term": "Low Battery Threshold", "context": "Low Battery Threshold", - "reference": "Modules/Settings/BatteryTab.qml:225", + "reference": "Modules/Settings/BatteryTab.qml:238", "comment": "" }, { @@ -9494,7 +9500,7 @@ { "term": "Maximum fingerprint attempts reached. Please use password.", "context": "Maximum fingerprint attempts reached. Please use password.", - "reference": "Modules/Lock/LockScreenContent.qml:77", + "reference": "Modules/Lock/LockScreenContent.qml:85", "comment": "" }, { @@ -9518,7 +9524,7 @@ { "term": "Maximum pinned entries reached", "context": "Maximum pinned entries reached", - "reference": "Services/ClipboardService.qml:344", + "reference": "Services/ClipboardService.qml:373", "comment": "" }, { @@ -9740,7 +9746,7 @@ { "term": "Mode", "context": "Mode", - "reference": "Modules/Settings/DankBarTab.qml:1328, Modules/Settings/FrameTab.qml:50, Modules/Settings/NetworkWifiTab.qml:763, Modules/Settings/NetworkWifiTab.qml:1124, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2025", + "reference": "Modules/Settings/DankBarTab.qml:1328, Modules/Settings/FrameTab.qml:50, Modules/Settings/NetworkWifiTab.qml:763, Modules/Settings/NetworkWifiTab.qml:1124, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2036", "comment": "" }, { @@ -9752,13 +9758,13 @@ { "term": "Model", "context": "Model", - "reference": "Modules/Settings/DisplayWidgetsTab.qml:225, Modules/Settings/PrinterTab.qml:1159, Modules/Settings/DisplayConfigTab.qml:517, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2016", + "reference": "Modules/Settings/DisplayWidgetsTab.qml:225, Modules/Settings/PrinterTab.qml:1159, Modules/Settings/DisplayConfigTab.qml:517, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2027", "comment": "" }, { "term": "Modified", "context": "Modified", - "reference": "Modals/DankLauncherV2/LauncherContent.qml:729, Modals/DankLauncherV2/LauncherContent.qml:736, Modals/DankLauncherV2/LauncherContent.qml:742, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2043, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2045", + "reference": "Modals/DankLauncherV2/LauncherContent.qml:729, Modals/DankLauncherV2/LauncherContent.qml:736, Modals/DankLauncherV2/LauncherContent.qml:742, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2054, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2056", "comment": "" }, { @@ -9974,7 +9980,7 @@ { "term": "Name", "context": "Name", - "reference": "Modals/DankLauncherV2/LauncherContent.qml:727, Modals/DankLauncherV2/LauncherContent.qml:736, Modals/DankLauncherV2/LauncherContent.qml:741, Modals/DankLauncherV2/LauncherContent.qml:924, Modules/ProcessList/ProcessesView.qml:249, Modules/Settings/PluginBrowser.qml:55, Modules/Settings/DisplayWidgetsTab.qml:225, Modules/Settings/AutoStartTab.qml:540, Modules/Settings/DesktopWidgetInstanceCard.qml:203, Modules/Settings/PrinterTab.qml:754, Modules/Settings/DisplayConfigTab.qml:517, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2016, Modules/Settings/Widgets/SystemMonitorVariantCard.qml:144", + "reference": "Modals/DankLauncherV2/LauncherContent.qml:727, Modals/DankLauncherV2/LauncherContent.qml:736, Modals/DankLauncherV2/LauncherContent.qml:741, Modals/DankLauncherV2/LauncherContent.qml:924, Modules/ProcessList/ProcessesView.qml:249, Modules/Settings/PluginBrowser.qml:55, Modules/Settings/DisplayWidgetsTab.qml:225, Modules/Settings/AutoStartTab.qml:540, Modules/Settings/DesktopWidgetInstanceCard.qml:203, Modules/Settings/PrinterTab.qml:754, Modules/Settings/DisplayConfigTab.qml:517, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2027, Modules/Settings/Widgets/SystemMonitorVariantCard.qml:144", "comment": "plugin browser sort option" }, { @@ -10130,7 +10136,7 @@ { "term": "New York, NY", "context": "New York, NY", - "reference": "Modules/Settings/TimeWeatherTab.qml:640", + "reference": "Modules/Settings/TimeWeatherTab.qml:642", "comment": "" }, { @@ -10214,7 +10220,7 @@ { "term": "No", "context": "No", - "reference": "Modules/Settings/PrinterTab.qml:1169, Modules/Settings/WindowRulesTab.qml:77, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2037, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2041, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2051, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2061, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2063", + "reference": "Modules/Settings/PrinterTab.qml:1169, Modules/Settings/WindowRulesTab.qml:77, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2048, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2052, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2062, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2072, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2074", "comment": "" }, { @@ -10334,7 +10340,7 @@ { "term": "No Weather Data Available", "context": "No Weather Data Available", - "reference": "Modules/DankDash/WeatherTab.qml:139, Modules/Settings/TimeWeatherTab.qml:679", + "reference": "Modules/DankDash/WeatherTab.qml:139, Modules/Settings/TimeWeatherTab.qml:681", "comment": "" }, { @@ -10754,7 +10760,7 @@ { "term": "No trigger", "context": "No trigger", - "reference": "Modals/DankLauncherV2/Controller.qml:1379, Modules/Settings/LauncherTab.qml:1028", + "reference": "Modals/DankLauncherV2/Controller.qml:1369, Modules/Settings/LauncherTab.qml:1028", "comment": "" }, { @@ -10850,7 +10856,7 @@ { "term": "Normal", "context": "Normal", - "reference": "Modals/WindowRuleModal.qml:1178, Modules/Settings/TypographyMotionTab.qml:458, Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2597, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2613, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2618", + "reference": "Modals/WindowRuleModal.qml:1178, Modules/Settings/TypographyMotionTab.qml:458, Modules/Settings/DisplayConfig/OutputCard.qml:311, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2608, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2624, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2629", "comment": "" }, { @@ -10970,7 +10976,7 @@ { "term": "Notification", "context": "Notification", - "reference": "Modules/Settings/BatteryTab.qml:246", + "reference": "Modules/Settings/BatteryTab.qml:218, Modules/Settings/BatteryTab.qml:259, Modules/Settings/BatteryTab.qml:304", "comment": "" }, { @@ -11018,7 +11024,7 @@ { "term": "Notification Type", "context": "Notification Type", - "reference": "Modules/Settings/BatteryTab.qml:244", + "reference": "Modules/Settings/BatteryTab.qml:216, Modules/Settings/BatteryTab.qml:257, Modules/Settings/BatteryTab.qml:302", "comment": "" }, { @@ -11036,7 +11042,7 @@ { "term": "Notify when limit is reached", "context": "Notify when limit is reached", - "reference": "Modules/Settings/BatteryTab.qml:210", + "reference": "Modules/Settings/BatteryTab.qml:208", "comment": "" }, { @@ -11198,7 +11204,7 @@ { "term": "Open", "context": "Open", - "reference": "Modals/DankLauncherV2/Controller.qml:1203, Modals/DankLauncherV2/Controller.qml:1207, Modals/DankLauncherV2/Controller.qml:1217, Modules/Notepad/NotepadTextEditor.qml:864, Modules/Settings/NetworkWifiTab.qml:566, Modules/Settings/NetworkWifiTab.qml:773, Modules/Settings/NetworkWifiTab.qml:969, Modules/Settings/NetworkWifiTab.qml:1134, Modules/ControlCenter/Details/NetworkDetail.qml:627, Modules/Notifications/Center/NotificationCard.qml:804, Modules/Notifications/Center/NotificationCard.qml:943", + "reference": "Modals/DankLauncherV2/Controller.qml:1193, Modals/DankLauncherV2/Controller.qml:1197, Modals/DankLauncherV2/Controller.qml:1207, Modules/Notepad/NotepadTextEditor.qml:864, Modules/Settings/NetworkWifiTab.qml:566, Modules/Settings/NetworkWifiTab.qml:773, Modules/Settings/NetworkWifiTab.qml:969, Modules/Settings/NetworkWifiTab.qml:1134, Modules/ControlCenter/Details/NetworkDetail.qml:627, Modules/Notifications/Center/NotificationCard.qml:804, Modules/Notifications/Center/NotificationCard.qml:943", "comment": "" }, { @@ -11270,7 +11276,7 @@ { "term": "Open folder", "context": "Open folder", - "reference": "Modals/DankLauncherV2/Controller.qml:1217", + "reference": "Modals/DankLauncherV2/Controller.qml:1207", "comment": "" }, { @@ -11282,7 +11288,7 @@ { "term": "Open in terminal", "context": "Open in terminal", - "reference": "Modals/DankLauncherV2/Controller.qml:1217", + "reference": "Modals/DankLauncherV2/Controller.qml:1207", "comment": "" }, { @@ -11708,7 +11714,13 @@ { "term": "Paste", "context": "Paste", - "reference": "dms-plugins/DankStickerSearch/DankStickerSearch.qml:248, dms-plugins/DankGifSearch/DankGifSearch.qml:195, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:248, dms-plugins/dms-plugins-official/DankGifSearch/DankGifSearch.qml:195, Modals/DankLauncherV2/Controller.qml:1222, Modals/Clipboard/ClipboardContextMenu.qml:70, Modules/Settings/ClipboardTab.qml:156", + "reference": "dms-plugins/DankStickerSearch/DankStickerSearch.qml:248, dms-plugins/DankGifSearch/DankGifSearch.qml:195, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:248, dms-plugins/dms-plugins-official/DankGifSearch/DankGifSearch.qml:195, Modals/DankLauncherV2/Controller.qml:1212, Modals/Clipboard/ClipboardContextMenu.qml:70, Modules/Settings/ClipboardTab.qml:156", + "comment": "" + }, + { + "term": "Paste failed: %1", + "context": "Paste failed: %1", + "reference": "Services/ClipboardService.qml:82", "comment": "" }, { @@ -11876,7 +11888,7 @@ { "term": "Pinned", "context": "Pinned", - "reference": "Modals/WindowRuleModal.qml:919, Modals/DankLauncherV2/Controller.qml:201, Modals/DankLauncherV2/Controller.qml:1225, Modules/Settings/WindowRulesTab.qml:56, Modules/ControlCenter/Details/BluetoothDetail.qml:357, Modules/ControlCenter/Details/BrightnessDetail.qml:239, Modules/ControlCenter/Details/AudioInputDetail.qml:297, Modules/ControlCenter/Details/AudioOutputDetail.qml:306, Modules/ControlCenter/Details/NetworkDetail.qml:696", + "reference": "Modals/WindowRuleModal.qml:919, Modals/DankLauncherV2/Controller.qml:191, Modals/DankLauncherV2/Controller.qml:1215, Modules/Settings/WindowRulesTab.qml:56, Modules/ControlCenter/Details/BluetoothDetail.qml:357, Modules/ControlCenter/Details/BrightnessDetail.qml:239, Modules/ControlCenter/Details/AudioInputDetail.qml:297, Modules/ControlCenter/Details/AudioOutputDetail.qml:306, Modules/ControlCenter/Details/NetworkDetail.qml:696", "comment": "" }, { @@ -12152,7 +12164,7 @@ { "term": "Position", "context": "Position", - "reference": "Modules/Settings/DockTab.qml:106, Modules/Settings/DankBarTab.qml:482, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2023", + "reference": "Modules/Settings/DockTab.qml:106, Modules/Settings/DankBarTab.qml:482, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2034", "comment": "" }, { @@ -12228,9 +12240,9 @@ "comment": "" }, { - "term": "Power Profiles Auto-Switching", - "context": "Power Profiles Auto-Switching", - "reference": "Modules/Settings/BatteryTab.qml:302", + "term": "Power Profiles & Saving", + "context": "Power Profiles & Saving", + "reference": "Modules/Settings/BatteryTab.qml:319", "comment": "" }, { @@ -12254,19 +12266,19 @@ { "term": "Power profile to use when AC power is connected.", "context": "Power profile to use when AC power is connected.", - "reference": "Modules/Settings/BatteryTab.qml:308", + "reference": "Modules/Settings/BatteryTab.qml:335", "comment": "" }, { "term": "Power profile to use when running on battery power.", "context": "Power profile to use when running on battery power.", - "reference": "Modules/Settings/BatteryTab.qml:326", + "reference": "Modules/Settings/BatteryTab.qml:353", "comment": "" }, { "term": "Power source", "context": "Power source", - "reference": "Modules/Settings/PowerSleepTab.qml:42, Modules/Settings/BatteryTab.qml:61", + "reference": "Modules/Settings/PowerSleepTab.qml:42, Modules/Settings/BatteryTab.qml:64", "comment": "" }, { @@ -12338,7 +12350,7 @@ { "term": "Pressure", "context": "Pressure", - "reference": "dms-plugins/DankDesktopWeather/DankDesktopWeather.qml:453, dms-plugins/dms-plugins-official/DankDesktopWeather/DankDesktopWeather.qml:453, Modules/DankDash/WeatherTab.qml:98, Modules/DankDash/WeatherForecastCard.qml:90, Modules/Settings/TimeWeatherTab.qml:1063", + "reference": "dms-plugins/DankDesktopWeather/DankDesktopWeather.qml:453, dms-plugins/dms-plugins-official/DankDesktopWeather/DankDesktopWeather.qml:453, Modules/DankDash/WeatherTab.qml:98, Modules/DankDash/WeatherForecastCard.qml:90, Modules/Settings/TimeWeatherTab.qml:1065", "comment": "" }, { @@ -12548,13 +12560,13 @@ { "term": "Profile not found", "context": "Profile not found", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:333, Modules/Settings/DisplayConfig/DisplayConfigState.qml:634", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:333, Modules/Settings/DisplayConfig/DisplayConfigState.qml:645", "comment": "" }, { "term": "Profile not found in monitors.json", "context": "Profile not found in monitors.json", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:683", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:694", "comment": "" }, { @@ -12566,13 +12578,13 @@ { "term": "Profile when Plugged In (AC)", "context": "Profile when Plugged In (AC)", - "reference": "Modules/Settings/BatteryTab.qml:307", + "reference": "Modules/Settings/BatteryTab.qml:334", "comment": "" }, { "term": "Profile when on Battery", "context": "Profile when on Battery", - "reference": "Modules/Settings/BatteryTab.qml:325", + "reference": "Modules/Settings/BatteryTab.qml:352", "comment": "" }, { @@ -12590,7 +12602,7 @@ { "term": "Qt colors applied successfully", "context": "Qt colors applied successfully", - "reference": "Common/Theme.qml:1986", + "reference": "Common/Theme.qml:1989", "comment": "" }, { @@ -12674,7 +12686,7 @@ { "term": "Rain Chance", "context": "Rain Chance", - "reference": "Modules/Settings/TimeWeatherTab.qml:1113", + "reference": "Modules/Settings/TimeWeatherTab.qml:1115", "comment": "" }, { @@ -13376,13 +13388,13 @@ { "term": "SDR Brightness", "context": "SDR Brightness", - "reference": "Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml:255, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2057", + "reference": "Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml:255, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2068", "comment": "" }, { "term": "SDR Saturation", "context": "SDR Saturation", - "reference": "Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml:289, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2059", + "reference": "Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml:289, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2070", "comment": "" }, { @@ -13502,7 +13514,7 @@ { "term": "Saved item deleted", "context": "Saved item deleted", - "reference": "Services/ClipboardService.qml:330", + "reference": "Services/ClipboardService.qml:359", "comment": "" }, { @@ -13520,7 +13532,7 @@ { "term": "Scale", "context": "Scale", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:193, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2027", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:193, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2038", "comment": "" }, { @@ -13790,7 +13802,7 @@ { "term": "Select", "context": "Select", - "reference": "Modals/DankLauncherV2/Controller.qml:1530", + "reference": "Modals/DankLauncherV2/Controller.qml:1520", "comment": "" }, { @@ -14144,7 +14156,7 @@ { "term": "Set the percentage at which the battery is considered low.", "context": "Set the percentage at which the battery is considered low.", - "reference": "Modules/Settings/BatteryTab.qml:226", + "reference": "Modules/Settings/BatteryTab.qml:239", "comment": "" }, { @@ -14162,7 +14174,7 @@ { "term": "Settings", "context": "Settings", - "reference": "Services/AppSearchService.qml:171, Services/AppSearchService.qml:324, Services/AppSearchService.qml:680, Services/PopoutService.qml:447, Services/PopoutService.qml:464, Modals/DankLauncherV2/Controller.qml:215, Modals/Settings/SettingsSidebar.qml:128, Modals/Settings/SettingsModal.qml:88, Modals/Settings/SettingsModal.qml:232, Modules/DankDash/DankDashPopout.qml:34, Modules/Settings/DankDashTab.qml:42, Modules/Dock/DockTrashContextMenu.qml:48", + "reference": "Services/AppSearchService.qml:171, Services/AppSearchService.qml:324, Services/AppSearchService.qml:680, Services/PopoutService.qml:447, Services/PopoutService.qml:464, Modals/DankLauncherV2/Controller.qml:205, Modals/Settings/SettingsSidebar.qml:128, Modals/Settings/SettingsModal.qml:88, Modals/Settings/SettingsModal.qml:232, Modules/DankDash/DankDashPopout.qml:34, Modules/Settings/DankDashTab.qml:42, Modules/Dock/DockTrashContextMenu.qml:48", "comment": "settings window title" }, { @@ -14264,13 +14276,13 @@ { "term": "Shift+Enter to copy", "context": "Shift+Enter to copy", - "reference": "Modals/DankLauncherV2/Controller.qml:1224", + "reference": "Modals/DankLauncherV2/Controller.qml:1214", "comment": "" }, { "term": "Shift+Enter to paste", "context": "Shift+Enter to paste", - "reference": "dms-plugins/DankStickerSearch/DankStickerSearch.qml:176, dms-plugins/DankGifSearch/DankGifSearch.qml:119, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:176, dms-plugins/dms-plugins-official/DankGifSearch/DankGifSearch.qml:119, Modals/DankLauncherV2/Controller.qml:1224", + "reference": "dms-plugins/DankStickerSearch/DankStickerSearch.qml:176, dms-plugins/DankGifSearch/DankGifSearch.qml:119, dms-plugins/dms-plugins-official/DankStickerSearch/DankStickerSearch.qml:176, dms-plugins/dms-plugins-official/DankGifSearch/DankGifSearch.qml:119, Modals/DankLauncherV2/Controller.qml:1214", "comment": "" }, { @@ -14696,13 +14708,13 @@ { "term": "Show a notification when battery reaches the charge limit.", "context": "Show a notification when battery reaches the charge limit.", - "reference": "Modules/Settings/BatteryTab.qml:211", + "reference": "Modules/Settings/BatteryTab.qml:209", "comment": "" }, { "term": "Show a warning popup when battery is running low.", "context": "Show a warning popup when battery is running low.", - "reference": "Modules/Settings/BatteryTab.qml:237", + "reference": "Modules/Settings/BatteryTab.qml:250", "comment": "" }, { @@ -14720,7 +14732,7 @@ { "term": "Show an urgent alert when battery reaches critical level.", "context": "Show an urgent alert when battery reaches critical level.", - "reference": "Modules/Settings/BatteryTab.qml:292", + "reference": "Modules/Settings/BatteryTab.qml:295", "comment": "" }, { @@ -15260,7 +15272,7 @@ { "term": "Status", "context": "Status", - "reference": "Widgets/DankIconPicker.qml:55, Modals/Settings/SettingsSidebar.qml:251, Modules/Settings/AboutTab.qml:698, Modules/Settings/NetworkStatusTab.qml:86, Modules/Settings/PrinterTab.qml:186, Modules/Settings/BatteryTab.qml:109", + "reference": "Widgets/DankIconPicker.qml:55, Modals/Settings/SettingsSidebar.qml:251, Modules/Settings/AboutTab.qml:698, Modules/Settings/NetworkStatusTab.qml:86, Modules/Settings/PrinterTab.qml:186, Modules/Settings/BatteryTab.qml:106", "comment": "" }, { @@ -15794,7 +15806,7 @@ { "term": "Text", "context": "Text", - "reference": "dms-plugins/DankKDEConnect/components/ShareDialog.qml:354, dms-plugins/dms-plugins-official/DankKDEConnect/components/ShareDialog.qml:354, Modals/DankLauncherV2/Controller.qml:1225, Modals/Clipboard/ClipboardEntry.qml:190, Modals/Clipboard/ClipboardContent.qml:16, Modules/Settings/LauncherTab.qml:737", + "reference": "dms-plugins/DankKDEConnect/components/ShareDialog.qml:354, dms-plugins/dms-plugins-official/DankKDEConnect/components/ShareDialog.qml:354, Modals/DankLauncherV2/Controller.qml:1215, Modals/Clipboard/ClipboardEntry.qml:199, Modals/Clipboard/ClipboardContent.qml:16, Modules/Settings/LauncherTab.qml:737", "comment": "KDE Connect share text button | text color" }, { @@ -15890,7 +15902,7 @@ { "term": "Theme worker failed (%1)", "context": "Theme worker failed (%1)", - "reference": "Common/Theme.qml:2152", + "reference": "Common/Theme.qml:2155", "comment": "" }, { @@ -15950,7 +15962,7 @@ { "term": "This install is still using hyprland.conf. Run dms setup to migrate before editing display settings.", "context": "This install is still using hyprland.conf. Run dms setup to migrate before editing display settings.", - "reference": "Modules/Settings/DisplayConfig/IncludeWarningBox.qml:63, Modules/Settings/DisplayConfig/DisplayConfigState.qml:1475", + "reference": "Modules/Settings/DisplayConfig/IncludeWarningBox.qml:63, Modules/Settings/DisplayConfig/DisplayConfigState.qml:1486", "comment": "" }, { @@ -16010,7 +16022,7 @@ { "term": "This will permanently remove this saved clipboard item. This action cannot be undone.", "context": "This will permanently remove this saved clipboard item. This action cannot be undone.", - "reference": "Services/ClipboardService.qml:320", + "reference": "Services/ClipboardService.qml:349", "comment": "" }, { @@ -16202,7 +16214,7 @@ { "term": "Toast", "context": "Toast", - "reference": "Modules/Settings/BatteryTab.qml:246", + "reference": "Modules/Settings/BatteryTab.qml:218, Modules/Settings/BatteryTab.qml:259, Modules/Settings/BatteryTab.qml:304", "comment": "" }, { @@ -16268,13 +16280,13 @@ { "term": "Too many attempts - locked out", "context": "Too many attempts - locked out", - "reference": "Modules/Lock/LockScreenContent.qml:71", + "reference": "Modules/Lock/LockScreenContent.qml:79", "comment": "" }, { "term": "Too many failed attempts - account may be locked", "context": "Too many failed attempts - account may be locked", - "reference": "Modules/Greetd/GreeterContent.qml:247", + "reference": "Modules/Greetd/GreeterContent.qml:248", "comment": "" }, { @@ -16346,13 +16358,13 @@ { "term": "Touch your security key...", "context": "Touch your security key...", - "reference": "Modules/Lock/LockScreenContent.qml:65", + "reference": "Modules/Lock/LockScreenContent.qml:73", "comment": "" }, { "term": "Transform", "context": "Transform", - "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:295, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2029", + "reference": "Modules/Settings/DisplayConfig/OutputCard.qml:295, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2040", "comment": "" }, { @@ -16412,7 +16424,7 @@ { "term": "Trigger: %1", "context": "Trigger: %1", - "reference": "Modals/DankLauncherV2/Controller.qml:1378, Modules/Settings/LauncherTab.qml:1028", + "reference": "Modals/DankLauncherV2/Controller.qml:1368, Modules/Settings/LauncherTab.qml:1028", "comment": "" }, { @@ -16598,7 +16610,7 @@ { "term": "Unknown", "context": "Unknown", - "reference": "Common/Theme.qml:1646, Services/WeatherService.qml:153, Services/WeatherService.qml:716, Services/WeatherService.qml:717, Services/WeatherService.qml:758, Services/WeatherService.qml:759, Services/WeatherService.qml:794, Services/WeatherService.qml:795, Services/WeatherService.qml:993, Services/WeatherService.qml:994, Services/BatteryService.qml:300, dms-plugins/DankKDEConnect/DankKDEConnect.qml:1582, dms-plugins/DankKDEConnect/components/DeviceCard.qml:298, dms-plugins/DankKDEConnect/components/KDEConnectDetailContent.qml:741, dms-plugins/dms-plugins-official/DankKDEConnect/DankKDEConnect.qml:1582, dms-plugins/dms-plugins-official/DankKDEConnect/components/DeviceCard.qml:298, dms-plugins/dms-plugins-official/DankKDEConnect/components/KDEConnectDetailContent.qml:741, Modules/Lock/LockScreenContent.qml:444, Modules/Lock/LockScreenContent.qml:564, Modules/Lock/LockScreenContent.qml:660, Modules/Settings/PluginBrowser.qml:1077, Modules/Settings/NetworkStatusTab.qml:79, Modules/Settings/NetworkStatusTab.qml:121, Modules/Settings/PrinterTab.qml:1638, Modules/Settings/ThemeBrowser.qml:506, Modules/Settings/NetworkEthernetTab.qml:146, Modules/Settings/NetworkEthernetTab.qml:169, Modules/Settings/NetworkEthernetTab.qml:317, Modules/Settings/NetworkEthernetTab.qml:428, Modules/Settings/NotificationsTab.qml:711, Modules/Settings/NetworkWifiTab.qml:537, Modules/Settings/NetworkWifiTab.qml:947, Modules/ControlCenter/Details/BatteryDetail.qml:184, Modules/ControlCenter/Components/HeaderPane.qml:63, Modules/ControlCenter/Components/DragDropGrid.qml:515, Modules/ControlCenter/Components/DragDropGrid.qml:753, Modules/DankDash/Overview/MediaOverviewCard.qml:91, Modules/DankBar/Popouts/BatteryPopout.qml:340", + "reference": "Common/Theme.qml:1646, Services/WeatherService.qml:153, Services/WeatherService.qml:716, Services/WeatherService.qml:717, Services/WeatherService.qml:758, Services/WeatherService.qml:759, Services/WeatherService.qml:794, Services/WeatherService.qml:795, Services/WeatherService.qml:993, Services/WeatherService.qml:994, Services/BatteryService.qml:300, dms-plugins/DankKDEConnect/DankKDEConnect.qml:1582, dms-plugins/DankKDEConnect/components/DeviceCard.qml:298, dms-plugins/DankKDEConnect/components/KDEConnectDetailContent.qml:741, dms-plugins/dms-plugins-official/DankKDEConnect/DankKDEConnect.qml:1582, dms-plugins/dms-plugins-official/DankKDEConnect/components/DeviceCard.qml:298, dms-plugins/dms-plugins-official/DankKDEConnect/components/KDEConnectDetailContent.qml:741, Modules/Lock/LockScreenContent.qml:451, Modules/Lock/LockScreenContent.qml:571, Modules/Lock/LockScreenContent.qml:667, Modules/Settings/PluginBrowser.qml:1077, Modules/Settings/NetworkStatusTab.qml:79, Modules/Settings/NetworkStatusTab.qml:121, Modules/Settings/PrinterTab.qml:1638, Modules/Settings/ThemeBrowser.qml:506, Modules/Settings/NetworkEthernetTab.qml:146, Modules/Settings/NetworkEthernetTab.qml:169, Modules/Settings/NetworkEthernetTab.qml:317, Modules/Settings/NetworkEthernetTab.qml:428, Modules/Settings/NotificationsTab.qml:711, Modules/Settings/NetworkWifiTab.qml:537, Modules/Settings/NetworkWifiTab.qml:947, Modules/ControlCenter/Details/BatteryDetail.qml:184, Modules/ControlCenter/Components/HeaderPane.qml:63, Modules/ControlCenter/Components/DragDropGrid.qml:515, Modules/ControlCenter/Components/DragDropGrid.qml:753, Modules/DankDash/Overview/MediaOverviewCard.qml:91, Modules/DankBar/Popouts/BatteryPopout.qml:340", "comment": "KDE Connect unknown device status | Status | battery status | power profile option | unknown author | widget status" }, { @@ -16631,6 +16643,12 @@ "reference": "Modules/ProcessList/SystemView.qml:202", "comment": "fallback gpu name" }, + { + "term": "Unknown Model", + "context": "Unknown Model", + "reference": "Modules/Settings/Widgets/SettingsDisplayPicker.qml:83", + "comment": "" + }, { "term": "Unknown Monitor", "context": "Unknown Monitor", @@ -17234,19 +17252,19 @@ { "term": "VRR", "context": "VRR", - "reference": "Modules/Settings/WindowRulesTab.qml:104, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2031", + "reference": "Modules/Settings/WindowRulesTab.qml:104, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2042", "comment": "" }, { "term": "VRR Fullscreen Only", "context": "VRR Fullscreen Only", - "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2065", + "reference": "Modules/Settings/DisplayConfig/DisplayConfigState.qml:2076", "comment": "" }, { "term": "VRR On-Demand", "context": "VRR On-Demand", - "reference": "Modals/WindowRuleModal.qml:1117, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2039", + "reference": "Modals/WindowRuleModal.qml:1117, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2050", "comment": "" }, { @@ -17342,7 +17360,7 @@ { "term": "Visibility", "context": "Visibility", - "reference": "Modules/DankDash/WeatherForecastCard.qml:100, Modules/Settings/TimeWeatherTab.qml:1157, Modules/Settings/DankBarTab.qml:647", + "reference": "Modules/DankDash/WeatherForecastCard.qml:100, Modules/Settings/TimeWeatherTab.qml:1159, Modules/Settings/DankBarTab.qml:647", "comment": "" }, { @@ -17690,7 +17708,7 @@ { "term": "Wind", "context": "Wind", - "reference": "dms-plugins/DankDesktopWeather/DankDesktopWeather.qml:425, dms-plugins/dms-plugins-official/DankDesktopWeather/DankDesktopWeather.qml:425, Modules/DankDash/WeatherTab.qml:93, Modules/Settings/TimeWeatherTab.qml:1006", + "reference": "dms-plugins/DankDesktopWeather/DankDesktopWeather.qml:425, dms-plugins/dms-plugins-official/DankDesktopWeather/DankDesktopWeather.qml:425, Modules/DankDash/WeatherTab.qml:93, Modules/Settings/TimeWeatherTab.qml:1008", "comment": "" }, { @@ -17876,7 +17894,7 @@ { "term": "Yes", "context": "Yes", - "reference": "Modules/Settings/PrinterTab.qml:1169, Modules/Settings/WindowRulesTab.qml:77, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2037, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2041, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2051, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2061, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2063", + "reference": "Modules/Settings/PrinterTab.qml:1169, Modules/Settings/WindowRulesTab.qml:77, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2048, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2052, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2062, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2072, Modules/Settings/DisplayConfig/DisplayConfigState.qml:2074", "comment": "" }, { @@ -18152,13 +18170,13 @@ { "term": "matugen not available or disabled - cannot apply GTK colors", "context": "matugen not available or disabled - cannot apply GTK colors", - "reference": "Common/Theme.qml:1956", + "reference": "Common/Theme.qml:1959", "comment": "" }, { "term": "matugen not available or disabled - cannot apply Qt colors", "context": "matugen not available or disabled - cannot apply Qt colors", - "reference": "Common/Theme.qml:1978", + "reference": "Common/Theme.qml:1981", "comment": "" }, { @@ -18395,12 +18413,6 @@ "reference": "Modules/Settings/PluginListItem.qml:169", "comment": "" }, - { - "term": "wtype not available - install wtype for paste support", - "context": "wtype not available - install wtype for paste support", - "reference": "Services/ClipboardService.qml:260, Services/ClipboardService.qml:271, Modals/DankLauncherV2/Controller.qml:183", - "comment": "" - }, { "term": "• Install only from trusted sources", "context": "• Install only from trusted sources", diff --git a/quickshell/translations/template.json b/quickshell/translations/template.json index 76da7250a..7045313a9 100644 --- a/quickshell/translations/template.json +++ b/quickshell/translations/template.json @@ -916,13 +916,6 @@ "reference": "", "comment": "" }, - { - "term": "Active Lock Screen Monitor", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "Active VPN", "translation": "", @@ -1196,13 +1189,6 @@ "reference": "", "comment": "" }, - { - "term": "All Monitors", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "All checks passed", "translation": "", @@ -2484,6 +2470,13 @@ "reference": "", "comment": "" }, + { + "term": "Battery Alerts", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Battery Charge Limit", "translation": "", @@ -2506,7 +2499,7 @@ "comment": "" }, { - "term": "Battery Protection & Charging", + "term": "Battery Protection", "translation": "", "context": "", "reference": "", @@ -3367,7 +3360,21 @@ "comment": "" }, { - "term": "Choose how to be notified about battery alerts.", + "term": "Choose how to be notified about critical battery alerts.", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { + "term": "Choose how to be notified about low battery alerts.", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { + "term": "Choose how to be notified when charge limit is reached.", "translation": "", "context": "", "reference": "", @@ -3458,7 +3465,7 @@ "comment": "" }, { - "term": "Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.", + "term": "Choose which monitors show the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.", "translation": "", "context": "", "reference": "", @@ -13663,6 +13670,13 @@ "reference": "", "comment": "" }, + { + "term": "Paste failed: %1", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Path copied to clipboard", "translation": "", @@ -14266,7 +14280,7 @@ "comment": "" }, { - "term": "Power Profiles Auto-Switching", + "term": "Power Profiles & Saving", "translation": "", "context": "", "reference": "", @@ -19403,6 +19417,13 @@ "reference": "", "comment": "" }, + { + "term": "Unknown Model", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Unknown Monitor", "translation": "", @@ -21461,13 +21482,6 @@ "reference": "", "comment": "" }, - { - "term": "wtype not available - install wtype for paste support", - "translation": "", - "context": "", - "reference": "", - "comment": "" - }, { "term": "• Install only from trusted sources", "translation": "",