mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-28 23:42:51 -05:00
clipboard: react to changes
This commit is contained in:
@@ -240,6 +240,7 @@ func runClipCopy(cmd *cobra.Command, args []string) {
|
|||||||
if err := copyFileToClipboard(filePath); err != nil {
|
if err := copyFileToClipboard(filePath); err != nil {
|
||||||
log.Fatalf("copy file: %v", err)
|
log.Fatalf("copy file: %v", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Downloaded and copied: %s\n", filePath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,24 +873,58 @@ func downloadToTempFile(rawURL string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: 30 * time.Second}
|
client := &http.Client{Timeout: 30 * time.Second}
|
||||||
resp, err := client.Get(rawURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("download: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
var data []byte
|
||||||
return "", fmt.Errorf("download failed: status %d", resp.StatusCode)
|
var contentType string
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
for attempt := 0; attempt < 3; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", rawURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = fmt.Errorf("create request: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
||||||
|
req.Header.Set("Accept", "image/*,video/*,*/*")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = fmt.Errorf("download (attempt %d): %w", attempt+1, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
resp.Body.Close()
|
||||||
|
lastErr = fmt.Errorf("download failed (attempt %d): status %d", attempt+1, resp.StatusCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err = io.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
lastErr = fmt.Errorf("read response (attempt %d): %w", attempt+1, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType = resp.Header.Get("Content-Type")
|
||||||
|
if idx := strings.Index(contentType, ";"); idx != -1 {
|
||||||
|
contentType = strings.TrimSpace(contentType[:idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = nil
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
if lastErr != nil {
|
||||||
if err != nil {
|
return "", lastErr
|
||||||
return "", fmt.Errorf("read response: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := resp.Header.Get("Content-Type")
|
if len(data) == 0 {
|
||||||
if idx := strings.Index(contentType, ";"); idx != -1 {
|
return "", fmt.Errorf("downloaded empty file")
|
||||||
contentType = strings.TrimSpace(contentType[:idx])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(contentType, "image/") && !strings.HasPrefix(contentType, "video/") {
|
if !strings.HasPrefix(contentType, "image/") && !strings.HasPrefix(contentType, "video/") {
|
||||||
|
|||||||
@@ -1543,16 +1543,56 @@ func (m *Manager) GetPinnedCount() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) CopyFile(filePath string) error {
|
func (m *Manager) CopyFile(filePath string) error {
|
||||||
if _, err := os.Stat(filePath); err != nil {
|
fileInfo, err := os.Stat(filePath)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("file not found: %w", err)
|
return fmt.Errorf("file not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := m.getConfig()
|
||||||
|
if fileInfo.Size() > cfg.MaxEntrySize {
|
||||||
|
return fmt.Errorf("file too large: %d > %d", fileInfo.Size(), cfg.MaxEntrySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
exportedPath, err := m.ExportFileForFlatpak(filePath)
|
exportedPath, err := m.ExportFileForFlatpak(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exportedPath = filePath
|
exportedPath = filePath
|
||||||
}
|
}
|
||||||
fileURI := "file://" + exportedPath
|
fileURI := "file://" + exportedPath
|
||||||
|
|
||||||
|
if imgData, imgMime, ok := m.tryReadImageFromURI([]byte("file://" + filePath)); ok {
|
||||||
|
entry := Entry{
|
||||||
|
Data: imgData,
|
||||||
|
MimeType: imgMime,
|
||||||
|
Size: len(imgData),
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
IsImage: true,
|
||||||
|
Preview: m.imagePreview(imgData, imgMime),
|
||||||
|
}
|
||||||
|
if err := m.storeEntry(entry); err != nil {
|
||||||
|
log.Errorf("Failed to store file entry: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry := Entry{
|
||||||
|
Data: fileData,
|
||||||
|
MimeType: "text/uri-list",
|
||||||
|
Size: len(fileData),
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
IsImage: false,
|
||||||
|
Preview: fmt.Sprintf("[[ file %s ]]", filepath.Base(filePath)),
|
||||||
|
}
|
||||||
|
if err := m.storeEntry(entry); err != nil {
|
||||||
|
log.Errorf("Failed to store file entry: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateState()
|
||||||
|
m.notifySubscribers()
|
||||||
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
if m.dataControlMgr == nil || m.dataDevice == nil {
|
if m.dataControlMgr == nil || m.dataDevice == nil {
|
||||||
log.Error("Data control manager or device not initialized")
|
log.Error("Data control manager or device not initialized")
|
||||||
|
|||||||
@@ -284,6 +284,20 @@ DankModal {
|
|||||||
modal: clipboardHistoryModal
|
modal: clipboardHistoryModal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DMSService
|
||||||
|
function onClipboardStateUpdate(data) {
|
||||||
|
if (!clipboardHistoryModal.shouldBeVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newHistory = data.history || [];
|
||||||
|
internalEntries = newHistory;
|
||||||
|
pinnedEntries = newHistory.filter(e => e.pinned);
|
||||||
|
pinnedCount = pinnedEntries.length;
|
||||||
|
updateFilteredModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConfirmModal {
|
ConfirmModal {
|
||||||
id: clearConfirmDialog
|
id: clearConfirmDialog
|
||||||
confirmButtonText: I18n.tr("Clear All")
|
confirmButtonText: I18n.tr("Clear All")
|
||||||
|
|||||||
@@ -60,12 +60,13 @@ Singleton {
|
|||||||
signal openUrlRequested(string url)
|
signal openUrlRequested(string url)
|
||||||
signal appPickerRequested(var data)
|
signal appPickerRequested(var data)
|
||||||
signal screensaverStateUpdate(var data)
|
signal screensaverStateUpdate(var data)
|
||||||
|
signal clipboardStateUpdate(var data)
|
||||||
|
|
||||||
property bool capsLockState: false
|
property bool capsLockState: false
|
||||||
property bool screensaverInhibited: false
|
property bool screensaverInhibited: false
|
||||||
property var screensaverInhibitors: []
|
property var screensaverInhibitors: []
|
||||||
|
|
||||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
|
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -392,6 +393,8 @@ Singleton {
|
|||||||
screensaverStateUpdate(data);
|
screensaverStateUpdate(data);
|
||||||
} else if (service === "dbus") {
|
} else if (service === "dbus") {
|
||||||
dbusSignalReceived(data.subscriptionId || "", data);
|
dbusSignalReceived(data.subscriptionId || "", data);
|
||||||
|
} else if (service === "clipboard") {
|
||||||
|
clipboardStateUpdate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +729,8 @@ Singleton {
|
|||||||
if (!response.error && response.result?.subscriptionId) {
|
if (!response.error && response.result?.subscriptionId) {
|
||||||
dbusSubscriptions[response.result.subscriptionId] = true;
|
dbusSubscriptions[response.result.subscriptionId] = true;
|
||||||
}
|
}
|
||||||
if (callback) callback(response);
|
if (callback)
|
||||||
|
callback(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,7 +741,8 @@ Singleton {
|
|||||||
if (!response.error) {
|
if (!response.error) {
|
||||||
delete dbusSubscriptions[subscriptionId];
|
delete dbusSubscriptions[subscriptionId];
|
||||||
}
|
}
|
||||||
if (callback) callback(response);
|
if (callback)
|
||||||
|
callback(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user