added clipcascade

This commit is contained in:
liph
2026-07-23 16:48:58 +02:00
parent 90e44adbae
commit cba86d9dce
31 changed files with 6406 additions and 0 deletions
+367
View File
@@ -0,0 +1,367 @@
import math
import os
import threading
import time
import webbrowser
from cli.echo import Echo
from cli.info import CustomDialog
from core.config import Config
from core.constants import *
from itertools import count
if PLATFORM != WINDOWS:
import subprocess
class TaskbarPanel:
def __init__(
self,
on_connect_callback: callable = None,
on_disconnect_callback: callable = None,
on_logoff_callback: callable = None,
new_version_available: list = None,
github_url: str = GITHUB_URL,
donation_url: str = None,
ws_interface=None, # type= interfaces.ws_interface.WSInterface
config: Config = None,
):
self.on_connect_callback = on_connect_callback
self.on_disconnect_callback = on_disconnect_callback
self.on_logoff_callback = on_logoff_callback
self.new_version_available = new_version_available
self.github_url = github_url
self.donation_url = donation_url
self.ws_interface = ws_interface
self.config = config
self.is_disconnecting = False
self.disconnecting_items = None
self.is_file_download_enabled = False
self.file_download_items = None
self.previous_stats: str = ""
self.previous_stats_items = None
# Initial state: Connected
self.is_connected = True
self.menu_items = None
self.numbered_menu = None
self.loop_terminate = False
self.update_stats() # Start the stats update thread
def run(self):
Echo("-" * 53)
self.menu_items, self.numbered_menu = self.create_menu()
while not self.loop_terminate:
Echo("\n")
self.display_menu()
# input
choice = input("Choice: \n")
Echo("Please wait...")
if choice == "": # refresh
continue
if choice.isdigit():
choice = int(choice)
else:
Echo("Invalid choice. Please enter a number.")
if choice in self.numbered_menu:
fun = self.numbered_menu[choice][1]
if fun is not None:
fun()
else:
Echo("Invalid choice.")
def get_max_width(self):
"""Determine the maximum width of menu item texts."""
menu_texts = [
item[0] if isinstance(item, tuple) else "" for item in self.menu_items
]
return max(len(text) for text in menu_texts) + 1
def display_menu(self):
counter = count(1)
max_width = self.get_max_width()
total_width = max_width + 8
# Box Top Border
print("\n" + "" * (max_width + 6) + "")
print(f"{' MAIN MENU ':^{max_width + 6}}")
print("" + "" * (max_width + 6) + "")
# Menu Items
for item in self.menu_items:
if isinstance(item, tuple):
width = max_width
if "disconnect" in item[0].lower() or "logs" in item[0].lower():
if "disconnecting" in item[0].lower():
width -= 1
else:
width += 1
else:
width -= 1
print(f"{next(counter):<2}. {item[0]:<{width}}")
else:
print(f"{'' * (max_width + 4)}")
# Box Bottom Border
print("" + "" * (max_width + 6) + "")
def create_menu(self, item_: tuple = None):
# Menu items
menu_items = [
"", # Menu Separator
("🗒️ Open Logs", self._open_logs),
("📂 Program Files", self._open_program_location),
"", # Menu Separator
("🏠 Homepage", self._open_homepage),
("❓ Help", self._open_help),
("💟 Donate", self._open_donate),
("🌐 GitHub", self._open_github),
"", # Menu Separator
("🔒 Logoff and Quit", self._on_logoff),
("❌ Quit", self._on_quit),
]
# Add connect/disconnect option (top of the menu - 0 index)
if not self.is_connected:
menu_items.insert(0, ("🔗 Connect", self._on_connect))
else:
if self.is_disconnecting and self.disconnecting_items is not None:
menu_items.insert(
self.disconnecting_items[1],
(self.disconnecting_items[0], self.disconnecting_items[2]),
)
else:
menu_items.insert(0, ("⛓️‍💥 Disconnect", self._on_disconnect))
# Add update option (before the last 3 items)
if self.new_version_available is not None and self.new_version_available[0]:
menu_items.insert(
len(menu_items) - 3,
(
f"🔄 Update ({self.new_version_available[2]}{self.new_version_available[1]})",
self._on_update,
),
)
# Add files download option (top of the menu - 0 index)
if self.is_file_download_enabled and self.file_download_items is not None:
menu_items.insert(
self.file_download_items[1],
(self.file_download_items[0], self.file_download_items[2]),
)
# Add stats option (top of the menu - 0 index)
if self.previous_stats_items is not None:
menu_items.insert(
self.previous_stats_items[1],
(self.previous_stats_items[0], self.previous_stats_items[2]),
)
# Create numbered menu
counter = count(1)
numbered_menu = {
next(counter): item for item in menu_items if isinstance(item, tuple)
}
return (menu_items, numbered_menu)
def update_menu(self, item_: tuple = None, no_dialog=False):
self.menu_items, self.numbered_menu = self.create_menu(item_=item_)
if not no_dialog:
CustomDialog(
"Menu Updated! Press Enter to Refresh. ⏎", msg_type="info"
).mainloop()
def update_stats(self):
threading.Thread(target=self._update_stats_thread, daemon=True).start()
def _update_stats_thread(self):
while True:
current_stats = self.ws_interface.get_stats()
if current_stats is not None and self.previous_stats != current_stats:
self.previous_stats = current_stats
self.previous_stats_items = (current_stats, 0, None)
self.update_menu(no_dialog=True)
time.sleep(1) # Sleep for 1 second
@staticmethod
def open_webbrowser(url):
try:
webbrowser.open(url)
except Exception as e:
CustomDialog(
f"Failed to open the browser. Here is the URL: {url}\nError: {e}",
msg_type="error",
).mainloop()
def _on_update(self):
TaskbarPanel.open_webbrowser(self.new_version_available[3])
def _on_connect(self):
if self.on_connect_callback:
try:
self.on_connect_callback()
except Exception as e:
pass
self.is_connected = True
self.update_menu(no_dialog=True)
def _on_disconnect(self):
if self.on_disconnect_callback:
self.on_disconnect_callback()
if self.ws_interface is not None and self.ws_interface.is_auto_reconnecting:
threading.Thread(target=self._wait_to_disconnect, daemon=True).start()
else:
self.is_connected = False
self.update_menu(no_dialog=True)
def _wait_to_disconnect(self):
"""
Wait for the auto-reconnect timer to expire before disconnecting.
"""
if self.ws_interface is None:
return
timeout = math.ceil(self.ws_interface.get_total_timeout() / 1000)
while timeout > 0:
self.is_disconnecting = True
self.disconnecting_items = (
f"⏳ Disconnecting... ({timeout} sec)",
0,
None,
) # text, location, callback
self.update_menu(no_dialog=True)
time.sleep(1) # seconds
timeout -= 1
self.is_disconnecting = False
self.disconnecting_items = None
self.ws_interface.is_auto_reconnecting = False
self.is_connected = False
self.update_menu()
def _open_homepage(self):
TaskbarPanel.open_webbrowser(self.config.data["server_url"])
def _open_github(self):
TaskbarPanel.open_webbrowser(self.github_url)
def _open_help(self):
TaskbarPanel.open_webbrowser(HELP_URL)
def _open_donate(self):
if self.donation_url is not None:
TaskbarPanel.open_webbrowser(self.donation_url)
def open_location(self, path):
if PLATFORM == WINDOWS:
os.startfile(path)
elif PLATFORM == MACOS:
subprocess.run(["open", path])
elif PLATFORM.startswith(LINUX):
subprocess.run(["xdg-open", path])
def _open_logs(self):
log_file_path = os.path.join(get_program_files_directory(), LOG_FILE_NAME)
if os.path.exists(log_file_path):
try:
self.open_location(log_file_path)
except Exception as e:
CustomDialog(
f"Failed to open the log file '{log_file_path}'.\nError: {e}",
msg_type="error",
).mainloop()
else:
CustomDialog(
f"Log file not found at '{log_file_path}'.", msg_type="error"
).mainloop()
def _open_program_location(self):
try:
program_location = get_program_files_directory()
self.open_location(program_location)
except Exception as e:
CustomDialog(
f"Failed to open the program location '{program_location}'.\nError: {e}",
msg_type="error",
).mainloop()
def enable_files_download(self, files):
self.is_file_download_enabled = True
self.file_download_items = (
"📥 Download File(s)",
0,
lambda: self._on_download(files),
)
self.update_menu()
def disable_files_download(self):
self.is_file_download_enabled = False
self.file_download_items = None
self.update_menu()
def _on_download(self, files):
try:
if self.config.data["default_file_download_location"] != "":
target_directory = self.config.data["default_file_download_location"]
else:
target_directory = input("Enter the location to save file(s): ").strip()
if not target_directory:
CustomDialog(
"No input provided. Please enter a valid directory path.",
msg_type="error",
).mainloop()
return
if not os.path.isdir(target_directory):
CustomDialog("Invalid directory path.", msg_type="error").mainloop()
return
if not os.access(target_directory, os.W_OK):
CustomDialog(
"Insufficient permissions to write to the directory.",
msg_type="error",
).mainloop()
return
CustomDialog(
f"Saving files to: {target_directory}", msg_type="info"
).mainloop()
# Save each file to the chosen directory
for filename, file_obj in files.items():
file_path = os.path.join(target_directory, filename)
with open(file_path, "wb") as f:
f.write(file_obj.getvalue())
logging.debug(f"Saved: {file_path}")
CustomDialog("Done.", msg_type="success").mainloop()
except Exception as e:
msg = f"An error occurred while downloading files. Error: {e}"
logging.error(msg)
CustomDialog(
msg,
msg_type="error",
).mainloop()
def _on_logoff(self):
try:
if self.on_logoff_callback:
self.on_logoff_callback()
self._on_quit()
except Exception as e:
CustomDialog(
f"An error occurred while logging off: {e}", msg_type="error"
).mainloop()
def _on_quit(self):
self.loop_terminate = True