mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
Compare commits
4 Commits
18901c7cde
...
088ed806ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
088ed806ae | ||
|
|
07d2c94676 | ||
|
|
0bc1b7a3c2 | ||
|
|
c5987b28c0 |
@@ -5,6 +5,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +29,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Geteuid() == 0 {
|
clipboard.MaybeServeAndExit()
|
||||||
|
|
||||||
|
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||||
log.Fatal("This program should not be run as root. Exiting.")
|
log.Fatal("This program should not be run as root. Exiting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,7 +26,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Geteuid() == 0 {
|
clipboard.MaybeServeAndExit()
|
||||||
|
|
||||||
|
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||||
log.Fatal("This program should not be run as root. Exiting.")
|
log.Fatal("This program should not be run as root. Exiting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,22 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// isReadOnlyCommand returns true if the CLI args indicate a command that is
|
||||||
|
// safe to run as root (e.g. shell completion, help).
|
||||||
|
func isReadOnlyCommand(args []string) bool {
|
||||||
|
for _, arg := range args[1:] {
|
||||||
|
if strings.HasPrefix(arg, "-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch arg {
|
||||||
|
case "completion", "help", "__complete":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func isArchPackageInstalled(packageName string) bool {
|
func isArchPackageInstalled(packageName string) bool {
|
||||||
cmd := exec.Command("pacman", "-Q", packageName)
|
cmd := exec.Command("pacman", "-Q", packageName)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,100 +12,166 @@ import (
|
|||||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const envServe = "_DMS_CLIPBOARD_SERVE"
|
||||||
|
const envMime = "_DMS_CLIPBOARD_MIME"
|
||||||
|
const envPasteOnce = "_DMS_CLIPBOARD_PASTE_ONCE"
|
||||||
|
const envCacheFile = "_DMS_CLIPBOARD_CACHE"
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
if os.Getenv(envServe) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeType := os.Getenv(envMime)
|
||||||
|
pasteOnce := os.Getenv(envPasteOnce) == "1"
|
||||||
|
cachePath := os.Getenv(envCacheFile)
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case cachePath != "":
|
||||||
|
data, err = os.ReadFile(cachePath)
|
||||||
|
os.Remove(cachePath)
|
||||||
|
default:
|
||||||
|
data, err = io.ReadAll(os.Stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "clipboard: read source: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := serveClipboard(data, mimeType, pasteOnce); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "clipboard: serve: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
func Copy(data []byte, mimeType string) error {
|
func Copy(data []byte, mimeType string) error {
|
||||||
return CopyReader(bytes.NewReader(data), mimeType, false, false)
|
return copyForkCached(data, mimeType, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
|
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
|
||||||
if foreground {
|
if foreground {
|
||||||
return copyServeWithWriter(func(writer io.Writer) error {
|
return serveClipboard(data, mimeType, pasteOnce)
|
||||||
total := 0
|
|
||||||
for total < len(data) {
|
|
||||||
n, err := writer.Write(data[total:])
|
|
||||||
total += n
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if total != len(data) {
|
|
||||||
return io.ErrShortWrite
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, mimeType, pasteOnce)
|
|
||||||
}
|
}
|
||||||
return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
|
return copyForkCached(data, mimeType, pasteOnce)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
|
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
|
||||||
if !foreground {
|
if foreground {
|
||||||
return copyFork(data, mimeType, pasteOnce)
|
buf, err := io.ReadAll(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read source: %w", err)
|
||||||
|
}
|
||||||
|
return serveClipboard(buf, mimeType, pasteOnce)
|
||||||
}
|
}
|
||||||
return copyServeReader(data, mimeType, pasteOnce)
|
return copyFork(data, mimeType, pasteOnce)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
func newForkCmd(mimeType string, pasteOnce bool, extra ...string) *exec.Cmd {
|
||||||
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
cmd := exec.Command(os.Args[0])
|
||||||
if pasteOnce {
|
|
||||||
args = append(args, "--paste-once")
|
|
||||||
}
|
|
||||||
args = append(args, "--type", mimeType)
|
|
||||||
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stdout = nil
|
|
||||||
cmd.Stderr = nil
|
cmd.Stderr = nil
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||||
|
cmd.Env = append(os.Environ(),
|
||||||
if stdinSource, ok := data.(*os.File); ok {
|
envServe+"=1",
|
||||||
cmd.Stdin = stdinSource
|
envMime+"="+mimeType,
|
||||||
return cmd.Start()
|
)
|
||||||
|
if pasteOnce {
|
||||||
|
cmd.Env = append(cmd.Env, envPasteOnce+"=1")
|
||||||
}
|
}
|
||||||
|
cmd.Env = append(cmd.Env, extra...)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
stdin, err := cmd.StdinPipe()
|
func waitReady(cmd *exec.Cmd) error {
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stdin pipe: %w", err)
|
return fmt.Errorf("stdout pipe: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return fmt.Errorf("start: %w", err)
|
return fmt.Errorf("start: %w", err)
|
||||||
}
|
}
|
||||||
|
var buf [1]byte
|
||||||
if _, err := io.Copy(stdin, data); err != nil {
|
if _, err := stdout.Read(buf[:]); err != nil {
|
||||||
stdin.Close()
|
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
||||||
return fmt.Errorf("write stdin: %w", err)
|
|
||||||
}
|
}
|
||||||
if err := stdin.Close(); err != nil {
|
|
||||||
return fmt.Errorf("close stdin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
|
func copyForkCached(data []byte, mimeType string, pasteOnce bool) error {
|
||||||
cachedData, err := createClipboardCacheFile()
|
cacheFile, err := createClipboardCacheFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create clipboard cache file: %w", err)
|
return fmt.Errorf("create cache file: %w", err)
|
||||||
}
|
}
|
||||||
defer os.Remove(cachedData.Name())
|
cachePath := cacheFile.Name()
|
||||||
|
|
||||||
if _, err := io.Copy(cachedData, data); err != nil {
|
if _, err := cacheFile.Write(data); err != nil {
|
||||||
return fmt.Errorf("cache clipboard data: %w", err)
|
cacheFile.Close()
|
||||||
|
os.Remove(cachePath)
|
||||||
|
return fmt.Errorf("write cache file: %w", err)
|
||||||
}
|
}
|
||||||
if err := cachedData.Close(); err != nil {
|
if err := cacheFile.Close(); err != nil {
|
||||||
return fmt.Errorf("close temp cache file: %w", err)
|
os.Remove(cachePath)
|
||||||
|
return fmt.Errorf("close cache file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return copyServeWithWriter(func(writer io.Writer) error {
|
cmd := newForkCmd(mimeType, pasteOnce, envCacheFile+"="+cachePath)
|
||||||
cachedFile, err := os.Open(cachedData.Name())
|
cmd.Stdin = nil
|
||||||
|
if err := waitReady(cmd); err != nil {
|
||||||
|
os.Remove(cachePath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||||
|
cmd := newForkCmd(mimeType, pasteOnce)
|
||||||
|
|
||||||
|
switch src := data.(type) {
|
||||||
|
case *os.File:
|
||||||
|
cmd.Stdin = src
|
||||||
|
return waitReady(cmd)
|
||||||
|
|
||||||
|
default:
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("open temp cache file: %w", err)
|
return fmt.Errorf("stdin pipe: %w", err)
|
||||||
}
|
}
|
||||||
defer cachedFile.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(writer, cachedFile); err != nil {
|
stdout, err := cmd.StdoutPipe()
|
||||||
return fmt.Errorf("write clipboard data: %w", err)
|
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
|
return nil
|
||||||
}, mimeType, pasteOnce)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalReady() {
|
||||||
|
if os.Getenv(envServe) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Stdout.Write([]byte{1})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createClipboardCacheFile() (*os.File, error) {
|
func createClipboardCacheFile() (*os.File, error) {
|
||||||
@@ -129,7 +194,7 @@ func createClipboardCacheFile() (*os.File, error) {
|
|||||||
return os.CreateTemp("", "dms-clipboard-*")
|
return os.CreateTemp("", "dms-clipboard-*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
|
func serveClipboard(data []byte, mimeType string, pasteOnce bool) error {
|
||||||
display, err := wlclient.Connect("")
|
display, err := wlclient.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wayland connect: %w", err)
|
return fmt.Errorf("wayland connect: %w", err)
|
||||||
@@ -171,12 +236,10 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
if bindErr != nil {
|
if bindErr != nil {
|
||||||
return fmt.Errorf("registry bind: %w", bindErr)
|
return fmt.Errorf("registry bind: %w", bindErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dataControlMgr == nil {
|
if dataControlMgr == nil {
|
||||||
return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
|
return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
|
||||||
}
|
}
|
||||||
defer dataControlMgr.Destroy()
|
defer dataControlMgr.Destroy()
|
||||||
|
|
||||||
if seat == nil {
|
if seat == nil {
|
||||||
return fmt.Errorf("no seat available")
|
return fmt.Errorf("no seat available")
|
||||||
}
|
}
|
||||||
@@ -215,18 +278,12 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
|
|
||||||
cancelled := make(chan struct{})
|
cancelled := make(chan struct{})
|
||||||
pasted := make(chan struct{}, 1)
|
pasted := make(chan struct{}, 1)
|
||||||
sendErr := make(chan error, 1)
|
|
||||||
|
|
||||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||||
defer syscall.Close(e.Fd)
|
_ = syscall.SetNonblock(e.Fd, false)
|
||||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
if err := writeTo(file); err != nil {
|
_, _ = file.Write(data)
|
||||||
select {
|
|
||||||
case sendErr <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case pasted <- struct{}{}:
|
case pasted <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -242,13 +299,12 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
}
|
}
|
||||||
|
|
||||||
display.Roundtrip()
|
display.Roundtrip()
|
||||||
|
signalReady()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-cancelled:
|
case <-cancelled:
|
||||||
return nil
|
return nil
|
||||||
case err := <-sendErr:
|
|
||||||
return err
|
|
||||||
case <-pasted:
|
case <-pasted:
|
||||||
if pasteOnce {
|
if pasteOnce {
|
||||||
return nil
|
return nil
|
||||||
@@ -502,12 +558,10 @@ func copyMultiServe(offers []Offer, pasteOnce bool) error {
|
|||||||
if bindErr != nil {
|
if bindErr != nil {
|
||||||
return fmt.Errorf("registry bind: %w", bindErr)
|
return fmt.Errorf("registry bind: %w", bindErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dataControlMgr == nil {
|
if dataControlMgr == nil {
|
||||||
return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
|
return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
|
||||||
}
|
}
|
||||||
defer dataControlMgr.Destroy()
|
defer dataControlMgr.Destroy()
|
||||||
|
|
||||||
if seat == nil {
|
if seat == nil {
|
||||||
return fmt.Errorf("no seat available")
|
return fmt.Errorf("no seat available")
|
||||||
}
|
}
|
||||||
@@ -535,12 +589,12 @@ func copyMultiServe(offers []Offer, pasteOnce bool) error {
|
|||||||
pasted := make(chan struct{}, 1)
|
pasted := make(chan struct{}, 1)
|
||||||
|
|
||||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||||
defer syscall.Close(e.Fd)
|
_ = syscall.SetNonblock(e.Fd, false)
|
||||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
if data, ok := offerMap[e.MimeType]; ok {
|
if data, ok := offerMap[e.MimeType]; ok {
|
||||||
file.Write(data)
|
_, _ = file.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("No recent clipboard entries found")
|
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service…")
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
@@ -195,7 +195,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("No saved clipboard entries")
|
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service…")
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
|
|||||||
@@ -60,15 +60,12 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
if (!clipboardAvailable) {
|
|
||||||
ToastService.showError(I18n.tr("Clipboard service not available"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
open();
|
open();
|
||||||
activeImageLoads = 0;
|
activeImageLoads = 0;
|
||||||
shouldHaveFocus = true;
|
shouldHaveFocus = true;
|
||||||
ClipboardService.reset();
|
ClipboardService.reset();
|
||||||
ClipboardService.refresh();
|
if (clipboardAvailable)
|
||||||
|
ClipboardService.refresh();
|
||||||
keyboardController.reset();
|
keyboardController.reset();
|
||||||
|
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
|
|||||||
@@ -50,14 +50,11 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
if (!clipboardAvailable) {
|
|
||||||
ToastService.showError(I18n.tr("Clipboard service not available"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
open();
|
open();
|
||||||
activeImageLoads = 0;
|
activeImageLoads = 0;
|
||||||
ClipboardService.reset();
|
ClipboardService.reset();
|
||||||
ClipboardService.refresh();
|
if (clipboardAvailable)
|
||||||
|
ClipboardService.refresh();
|
||||||
keyboardController.reset();
|
keyboardController.reset();
|
||||||
|
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
@@ -122,10 +119,10 @@ DankPopout {
|
|||||||
onBackgroundClicked: hide()
|
onBackgroundClicked: hide()
|
||||||
|
|
||||||
onShouldBeVisibleChanged: {
|
onShouldBeVisibleChanged: {
|
||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
}
|
if (clipboardAvailable)
|
||||||
ClipboardService.refresh();
|
ClipboardService.refresh();
|
||||||
keyboardController.reset();
|
keyboardController.reset();
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
if (contentLoader.item?.searchField) {
|
if (contentLoader.item?.searchField) {
|
||||||
|
|||||||
@@ -255,6 +255,12 @@ Singleton {
|
|||||||
return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash);
|
return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClipboardAvailableChanged: {
|
||||||
|
if (!clipboardAvailable || refCount <= 0)
|
||||||
|
return;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: DMSService
|
target: DMSService
|
||||||
enabled: root.refCount > 0
|
enabled: root.refCount > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user