mirror of
https://github.com/vinefeeder/TwinVine.git
synced 2026-07-15 18:10:04 +02:00
Gui update & Services
This commit is contained in:
+190
@@ -0,0 +1,190 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFrame, QLabel
|
||||||
|
from PyQt6.QtCore import QProcess
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
CFG = os.path.abspath("./packages/envied/src/envied/envied.yaml")
|
||||||
|
class EnviedPanel(QFrame):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle("Envied Control ")
|
||||||
|
# Frame styling: 1px white border (no pink)
|
||||||
|
self.setObjectName("enviedFrame")
|
||||||
|
self.setStyleSheet("#enviedFrame { background-color: #2D2D2D; border: 1px solid pink; }")
|
||||||
|
# ("color: #f5c2e7; border: none; background-color:#1E1E2E;padding: 5px;")
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
layout.setSpacing(5)
|
||||||
|
|
||||||
|
|
||||||
|
# Helper to make consistently styled buttons
|
||||||
|
def make_btn(text):
|
||||||
|
b = QPushButton(text)
|
||||||
|
b.setStyleSheet(
|
||||||
|
"background-color: #1E1E2E; \
|
||||||
|
color: #f5c2e7; \
|
||||||
|
border-width: 2px;\
|
||||||
|
border-color: pink;\
|
||||||
|
font: 14px;\
|
||||||
|
min-width: 10em;\
|
||||||
|
padding: 5px;"
|
||||||
|
)
|
||||||
|
b.setAutoFillBackground(True)
|
||||||
|
b.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
|
||||||
|
return b
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
btn_run = make_btn("run envied")
|
||||||
|
btn_check = make_btn("env check")
|
||||||
|
btn_info = make_btn("env info")
|
||||||
|
btn_cfg = make_btn("config")
|
||||||
|
|
||||||
|
layout.addWidget(btn_run)
|
||||||
|
layout.addWidget(btn_check)
|
||||||
|
layout.addWidget(btn_info)
|
||||||
|
layout.addWidget(btn_cfg)
|
||||||
|
|
||||||
|
# --- Wire up clicks ---
|
||||||
|
# Option A: non-blocking (recommended): use QProcess so the GUI stays responsive
|
||||||
|
btn_run.clicked.connect(lambda: open_prefilled_terminal())
|
||||||
|
btn_check.clicked.connect(lambda: self.run_cmd(["uv", "run", "envied", "env", "check"]))
|
||||||
|
btn_info.clicked.connect(lambda: self.run_cmd(["uv", "run", "envied", "env", "info"]))
|
||||||
|
'''btn_cfg.clicked.connect(
|
||||||
|
lambda: os.system('nano ./packages/envied/src/envied/envied.yaml') if os.name == 'posix'
|
||||||
|
else os.system('notepad.exe ./packages/envied/src/envied/envied.yaml')
|
||||||
|
)'''
|
||||||
|
btn_cfg.clicked.connect(open_config_detached)
|
||||||
|
|
||||||
|
# ---- Helpers ----
|
||||||
|
def run_cmd(self, argv):
|
||||||
|
"""
|
||||||
|
Non-blocking run using QProcess.
|
||||||
|
"""
|
||||||
|
proc = QProcess(self)
|
||||||
|
# Optional: forward output to your terminal
|
||||||
|
proc.setProgram(argv[0])
|
||||||
|
proc.setArguments(argv[1:])
|
||||||
|
# Show output in your launching terminal (detach these if not needed)
|
||||||
|
proc.readyReadStandardOutput.connect(lambda p=proc: print(p.readAllStandardOutput().data().decode(), end=""))
|
||||||
|
proc.readyReadStandardError.connect(lambda p=proc: print(p.readAllStandardError().data().decode(), end=""))
|
||||||
|
proc.start()
|
||||||
|
|
||||||
|
def run_script(self, what):
|
||||||
|
"""
|
||||||
|
Placeholder hook for your 'config' action.
|
||||||
|
e.g., open a config dialog or run a script.
|
||||||
|
"""
|
||||||
|
print(f"[EnviedPanel] run_script({what}) not implemented yet.")
|
||||||
|
|
||||||
|
def get_terminal():
|
||||||
|
terminals = ["gnome-terminal", "xterm", "konsole", "lxterminal", "xfce4-terminal"]
|
||||||
|
for term in terminals:
|
||||||
|
if shutil.which(term):
|
||||||
|
return term
|
||||||
|
elif os.__name__ =="nt":
|
||||||
|
TERMINALS = ["WindowsTerminal.exe", "OpenConsole.exe","powershell.exe", "Terminal.exe", "cmd.exe"]
|
||||||
|
for TERMINAL in TERMINALS:
|
||||||
|
if shutil.which(TERMINAL):
|
||||||
|
return TERMINAL
|
||||||
|
raise EnvironmentError("No suitable terminal emulator found.")
|
||||||
|
|
||||||
|
def open_prefilled_terminal():
|
||||||
|
prefill = 'uv run envied dl --select-titles '
|
||||||
|
term = get_terminal()
|
||||||
|
|
||||||
|
if term == "gnome-terminal":
|
||||||
|
# -l starts login shell; -c runs the command, then we keep the shell open with 'exec bash'
|
||||||
|
cmd = [
|
||||||
|
"gnome-terminal",
|
||||||
|
"--",
|
||||||
|
"bash", "-lc",
|
||||||
|
# read: -e enables readline, -i sets initial text
|
||||||
|
f'read -e -i "{prefill}" cmd; eval "$cmd"; exec bash'
|
||||||
|
]
|
||||||
|
elif term == "konsole":
|
||||||
|
cmd = [
|
||||||
|
"konsole", "-e",
|
||||||
|
"bash", "-lc",
|
||||||
|
f'read -e -i "{prefill}" cmd; eval "$cmd"; exec bash'
|
||||||
|
]
|
||||||
|
elif term =="lxterminal": # xterm fallback
|
||||||
|
cmd = [
|
||||||
|
"xterm", "-e",
|
||||||
|
"bash", "-lc",
|
||||||
|
f'read -e -i "{prefill}" cmd; eval "$cmd"; exec bash'
|
||||||
|
]
|
||||||
|
|
||||||
|
elif os.name == "nt":
|
||||||
|
# --- Windows: open PowerShell (or Windows Terminal -> PowerShell)
|
||||||
|
# and pre-fill the command line using PSReadLine.
|
||||||
|
# This requires PSReadLine (present by default on modern Windows).
|
||||||
|
ps_prefill = prefill.replace('"', r'`"') # escape quotes for PowerShell
|
||||||
|
|
||||||
|
if shutil.which("wt.exe"):
|
||||||
|
# Windows Terminal present: open a new tab running PowerShell
|
||||||
|
cmd = [
|
||||||
|
"wt.exe",
|
||||||
|
"powershell",
|
||||||
|
"-NoExit",
|
||||||
|
"-Command",
|
||||||
|
f"[Microsoft.PowerShell.PSConsoleReadLine]::Insert(\"{ps_prefill}\")"
|
||||||
|
]
|
||||||
|
elif shutil.which("powershell.exe"):
|
||||||
|
# Fallback: plain PowerShell
|
||||||
|
cmd = [
|
||||||
|
"powershell.exe",
|
||||||
|
"-NoExit",
|
||||||
|
"-Command",
|
||||||
|
f"[Microsoft.PowerShell.PSConsoleReadLine]::Insert(\"{ps_prefill}\")"
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Last resort: cmd.exe can't truly prefill. Keep the window open and hint user.
|
||||||
|
cmd = [
|
||||||
|
"cmd.exe", "/K",
|
||||||
|
f'echo Please use PowerShell for prefill. Intended command: {prefill}'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
subprocess.Popen(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def open_config_detached():
|
||||||
|
if os.name == "nt":
|
||||||
|
QProcess.startDetached("notepad.exe", [CFG])
|
||||||
|
return
|
||||||
|
|
||||||
|
# POSIX
|
||||||
|
# Easiest: open in the default GUI editor
|
||||||
|
if shutil.which("xdg-open"):
|
||||||
|
QProcess.startDetached("xdg-open", [CFG])
|
||||||
|
return
|
||||||
|
|
||||||
|
# If you insist on nano, run it *inside* a terminal emulator
|
||||||
|
# (xterm is very common and works out of the box)
|
||||||
|
if shutil.which("xterm"):
|
||||||
|
QProcess.startDetached("xterm", ["-e", "nano", CFG])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Last resort (may fail without a TTY)
|
||||||
|
QProcess.startDetached("nano", [CFG])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
envied_panel = EnviedPanel()
|
||||||
|
window = envied_panel
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
subprocess.Popen(cmd)
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Group or Username to postfix to the end of all download fienames following a dash
|
||||||
|
#tag: ''
|
||||||
|
|
||||||
|
# Set terminal background color (custom option not in CONFIG.md)
|
||||||
|
set_terminal_bg: false
|
||||||
|
|
||||||
|
# Muxing configuration
|
||||||
|
muxing:
|
||||||
|
set_title: false
|
||||||
|
|
||||||
|
# shakapackager or mp4decrypt
|
||||||
|
decryption: shakapackager
|
||||||
|
|
||||||
|
scene_naming: false
|
||||||
|
series_year: false
|
||||||
|
|
||||||
|
# Widevine pssh display; fold, crop or ellipsis
|
||||||
|
pssh_display: ellipsis
|
||||||
|
|
||||||
|
# Login credentials for each Service
|
||||||
|
credentials:
|
||||||
|
ALL4: angela.slaney@gmail.com:Nood13sh
|
||||||
|
ROKU: moretonplumber@yandex.com:$WPJ~Mdh2:X@EtB
|
||||||
|
TVNZ: moretonplumber@yandex.com:penumbra
|
||||||
|
TPTV: moretonplumber@yandex.com:penumbra1234
|
||||||
|
CBC: moretonplumber@yandex.com:penumbra
|
||||||
|
# Override default directories used across unshackle
|
||||||
|
directories:
|
||||||
|
cache: Cache
|
||||||
|
cookies: Cookies
|
||||||
|
dcsl: DCSL # Device Certificate Status List
|
||||||
|
downloads: /home/angela/Downloads/devine/
|
||||||
|
logs: Logs
|
||||||
|
temp: Temp
|
||||||
|
wvds: WVDs
|
||||||
|
prds: PRDs
|
||||||
|
# Additional directories that can be configured:
|
||||||
|
# commands: Commands
|
||||||
|
#services:
|
||||||
|
# - ./envied/services/
|
||||||
|
vaults: vaults/
|
||||||
|
|
||||||
|
# fonts: Fonts
|
||||||
|
|
||||||
|
# Pre-define which Widevine or PlayReady device to use for each Service
|
||||||
|
cdm:
|
||||||
|
default: device
|
||||||
|
|
||||||
|
# Use pywidevine Serve-compliant Remote CDMs
|
||||||
|
remote_cdm:
|
||||||
|
- name: "CDRM_Project_API"
|
||||||
|
Device Type: 'ANDROID'
|
||||||
|
System ID: 22590
|
||||||
|
Security Level: 3
|
||||||
|
Host: https://cdrm-project.com/remotecdm/widevine
|
||||||
|
Secret: 'CDRM'
|
||||||
|
Device Name: public
|
||||||
|
|
||||||
|
|
||||||
|
# Key Vaults store your obtained Content Encryption Keys (CEKs)
|
||||||
|
key_vaults:
|
||||||
|
- type: SQLite
|
||||||
|
name: Local vault
|
||||||
|
path: /home/angela/Programming/gits/devine-services/key_store.db
|
||||||
|
|
||||||
|
- type: HTTPAPI
|
||||||
|
name: drmlab
|
||||||
|
host: http://api.drmlab.io/vault/
|
||||||
|
password: gEX75q7I5YVkvgF5SUkcNd41IbGrDtTT
|
||||||
|
#- type: API
|
||||||
|
# name: "Online Vault"
|
||||||
|
# uri: "https://cdrm-project.com/api/cache"
|
||||||
|
# token: "CDRM"
|
||||||
|
|
||||||
|
|
||||||
|
# Choose what software to use to download data
|
||||||
|
#downloader: n_m3u8dl_re
|
||||||
|
# Options: requests | aria2c | curl_impersonate | n_m3u8dl_re
|
||||||
|
# Can also be a mapping:
|
||||||
|
downloader:
|
||||||
|
# NF: requests
|
||||||
|
# AMZN: n_m3u8dl_re
|
||||||
|
ALL4: n_m3u8dl_re
|
||||||
|
default: n_m3u8dl_re
|
||||||
|
|
||||||
|
# aria2c downloader configuration
|
||||||
|
aria2c:
|
||||||
|
max_concurrent_downloads: 4
|
||||||
|
max_connection_per_server: 3
|
||||||
|
split: 5
|
||||||
|
file_allocation: falloc # none | prealloc | falloc | trunc
|
||||||
|
|
||||||
|
# N_m3u8DL-RE downloader configuration
|
||||||
|
n_m3u8dl_re:
|
||||||
|
thread_count: 16
|
||||||
|
ad_keyword: "advertisement"
|
||||||
|
use_proxy: true
|
||||||
|
|
||||||
|
# curl_impersonate downloader configuration
|
||||||
|
curl_impersonate:
|
||||||
|
browser: chrome120
|
||||||
|
|
||||||
|
# Pre-define default options and switches of the dl command
|
||||||
|
dl:
|
||||||
|
best: true
|
||||||
|
sub_format: srt
|
||||||
|
downloads: 4
|
||||||
|
workers: 16
|
||||||
|
|
||||||
|
# Chapter Name to use when exporting a Chapter without a Name
|
||||||
|
chapter_fallback_name: "Chapter {j:02}"
|
||||||
|
|
||||||
|
# Case-Insensitive dictionary of headers for all Services
|
||||||
|
headers:
|
||||||
|
Accept-Language: "en-US,en;q=0.8"
|
||||||
|
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36"
|
||||||
|
|
||||||
|
# Override default filenames used across unshackle
|
||||||
|
filenames:
|
||||||
|
log: "envied_{name}_{time}.log"
|
||||||
|
config: "config.yaml"
|
||||||
|
root_config: "envied.yaml"
|
||||||
|
chapters: "Chapters_{title}_{random}.txt"
|
||||||
|
subtitle: "Subtitle_{id}_{language}.srt"
|
||||||
|
|
||||||
|
# API key for The Movie Database (TMDB)
|
||||||
|
tmdb_api_key: ""
|
||||||
|
|
||||||
|
# conversion_method:
|
||||||
|
# - auto (default): Smart routing - subby for WebVTT/SAMI, standard for others
|
||||||
|
# - subby: Always use subby with advanced processing
|
||||||
|
# - pycaption: Use only pycaption library (no SubtitleEdit, no subby)
|
||||||
|
# - subtitleedit: Prefer SubtitleEdit when available, fall back to pycaption
|
||||||
|
subtitle:
|
||||||
|
conversion_method: auto
|
||||||
|
sdh_method: auto
|
||||||
|
|
||||||
|
|
||||||
|
# Configuration data for each Service
|
||||||
|
services:
|
||||||
|
# Service-specific configuration goes here
|
||||||
|
# EXAMPLE:
|
||||||
|
# api_key: "service_specific_key"
|
||||||
|
|
||||||
|
# Legacy NordVPN configuration (use proxy_providers instead)
|
||||||
|
proxy_providers:
|
||||||
|
nordvpn:
|
||||||
|
username: gBM9ejjmY1J15jccH12VT7Qy
|
||||||
|
password: 1kmuCrRXnT55uAjiUTd8twv9
|
||||||
|
server_map:
|
||||||
|
us: 6918
|
||||||
|
uk: 2613
|
||||||
|
nz: 100
|
||||||
@@ -87,7 +87,7 @@ class TPTV(Service):
|
|||||||
self.profile = ctx.parent.params.get("profile")
|
self.profile = ctx.parent.params.get("profile")
|
||||||
if not self.profile:
|
if not self.profile:
|
||||||
self.profile = "default"
|
self.profile = "default"
|
||||||
|
self.session = requests.session()
|
||||||
self.session.headers.update(self.config["headers"])
|
self.session.headers.update(self.config["headers"])
|
||||||
|
|
||||||
def authenticate(self, cookies: Optional[MozillaCookieJar] = None, credential: Optional[Credential] = None) -> None:
|
def authenticate(self, cookies: Optional[MozillaCookieJar] = None, credential: Optional[Credential] = None) -> None:
|
||||||
@@ -147,7 +147,7 @@ class TPTV(Service):
|
|||||||
tokens = res
|
tokens = res
|
||||||
self.log.info(" + Acquired tokens...")
|
self.log.info(" + Acquired tokens...")
|
||||||
|
|
||||||
cache.set(tokens)
|
# cache.set(tokens)
|
||||||
|
|
||||||
self.authorization = tokens
|
self.authorization = tokens
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from PyQt6.QtWidgets import (QApplication,QWidget,QVBoxLayout,QLabel,QLineEdit,QPushButton,QCheckBox,QFrame,)
|
from PyQt6.QtWidgets import (QApplication,QWidget,QVBoxLayout,QLabel,QLineEdit,QPushButton,QCheckBox,QFrame,)
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt, QProcess
|
||||||
from PyQt6.QtGui import QPalette, QColor
|
from PyQt6.QtGui import QPalette, QColor
|
||||||
from PyQt6.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
from PyQt6.QtWidgets import QHBoxLayout, QSlider
|
from PyQt6.QtWidgets import QHBoxLayout, QSlider
|
||||||
@@ -61,23 +61,6 @@ _CFG_NAME = "config.yaml"
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''def load_config_with_fallback() -> tuple[dict, Path | None]:
|
|
||||||
"""
|
|
||||||
Returns (config_dict, user_path_if_used_or_None).
|
|
||||||
Prefers user config; falls back to package-bundled default.
|
|
||||||
"""
|
|
||||||
user_path = _user_config_path()
|
|
||||||
if user_path.exists():
|
|
||||||
data = yaml.safe_load(user_path.read_text(encoding="utf-8")) or {}
|
|
||||||
return data, user_path
|
|
||||||
|
|
||||||
# packaged default
|
|
||||||
with resources.files(_PKG_NAME).joinpath(_CFG_NAME).open("rb") as f:
|
|
||||||
data = yaml.safe_load(f) or {}
|
|
||||||
return data, None'''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def derive_loader_class_name(service_modname: str) -> str:
|
def derive_loader_class_name(service_modname: str) -> str:
|
||||||
"""
|
"""
|
||||||
Derive a class name from a service module name.
|
Derive a class name from a service module name.
|
||||||
@@ -319,16 +302,15 @@ class VineFeeder(QWidget):
|
|||||||
|
|
||||||
# Set button text to white in dark mode, remove red border
|
# Set button text to white in dark mode, remove red border
|
||||||
for i in range(self.highlighted_layout.count()):
|
for i in range(self.highlighted_layout.count()):
|
||||||
button = self.highlighted_layout.itemAt(i).widget()
|
b = self.highlighted_layout.itemAt(i).widget()
|
||||||
if isinstance(button, QPushButton):
|
if isinstance(b, QPushButton) and b.objectName() != "enviedButton": # <-- skip Envied
|
||||||
button.setStyleSheet("""
|
b.setStyleSheet("""
|
||||||
color: white;
|
color: white;
|
||||||
background-color:#1E1E2E;
|
background-color:#1E1E2E;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
""")
|
""")
|
||||||
button.repaint() # Force update of the button's appearance
|
b.repaint()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.setPalette(QApplication.palette())
|
self.setPalette(QApplication.palette())
|
||||||
self.search_url_label.setStyleSheet("color: black;")
|
self.search_url_label.setStyleSheet("color: black;")
|
||||||
@@ -336,15 +318,15 @@ class VineFeeder(QWidget):
|
|||||||
self.batch_label.setStyleSheet("color: black;")
|
self.batch_label.setStyleSheet("color: black;")
|
||||||
|
|
||||||
for i in range(self.highlighted_layout.count()):
|
for i in range(self.highlighted_layout.count()):
|
||||||
button = self.highlighted_layout.itemAt(i).widget()
|
b = self.highlighted_layout.itemAt(i).widget()
|
||||||
if isinstance(button, QPushButton):
|
if isinstance(b, QPushButton) and b.objectName() != "enviedButton": # <-- skip Envied
|
||||||
button.setStyleSheet("""
|
b.setStyleSheet("""
|
||||||
color: black;
|
color: black;
|
||||||
background-color: #aeaeae;
|
background-color: #aeaeae;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
""")
|
""")
|
||||||
button.repaint() # Force update of the button's appearance
|
b.repaint()
|
||||||
# Update batch_label based on dark mode and batch mode state
|
# Update batch_label based on dark mode and batch mode state
|
||||||
if self.batch_slider.value() == 1:
|
if self.batch_slider.value() == 1:
|
||||||
self.batch_label.setStyleSheet("color: lightgreen; padding-left: 5px; border: none;")
|
self.batch_label.setStyleSheet("color: lightgreen; padding-left: 5px; border: none;")
|
||||||
@@ -477,8 +459,37 @@ class VineFeeder(QWidget):
|
|||||||
button.clicked.connect(
|
button.clicked.connect(
|
||||||
lambda: self.run_script("./gui.py"))
|
lambda: self.run_script("./gui.py"))
|
||||||
self.highlighted_layout.addWidget(button)
|
self.highlighted_layout.addWidget(button)
|
||||||
|
if os.name == "posix": # linux
|
||||||
|
button = QPushButton("Envied")
|
||||||
|
button.setObjectName("enviedButton") # <-- add this
|
||||||
|
button.setStyleSheet("color: #f5c2e7; border: none; background-color:#1E1E2E;padding: 5px;") # your pink
|
||||||
|
button.clicked.connect(lambda: self.run_script("./envied_gui.py"))
|
||||||
|
|
||||||
|
elif os.name == "nt":
|
||||||
|
button = QPushButton("Envied Config")
|
||||||
|
button.setObjectName("enviedButton") # <-- add this
|
||||||
|
button.setStyleSheet("color: #f5c2e7; border: none; background-color:#1E1E2E;padding: 5px;") # your pink
|
||||||
|
|
||||||
|
|
||||||
|
CFG = os.path.abspath("./packages/envied/src/envied/envied.yaml")
|
||||||
|
button.clicked.connect(lambda: QProcess.startDetached("notepad.exe", [CFG]))
|
||||||
|
self.highlighted_layout.addWidget(button)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(self, argv):
|
||||||
|
"""
|
||||||
|
Non-blocking run using QProcess.
|
||||||
|
"""
|
||||||
|
proc = QProcess(self)
|
||||||
|
# Optional: forward output to your terminal
|
||||||
|
proc.setProgram(argv[0])
|
||||||
|
proc.setArguments(argv[1:])
|
||||||
|
# Show output in your launching terminal (detach these if not needed)
|
||||||
|
proc.readyReadStandardOutput.connect(lambda p=proc: print(p.readAllStandardOutput().data().decode(), end=""))
|
||||||
|
proc.readyReadStandardError.connect(lambda p=proc: print(p.readAllStandardError().data().decode(), end=""))
|
||||||
|
proc.start()
|
||||||
|
|
||||||
def run_script(self, path: str):
|
def run_script(self, path: str):
|
||||||
"""
|
"""
|
||||||
Rules:
|
Rules:
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
BATCH_DOWNLOAD: false
|
BATCH_DOWNLOAD: false
|
||||||
TERMINAL_RESET: true
|
TERMINAL_RESET: false
|
||||||
|
TERMINAL: gnome-terminal
|
||||||
|
|||||||
Reference in New Issue
Block a user