mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-30 08:22:51 -05:00
clipboard: re-add ownership option
This commit is contained in:
@@ -208,6 +208,9 @@ func handleSetConfig(conn net.Conn, req models.Request, m *Manager) {
|
|||||||
if v, ok := req.Params["disableHistory"].(bool); ok {
|
if v, ok := req.Params["disableHistory"].(bool); ok {
|
||||||
cfg.DisableHistory = v
|
cfg.DisableHistory = v
|
||||||
}
|
}
|
||||||
|
if v, ok := req.Params["disablePersist"].(bool); ok {
|
||||||
|
cfg.DisablePersist = v
|
||||||
|
}
|
||||||
|
|
||||||
if err := m.SetConfig(cfg); err != nil {
|
if err := m.SetConfig(cfg); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
|||||||
@@ -229,10 +229,18 @@ func (m *Manager) setupDataDeviceSync() {
|
|||||||
m.offerMutex.RUnlock()
|
m.offerMutex.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
wasOwner := m.isOwner
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
|
||||||
if offer == nil {
|
if offer == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if wasOwner {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m.currentOffer = offer
|
m.currentOffer = offer
|
||||||
|
|
||||||
m.offerMutex.RLock()
|
m.offerMutex.RLock()
|
||||||
@@ -311,6 +319,10 @@ func (m *Manager) readAndStore(r *os.File, mimeType string) {
|
|||||||
m.storeClipboardEntry(data, mimeType)
|
m.storeClipboardEntry(data, mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cfg.DisablePersist {
|
||||||
|
m.persistClipboard([]string{mimeType}, map[string][]byte{mimeType: data})
|
||||||
|
}
|
||||||
|
|
||||||
m.updateState()
|
m.updateState()
|
||||||
m.notifySubscribers()
|
m.notifySubscribers()
|
||||||
}
|
}
|
||||||
@@ -336,6 +348,105 @@ func (m *Manager) storeClipboardEntry(data []byte, mimeType string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) persistClipboard(mimeTypes []string, data map[string][]byte) {
|
||||||
|
m.persistMutex.Lock()
|
||||||
|
m.persistMimeTypes = mimeTypes
|
||||||
|
m.persistData = data
|
||||||
|
m.persistMutex.Unlock()
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
|
m.takePersistOwnership()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) takePersistOwnership() {
|
||||||
|
if m.dataControlMgr == nil || m.dataDevice == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.getConfig().DisablePersist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.persistMutex.RLock()
|
||||||
|
mimeTypes := m.persistMimeTypes
|
||||||
|
m.persistMutex.RUnlock()
|
||||||
|
|
||||||
|
if len(mimeTypes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dataMgr := m.dataControlMgr.(*ext_data_control.ExtDataControlManagerV1)
|
||||||
|
|
||||||
|
source, err := dataMgr.CreateDataSource()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create persist source: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mime := range mimeTypes {
|
||||||
|
if err := source.Offer(mime); err != nil {
|
||||||
|
log.Errorf("Failed to offer mime type %s: %v", mime, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||||
|
fd := e.Fd
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
|
m.persistMutex.RLock()
|
||||||
|
d := m.persistData[e.MimeType]
|
||||||
|
m.persistMutex.RUnlock()
|
||||||
|
|
||||||
|
if len(d) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file := os.NewFile(uintptr(fd), "clipboard-pipe")
|
||||||
|
defer file.Close()
|
||||||
|
file.Write(d)
|
||||||
|
})
|
||||||
|
|
||||||
|
source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
m.isOwner = false
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
if m.currentSource != nil {
|
||||||
|
oldSource := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||||
|
oldSource.Destroy()
|
||||||
|
}
|
||||||
|
m.currentSource = source
|
||||||
|
|
||||||
|
device := m.dataDevice.(*ext_data_control.ExtDataControlDeviceV1)
|
||||||
|
if err := device.SetSelection(source); err != nil {
|
||||||
|
log.Errorf("Failed to set persist selection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
m.isOwner = true
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) releaseOwnership() {
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
m.isOwner = false
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
|
||||||
|
m.persistMutex.Lock()
|
||||||
|
m.persistData = nil
|
||||||
|
m.persistMimeTypes = nil
|
||||||
|
m.persistMutex.Unlock()
|
||||||
|
|
||||||
|
if m.currentSource != nil {
|
||||||
|
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||||
|
source.Destroy()
|
||||||
|
m.currentSource = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) storeEntry(entry Entry) error {
|
func (m *Manager) storeEntry(entry Entry) error {
|
||||||
if m.db == nil {
|
if m.db == nil {
|
||||||
return fmt.Errorf("database not available")
|
return fmt.Errorf("database not available")
|
||||||
@@ -1198,7 +1309,13 @@ func (m *Manager) applyConfigChange(newCfg Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Clipboard config reloaded: disableHistory=%v", newCfg.DisableHistory)
|
if newCfg.DisablePersist && !oldCfg.DisablePersist {
|
||||||
|
log.Info("Clipboard persist disabled, releasing ownership")
|
||||||
|
m.releaseOwnership()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Clipboard config reloaded: disableHistory=%v disablePersist=%v",
|
||||||
|
newCfg.DisableHistory, newCfg.DisablePersist)
|
||||||
|
|
||||||
m.updateState()
|
m.updateState()
|
||||||
m.notifySubscribers()
|
m.notifySubscribers()
|
||||||
|
|||||||
@@ -289,6 +289,81 @@ func TestManager_ConcurrentOfferAccess(t *testing.T) {
|
|||||||
wg.Wait()
|
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{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
const goroutines = 30
|
||||||
|
const iterations = 100
|
||||||
|
|
||||||
|
for i := 0; i < goroutines/2; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
_ = m.isOwner
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < goroutines/2; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < iterations; j++ {
|
||||||
|
m.ownerLock.Lock()
|
||||||
|
m.isOwner = j%2 == 0
|
||||||
|
m.ownerLock.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func TestItob(t *testing.T) {
|
func TestItob(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input uint64
|
input uint64
|
||||||
@@ -383,6 +458,7 @@ func TestDefaultConfig(t *testing.T) {
|
|||||||
assert.False(t, cfg.ClearAtStartup)
|
assert.False(t, cfg.ClearAtStartup)
|
||||||
assert.False(t, cfg.Disabled)
|
assert.False(t, cfg.Disabled)
|
||||||
assert.False(t, cfg.DisableHistory)
|
assert.False(t, cfg.DisableHistory)
|
||||||
|
assert.True(t, cfg.DisablePersist)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_PostDelegatesToWlContext(t *testing.T) {
|
func TestManager_PostDelegatesToWlContext(t *testing.T) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Config struct {
|
|||||||
|
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
DisableHistory bool `json:"disableHistory"`
|
DisableHistory bool `json:"disableHistory"`
|
||||||
|
DisablePersist bool `json:"disablePersist"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
@@ -29,6 +30,7 @@ func DefaultConfig() Config {
|
|||||||
MaxEntrySize: 5 * 1024 * 1024,
|
MaxEntrySize: 5 * 1024 * 1024,
|
||||||
AutoClearDays: 0,
|
AutoClearDays: 0,
|
||||||
ClearAtStartup: false,
|
ClearAtStartup: false,
|
||||||
|
DisablePersist: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +135,13 @@ type Manager struct {
|
|||||||
sourceMimeTypes []string
|
sourceMimeTypes []string
|
||||||
sourceMutex sync.RWMutex
|
sourceMutex sync.RWMutex
|
||||||
|
|
||||||
|
persistData map[string][]byte
|
||||||
|
persistMimeTypes []string
|
||||||
|
persistMutex sync.RWMutex
|
||||||
|
|
||||||
|
isOwner bool
|
||||||
|
ownerLock sync.Mutex
|
||||||
|
|
||||||
initialized bool
|
initialized bool
|
||||||
|
|
||||||
alive bool
|
alive bool
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ func handleClipboardSetConfig(conn net.Conn, req models.Request) {
|
|||||||
if v, ok := req.Params["disableHistory"].(bool); ok {
|
if v, ok := req.Params["disableHistory"].(bool); ok {
|
||||||
cfg.DisableHistory = v
|
cfg.DisableHistory = v
|
||||||
}
|
}
|
||||||
|
if v, ok := req.Params["disablePersist"].(bool); ok {
|
||||||
|
cfg.DisablePersist = v
|
||||||
|
}
|
||||||
|
|
||||||
if err := clipboard.SaveConfig(cfg); err != nil {
|
if err := clipboard.SaveConfig(cfg); err != nil {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
|||||||
@@ -13,31 +13,88 @@ Item {
|
|||||||
property bool saving: false
|
property bool saving: false
|
||||||
|
|
||||||
readonly property var maxHistoryOptions: [
|
readonly property var maxHistoryOptions: [
|
||||||
{ text: "25", value: 25 },
|
{
|
||||||
{ text: "50", value: 50 },
|
text: "25",
|
||||||
{ text: "100", value: 100 },
|
value: 25
|
||||||
{ text: "200", value: 200 },
|
},
|
||||||
{ text: "500", value: 500 },
|
{
|
||||||
{ text: "1000", value: 1000 }
|
text: "50",
|
||||||
|
value: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "100",
|
||||||
|
value: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "200",
|
||||||
|
value: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "500",
|
||||||
|
value: 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "1000",
|
||||||
|
value: 1000
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
readonly property var maxEntrySizeOptions: [
|
readonly property var maxEntrySizeOptions: [
|
||||||
{ text: "1 MB", value: 1048576 },
|
{
|
||||||
{ text: "2 MB", value: 2097152 },
|
text: "1 MB",
|
||||||
{ text: "5 MB", value: 5242880 },
|
value: 1048576
|
||||||
{ text: "10 MB", value: 10485760 },
|
},
|
||||||
{ text: "20 MB", value: 20971520 },
|
{
|
||||||
{ text: "50 MB", value: 52428800 }
|
text: "2 MB",
|
||||||
|
value: 2097152
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "5 MB",
|
||||||
|
value: 5242880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "10 MB",
|
||||||
|
value: 10485760
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "20 MB",
|
||||||
|
value: 20971520
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "50 MB",
|
||||||
|
value: 52428800
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
readonly property var autoClearOptions: [
|
readonly property var autoClearOptions: [
|
||||||
{ text: I18n.tr("Never"), value: 0 },
|
{
|
||||||
{ text: I18n.tr("1 day"), value: 1 },
|
text: I18n.tr("Never"),
|
||||||
{ text: I18n.tr("3 days"), value: 3 },
|
value: 0
|
||||||
{ text: I18n.tr("7 days"), value: 7 },
|
},
|
||||||
{ text: I18n.tr("14 days"), value: 14 },
|
{
|
||||||
{ text: I18n.tr("30 days"), value: 30 },
|
text: I18n.tr("1 day"),
|
||||||
{ text: I18n.tr("90 days"), value: 90 }
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("3 days"),
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("7 days"),
|
||||||
|
value: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("14 days"),
|
||||||
|
value: 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("30 days"),
|
||||||
|
value: 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("90 days"),
|
||||||
|
value: 90
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
function getMaxHistoryText(value) {
|
function getMaxHistoryText(value) {
|
||||||
@@ -139,9 +196,7 @@ Item {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
text: !DMSService.isConnected
|
text: !DMSService.isConnected ? I18n.tr("DMS service is not connected. Clipboard settings are unavailable.") : I18n.tr("Failed to load clipboard configuration.")
|
||||||
? I18n.tr("DMS service is not connected. Clipboard settings are unavailable.")
|
|
||||||
: I18n.tr("Failed to load clipboard configuration.")
|
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width - Theme.iconSizeSmall - Theme.spacingM
|
width: parent.width - Theme.iconSizeSmall - Theme.spacingM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -257,6 +312,16 @@ Item {
|
|||||||
checked: root.config.disableHistory ?? false
|
checked: root.config.disableHistory ?? false
|
||||||
onToggled: checked => root.saveConfig("disableHistory", checked)
|
onToggled: checked => root.saveConfig("disableHistory", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tab: "clipboard"
|
||||||
|
tags: ["clipboard", "disable", "persist", "ownership"]
|
||||||
|
settingKey: "disablePersist"
|
||||||
|
text: I18n.tr("Disable Clipboard Ownership")
|
||||||
|
description: I18n.tr("Don't preserve clipboard when apps close")
|
||||||
|
checked: root.config.disablePersist ?? true
|
||||||
|
onToggled: checked => root.saveConfig("disablePersist", checked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user