diff --git a/gui.py b/gui.py index db6f0f4..aee7542 100644 --- a/gui.py +++ b/gui.py @@ -23,13 +23,41 @@ def normalize_file_path(path_str: str) -> str: else: path_str = urllib.parse.unquote(path_str[7:]) return os.path.abspath(path_str) + +def copy_to_clipboard(text: str): + if not text: + return + try: + cb = QApplication.clipboard() + if cb: + cb.setText(text, QClipboard.Mode.Clipboard) + cb.setText(text, QClipboard.Mode.Selection) + except Exception as e: + print(f"Qt clipboard error: {e}") + + # Fallback to system CLI clipboard utilities for Wayland/X11 persistence + if shutil.which("wl-copy"): + try: + subprocess.run(["wl-copy", text], check=False) + except Exception as e: + print(f"wl-copy error: {e}") + elif shutil.which("xclip"): + try: + subprocess.run(["xclip", "-selection", "clipboard"], input=text.encode("utf-8"), check=False) + except Exception as e: + print(f"xclip error: {e}") + elif shutil.which("xsel"): + try: + subprocess.run(["xsel", "--clipboard", "--input"], input=text.encode("utf-8"), check=False) + except Exception as e: + print(f"xsel error: {e}") from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData -from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag +from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag, QClipboard from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket from PySide6.QtWidgets import ( QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox, - QFormLayout, QMessageBox, QFileDialog, QSlider, QScrollArea, QFrame, QGridLayout, QWidget + QFormLayout, QMessageBox, QFileDialog, QSlider, QScrollArea, QFrame, QGridLayout, QWidget, QSizePolicy ) # Configuration paths @@ -70,7 +98,8 @@ DEFAULT_CONFIG = { "enable_notifications": True, "show_progress_dialog": True, "autostart": False, - "icon_theme": "dark" + "icon_theme": "dark", + "clipboard_url_type": "post" } def load_config(): @@ -353,7 +382,17 @@ class SettingsDialog(QDialog): theme_layout.addWidget(self.cb_icon_theme) theme_layout.addStretch() behavior_layout.addLayout(theme_layout) - + + # Copied Link Format Selection + url_type_layout = QHBoxLayout() + url_type_label = QLabel("Copied Link Format:") + self.cb_url_type = QComboBox() + self.cb_url_type.addItems(["Post Page URL (e.g. /v/123)", "Direct File URL (e.g. /files/123.png)"]) + url_type_layout.addWidget(url_type_label) + url_type_layout.addWidget(self.cb_url_type) + url_type_layout.addStretch() + behavior_layout.addLayout(url_type_layout) + main_layout.addWidget(grp_behavior) # Section 4: Sound Notification @@ -442,6 +481,9 @@ class SettingsDialog(QDialog): icon_theme = config.get("icon_theme", "dark").lower() self.cb_icon_theme.setCurrentIndex(1 if icon_theme == "light" else 0) + url_type = config.get("clipboard_url_type", "post").lower() + self.cb_url_type.setCurrentIndex(1 if url_type == "direct" else 0) + self.chk_sound.setChecked(config.get("play_success_sound", False)) self.txt_sound_path.setText(config.get("success_audio_path", "")) @@ -520,6 +562,7 @@ class SettingsDialog(QDialog): "show_progress_dialog": self.chk_progress.isChecked(), "autostart": self.chk_autostart.isChecked(), "icon_theme": "light" if self.cb_icon_theme.currentIndex() == 1 else "dark", + "clipboard_url_type": "direct" if self.cb_url_type.currentIndex() == 1 else "post", "play_success_sound": self.chk_sound.isChecked(), "success_audio_path": self.txt_sound_path.text().strip(), "success_audio_volume": self.slider_volume.value() @@ -575,7 +618,9 @@ class GalleryCard(QFrame): self.item = item self.on_delete = on_delete self.setFrameShape(QFrame.StyledPanel) - self.setFixedHeight(195) + self.setFixedHeight(190) + self.setMinimumWidth(160) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) self.setStyleSheet(""" GalleryCard { background-color: #1e1e24; @@ -595,7 +640,8 @@ class GalleryCard(QFrame): # Thumbnail with Drag & Drop support file_path = item.get("file_path", "") self.thumb_label = DraggableImageLabel(file_path) - self.thumb_label.setFixedSize(160, 100) + self.thumb_label.setFixedHeight(95) + self.thumb_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.thumb_label.setCursor(Qt.CursorShape.OpenHandCursor) self.thumb_label.setToolTip("Click & Drag to drop file into other apps") @@ -664,7 +710,7 @@ class GalleryCard(QFrame): def on_copy_link(self): url = self.item.get("url", "") if url: - QApplication.clipboard().setText(url) + copy_to_clipboard(url) def on_open_url(self): url = self.item.get("url", "") @@ -715,25 +761,42 @@ class GalleryWindow(QDialog): # Scroll Area for Gallery Cards self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) + self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.scroll_area.setStyleSheet("QScrollArea { border: 1px solid #333340; background: #141418; border-radius: 6px; }") self.scroll_content = QWidget() self.grid_layout = QGridLayout(self.scroll_content) self.grid_layout.setContentsMargins(12, 12, 12, 12) self.grid_layout.setSpacing(12) - self.grid_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + self.grid_layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft) self.scroll_area.setWidget(self.scroll_content) main_layout.addWidget(self.scroll_area) + self.current_cols = 0 self.load_items() + def get_column_count(self): + w = self.scroll_area.viewport().width() + if w <= 0: + w = self.width() - 40 + card_min_w = 160 + spacing = 12 + margin_padding = 40 + cols = max(1, (w - margin_padding + spacing) // (card_min_w + spacing)) + return cols + def load_items(self): while self.grid_layout.count(): child = self.grid_layout.takeAt(0) if child.widget(): child.widget().deleteLater() + # Reset column stretches + for c in range(20): + self.grid_layout.setColumnStretch(c, 0) + history = load_history() query = self.txt_search.text().strip().lower() if query: @@ -746,13 +809,24 @@ class GalleryWindow(QDialog): self.grid_layout.addWidget(lbl_empty, 0, 0) return - cols = 4 + cols = self.get_column_count() + self.current_cols = cols + + for c in range(cols): + self.grid_layout.setColumnStretch(c, 1) + for idx, item in enumerate(history): card = GalleryCard(item, on_delete=self.delete_item) row = idx // cols col = idx % cols self.grid_layout.addWidget(card, row, col) + def resizeEvent(self, event): + super().resizeEvent(event) + cols = self.get_column_count() + if cols != getattr(self, "current_cols", 0): + self.load_items() + def delete_item(self, item): history = load_history() history = [h for h in history if h.get("url") != item.get("url")] @@ -1240,9 +1314,19 @@ class SystemTrayApp(QObject): try: res = json.loads(response_text) - if res.get("success") and res.get("url"): - item_url = res.get("url") - QApplication.clipboard().setText(item_url) + if res.get("success") and (res.get("url") or res.get("file_url") or res.get("direct_url") or res.get("target_url") or res.get("file")): + config = get_env_config() + url_type = config.get("clipboard_url_type", "post").lower() + + post_url = res.get("url") or res.get("post_url") or "" + direct_url = res.get("file_url") or res.get("direct_url") or res.get("target_url") or res.get("file") or "" + + if url_type == "direct" and direct_url: + item_url = direct_url + else: + item_url = post_url or direct_url or res.get("url", "") + + copy_to_clipboard(item_url) # Add to history gallery thumb = generate_thumbnail(file_path)