This commit is contained in:
2026-08-11 23:05:38 +02:00
parent da16a66d41
commit 1d5e5d7a4b

108
gui.py
View File

@@ -23,13 +23,41 @@ def normalize_file_path(path_str: str) -> str:
else: else:
path_str = urllib.parse.unquote(path_str[7:]) path_str = urllib.parse.unquote(path_str[7:])
return os.path.abspath(path_str) 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.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.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout, QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox, 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 # Configuration paths
@@ -70,7 +98,8 @@ DEFAULT_CONFIG = {
"enable_notifications": True, "enable_notifications": True,
"show_progress_dialog": True, "show_progress_dialog": True,
"autostart": False, "autostart": False,
"icon_theme": "dark" "icon_theme": "dark",
"clipboard_url_type": "post"
} }
def load_config(): def load_config():
@@ -353,7 +382,17 @@ class SettingsDialog(QDialog):
theme_layout.addWidget(self.cb_icon_theme) theme_layout.addWidget(self.cb_icon_theme)
theme_layout.addStretch() theme_layout.addStretch()
behavior_layout.addLayout(theme_layout) 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) main_layout.addWidget(grp_behavior)
# Section 4: Sound Notification # Section 4: Sound Notification
@@ -442,6 +481,9 @@ class SettingsDialog(QDialog):
icon_theme = config.get("icon_theme", "dark").lower() icon_theme = config.get("icon_theme", "dark").lower()
self.cb_icon_theme.setCurrentIndex(1 if icon_theme == "light" else 0) 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.chk_sound.setChecked(config.get("play_success_sound", False))
self.txt_sound_path.setText(config.get("success_audio_path", "")) self.txt_sound_path.setText(config.get("success_audio_path", ""))
@@ -520,6 +562,7 @@ class SettingsDialog(QDialog):
"show_progress_dialog": self.chk_progress.isChecked(), "show_progress_dialog": self.chk_progress.isChecked(),
"autostart": self.chk_autostart.isChecked(), "autostart": self.chk_autostart.isChecked(),
"icon_theme": "light" if self.cb_icon_theme.currentIndex() == 1 else "dark", "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(), "play_success_sound": self.chk_sound.isChecked(),
"success_audio_path": self.txt_sound_path.text().strip(), "success_audio_path": self.txt_sound_path.text().strip(),
"success_audio_volume": self.slider_volume.value() "success_audio_volume": self.slider_volume.value()
@@ -575,7 +618,9 @@ class GalleryCard(QFrame):
self.item = item self.item = item
self.on_delete = on_delete self.on_delete = on_delete
self.setFrameShape(QFrame.StyledPanel) self.setFrameShape(QFrame.StyledPanel)
self.setFixedHeight(195) self.setFixedHeight(190)
self.setMinimumWidth(160)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.setStyleSheet(""" self.setStyleSheet("""
GalleryCard { GalleryCard {
background-color: #1e1e24; background-color: #1e1e24;
@@ -595,7 +640,8 @@ class GalleryCard(QFrame):
# Thumbnail with Drag & Drop support # Thumbnail with Drag & Drop support
file_path = item.get("file_path", "") file_path = item.get("file_path", "")
self.thumb_label = DraggableImageLabel(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.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.thumb_label.setCursor(Qt.CursorShape.OpenHandCursor) self.thumb_label.setCursor(Qt.CursorShape.OpenHandCursor)
self.thumb_label.setToolTip("Click & Drag to drop file into other apps") self.thumb_label.setToolTip("Click & Drag to drop file into other apps")
@@ -664,7 +710,7 @@ class GalleryCard(QFrame):
def on_copy_link(self): def on_copy_link(self):
url = self.item.get("url", "") url = self.item.get("url", "")
if url: if url:
QApplication.clipboard().setText(url) copy_to_clipboard(url)
def on_open_url(self): def on_open_url(self):
url = self.item.get("url", "") url = self.item.get("url", "")
@@ -715,25 +761,42 @@ class GalleryWindow(QDialog):
# Scroll Area for Gallery Cards # Scroll Area for Gallery Cards
self.scroll_area = QScrollArea() self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True) 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_area.setStyleSheet("QScrollArea { border: 1px solid #333340; background: #141418; border-radius: 6px; }")
self.scroll_content = QWidget() self.scroll_content = QWidget()
self.grid_layout = QGridLayout(self.scroll_content) self.grid_layout = QGridLayout(self.scroll_content)
self.grid_layout.setContentsMargins(12, 12, 12, 12) self.grid_layout.setContentsMargins(12, 12, 12, 12)
self.grid_layout.setSpacing(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) self.scroll_area.setWidget(self.scroll_content)
main_layout.addWidget(self.scroll_area) main_layout.addWidget(self.scroll_area)
self.current_cols = 0
self.load_items() 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): def load_items(self):
while self.grid_layout.count(): while self.grid_layout.count():
child = self.grid_layout.takeAt(0) child = self.grid_layout.takeAt(0)
if child.widget(): if child.widget():
child.widget().deleteLater() child.widget().deleteLater()
# Reset column stretches
for c in range(20):
self.grid_layout.setColumnStretch(c, 0)
history = load_history() history = load_history()
query = self.txt_search.text().strip().lower() query = self.txt_search.text().strip().lower()
if query: if query:
@@ -746,13 +809,24 @@ class GalleryWindow(QDialog):
self.grid_layout.addWidget(lbl_empty, 0, 0) self.grid_layout.addWidget(lbl_empty, 0, 0)
return 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): for idx, item in enumerate(history):
card = GalleryCard(item, on_delete=self.delete_item) card = GalleryCard(item, on_delete=self.delete_item)
row = idx // cols row = idx // cols
col = idx % cols col = idx % cols
self.grid_layout.addWidget(card, row, col) 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): def delete_item(self, item):
history = load_history() history = load_history()
history = [h for h in history if h.get("url") != item.get("url")] history = [h for h in history if h.get("url") != item.get("url")]
@@ -1240,9 +1314,19 @@ class SystemTrayApp(QObject):
try: try:
res = json.loads(response_text) res = json.loads(response_text)
if res.get("success") and res.get("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")):
item_url = res.get("url") config = get_env_config()
QApplication.clipboard().setText(item_url) 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 # Add to history gallery
thumb = generate_thumbnail(file_path) thumb = generate_thumbnail(file_path)