From a53ccf6b2aec3bf42b27566aa210fe42ea41bf6b Mon Sep 17 00:00:00 2001 From: OptimiDev Date: Mon, 20 Oct 2025 20:13:09 +0200 Subject: [PATCH] Initial Commit+ --- .idea/.gitignore | 8 + .idea/PIRC.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + __pycache__/pirc_client.cpython-313.pyc | Bin 0 -> 22248 bytes __pycache__/pirc_setup_wizard.cpython-313.pyc | Bin 0 -> 9408 bytes main.py | 28 ++ pirc_client.py | 350 ++++++++++++++++++ pirc_config.json | 10 + pirc_setup_wizard.py | 138 +++++++ pyproject.toml | 9 + uv.lock | 126 +++++++ 14 files changed, 706 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/PIRC.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 __pycache__/pirc_client.cpython-313.pyc create mode 100644 __pycache__/pirc_setup_wizard.cpython-313.pyc create mode 100644 main.py create mode 100644 pirc_client.py create mode 100644 pirc_config.json create mode 100644 pirc_setup_wizard.py create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/PIRC.iml b/.idea/PIRC.iml new file mode 100644 index 0000000..bec9418 --- /dev/null +++ b/.idea/PIRC.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..462ce8b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..013ad0b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/__pycache__/pirc_client.cpython-313.pyc b/__pycache__/pirc_client.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af26360f0629ef6ac217e23501a5ae63031501e1 GIT binary patch literal 22248 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 E06}+qO#lD@ literal 0 HcmV?d00001 diff --git a/__pycache__/pirc_setup_wizard.cpython-313.pyc b/__pycache__/pirc_setup_wizard.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc837860fa85236e010657548d8b7299f0109c67 GIT binary patch literal 9408 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 QSo7&&$G;7G&)7Ww2c@5OKL7v# literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..3072cd4 --- /dev/null +++ b/main.py @@ -0,0 +1,28 @@ +# main.py +import sys +import json +import os +from PySide6.QtWidgets import QApplication +from pirc_setup_wizard import PIRCSetupWizard +from pirc_client import PIRCClient + +CONFIG_FILE = "pirc_config.json" + +def main(): + app = QApplication(sys.argv) + + # Check if config exists + if not os.path.exists(CONFIG_FILE): + wizard = PIRCSetupWizard() + if wizard.exec() != 0: + pass # Wizard handled saving config + else: + print("Setup cancelled.") + sys.exit(0) + + client = PIRCClient() + client.show() + sys.exit(app.exec()) + +if __name__ == "__main__": + main() diff --git a/pirc_client.py b/pirc_client.py new file mode 100644 index 0000000..746ffd1 --- /dev/null +++ b/pirc_client.py @@ -0,0 +1,350 @@ +import sys +import socket +import threading +import json +import os +from datetime import datetime +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, + QLineEdit, QPushButton, QLabel, QListWidget, QListWidgetItem, + QMessageBox, QDialog, QDialogButtonBox, QFormLayout, QLineEdit as QLE +) +from PySide6.QtCore import Qt, Signal, QObject +from PySide6.QtGui import QIcon + +CONFIG_FILE = "pirc_config.json" + + +class IRCWorker(QObject): + message_received = Signal(str) + system_message = Signal(str) + user_list_update = Signal(list) + disconnected = Signal() + + def __init__(self, server, port, nick, channels): + super().__init__() + self.server = server + self.port = port + self.nick = nick + self.channels = channels + self.running = True + self.sock = None + self.current_users = [] + + def start(self): + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.server, self.port)) + self.sock.send(f"NICK {self.nick}\r\n".encode("utf-8")) + self.sock.send(f"USER {self.nick} 0 * :PIRC User\r\n".encode("utf-8")) + + for channel in self.channels: + self.sock.send(f"JOIN {channel}\r\n".encode("utf-8")) + self.system_message.emit(f"Joined {channel}") + + threading.Thread(target=self.listen, daemon=True).start() + except Exception as e: + self.system_message.emit(f"Connection failed: {e}") + self.disconnected.emit() + + def listen(self): + try: + while self.running: + data = self.sock.recv(4096).decode("utf-8", errors="ignore") + if not data: + break + + for line in data.split("\r\n"): + if not line: + continue + + # PING/PONG + if line.startswith("PING"): + self.sock.send(f"PONG {line.split()[1]}\r\n".encode("utf-8")) + continue + + # Ignore raw handshake notices + if line.startswith(":") and "NOTICE * :***" in line: + continue + + parts = line.split() + if len(parts) > 1 and parts[1].isdigit(): + code = int(parts[1]) + if code == 353: # NAMES list + name_parts = line.split(":", 2) + if len(name_parts) < 3: + continue + users_chunk = name_parts[2].split() + self.current_users.extend(users_chunk) + elif code == 366: # End of NAMES list + self.user_list_update.emit(self.current_users) + self.current_users = [] + elif code in [0o01, 332, 333]: + self.system_message.emit(line) + continue + # PRIVMSG + if "PRIVMSG" in line: + msg_parts = line.split(":", 2) + if len(msg_parts) >= 3: + prefix = msg_parts[1].split("!")[0] + message = msg_parts[2] + self.message_received.emit(f"<{prefix}> {message}") + continue + + # JOIN + if "JOIN" in line: + user = line.split("!")[0].replace(":", "") + chan = line.split("JOIN")[1].strip() + if user not in self.current_users: + self.current_users.append(user) + self.user_list_update.emit(self.current_users) + self.system_message.emit(f"{user} joined {chan}") + continue + + # PART/QUIT + if "PART" in line or "QUIT" in line: + user = line.split("!")[0].replace(":", "") + if user in self.current_users: + self.current_users.remove(user) + self.user_list_update.emit(self.current_users) + self.system_message.emit(f"{user} left") + continue + + # Other system messages + self.system_message.emit(line) + except Exception as e: + self.system_message.emit(f"Error: {e}") + finally: + self.disconnected.emit() + + def send_message(self, channel, message): + try: + self.sock.send(f"PRIVMSG {channel} :{message}\r\n".encode("utf-8")) + except Exception as e: + self.system_message.emit(f"Failed to send message: {e}") + + def stop(self): + self.running = False + try: + self.sock.send(b"QUIT :Goodbye!\r\n") + self.sock.close() + except: + pass + + +class AddServerDialog(QDialog): + def __init__(self): + super().__init__() + self.setWindowTitle("Add Server") + self.setFixedSize(300, 200) + layout = QFormLayout() + self.server_input = QLE() + self.port_input = QLE() + self.port_input.setText("6667") + self.channels_input = QLE() + layout.addRow("Server:", self.server_input) + layout.addRow("Port:", self.port_input) + layout.addRow("Channels (comma-separated):", self.channels_input) + buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + buttons.accepted.connect(self.accept) + buttons.rejected.connect(self.reject) + layout.addWidget(buttons) + self.setLayout(layout) + + def get_data(self): + return ( + self.server_input.text().strip(), + int(self.port_input.text().strip()), + [c.strip() for c in self.channels_input.text().split(",") if c.strip()] + ) + + +class PIRCClient(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("PIRC — Python IRC Client") + self.setGeometry(100, 100, 1000, 600) + self.worker = None + + self.load_config() + self.init_ui() + + if self.favorites: + first = self.favorites[0] + self.connect_to_server(first["server"], first.get("port", 6667), first["channels"]) + else: + self.system_message("No favorite servers found — click the + button to connect.") + + def load_config(self): + if os.path.exists(CONFIG_FILE): + with open(CONFIG_FILE, "r") as f: + cfg = json.load(f) + self.nick = cfg.get("username", "PIRCUser") + self.favorites = cfg.get("favorites", []) + else: + self.nick = "PIRCUser" + self.favorites = [] + + def save_config(self): + cfg = { + "username": self.nick, + "favorites": self.favorites + } + with open(CONFIG_FILE, "w") as f: + json.dump(cfg, f, indent=4) + + def init_ui(self): + layout = QHBoxLayout() + + # Server sidebar + sidebar_layout = QVBoxLayout() + self.server_list = QListWidget() + for fav in self.favorites: + item = QListWidgetItem(fav["server"]) + self.server_list.addItem(item) + self.server_list.itemClicked.connect(self.server_selected) + sidebar_layout.addWidget(self.server_list) + + # Add server button with + icon + self.add_server_button = QPushButton() + self.add_server_button.setIcon(QIcon("add.png")) + self.add_server_button.setText("") # icon only + self.add_server_button.clicked.connect(self.add_server) + self.disconnect_button = QPushButton("Disconnect") + self.disconnect_button.clicked.connect(self.disconnect_server) + sidebar_layout.addWidget(self.add_server_button) + sidebar_layout.addWidget(self.disconnect_button) + layout.addLayout(sidebar_layout, 2) + + # Chat + user list + chat_layout = QVBoxLayout() + self.info_label = QLabel("Disconnected") + self.info_label.setStyleSheet("font-weight: bold; padding: 4px;") + chat_layout.addWidget(self.info_label) + + chat_area_layout = QHBoxLayout() + self.chat_display = QTextEdit() + self.chat_display.setReadOnly(True) + chat_area_layout.addWidget(self.chat_display, 7) + + # User list sidebar + self.user_list = QListWidget() + chat_area_layout.addWidget(self.user_list, 3) + + chat_layout.addLayout(chat_area_layout, 8) + + # Message input + input_layout = QHBoxLayout() + self.message_input = QLineEdit() + self.message_input.returnPressed.connect(self.send_message) + self.send_button = QPushButton("Send") + self.send_button.clicked.connect(self.send_message) + input_layout.addWidget(self.message_input) + input_layout.addWidget(self.send_button) + chat_layout.addLayout(input_layout) + + layout.addLayout(chat_layout, 8) + self.setLayout(layout) + + def connect_to_server(self, server, port, channels): + if self.worker: + self.disconnect_server() + self.worker = IRCWorker(server, port, self.nick, channels) + self.worker.message_received.connect(self.display_message) + self.worker.system_message.connect(self.system_message) + self.worker.user_list_update.connect(self.update_user_list) + self.worker.disconnected.connect(self.handle_disconnect) + self.worker.start() + self.info_label.setText(f"Connected to {server} as {self.nick}") + self.system_message(f"Connected to {server}") + + def disconnect_server(self): + if self.worker: + self.worker.stop() + self.worker = None + self.system_message("Disconnected from server.") + self.info_label.setText("Disconnected") + self.user_list.clear() + + def handle_disconnect(self): + self.worker = None + self.system_message("Connection closed by server.") + self.info_label.setText("Disconnected") + self.user_list.clear() + + def server_selected(self, item): + selected_server = item.text() + fav = next((f for f in self.favorites if f["server"] == selected_server), None) + if fav: + self.connect_to_server(fav["server"], fav.get("port", 6667), fav["channels"]) + + def add_server(self): + dialog = AddServerDialog() + if dialog.exec() == QDialog.Accepted: + server, port, channels = dialog.get_data() + if not server: + QMessageBox.warning(self, "Error", "Server cannot be empty.") + return + new_server = {"server": server, "port": port, "channels": channels} + self.favorites.append(new_server) + self.save_config() + self.server_list.addItem(server) + self.connect_to_server(server, port, channels) + + def display_message(self, msg): + timestamp = datetime.now().strftime("[%H:%M:%S]") + self.chat_display.append(f"{timestamp} {msg}") + + def system_message(self, msg): + timestamp = datetime.now().strftime("[%H:%M:%S]") + self.chat_display.append( + f'
{timestamp} [System] {msg}
' + ) + + def update_user_list(self, users): + self.user_list.clear() + admins = [] + normal_users = [] + + for user in users: + if user.startswith("@"): + admins.append(user[1:]) # remove @ for display + elif user.startswith("+"): + normal_users.append(user[1:]) # optional remove + + else: + normal_users.append(user) + + admins.sort() + normal_users.sort() + ordered_users = admins + normal_users + + for user in ordered_users: + item = QListWidgetItem(user) + if user in admins: + item.setIcon(QIcon("admin.png")) + else: + item.setIcon(QIcon("user.png")) + self.user_list.addItem(item) + + def send_message(self): + text = self.message_input.text().strip() + if not text: + return + if self.worker and self.worker.channels: + self.worker.send_message(self.worker.channels[0], text) + self.display_message(f"<{self.nick}> {text}") + else: + self.system_message("Not connected to a server.") + self.message_input.clear() + + def closeEvent(self, event): + if self.worker: + self.worker.stop() + super().closeEvent(event) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + client = PIRCClient() + client.show() + sys.exit(app.exec()) diff --git a/pirc_config.json b/pirc_config.json new file mode 100644 index 0000000..9b9c7f6 --- /dev/null +++ b/pirc_config.json @@ -0,0 +1,10 @@ +{ + "username": "OptimiDev", + "favorites": [ + { + "server": "irc.libera.chat", + "port": 6667, + "channels": ["#python"] + } + ] +} diff --git a/pirc_setup_wizard.py b/pirc_setup_wizard.py new file mode 100644 index 0000000..db7433a --- /dev/null +++ b/pirc_setup_wizard.py @@ -0,0 +1,138 @@ +# pirC_setup_wizard.py +import sys +import json +from PySide6.QtWidgets import ( + QApplication, QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton, + QListWidget, QHBoxLayout, QStackedLayout, QWidget, QDialogButtonBox +) + +CONFIG_FILE = "pirc_config.json" + +class PIRCSetupWizard(QDialog): + def __init__(self): + super().__init__() + self.setWindowTitle("PIRC Setup Wizard") + self.setGeometry(200, 200, 500, 400) + + self.nicknames = [] + self.favorites = [] + + # Multi-step layout + self.layout = QVBoxLayout() + self.stack = QStackedLayout() + self.layout.addLayout(self.stack) + self.setLayout(self.layout) + + # Step 0: Welcome + self.step_welcome = QWidget() + w_layout = QVBoxLayout() + w_layout.addWidget(QLabel( + "Welcome to PIRC!\n\nThis wizard will guide you through the setup:\n" + "1. Add your usernames (multiple allowed)\n" + "2. Add favorite servers/channels (optional)\n" + "3. Open the chat window\n" + )) + self.next0 = QPushButton("Start") + self.next0.clicked.connect(lambda: self.stack.setCurrentIndex(1)) + w_layout.addWidget(self.next0) + self.step_welcome.setLayout(w_layout) + self.stack.addWidget(self.step_welcome) + + # Step 1: Usernames + self.step_users = QWidget() + u_layout = QVBoxLayout() + u_layout.addWidget(QLabel("Step 1: Add your usernames")) + self.nick_input = QLineEdit() + self.nick_input.setPlaceholderText("Enter a username") + u_layout.addWidget(self.nick_input) + self.nick_list = QListWidget() + u_layout.addWidget(self.nick_list) + h_btn_layout = QHBoxLayout() + self.add_nick_btn = QPushButton("Add") + self.add_nick_btn.clicked.connect(self.add_nickname) + self.remove_nick_btn = QPushButton("Remove Selected") + self.remove_nick_btn.clicked.connect(self.remove_nickname) + h_btn_layout.addWidget(self.add_nick_btn) + h_btn_layout.addWidget(self.remove_nick_btn) + u_layout.addLayout(h_btn_layout) + self.next1 = QPushButton("Next") + self.next1.clicked.connect(lambda: self.stack.setCurrentIndex(2)) + u_layout.addWidget(self.next1) + self.step_users.setLayout(u_layout) + self.stack.addWidget(self.step_users) + + # Step 2: Favorites + self.step_fav = QWidget() + f_layout = QVBoxLayout() + f_layout.addWidget(QLabel("Step 2: Add favorite servers/channels (optional)")) + self.server_input = QLineEdit() + self.server_input.setPlaceholderText("Server address (e.g., irc.libera.chat)") + self.channel_input = QLineEdit() + self.channel_input.setPlaceholderText("Channel (e.g., #python)") + f_layout.addWidget(self.server_input) + f_layout.addWidget(self.channel_input) + self.fav_list = QListWidget() + f_layout.addWidget(self.fav_list) + h_fav_btn_layout = QHBoxLayout() + self.add_fav_btn = QPushButton("Add") + self.add_fav_btn.clicked.connect(self.add_favorite) + self.remove_fav_btn = QPushButton("Remove Selected") + self.remove_fav_btn.clicked.connect(self.remove_favorite) + h_fav_btn_layout.addWidget(self.add_fav_btn) + h_fav_btn_layout.addWidget(self.remove_fav_btn) + f_layout.addLayout(h_fav_btn_layout) + self.next2 = QPushButton("Finish") + self.next2.clicked.connect(self.finish_setup) + f_layout.addWidget(self.next2) + self.step_fav.setLayout(f_layout) + self.stack.addWidget(self.step_fav) + + # --- Nicknames --- + def add_nickname(self): + nick = self.nick_input.text().strip() + if nick: + self.nicknames.append(nick) + self.nick_list.addItem(nick) + self.nick_input.clear() + + def remove_nickname(self): + selected = self.nick_list.currentRow() + if selected >= 0: + self.nick_list.takeItem(selected) + self.nicknames.pop(selected) + + # --- Favorites --- + def add_favorite(self): + server = self.server_input.text().strip() + channel = self.channel_input.text().strip() + if server and channel: + self.favorites.append({"server": server, "channel": channel}) + self.fav_list.addItem(f"{server} {channel}") + self.server_input.clear() + self.channel_input.clear() + + def remove_favorite(self): + selected = self.fav_list.currentRow() + if selected >= 0: + self.fav_list.takeItem(selected) + self.favorites.pop(selected) + + # --- Finish --- + def finish_setup(self): + config = { + "nicknames": self.nicknames, + "favorites": self.favorites + } + with open(CONFIG_FILE, "w") as f: + json.dump(config, f, indent=4) + self.accept() # Close wizard + +# --- Test --- +if __name__ == "__main__": + app = QApplication(sys.argv) + wizard = PIRCSetupWizard() + if wizard.exec() == QDialog.Accepted: + print("Setup completed!") + with open(CONFIG_FILE, "r") as f: + print(json.load(f)) + sys.exit(0) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2fc14bb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "pirc" +version = "0.1.0" +description = "Add your description here" +requires-python = ">=3.13" +dependencies = [ + "pyqt6>=6.9.1", + "pyside6>=6.10.0", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..76db6b4 --- /dev/null +++ b/uv.lock @@ -0,0 +1,126 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "pirc" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pyqt6" }, + { name = "pyside6" }, +] + +[package.metadata] +requires-dist = [ + { name = "pyqt6", specifier = ">=6.9.1" }, + { name = "pyside6", specifier = ">=6.10.0" }, +] + +[[package]] +name = "pyqt6" +version = "6.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyqt6-qt6" }, + { name = "pyqt6-sip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/1b/567f46eb43ca961efd38d7a0b73efb70d7342854f075fd919179fdb2a571/pyqt6-6.9.1.tar.gz", hash = "sha256:50642be03fb40f1c2111a09a1f5a0f79813e039c15e78267e6faaf8a96c1c3a6", size = 1067230, upload-time = "2025-06-06T08:49:30.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/c4/fc2a69cf3df09b213185ef5a677c3940cd20e7855d29e40061a685b9c6ee/pyqt6-6.9.1-cp39-abi3-macosx_10_14_universal2.whl", hash = "sha256:33c23d28f6608747ecc8bfd04c8795f61631af9db4fb1e6c2a7523ec4cc916d9", size = 59770566, upload-time = "2025-06-06T08:48:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d5/78/92f3c46440a83ebe22ae614bd6792e7b052bcb58ff128f677f5662015184/pyqt6-6.9.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:37884df27f774e2e1c0c96fa41e817a222329b80ffc6241725b0dc8c110acb35", size = 37804959, upload-time = "2025-06-06T08:48:39.587Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5e/e77fa2761d809cd08d724f44af01a4b6ceb0ff9648e43173187b0e4fac4e/pyqt6-6.9.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:055870b703c1a49ca621f8a89e2ec4d848e6c739d39367eb9687af3b056d9aa3", size = 40414608, upload-time = "2025-06-06T08:49:00.26Z" }, + { url = "https://files.pythonhosted.org/packages/c4/09/69cf80456b6a985e06dd24ed0c2d3451e43567bf2807a5f3a86ef7a74a2e/pyqt6-6.9.1-cp39-abi3-win_amd64.whl", hash = "sha256:15b95bd273bb6288b070ed7a9503d5ff377aa4882dd6d175f07cad28cdb21da0", size = 25717996, upload-time = "2025-06-06T08:49:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/52/b3/0839d8fd18b86362a4de384740f2f6b6885b5d06fda7720f8a335425e316/pyqt6-6.9.1-cp39-abi3-win_arm64.whl", hash = "sha256:08792c72d130a02e3248a120f0b9bbb4bf4319095f92865bc5b365b00518f53d", size = 25212132, upload-time = "2025-06-06T08:49:27.41Z" }, +] + +[[package]] +name = "pyqt6-qt6" +version = "6.9.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/6f/fe2cd9cb2201c685be2f50c8c915df97848cac3dca4bad44bc3aed56fc63/pyqt6_qt6-6.9.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:183b62be49216da80c7df1931d74885610a88f74812489d29610d13b7c215a1c", size = 66568266, upload-time = "2025-09-01T11:43:31.339Z" }, + { url = "https://files.pythonhosted.org/packages/db/1d/47dc51b4383b350f4ff6b1db461b01eba580030683ffa65475b4fdd9b80d/pyqt6_qt6-6.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7897fb74ee21bdc87b5ccf84e94f4a551377e792fd180a9211c17eb41c3338a3", size = 60859706, upload-time = "2025-09-01T11:43:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/a8/07/21f7dc188e35b46631707f3b40ace5643a0e03a8e1e446854826d08a04ae/pyqt6_qt6-6.9.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9abfc0ee4a8293a6442128ae3f87f68e82e2a949d7b9caabd98c86ba5679ab48", size = 82322871, upload-time = "2025-09-01T11:43:41.685Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c0/da658e735817feaa35ddfddb4c5d699291e8b8e3138e69ad7ae1a38a7db8/pyqt6_qt6-6.9.2-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:940aac6462532578e8ddefe0494cd17e33a85e0f3cfb21c612f56ab9ad7bc871", size = 80826693, upload-time = "2025-09-01T11:43:46.823Z" }, + { url = "https://files.pythonhosted.org/packages/63/3a/d811ed1aa579b93ab56188d1371b05eacb4188599d83e72b761263a10f92/pyqt6_qt6-6.9.2-py3-none-win_amd64.whl", hash = "sha256:f9289768039bef4a63e5949b7f8cfbbddc3b6d24bd58c21ba0f2921bed8d1c08", size = 74147171, upload-time = "2025-09-01T11:43:53.468Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/7db6c5ddcb60ef3ecca2040274a30e8bc35b569c49e25e1cf2ef9f159426/pyqt6_qt6-6.9.2-py3-none-win_arm64.whl", hash = "sha256:8f82944ef68c8f8c78aa8eca4832c7bc05116c6de00a3bad8af5a0d63d1caafb", size = 54534019, upload-time = "2025-09-01T11:43:58.763Z" }, +] + +[[package]] +name = "pyqt6-sip" +version = "13.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/4a/96daf6c2e4f689faae9bd8cebb52754e76522c58a6af9b5ec86a2e8ec8b4/pyqt6_sip-13.10.2.tar.gz", hash = "sha256:464ad156bf526500ce6bd05cac7a82280af6309974d816739b4a9a627156fafe", size = 92548, upload-time = "2025-05-23T12:26:49.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/1e/979ea64c98ca26979d8ce11e9a36579e17d22a71f51d7366d6eec3c82c13/pyqt6_sip-13.10.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8b5d06a0eac36038fa8734657d99b5fe92263ae7a0cd0a67be6acfe220a063e1", size = 112227, upload-time = "2025-05-23T12:26:38.758Z" }, + { url = "https://files.pythonhosted.org/packages/d9/21/84c230048e3bfef4a9209d16e56dcd2ae10590d03a31556ae8b5f1dcc724/pyqt6_sip-13.10.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad376a6078da37b049fdf9d6637d71b52727e65c4496a80b753ddc8d27526aca", size = 322920, upload-time = "2025-05-23T12:26:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/c6a28a142f14e735088534cc92951c3f48cccd77cdd4f3b10d7996be420f/pyqt6_sip-13.10.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3dde8024d055f496eba7d44061c5a1ba4eb72fc95e5a9d7a0dbc908317e0888b", size = 303833, upload-time = "2025-05-23T12:26:41.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/63/e5adf350c1c3123d4865c013f164c5265512fa79f09ad464fb2fdf9f9e61/pyqt6_sip-13.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:0b097eb58b4df936c4a2a88a2f367c8bb5c20ff049a45a7917ad75d698e3b277", size = 53527, upload-time = "2025-05-23T12:26:42.625Z" }, + { url = "https://files.pythonhosted.org/packages/58/74/2df4195306d050fbf4963fb5636108a66e5afa6dc05fd9e81e51ec96c384/pyqt6_sip-13.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:cc6a1dfdf324efaac6e7b890a608385205e652845c62130de919fd73a6326244", size = 45373, upload-time = "2025-05-23T12:26:43.536Z" }, + { url = "https://files.pythonhosted.org/packages/23/57/74b4eb7a51b9133958daa8409b55de95e44feb694d4e2e3eba81a070ca20/pyqt6_sip-13.10.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8a76a06a8e5c5b1f17a3f6f3c834ca324877e07b960b18b8b9bbfd9c536ec658", size = 112354, upload-time = "2025-10-08T08:44:00.22Z" }, + { url = "https://files.pythonhosted.org/packages/f2/cb/fdef02e0d6ee8443a9683a43650d61c6474b634b6ae6e1c6f097da6310bf/pyqt6_sip-13.10.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9128d770a611200529468397d710bc972f1dcfe12bfcbb09a3ccddcd4d54fa5b", size = 323488, upload-time = "2025-10-08T08:44:01.965Z" }, + { url = "https://files.pythonhosted.org/packages/8c/5b/8ede8d6234c3ea884cbd097d7d47ff9910fb114efe041af62b4453acd23b/pyqt6_sip-13.10.2-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d820a0fae7315932c08f27dc0a7e33e0f50fe351001601a8eb9cf6f22b04562e", size = 303881, upload-time = "2025-10-08T08:44:04.086Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/b5e78b072d1594643b0f1ff348f2bf54d4adb5a3f9b9f0989c54e33238d6/pyqt6_sip-13.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:3213bb6e102d3842a3bb7e59d5f6e55f176c80880ff0b39d0dac0cfe58313fb3", size = 55098, upload-time = "2025-10-08T08:44:08.943Z" }, + { url = "https://files.pythonhosted.org/packages/e2/91/357e9fcef5d830c3d50503d35e0357818aca3540f78748cc214dfa015d00/pyqt6_sip-13.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:ce33ff1f94960ad4b08035e39fa0c3c9a67070bec39ffe3e435c792721504726", size = 46088, upload-time = "2025-10-08T08:44:10.014Z" }, +] + +[[package]] +name = "pyside6" +version = "6.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-addons" }, + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0", size = 558115, upload-time = "2025-10-08T09:47:09.246Z" }, + { url = "https://files.pythonhosted.org/packages/4e/76/0961c8c5653ecb60a6881b649dcb6b71a6be5bd1c8d441ecc48ac7f50b1a/pyside6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ae8c3c8339cd7c3c9faa7cc5c52670dcc8662ccf4b63a6fed61c6345b90c4c01", size = 557762, upload-time = "2025-10-08T09:47:11.819Z" }, + { url = "https://files.pythonhosted.org/packages/c8/73/6187502fff8b6599443d15c46dd900b2ded24be5aacb2becce33f6faf566/pyside6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9f402f883e640048fab246d36e298a5e16df9b18ba2e8c519877e472d3602820", size = 558299, upload-time = "2025-10-08T09:47:14.255Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/94794ebaf198bbdb35cb77f19f38370f9b323b036ab149874bc33c38faab/pyside6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f", size = 564367, upload-time = "2025-10-08T09:47:16.287Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cc/552331d413c1b933d54ed45e33cc7ff29d0b239677975fe2528e7ac8bfbc/pyside6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:4b709bdeeb89d386059343a5a706fc185cee37b517bda44c7d6b64d5fdaf3339", size = 548826, upload-time = "2025-10-08T09:47:18.399Z" }, +] + +[[package]] +name = "pyside6-addons" +version = "6.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c", size = 322248256, upload-time = "2025-10-08T09:47:37.844Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b8/d129210f2c7366b4e1bf5bb6230be42052b29e8ba1b1d7db6ef333cf5a39/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:08d4ed46c4c9a353a9eb84134678f8fdd4ce17fb8cce2b3686172a7575025464", size = 170238987, upload-time = "2025-10-08T09:47:51.446Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ae/ede1edd009395092219f3437d2ee59f9ba93739c28c040542ed47c6cc831/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:15d32229d681be0bba1b936c4a300da43d01e1917ada5b57f9e03a387c245ab0", size = 165939425, upload-time = "2025-10-08T09:48:02.073Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5d/a3c32f85ac7f905c95679967c0ddda0ba043c273b75623cc90d8185064e4/pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859", size = 164814172, upload-time = "2025-10-08T09:48:12.891Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2a/4ff71b09571202c8e1320c45276fc1d0cd81ee53107dfc17bb22d4243f88/pyside6_addons-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:92536427413f3b6557cf53f1a515cd766725ee46a170aff57ad2ff1dfce0ffb1", size = 34104251, upload-time = "2025-10-08T09:48:18.287Z" }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a", size = 105034090, upload-time = "2025-10-08T09:48:24.944Z" }, + { url = "https://files.pythonhosted.org/packages/5c/75/e17efc7eb900993e0e3925885635c6cf373c817196f09bcbcc102b00ac94/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1d5e013a8698e37ab8ef360e6960794eb5ef20832a8d562e649b8c5a0574b2d8", size = 76362150, upload-time = "2025-10-08T09:48:31.849Z" }, + { url = "https://files.pythonhosted.org/packages/06/62/fbd1e81caafcda97b147c03f5b06cfaadd8da5fa8298f527d2ec648fa5b7/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b1dd0864f0577a448fb44426b91cafff7ee7cccd1782ba66491e1c668033f998", size = 75454169, upload-time = "2025-10-08T09:48:38.21Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3a/d8211d17e6ca70f641c6ebd309f08ef18930acda60e74082c75875a274da/pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe", size = 74361794, upload-time = "2025-10-08T09:48:44.335Z" }, + { url = "https://files.pythonhosted.org/packages/61/e9/0e22e3c10325c4ff09447fadb43f7962afb82cef0b65358f5704251c6b32/pyside6_essentials-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:6dd0936394cb14da2fd8e869899f5e0925a738b1c8d74c2f22503720ea363fb1", size = 55099467, upload-time = "2025-10-08T09:48:50.902Z" }, +] + +[[package]] +name = "shiboken6" +version = "6.10.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543", size = 476535, upload-time = "2025-10-08T09:49:08Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/4ffa3284a17b6b765d45b41c9a7f1b2cde6c617c853ac6f170fb62bbbece/shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e612734da515d683696980107cdc0396a3ae0f07b059f0f422ec8a2333810234", size = 271098, upload-time = "2025-10-08T09:49:09.47Z" }, + { url = "https://files.pythonhosted.org/packages/31/29/00e26f33a0fb259c2edce9c761a7a438d7531ca514bdb1a4c072673bd437/shiboken6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b01377e68d14132360efb0f4b7233006d26aa8ae9bb50edf00960c2a5f52d148", size = 267698, upload-time = "2025-10-08T09:49:10.694Z" }, + { url = "https://files.pythonhosted.org/packages/11/30/e4624a7e3f0dc9796b701079b77defcce0d32d1afc86bb1d0df04bc3d9e2/shiboken6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717", size = 1234227, upload-time = "2025-10-08T09:49:12.774Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e5/0ab862005ea87dc8647ba958a3099b3b0115fd6491c65da5c5a0f6364db1/shiboken6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:dfc4beab5fec7dbbebbb418f3bf99af865d6953aa0795435563d4cbb82093b61", size = 1794775, upload-time = "2025-10-08T09:49:14.641Z" }, +]