mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-29 06:27:47 -04:00
1 parent
e4c4fe44ea
commit
df8b1d85ca
3 files changed
+128
-16
No files matched your search
@@ -2,6 +2,7 @@ package clipboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -249,6 +250,9 @@ func (m *Manager) setupRegistry() error {
|
||||
m.dataControlMgr = dataControlMgr
|
||||
log.Info("Bound ext_data_control_manager_v1")
|
||||
case "wl_seat":
|
||||
if m.seat != nil {
|
||||
return
|
||||
}
|
||||
seat := wlclient.NewSeat(ctx)
|
||||
if err := registry.Bind(e.Name, e.Interface, e.Version, seat); err != nil {
|
||||
log.Errorf("Failed to bind wl_seat: %v", err)
|
||||
@@ -634,18 +638,27 @@ func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
|
||||
if maxHistory < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
type row struct {
|
||||
key []byte
|
||||
entry Entry
|
||||
}
|
||||
var rows []row
|
||||
c := b.Cursor()
|
||||
var count int
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err == nil && entry.Pinned {
|
||||
if err != nil || entry.Pinned {
|
||||
continue
|
||||
}
|
||||
if count < maxHistory {
|
||||
count++
|
||||
continue
|
||||
}
|
||||
if err := b.Delete(k); err != nil {
|
||||
rows = append(rows, row{slices.Clone(k), entry})
|
||||
}
|
||||
|
||||
slices.SortFunc(rows, func(a, b row) int {
|
||||
return compareEntriesNewestFirst(a.entry, b.entry)
|
||||
})
|
||||
|
||||
for _, r := range rows[min(maxHistory, len(rows)):] {
|
||||
if err := b.Delete(r.key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -663,7 +676,7 @@ func encodeEntry(e Entry) ([]byte, error) {
|
||||
binary.Write(buf, binary.BigEndian, uint32(len(e.Preview)))
|
||||
buf.WriteString(e.Preview)
|
||||
binary.Write(buf, binary.BigEndian, int32(e.Size))
|
||||
binary.Write(buf, binary.BigEndian, e.Timestamp.Unix())
|
||||
binary.Write(buf, binary.BigEndian, e.Timestamp.UnixNano())
|
||||
if e.IsImage {
|
||||
buf.WriteByte(1)
|
||||
} else {
|
||||
@@ -729,7 +742,13 @@ func decodeEntryFields(data []byte, withData bool) (Entry, error) {
|
||||
|
||||
var timestamp int64
|
||||
binary.Read(buf, binary.BigEndian, ×tamp)
|
||||
e.Timestamp = time.Unix(timestamp, 0)
|
||||
// Rows written before the switch to nanosecond precision hold seconds;
|
||||
// no second-resolution stamp reaches 1e12 until the year 33658.
|
||||
if timestamp < 1e12 {
|
||||
e.Timestamp = time.Unix(timestamp, 0)
|
||||
} else {
|
||||
e.Timestamp = time.Unix(0, timestamp)
|
||||
}
|
||||
|
||||
var isImage byte
|
||||
binary.Read(buf, binary.BigEndian, &isImage)
|
||||
@@ -1105,9 +1124,25 @@ func (m *Manager) GetHistory() []Entry {
|
||||
go m.deleteStaleEntries(stale)
|
||||
}
|
||||
|
||||
sortEntriesNewestFirst(history)
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// sortEntriesNewestFirst orders by timestamp, falling back to id: TouchEntry
|
||||
// rewrites a row in place to keep its id stable, so bucket key order stops
|
||||
// tracking recency.
|
||||
func sortEntriesNewestFirst(entries []Entry) {
|
||||
slices.SortFunc(entries, compareEntriesNewestFirst)
|
||||
}
|
||||
|
||||
func compareEntriesNewestFirst(a, b Entry) int {
|
||||
if c := b.Timestamp.Compare(a.Timestamp); c != 0 {
|
||||
return c
|
||||
}
|
||||
return cmp.Compare(b.ID, a.ID)
|
||||
}
|
||||
|
||||
func (m *Manager) deleteStaleEntries(ids []uint64) {
|
||||
if m.db == nil {
|
||||
return
|
||||
@@ -1243,9 +1278,24 @@ func (m *Manager) TouchEntry(id uint64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
entry.Timestamp = time.Now()
|
||||
// Round(0) drops the monotonic reading so the in-memory stamp compares the
|
||||
// same way as the one decoded back from the bucket.
|
||||
entry.Timestamp = time.Now().Round(0)
|
||||
|
||||
if err := m.storeEntry(*entry); err != nil {
|
||||
// storeEntry would dedupe the row against itself and hand out a fresh
|
||||
// sequence number, invalidating the id the UI is still holding.
|
||||
err = m.dbUpdate(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("clipboard"))
|
||||
if b == nil {
|
||||
return fmt.Errorf("clipboard bucket missing")
|
||||
}
|
||||
encoded, err := encodeEntry(*entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put(itob(id), encoded)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2200,6 +2250,8 @@ func (m *Manager) GetPinnedEntries() []Entry {
|
||||
log.Errorf("Failed to get pinned entries: %v", err)
|
||||
}
|
||||
|
||||
sortEntriesNewestFirst(pinned)
|
||||
|
||||
return pinned
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package clipboard
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"net"
|
||||
@@ -446,7 +447,9 @@ func TestUnpinEntry_KeepsTopUnpinnedDuplicate(t *testing.T) {
|
||||
|
||||
history = m.GetHistory()
|
||||
require.Len(t, history, 3)
|
||||
require.Equal(t, topHistoryDuplicate.ID, history[0].ID)
|
||||
// GetHistory sorts by timestamp, so the newer row leads even though its
|
||||
// id is lower.
|
||||
require.Equal(t, olderHistoryDuplicate.ID, history[0].ID)
|
||||
require.NoError(t, m.UnpinEntry(pinnedID))
|
||||
|
||||
history = m.GetHistory()
|
||||
@@ -1088,3 +1091,59 @@ func TestApplyConfigChange_DisableReleasesDataDevice(t *testing.T) {
|
||||
require.ErrorIs(t, m.CopyText("secret"), errSelectionUnavailable)
|
||||
assert.Empty(t, m.GetHistory())
|
||||
}
|
||||
|
||||
func TestTouchEntry_SurvivesTrimWhenHistoryFull(t *testing.T) {
|
||||
m := newTestManagerWithDB(t)
|
||||
m.config.MaxHistory = 3
|
||||
|
||||
for i := range 3 {
|
||||
require.NoError(t, m.storeEntry(Entry{
|
||||
Data: []byte(fmt.Sprintf("entry-%d", i)),
|
||||
MimeType: "text/plain",
|
||||
Timestamp: time.Now(),
|
||||
}))
|
||||
}
|
||||
|
||||
oldest := m.GetHistory()[2]
|
||||
require.NoError(t, m.TouchEntry(oldest.ID))
|
||||
|
||||
// A new copy trims the bucket back to MaxHistory.
|
||||
require.NoError(t, m.storeEntry(Entry{
|
||||
Data: []byte("newcomer"),
|
||||
MimeType: "text/plain",
|
||||
Timestamp: time.Now(),
|
||||
}))
|
||||
|
||||
history := m.GetHistory()
|
||||
require.Len(t, history, 3)
|
||||
ids := make([]uint64, len(history))
|
||||
for i, e := range history {
|
||||
ids[i] = e.ID
|
||||
}
|
||||
assert.Contains(t, ids, oldest.ID, "the touched row must survive trimming")
|
||||
assert.Equal(t, oldest.ID, history[1].ID, "the touched row ranks second, behind the newer copy")
|
||||
}
|
||||
|
||||
func TestGetHistory_SameSecondOrdersByID(t *testing.T) {
|
||||
m := newTestManagerWithDB(t)
|
||||
m.config.MaxHistory = 10
|
||||
|
||||
stamp := time.Now()
|
||||
for i := range 3 {
|
||||
require.NoError(t, m.storeEntry(Entry{
|
||||
Data: []byte(fmt.Sprintf("same-second-%d", i)),
|
||||
MimeType: "text/plain",
|
||||
Timestamp: stamp,
|
||||
}))
|
||||
}
|
||||
|
||||
// Touching the oldest row rewrites it in place, so the bucket now yields
|
||||
// it first even though every row shares a timestamp.
|
||||
oldest := m.GetHistory()[2]
|
||||
require.NoError(t, m.TouchEntry(oldest.ID))
|
||||
|
||||
history := m.GetHistory()
|
||||
require.Len(t, history, 3)
|
||||
assert.Equal(t, oldest.ID, history[0].ID, "the touched row must lead: its timestamp is newer")
|
||||
assert.Greater(t, history[1].ID, history[2].ID, "rows still tied on timestamp break on newest id")
|
||||
}
|
||||
Reference in new issue
Block a user