mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Merge pull request #78 from KeksPirates/feat/contextmenu
Enhance download management with context menus and tray icon features
This commit is contained in:
@@ -51,6 +51,7 @@ SoftwareManager is a Python-based GUI tool that simplifies searching and downloa
|
|||||||
psutil (7.1.0)
|
psutil (7.1.0)
|
||||||
libtorrent (2.0.11)
|
libtorrent (2.0.11)
|
||||||
libtorrent-windows-dll (0.0.3)
|
libtorrent-windows-dll (0.0.3)
|
||||||
|
send2trash (2.1.0)
|
||||||
```
|
```
|
||||||
<!-- python-dependencies:end -->
|
<!-- python-dependencies:end -->
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ plyer==2.1.0
|
|||||||
psutil==7.1.0
|
psutil==7.1.0
|
||||||
libtorrent==2.0.11
|
libtorrent==2.0.11
|
||||||
libtorrent-windows-dll==0.0.3
|
libtorrent-windows-dll==0.0.3
|
||||||
|
send2trash==2.1.0
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ class SteamripScraper:
|
|||||||
|
|
||||||
links = []
|
links = []
|
||||||
names = []
|
names = []
|
||||||
|
|
||||||
for gamehtml in games:
|
for gamehtml in games:
|
||||||
link = gamehtml.find("a", href=lambda x: x and x.startswith("/"))
|
link = gamehtml.find("a", href=lambda x: x and x.startswith("/"))
|
||||||
links += re.findall(r'(?<=href=")[^"]*', link.__str__())
|
if link:
|
||||||
name = gamehtml.find("a", href=lambda x: x and x.startswith("/"))
|
links.append("https://steamrip.com" + link["href"])
|
||||||
names += re.findall(r'(?<=\/">)[^<]*', name.__str__())
|
names.append(link.get_text())
|
||||||
|
|
||||||
# construct the list[dict[str,str]]
|
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
@@ -55,8 +54,7 @@ class SteamripScraper:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def scrape_steamrip_game_downloads(self, gamelink):
|
def scrape_steamrip_game_downloads(self, url):
|
||||||
url = "https://steamrip.com" + gamelink
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
soup = BeautifulSoup(response.text, "html.parser")
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
download_link_elements = soup.find_all("a",class_="shortc-button")
|
download_link_elements = soup.find_all("a",class_="shortc-button")
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
|
from PySide6.QtCore import Qt, QPoint, Signal, QThreadPool, QThread
|
||||||
from utils.logging.logs import consoleLog, remove_download_log
|
from utils.logging.logs import consoleLog, remove_download_log
|
||||||
|
from utils.network.download import download_selected
|
||||||
|
from utils.data.tracker import get_magnet_link
|
||||||
|
from utils.general.wrappers import run_thread
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
from PySide6.QtCore import Qt, QPoint
|
|
||||||
from utils.data.state import state
|
from utils.data.state import state
|
||||||
|
from send2trash import send2trash
|
||||||
from PySide6 import QtWidgets
|
from PySide6 import QtWidgets
|
||||||
|
import threading
|
||||||
|
import webbrowser
|
||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class ContextMenu:
|
|
||||||
|
class MagnetWorker(QThread):
|
||||||
|
finished = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, url, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
magnet = get_magnet_link(self.url)
|
||||||
|
if magnet:
|
||||||
|
self.finished.emit(magnet)
|
||||||
|
|
||||||
|
class ContextMenu_Downloads:
|
||||||
def __init__(self, main_window):
|
def __init__(self, main_window):
|
||||||
self.main_window = main_window
|
self.main_window = main_window
|
||||||
self.context_menu = QtWidgets.QMenu(main_window)
|
self.context_menu = QtWidgets.QMenu(main_window)
|
||||||
self.context_menu.addAction("Open Containing Folder", self.openFolderAction)
|
self.context_menu.addAction("Open Containing Folder", self.openFolderAction)
|
||||||
self.context_menu.addAction("Copy Magnet URI", self.copyMagnetURIAction)
|
self.context_menu.addAction("Copy Download Link", self.copyMagnetURIAction)
|
||||||
self.context_menu.addAction("Remove from list", self.cancelDownloadAction)
|
self.context_menu.addAction("Remove from list", self.cancelDownloadAction)
|
||||||
self.context_menu.addAction("Delete File", self.deleteFileAction)
|
self.context_menu.addAction("Delete File", self.deleteFileAction)
|
||||||
|
|
||||||
@@ -62,30 +81,44 @@ class ContextMenu:
|
|||||||
clipboard.setText(magnet_link)
|
clipboard.setText(magnet_link)
|
||||||
|
|
||||||
def cancelDownloadAction(self):
|
def cancelDownloadAction(self):
|
||||||
if not hasattr(self, '_context_menu_row'):
|
selected_rows = sorted(set(index.row() for index in self.downloadList.selectedIndexes()), reverse=True)
|
||||||
return
|
|
||||||
row = self._context_menu_row
|
|
||||||
with state.downloads_lock:
|
|
||||||
if row < 0 or row >= len(state.active_downloads):
|
|
||||||
return
|
|
||||||
keys = list(state.active_downloads.keys())
|
|
||||||
magnet_link = keys[row]
|
|
||||||
magnetdl = state.active_downloads[magnet_link]
|
|
||||||
|
|
||||||
# Cache the name BEFORE removing from session
|
if not selected_rows:
|
||||||
try:
|
return
|
||||||
torrent_name = magnetdl.status().name
|
|
||||||
except RuntimeError:
|
names = []
|
||||||
torrent_name = "Unknown"
|
items_to_remove = []
|
||||||
|
|
||||||
|
with state.downloads_lock:
|
||||||
|
keys = list(state.active_downloads.keys())
|
||||||
|
|
||||||
|
for row in selected_rows:
|
||||||
|
if 0 <= row < len(keys):
|
||||||
|
magnet_link = keys[row]
|
||||||
|
magnetdl = state.active_downloads[magnet_link]
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = magnetdl.status().name
|
||||||
|
except RuntimeError:
|
||||||
|
name = "Unknown"
|
||||||
|
|
||||||
|
names.append(name)
|
||||||
|
items_to_remove.append((magnet_link, magnetdl, name))
|
||||||
|
|
||||||
confirm = QMessageBox.question(
|
confirm = QMessageBox.question(
|
||||||
self.main_window, "Cancel Download",
|
self.main_window,
|
||||||
f"Are you sure you want to cancel the download of '{torrent_name}'?",
|
"Cancel Downloads",
|
||||||
|
f"Are you sure you want to cancel {len(names)} download(s)?\n\n" + "\n".join(names),
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
)
|
)
|
||||||
if confirm == QMessageBox.StandardButton.Yes:
|
|
||||||
|
if confirm != QMessageBox.StandardButton.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
for magnet_link, magnetdl, name in items_to_remove:
|
||||||
with state.downloads_lock:
|
with state.downloads_lock:
|
||||||
state.active_downloads.pop(magnet_link, None)
|
state.active_downloads.pop(magnet_link, None)
|
||||||
|
|
||||||
remove_download_log(magnet_link)
|
remove_download_log(magnet_link)
|
||||||
|
|
||||||
if state.dl_session:
|
if state.dl_session:
|
||||||
@@ -94,53 +127,71 @@ class ContextMenu:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
consoleLog(f"Exception while removing download from LibTorrent: {e}")
|
consoleLog(f"Exception while removing download from LibTorrent: {e}")
|
||||||
|
|
||||||
consoleLog(f"Cancelled download: {torrent_name}", True)
|
consoleLog(f"Cancelled download: {name}", True)
|
||||||
|
|
||||||
|
|
||||||
def deleteFileAction(self):
|
def deleteFileAction(self):
|
||||||
if not hasattr(self, '_context_menu_row'):
|
selected_rows = sorted(set(index.row() for index in self.downloadList.selectedIndexes()), reverse=True)
|
||||||
|
|
||||||
|
if not selected_rows:
|
||||||
return
|
return
|
||||||
row = self._context_menu_row
|
|
||||||
|
items_to_remove = []
|
||||||
|
|
||||||
with state.downloads_lock:
|
with state.downloads_lock:
|
||||||
if row < 0 or row >= len(state.active_downloads):
|
keys = list(state.active_downloads.keys())
|
||||||
return
|
|
||||||
magnet_link = list(state.active_downloads.keys())[row]
|
for row in selected_rows:
|
||||||
magnetdl = state.active_downloads[magnet_link]
|
if 0 <= row < len(keys):
|
||||||
try:
|
magnet_link = keys[row]
|
||||||
status = magnetdl.status()
|
magnetdl = state.active_downloads[magnet_link]
|
||||||
save_path = status.save_path
|
|
||||||
torrent_name = status.name
|
try:
|
||||||
except RuntimeError:
|
status = magnetdl.status()
|
||||||
consoleLog("Error: torrent handle already invalid", True)
|
save_path = status.save_path
|
||||||
with state.downloads_lock:
|
torrent_name = status.name
|
||||||
state.active_downloads.pop(magnet_link, None)
|
except RuntimeError:
|
||||||
remove_download_log(magnet_link)
|
consoleLog("Error: torrent handle already invalid", True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
download_path = os.path.join(save_path, torrent_name)
|
||||||
|
|
||||||
|
items_to_remove.append((magnet_link, magnetdl, torrent_name, download_path))
|
||||||
|
|
||||||
|
if not items_to_remove:
|
||||||
return
|
return
|
||||||
download_path = os.path.join(save_path, torrent_name)
|
|
||||||
|
names = [item[2] for item in items_to_remove]
|
||||||
|
|
||||||
confirm = QMessageBox.question(
|
confirm = QMessageBox.question(
|
||||||
self.main_window, "Delete Files",
|
self.main_window,
|
||||||
f"Are you sure you want to delete the downloaded files of '{torrent_name}'? This action cannot be undone.",
|
"Delete Files",
|
||||||
|
f"Are you sure you want to delete {len(names)} download(s)?\n\n" + "\n".join(names),
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
)
|
)
|
||||||
if confirm == QMessageBox.StandardButton.Yes:
|
|
||||||
|
if confirm != QMessageBox.StandardButton.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
for magnet_link, magnetdl, torrent_name, download_path in items_to_remove:
|
||||||
with state.downloads_lock:
|
with state.downloads_lock:
|
||||||
state.active_downloads.pop(magnet_link, None)
|
state.active_downloads.pop(magnet_link, None)
|
||||||
|
|
||||||
remove_download_log(magnet_link)
|
remove_download_log(magnet_link)
|
||||||
|
|
||||||
if state.dl_session:
|
if state.dl_session:
|
||||||
try:
|
try:
|
||||||
state.dl_session.remove_torrent(magnetdl)
|
state.dl_session.remove_torrent(magnetdl)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
consoleLog(f"Exception while removing download from LibTorrent: {e}")
|
consoleLog(f"Exception while removing download from LibTorrent: {e}")
|
||||||
|
|
||||||
if download_path and os.path.exists(download_path):
|
if download_path and os.path.exists(download_path):
|
||||||
import shutil
|
|
||||||
last_error = None
|
last_error = None
|
||||||
for attempt in range(3):
|
for attempt in range(3):
|
||||||
try:
|
try:
|
||||||
if os.path.isfile(download_path):
|
if os.path.isfile(download_path) or os.path.isdir(download_path):
|
||||||
os.remove(download_path)
|
send2trash(download_path)
|
||||||
else:
|
|
||||||
shutil.rmtree(download_path)
|
|
||||||
consoleLog(f"Deleted files for: {torrent_name}", True)
|
consoleLog(f"Deleted files for: {torrent_name}", True)
|
||||||
last_error = None
|
last_error = None
|
||||||
break
|
break
|
||||||
@@ -148,7 +199,75 @@ class ContextMenu:
|
|||||||
last_error = e
|
last_error = e
|
||||||
if attempt < 2:
|
if attempt < 2:
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
if last_error:
|
if last_error:
|
||||||
consoleLog(f"Error deleting files: {last_error}", True)
|
consoleLog(f"Error deleting files: {last_error}", True)
|
||||||
else:
|
else:
|
||||||
consoleLog(f"Removed entry (files not found): {torrent_name}", True)
|
consoleLog(f"Removed entry (files not found): {torrent_name}", True)
|
||||||
|
|
||||||
|
class ContextMenu_TrackerTable:
|
||||||
|
def __init__(self, main_window):
|
||||||
|
self.main_window = main_window
|
||||||
|
self.context_menu = QtWidgets.QMenu(main_window)
|
||||||
|
self.context_menu.addAction("Copy Download Link", self.copyMagnetURIAction)
|
||||||
|
self.context_menu.addAction("Open in Browser", self.openInBrowserAction)
|
||||||
|
self.context_menu.addAction("Download", self.downloadItemAction)
|
||||||
|
|
||||||
|
state.trackertable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
|
state.trackertable.customContextMenuRequested.connect(self._show_context_menu)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def TrackerTable(self):
|
||||||
|
return state.trackertable
|
||||||
|
|
||||||
|
def _show_context_menu(self, pos: QPoint):
|
||||||
|
index = self.TrackerTable.indexAt(pos)
|
||||||
|
if index.isValid():
|
||||||
|
self._context_menu_row = index.row()
|
||||||
|
self.context_menu.exec(self.TrackerTable.viewport().mapToGlobal(pos))
|
||||||
|
|
||||||
|
def copyMagnetURIAction(self):
|
||||||
|
if not hasattr(self, '_context_menu_row'):
|
||||||
|
return
|
||||||
|
row = self._context_menu_row
|
||||||
|
|
||||||
|
if not state.posts or row < 0 or row >= len(state.posts):
|
||||||
|
return
|
||||||
|
|
||||||
|
post = state.posts[row]
|
||||||
|
url = post.get("url")
|
||||||
|
if not url:
|
||||||
|
return
|
||||||
|
|
||||||
|
worker = MagnetWorker(url, parent=self.main_window)
|
||||||
|
worker.finished.connect(self._on_magnet_fetched)
|
||||||
|
worker.finished.connect(worker.deleteLater)
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
def _on_magnet_fetched(self, magnet):
|
||||||
|
clipboard = QtWidgets.QApplication.clipboard()
|
||||||
|
clipboard.setText(magnet)
|
||||||
|
consoleLog("Magnet URI copied to clipboard!", True)
|
||||||
|
|
||||||
|
def openInBrowserAction(self):
|
||||||
|
if not hasattr(self, '_context_menu_row'):
|
||||||
|
return
|
||||||
|
row = self._context_menu_row
|
||||||
|
|
||||||
|
if not state.posts or row < 0 or row >= len(state.posts):
|
||||||
|
return
|
||||||
|
|
||||||
|
post = state.posts[row]
|
||||||
|
url = post.get("url")
|
||||||
|
if url:
|
||||||
|
try: webbrowser.open(url)
|
||||||
|
except Exception as e: consoleLog(f"Failed to open URL: {e}", True)
|
||||||
|
|
||||||
|
def downloadItemAction(self):
|
||||||
|
if not hasattr(self, '_context_menu_row'):
|
||||||
|
return
|
||||||
|
row = self._context_menu_row
|
||||||
|
if not state.posts or row < 0 or row >= len(state.posts):
|
||||||
|
return
|
||||||
|
|
||||||
|
run_thread(threading.Thread(target=download_selected, args=(state.trackertable.selectedItems(),)))
|
||||||
|
|||||||
@@ -297,6 +297,11 @@ def settings_dialog(self):
|
|||||||
interface_layout.addWidget(interface_select)
|
interface_layout.addWidget(interface_select)
|
||||||
interface_container.setLayout(interface_layout)
|
interface_container.setLayout(interface_layout)
|
||||||
|
|
||||||
|
# Close To Tray
|
||||||
|
close_to_tray_container, close_to_tray_checkbox = create_widget(QCheckBox, "Close to Tray: ")
|
||||||
|
close_to_tray_checkbox.setChecked(state.close_to_tray)
|
||||||
|
close_to_tray_checkbox.toggled.connect(lambda checked: setattr(state, 'close_to_tray', checked))
|
||||||
|
|
||||||
|
|
||||||
# Save / Cancel buttons
|
# Save / Cancel buttons
|
||||||
layout = QHBoxLayout()
|
layout = QHBoxLayout()
|
||||||
@@ -322,7 +327,8 @@ def settings_dialog(self):
|
|||||||
image_opacity.value(),
|
image_opacity.value(),
|
||||||
image_as_wallpaper=image_mode_checkbox.isChecked(),
|
image_as_wallpaper=image_mode_checkbox.isChecked(),
|
||||||
image_position=image_position_combo.currentText(),
|
image_position=image_position_combo.currentText(),
|
||||||
accent_color=accent_color_input.text().strip()
|
accent_color=accent_color_input.text().strip(),
|
||||||
|
close_to_tray=close_to_tray_checkbox.isChecked()
|
||||||
)
|
)
|
||||||
color = _accent_selection_color()
|
color = _accent_selection_color()
|
||||||
if color:
|
if color:
|
||||||
@@ -337,7 +343,7 @@ def settings_dialog(self):
|
|||||||
layout.addWidget(save_btn)
|
layout.addWidget(save_btn)
|
||||||
|
|
||||||
tabs = QtWidgets.QTabWidget()
|
tabs = QtWidgets.QTabWidget()
|
||||||
create_tab("General", [autoresume_container, update_checkbox_container, transparent_window_container, accent_color_container], tabs=tabs, stretch=True)
|
create_tab("General", [autoresume_container, update_checkbox_container, transparent_window_container, accent_color_container, close_to_tray_container], tabs=tabs, stretch=True)
|
||||||
create_tab("Image", [enable_image_container, image_mode_container, image_position_container, image_width_container, image_offset_container, image_opacity_container], tabs=tabs, stretch=True)
|
create_tab("Image", [enable_image_container, image_mode_container, image_position_container, image_width_container, image_offset_container, image_opacity_container], tabs=tabs, stretch=True)
|
||||||
create_tab("Paths", [download_path_container, image_path_container], tabs=tabs, stretch=True)
|
create_tab("Paths", [download_path_container, image_path_container], tabs=tabs, stretch=True)
|
||||||
create_tab("Network", [interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container], tabs=tabs, stretch=True)
|
create_tab("Network", [interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container], tabs=tabs, stretch=True)
|
||||||
|
|||||||
+12
-2
@@ -132,6 +132,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self._tracker_hover_delegate = TrackerHoverDelegate(lambda: self._tracker_hovered_row, self)
|
self._tracker_hover_delegate = TrackerHoverDelegate(lambda: self._tracker_hovered_row, self)
|
||||||
|
|
||||||
state.trackertable = _create_tracker_table(self)
|
state.trackertable = _create_tracker_table(self)
|
||||||
|
state.trackertable.cellDoubleClicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.selectedItems(),))))
|
||||||
|
|
||||||
container = QWidget()
|
container = QWidget()
|
||||||
containerLayout = QVBoxLayout()
|
containerLayout = QVBoxLayout()
|
||||||
@@ -251,7 +252,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self.active_timer.timeout.connect(self.show_empty_downloads)
|
self.active_timer.timeout.connect(self.show_empty_downloads)
|
||||||
self.active_timer.start(500)
|
self.active_timer.start(500)
|
||||||
|
|
||||||
self._context_menu = interface.dialogs.contextmenu.ContextMenu(self)
|
self._context_menu_downloads = interface.dialogs.contextmenu.ContextMenu_Downloads(self)
|
||||||
|
self._context_menu_trackertable = interface.dialogs.contextmenu.ContextMenu_TrackerTable(self)
|
||||||
self.image_overlay = Image(self)
|
self.image_overlay = Image(self)
|
||||||
|
|
||||||
def _apply_default_headers(self, table):
|
def _apply_default_headers(self, table):
|
||||||
@@ -352,7 +354,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
table.setRowCount(len(state.posts))
|
table.setRowCount(len(state.posts))
|
||||||
for x, rowdata in enumerate(state.posts):
|
for x, rowdata in enumerate(state.posts):
|
||||||
for y, (key, data) in enumerate(rowdata.items()):
|
for y, (_, data) in enumerate(rowdata.items()):
|
||||||
item = QTableWidgetItem(str(data))
|
item = QTableWidgetItem(str(data))
|
||||||
item.setData(Qt.ItemDataRole.UserRole, x)
|
item.setData(Qt.ItemDataRole.UserRole, x)
|
||||||
table.setItem(x, y, item)
|
table.setItem(x, y, item)
|
||||||
@@ -395,6 +397,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
super().changeEvent(event)
|
super().changeEvent(event)
|
||||||
|
|
||||||
def closeEvent(self, event: QCloseEvent):
|
def closeEvent(self, event: QCloseEvent):
|
||||||
|
if state.close_to_tray is True:
|
||||||
|
event.ignore()
|
||||||
|
self.hide()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.shutdown(event)
|
||||||
|
|
||||||
|
def shutdown(self, event: QCloseEvent):
|
||||||
closehelper()
|
closehelper()
|
||||||
event.accept()
|
event.accept()
|
||||||
from utils.general.shutdown import force_exit
|
from utils.general.shutdown import force_exit
|
||||||
|
|||||||
+82
-3
@@ -1,26 +1,76 @@
|
|||||||
from network.libtorrent_misc import send_notification, update_log, check_deleted_files
|
from network.libtorrent_misc import send_notification, update_log, check_deleted_files
|
||||||
from utils.logging.loghandler import split_data, check_completed, check_downloads
|
from utils.logging.loghandler import split_data, check_completed, check_downloads
|
||||||
from network.interface import list_interfaces, init_interfaces
|
from network.interface import list_interfaces, init_interfaces
|
||||||
|
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||||
from utils.logging.logs import get_download_logs
|
from utils.logging.logs import get_download_logs
|
||||||
from utils.logging.logs import set_main_window
|
from utils.logging.logs import set_main_window
|
||||||
from network.libtorrent_int import check_space
|
from network.libtorrent_int import check_space
|
||||||
from utils.general.shutdown import closehelper
|
from utils.general.shutdown import closehelper
|
||||||
from utils.general.wrappers import run_thread
|
from utils.general.wrappers import run_thread
|
||||||
|
from interface.assets.base64_icons import logo_base64
|
||||||
|
from PySide6.QtWidgets import QSystemTrayIcon, QMenu
|
||||||
from utils.general.shutdown import force_exit
|
from utils.general.shutdown import force_exit
|
||||||
from utils.config.config import read_config
|
from utils.config.config import read_config
|
||||||
|
from PySide6.QtGui import QAction, QPixmap
|
||||||
from utils.logging.logs import consoleLog
|
from utils.logging.logs import consoleLog
|
||||||
from interface.gui import MainWindow
|
from interface.gui import MainWindow
|
||||||
|
from PySide6.QtCore import Qt, QObject
|
||||||
from utils.data.state import state
|
from utils.data.state import state
|
||||||
from PySide6 import QtWidgets
|
from PySide6 import QtWidgets
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
import qdarktheme
|
import qdarktheme
|
||||||
import threading
|
import threading
|
||||||
import platform
|
import platform
|
||||||
|
import base64
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
SERVER_NAME = "SoftwareManager_KeksPirates"
|
||||||
|
|
||||||
|
class SingleInstance(QObject):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.server = QLocalServer()
|
||||||
|
|
||||||
|
socket = QLocalSocket()
|
||||||
|
socket.connectToServer(SERVER_NAME)
|
||||||
|
|
||||||
|
if socket.waitForConnected(100):
|
||||||
|
socket.write(b"raise")
|
||||||
|
socket.flush()
|
||||||
|
socket.waitForBytesWritten(100)
|
||||||
|
self.is_running = True
|
||||||
|
else:
|
||||||
|
QLocalServer.removeServer(SERVER_NAME)
|
||||||
|
self.server.listen(SERVER_NAME)
|
||||||
|
self.server.newConnection.connect(self.handle_connection)
|
||||||
|
self.is_running = False
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
def handle_connection(self):
|
||||||
|
socket = self.server.nextPendingConnection()
|
||||||
|
socket.readyRead.connect(lambda: self.read_socket(socket))
|
||||||
|
|
||||||
|
def read_socket(self, socket):
|
||||||
|
msg = socket.readAll().data()
|
||||||
|
if msg == b"raise":
|
||||||
|
self.on_raise()
|
||||||
|
|
||||||
|
def on_raise(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_running(app):
|
||||||
|
single = SingleInstance()
|
||||||
|
if single.is_running:
|
||||||
|
print("Another instance is already running. Exiting this instance.")
|
||||||
|
closehelper()
|
||||||
|
force_exit()
|
||||||
|
|
||||||
|
app._single_instance = single
|
||||||
|
|
||||||
|
|
||||||
def run_gui(app):
|
def run_gui(app):
|
||||||
|
single = app._single_instance
|
||||||
custom_colors = {}
|
custom_colors = {}
|
||||||
if state.accent_color:
|
if state.accent_color:
|
||||||
custom_colors["primary"] = state.accent_color
|
custom_colors["primary"] = state.accent_color
|
||||||
@@ -33,8 +83,37 @@ def run_gui(app):
|
|||||||
transparent_colors = {"background": "#00000000", **custom_colors}
|
transparent_colors = {"background": "#00000000", **custom_colors}
|
||||||
qdarktheme.setup_theme("auto", custom_colors=transparent_colors)
|
qdarktheme.setup_theme("auto", custom_colors=transparent_colors)
|
||||||
|
|
||||||
|
pixmap = QPixmap()
|
||||||
|
image_data = base64.b64decode(logo_base64)
|
||||||
|
pixmap.loadFromData(image_data)
|
||||||
|
|
||||||
|
tray = QSystemTrayIcon()
|
||||||
|
tray.setIcon(pixmap)
|
||||||
|
tray.setVisible(True)
|
||||||
|
|
||||||
|
menu = QMenu()
|
||||||
|
|
||||||
|
show = QAction("Show")
|
||||||
|
show.triggered.connect(lambda: widget.show())
|
||||||
|
menu.addAction(show)
|
||||||
|
|
||||||
|
quit = QAction("Quit")
|
||||||
|
quit.triggered.connect(lambda: (closehelper(), force_exit()))
|
||||||
|
menu.addAction(quit)
|
||||||
|
|
||||||
|
tray.activated.connect(lambda reason: widget.show() if reason == QSystemTrayIcon.ActivationReason.Trigger else None)
|
||||||
|
tray.setContextMenu(menu)
|
||||||
|
|
||||||
|
def show_from_tray():
|
||||||
|
widget.show()
|
||||||
|
widget.raise_()
|
||||||
|
widget.activateWindow()
|
||||||
|
|
||||||
|
single.on_raise = show_from_tray
|
||||||
|
|
||||||
set_main_window(widget)
|
set_main_window(widget)
|
||||||
widget.show()
|
widget.show()
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
def keyboardinterrupthandler(signum, frame):
|
def keyboardinterrupthandler(signum, frame):
|
||||||
@@ -45,10 +124,10 @@ def keyboardinterrupthandler(signum, frame):
|
|||||||
def main():
|
def main():
|
||||||
# Begin counting startup time
|
# Begin counting startup time
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
# Initialize UI Engine
|
# Initialize UI Engine
|
||||||
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
|
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
|
||||||
|
# Check if SoftwareManager is already running
|
||||||
|
check_running(app)
|
||||||
# Parse saved files
|
# Parse saved files
|
||||||
read_config()
|
read_config()
|
||||||
logs = get_download_logs()
|
logs = get_download_logs()
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ def create_config():
|
|||||||
"ignore_updates": str(state.ignore_updates),
|
"ignore_updates": str(state.ignore_updates),
|
||||||
"autoresume": str(state.autoresume),
|
"autoresume": str(state.autoresume),
|
||||||
"window_transparency": str(state.window_transparency),
|
"window_transparency": str(state.window_transparency),
|
||||||
"accent_color": str(state.accent_color)
|
"accent_color": str(state.accent_color),
|
||||||
|
"close_to_tray": str(state.close_to_tray)
|
||||||
}
|
}
|
||||||
|
|
||||||
config["Network"] = {
|
config["Network"] = {
|
||||||
@@ -86,6 +87,7 @@ def read_config():
|
|||||||
state.autoresume = config.getboolean("General", "autoresume", fallback=state.autoresume)
|
state.autoresume = config.getboolean("General", "autoresume", fallback=state.autoresume)
|
||||||
state.window_transparency = config.getboolean("General", "window_transparency", fallback=state.window_transparency)
|
state.window_transparency = config.getboolean("General", "window_transparency", fallback=state.window_transparency)
|
||||||
state.accent_color = config.get("General", "accent_color", fallback=state.accent_color)
|
state.accent_color = config.get("General", "accent_color", fallback=state.accent_color)
|
||||||
|
state.close_to_tray = config.getboolean("General", "close_to_tray", fallback=state.close_to_tray)
|
||||||
|
|
||||||
# Network
|
# Network
|
||||||
state.api_url = config.get("Network", "api_url", fallback=state.api_url)
|
state.api_url = config.get("Network", "api_url", fallback=state.api_url)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import platform
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save_settings(close=lambda: None, apiurl=None, download_path=None, down_speed_limit=None, up_speed_limit=None, image_path=None, autoresume=None, max_connections=None, max_downloads=None, bound_interface=None, image_width=None, image_offset=None, image_opacity=None, image_as_wallpaper=None, image_position=None, accent_color=None):
|
def save_settings(close=lambda: None, apiurl=None, download_path=None, down_speed_limit=None, up_speed_limit=None, image_path=None, autoresume=None, max_connections=None, max_downloads=None, bound_interface=None, image_width=None, image_offset=None, image_opacity=None, image_as_wallpaper=None, image_position=None, accent_color=None, close_to_tray=None):
|
||||||
if apiurl is not None:
|
if apiurl is not None:
|
||||||
state.api_url = apiurl
|
state.api_url = apiurl
|
||||||
if download_path is not None:
|
if download_path is not None:
|
||||||
@@ -43,6 +43,8 @@ def save_settings(close=lambda: None, apiurl=None, download_path=None, down_spee
|
|||||||
if state.window_transparency and platform.system() != "Windows":
|
if state.window_transparency and platform.system() != "Windows":
|
||||||
custom_colors["background"] = "#00000000"
|
custom_colors["background"] = "#00000000"
|
||||||
qdarktheme.setup_theme("auto", custom_colors=custom_colors if custom_colors else None)
|
qdarktheme.setup_theme("auto", custom_colors=custom_colors if custom_colors else None)
|
||||||
|
if close_to_tray is not None:
|
||||||
|
state.close_to_tray = close_to_tray
|
||||||
|
|
||||||
update_settings() # Update LibTorrent Session Settings
|
update_settings() # Update LibTorrent Session Settings
|
||||||
consoleLog("Saved Settings")
|
consoleLog("Saved Settings")
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class AppState(QObject):
|
|||||||
self.interfaces: List = []
|
self.interfaces: List = []
|
||||||
self.active_interfaces: List = []
|
self.active_interfaces: List = []
|
||||||
self.bound_interface: Any = None
|
self.bound_interface: Any = None
|
||||||
|
self.close_to_tray: bool = True
|
||||||
|
|
||||||
# Image
|
# Image
|
||||||
self._image_enabled: bool = False
|
self._image_enabled: bool = False
|
||||||
|
|||||||
Reference in New Issue
Block a user