diff --git a/core/internal/server/clipboard/manager.go b/core/internal/server/clipboard/manager.go index ae66adab2..ec71cbe64 100644 --- a/core/internal/server/clipboard/manager.go +++ b/core/internal/server/clipboard/manager.go @@ -1336,7 +1336,18 @@ func (m *Manager) reopenDB() { m.db = db } +var errSelectionUnavailable = errors.New("clipboard selection unavailable: tracking disabled or no data control protocol") + +// Both are bound once in NewManager; a disabled manager never binds them but +// still serves copy requests, so callers must fail before touching history. +func (m *Manager) canTakeSelection() bool { + return m.dataControlMgr != nil && m.dataDevice != nil +} + func (m *Manager) SetClipboard(data []byte, mimeType string) error { + if !m.canTakeSelection() { + return errSelectionUnavailable + } if int64(len(data)) > m.config.MaxEntrySize { return fmt.Errorf("data too large") } @@ -1352,6 +1363,9 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error { // representation plus its stored alternate, so history restores keep // both the text and image sides pasteable. func (m *Manager) SetClipboardEntry(entry *Entry) error { + if !m.canTakeSelection() { + return errSelectionUnavailable + } if int64(len(entry.Data)) > m.config.MaxEntrySize { return fmt.Errorf("data too large") } @@ -2157,6 +2171,9 @@ func (m *Manager) GetPinnedCount() int { } func (m *Manager) CopyFile(filePath string) error { + if !m.canTakeSelection() { + return errSelectionUnavailable + } fileInfo, err := os.Stat(filePath) if err != nil { return fmt.Errorf("file not found: %w", err) diff --git a/core/internal/server/clipboard/manager_test.go b/core/internal/server/clipboard/manager_test.go index 5cceec0f6..6c4083f18 100644 --- a/core/internal/server/clipboard/manager_test.go +++ b/core/internal/server/clipboard/manager_test.go @@ -20,6 +20,7 @@ import ( mocks_wlcontext "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlcontext" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/dankgo/ipc" + "github.com/AvengeMedia/dankgo/wayland/ext_data_control" ) type clipboardTestConn struct { @@ -48,14 +49,14 @@ func newTestManagerWithDB(t *testing.T) *Manager { }) mockCtx := mocks_wlcontext.NewMockWaylandContext(t) - mockCtx.EXPECT().Post(mock.AnythingOfType("func()")).Run(func(fn func()) { - fn() - }).Maybe() + mockCtx.EXPECT().Post(mock.AnythingOfType("func()")).Return().Maybe() return &Manager{ - config: DefaultConfig(), - db: db, - wlCtx: mockCtx, + config: DefaultConfig(), + db: db, + wlCtx: mockCtx, + dataControlMgr: &ext_data_control.ExtDataControlManagerV1{}, + dataDevice: &ext_data_control.ExtDataControlDeviceV1{}, } } @@ -1015,3 +1016,27 @@ func TestHandleCopyEntry_OversizedFileEntryRestoresExactPath(t *testing.T) { require.NotNil(t, resp.Result) assert.Equal(t, path, (*resp.Result)["filePath"]) } + +// A manager built with tracking disabled never binds data control but still +// serves clipboard.copyFile; answering success there made dms screenshot skip +// its local copy and leave the clipboard empty. +func TestCopyFile_DisabledTrackingRefusesCopy(t *testing.T) { + t.Setenv("XDG_CACHE_HOME", t.TempDir()) + t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + + mockCtx := mocks_wlcontext.NewMockWaylandContext(t) + mockCtx.EXPECT().Display().Return(nil).Maybe() + mockCtx.EXPECT().Post(mock.AnythingOfType("func()")).Run(func(fn func()) { fn() }).Maybe() + + cfg := DefaultConfig() + cfg.Disabled = true + m, err := NewManager(mockCtx, cfg) + require.NoError(t, err) + t.Cleanup(m.Close) + + path := filepath.Join(t.TempDir(), "shot.png") + require.NoError(t, os.WriteFile(path, []byte("png-bytes"), 0o644)) + + require.ErrorIs(t, m.CopyFile(path), errSelectionUnavailable) + assert.Empty(t, m.GetHistory()) +} diff --git a/core/internal/server/sysupdate/backend_pkg.go b/core/internal/server/sysupdate/backend_pkg.go index 33aa8be4d..b09a937df 100644 --- a/core/internal/server/sysupdate/backend_pkg.go +++ b/core/internal/server/sysupdate/backend_pkg.go @@ -5,23 +5,26 @@ import ( "errors" "os/exec" "regexp" + "runtime" "strings" ) func init() { - // FreeBSD's pkg(8) is the native binary package manager. Register it after - // the Linux backends; IsAvailable keeps this harmless on other systems. RegisterSystemBackend(func() Backend { return &pkgBackend{} }) } type pkgBackend struct{} -func (pkgBackend) ID() string { return "pkg" } -func (pkgBackend) DisplayName() string { return "FreeBSD pkg" } -func (pkgBackend) Repo() RepoKind { return RepoSystem } -func (pkgBackend) NeedsAuth() bool { return true } -func (pkgBackend) RunsInTerminal() bool { return false } -func (pkgBackend) IsAvailable(_ context.Context) bool { return commandExists("pkg") } +func (pkgBackend) ID() string { return "pkg" } +func (pkgBackend) DisplayName() string { return "FreeBSD pkg" } +func (pkgBackend) Repo() RepoKind { return RepoSystem } +func (pkgBackend) NeedsAuth() bool { return true } +func (pkgBackend) RunsInTerminal() bool { return false } + +// Gentoo's mgorny-dev-scripts and other distros ship an unrelated pkg binary. +func (pkgBackend) IsAvailable(_ context.Context) bool { + return runtime.GOOS == "freebsd" && commandExists("pkg") +} // pkg upgrade -n prints upgrade rows like: //