tinyGuiUpdate , Updating script v1 finnished
This commit is contained in:
0
gui/__init__.py
Normal file
0
gui/__init__.py
Normal file
BIN
gui/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gui/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gui/__pycache__/installer_gui.cpython-313.pyc
Normal file
BIN
gui/__pycache__/installer_gui.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gui/__pycache__/main_panel.cpython-313.pyc
Normal file
BIN
gui/__pycache__/main_panel.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gui/__pycache__/process_runner.cpython-313.pyc
Normal file
BIN
gui/__pycache__/process_runner.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gui/__pycache__/terminal_helper.cpython-313.pyc
Normal file
BIN
gui/__pycache__/terminal_helper.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gui/__pycache__/updater_gui.cpython-313.pyc
Normal file
BIN
gui/__pycache__/updater_gui.cpython-313.pyc
Normal file
Binary file not shown.
13
gui/assets/jibo.svg
Normal file
13
gui/assets/jibo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#f2f2f2"/>
|
||||
<stop offset="1" stop-color="#d9d9d9"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="256" height="256" fill="none"/>
|
||||
<circle cx="128" cy="128" r="110" fill="url(#g)" stroke="#9a9a9a" stroke-width="6"/>
|
||||
<circle cx="128" cy="128" r="70" fill="#ffffff" stroke="#b3b3b3" stroke-width="6"/>
|
||||
<rect x="88" y="168" width="80" height="26" rx="13" fill="#c7c7c7"/>
|
||||
<text x="128" y="128" font-family="sans-serif" font-size="28" text-anchor="middle" fill="#444">Jibo</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 696 B |
37
gui/installer_gui.py
Normal file
37
gui/installer_gui.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QUrl
|
||||
from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
|
||||
from .process_runner import ProcessRunner, resolve_python
|
||||
from .terminal_helper import TerminalHelper
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QGuiApplication(sys.argv)
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
runner = ProcessRunner()
|
||||
terminal = TerminalHelper()
|
||||
engine.rootContext().setContextProperty("runner", runner)
|
||||
engine.rootContext().setContextProperty("terminal", terminal)
|
||||
engine.rootContext().setContextProperty("pyExec", resolve_python())
|
||||
engine.rootContext().setContextProperty("toolScript", "jibo_automod.py")
|
||||
engine.rootContext().setContextProperty("toolTitle", "Installer")
|
||||
|
||||
qml_path = Path(__file__).resolve().parent / "qml" / "ToolRunner.qml"
|
||||
engine.load(QUrl.fromLocalFile(str(qml_path)))
|
||||
|
||||
if not engine.rootObjects():
|
||||
return 1
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
59
gui/main_panel.py
Normal file
59
gui/main_panel.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QUrl, QObject, Slot
|
||||
from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
|
||||
from .process_runner import ConnectionMonitor, resolve_python, resolve_python_invocation
|
||||
|
||||
|
||||
class Launcher(QObject):
|
||||
def __init__(self, python_program: str, python_prefix: list[str]) -> None:
|
||||
super().__init__()
|
||||
self._python_program = python_program
|
||||
self._python_prefix = list(python_prefix)
|
||||
|
||||
@Slot()
|
||||
def launchInstaller(self) -> None:
|
||||
# Start installer GUI in a separate process.
|
||||
import subprocess
|
||||
subprocess.Popen(
|
||||
[self._python_program, *self._python_prefix, "-m", "gui.installer_gui"],
|
||||
cwd=str(Path(__file__).resolve().parents[1]),
|
||||
)
|
||||
|
||||
@Slot()
|
||||
def launchUpdater(self) -> None:
|
||||
import subprocess
|
||||
subprocess.Popen(
|
||||
[self._python_program, *self._python_prefix, "-m", "gui.updater_gui"],
|
||||
cwd=str(Path(__file__).resolve().parents[1]),
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QGuiApplication(sys.argv)
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
conn = ConnectionMonitor()
|
||||
py_program, py_prefix = resolve_python_invocation()
|
||||
py_exec = resolve_python()
|
||||
engine.rootContext().setContextProperty("conn", conn)
|
||||
engine.rootContext().setContextProperty("pyExec", py_exec)
|
||||
engine.rootContext().setContextProperty("launcher", Launcher(py_program, py_prefix))
|
||||
|
||||
qml_path = Path(__file__).resolve().parent / "qml" / "MainPanel.qml"
|
||||
engine.load(QUrl.fromLocalFile(str(qml_path)))
|
||||
|
||||
if not engine.rootObjects():
|
||||
return 1
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
185
gui/process_runner.py
Normal file
185
gui/process_runner.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import QObject, QProcess, QTimer, Signal, Slot, Property
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def resolve_python_invocation() -> tuple[str, list[str]]:
|
||||
"""Return (program, prefix_args) to invoke Python reliably.
|
||||
|
||||
On Windows, prefer the repo-local venv; otherwise prefer the `py -3` launcher
|
||||
when present so we don’t depend on `python.exe` being on PATH.
|
||||
"""
|
||||
|
||||
venv_py = REPO_ROOT / ".venv" / ("Scripts" if os.name == "nt" else "bin") / (
|
||||
"python.exe" if os.name == "nt" else "python"
|
||||
)
|
||||
if venv_py.exists():
|
||||
return (str(venv_py), [])
|
||||
|
||||
if os.name == "nt" and shutil.which("py"):
|
||||
return ("py", ["-3"])
|
||||
|
||||
if shutil.which("python3"):
|
||||
return ("python3", [])
|
||||
|
||||
return (sys.executable or "python", [])
|
||||
|
||||
|
||||
def resolve_python() -> str:
|
||||
program, prefix = resolve_python_invocation()
|
||||
if prefix:
|
||||
# Best-effort string representation (mostly for display)
|
||||
return " ".join([program] + prefix)
|
||||
return program
|
||||
|
||||
|
||||
def can_connect(host: str, port: int, timeout_s: float = 0.8) -> bool:
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout_s):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _pick_terminal_command() -> Optional[list[str]]:
|
||||
if os.name == "nt":
|
||||
return None
|
||||
|
||||
candidates: list[list[str]] = []
|
||||
# Debian/Ubuntu alternative system
|
||||
candidates.append(["x-terminal-emulator", "-e"])
|
||||
candidates.append(["gnome-terminal", "--"])
|
||||
candidates.append(["konsole", "-e"])
|
||||
candidates.append(["xfce4-terminal", "-e"])
|
||||
candidates.append(["xterm", "-e"])
|
||||
|
||||
for cmd in candidates:
|
||||
if shutil.which(cmd[0]):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
|
||||
def spawn_in_terminal(argv: list[str]) -> bool:
|
||||
"""Best-effort external terminal launcher.
|
||||
|
||||
Returns True if a terminal was spawned, False otherwise.
|
||||
"""
|
||||
|
||||
if os.name == "nt":
|
||||
# Use cmd.exe window, keep it open (/k)
|
||||
# Build a single string command for cmd.
|
||||
cmdline = " ".join(shlex.quote(a) for a in argv)
|
||||
subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", cmdline], shell=False)
|
||||
return True
|
||||
|
||||
term = _pick_terminal_command()
|
||||
if not term:
|
||||
return False
|
||||
|
||||
subprocess.Popen(term + argv, cwd=str(REPO_ROOT))
|
||||
return True
|
||||
|
||||
|
||||
class ProcessRunner(QObject):
|
||||
runningChanged = Signal()
|
||||
exitCodeChanged = Signal()
|
||||
outputAppended = Signal(str)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._proc = QProcess(self)
|
||||
self._proc.setProcessChannelMode(QProcess.MergedChannels)
|
||||
self._proc.readyReadStandardOutput.connect(self._on_ready)
|
||||
self._proc.finished.connect(self._on_finished)
|
||||
self._exit_code: int = -1
|
||||
|
||||
@Property(bool, notify=runningChanged)
|
||||
def running(self) -> bool:
|
||||
return self._proc.state() != QProcess.NotRunning
|
||||
|
||||
@Property(int, notify=exitCodeChanged)
|
||||
def exitCode(self) -> int:
|
||||
return self._exit_code
|
||||
|
||||
@Slot(str, list)
|
||||
def start(self, program: str, arguments: list) -> None:
|
||||
if self.running:
|
||||
return
|
||||
self._exit_code = -1
|
||||
self.exitCodeChanged.emit()
|
||||
self._proc.setProgram(program)
|
||||
self._proc.setArguments([str(a) for a in arguments])
|
||||
self._proc.setWorkingDirectory(str(REPO_ROOT))
|
||||
self._proc.start()
|
||||
self.runningChanged.emit()
|
||||
|
||||
@Slot()
|
||||
def stop(self) -> None:
|
||||
if not self.running:
|
||||
return
|
||||
self._proc.terminate()
|
||||
if not self._proc.waitForFinished(1500):
|
||||
self._proc.kill()
|
||||
self.runningChanged.emit()
|
||||
|
||||
def _on_ready(self) -> None:
|
||||
data = bytes(self._proc.readAllStandardOutput()).decode("utf-8", errors="replace")
|
||||
if data:
|
||||
self.outputAppended.emit(data)
|
||||
|
||||
def _on_finished(self, exit_code: int, _status) -> None:
|
||||
self._exit_code = int(exit_code)
|
||||
self.exitCodeChanged.emit()
|
||||
self.runningChanged.emit()
|
||||
|
||||
|
||||
class ConnectionMonitor(QObject):
|
||||
hostChanged = Signal()
|
||||
connectedChanged = Signal()
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._host = ""
|
||||
self._connected = False
|
||||
self._timer = QTimer(self)
|
||||
self._timer.setInterval(1000)
|
||||
self._timer.timeout.connect(self._poll)
|
||||
self._timer.start()
|
||||
|
||||
@Property(str, notify=hostChanged)
|
||||
def host(self) -> str:
|
||||
return self._host
|
||||
|
||||
@host.setter
|
||||
def host(self, value: str) -> None:
|
||||
value = (value or "").strip()
|
||||
if value == self._host:
|
||||
return
|
||||
self._host = value
|
||||
self.hostChanged.emit()
|
||||
self._poll()
|
||||
|
||||
@Property(bool, notify=connectedChanged)
|
||||
def connected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
def _poll(self) -> None:
|
||||
host = self._host
|
||||
connected = False
|
||||
if host:
|
||||
connected = can_connect(host, 22)
|
||||
if connected != self._connected:
|
||||
self._connected = connected
|
||||
self.connectedChanged.emit()
|
||||
168
gui/qml/MainPanel.qml
Normal file
168
gui/qml/MainPanel.qml
Normal file
@@ -0,0 +1,168 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
ApplicationWindow {
|
||||
id: win
|
||||
width: 880
|
||||
height: 520
|
||||
visible: true
|
||||
title: "Jibo Tools"
|
||||
|
||||
property string host: hostField.text.trim()
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 14
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: "Connection"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 10
|
||||
height: 10
|
||||
radius: 5
|
||||
color: conn.connected ? "#2ecc71" : (host.length > 0 ? "#e67e22" : "#bdc3c7")
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: conn.connected ? "SSH reachable" : (host.length > 0 ? "Not reachable" : "No IP")
|
||||
color: "#555"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
TextField {
|
||||
id: hostField
|
||||
placeholderText: "Jibo IP (e.g. 192.168.1.50)"
|
||||
Layout.preferredWidth: 280
|
||||
onTextChanged: conn.host = text
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 16
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillHeight: true
|
||||
radius: 14
|
||||
color: "#f6f6f6"
|
||||
border.color: "#e4e4e4"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: "Your Jibo"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Rectangle {
|
||||
id: jiboCard
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: 240
|
||||
height: 240
|
||||
radius: 18
|
||||
color: "#ffffff"
|
||||
border.color: "#e4e4e4"
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 200
|
||||
height: 200
|
||||
source: "../assets/jibo.svg"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Best-effort: open Chrome remote devices page.
|
||||
Qt.openUrlExternally("chrome://inspect/#devices")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Text {
|
||||
text: "Click Jibo to open chrome://inspect"
|
||||
color: "#666"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: 14
|
||||
color: "#f6f6f6"
|
||||
border.color: "#e4e4e4"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: "Actions"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Installer and updater remain available via CLI.\nUse the buttons below to launch their GUIs."
|
||||
color: "#555"
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: "Install"
|
||||
enabled: true
|
||||
onClicked: {
|
||||
launcher.launchInstaller()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: "Check for updates"
|
||||
enabled: true
|
||||
onClicked: {
|
||||
launcher.launchUpdater()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
142
gui/qml/ToolRunner.qml
Normal file
142
gui/qml/ToolRunner.qml
Normal file
@@ -0,0 +1,142 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ApplicationWindow {
|
||||
id: win
|
||||
width: 900
|
||||
height: 560
|
||||
visible: true
|
||||
title: (typeof toolTitle === "string" ? toolTitle : "Tool")
|
||||
|
||||
property string script: (typeof toolScript === "string" ? toolScript : "")
|
||||
property bool isUpdater: script.indexOf("jibo_updater.py") >= 0
|
||||
|
||||
function buildArgs() {
|
||||
var args = []
|
||||
args.push(script)
|
||||
|
||||
if (isUpdater) {
|
||||
var h = hostField.text.trim()
|
||||
if (h.length > 0) {
|
||||
args.push("--ip")
|
||||
args.push(h)
|
||||
}
|
||||
}
|
||||
|
||||
var extra = extraArgs.text.trim()
|
||||
if (extra.length > 0) {
|
||||
// naive split (keeps GUI minimal)
|
||||
var parts = extra.split(/\s+/)
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
if (parts[i].length > 0) args.push(parts[i])
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: title
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Button {
|
||||
text: runner.running ? "Stop" : "Start"
|
||||
onClicked: {
|
||||
if (runner.running) {
|
||||
runner.stop()
|
||||
} else {
|
||||
runner.start(pyExec, buildArgs())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Open in terminal"
|
||||
enabled: !runner.running
|
||||
onClicked: {
|
||||
terminal.openTerminal(pyExec, buildArgs())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
TextField {
|
||||
id: hostField
|
||||
visible: isUpdater
|
||||
placeholderText: "Jibo IP (required for updater)"
|
||||
Layout.preferredWidth: 260
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: extraArgs
|
||||
placeholderText: "Extra arguments (optional)"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: 12
|
||||
color: "#0f0f0f"
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: log
|
||||
readOnly: true
|
||||
wrapMode: TextArea.Wrap
|
||||
color: "#e8e8e8"
|
||||
font.family: "monospace"
|
||||
background: null
|
||||
text: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: runner.running ? "Running..." : (runner.exitCode >= 0 ? ("Exit: " + runner.exitCode) : "Idle")
|
||||
color: "#666"
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Button {
|
||||
text: "Clear log"
|
||||
onClicked: log.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: runner
|
||||
function onOutputAppended(chunk) {
|
||||
log.text += chunk
|
||||
log.cursorPosition = log.length
|
||||
}
|
||||
}
|
||||
}
|
||||
12
gui/terminal_helper.py
Normal file
12
gui/terminal_helper.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import QObject, Slot
|
||||
|
||||
from .process_runner import spawn_in_terminal
|
||||
|
||||
|
||||
class TerminalHelper(QObject):
|
||||
@Slot(str, list, result=bool)
|
||||
def openTerminal(self, program: str, arguments: list) -> bool:
|
||||
argv = [program] + [str(a) for a in arguments]
|
||||
return spawn_in_terminal(argv)
|
||||
37
gui/updater_gui.py
Normal file
37
gui/updater_gui.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QUrl
|
||||
from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
|
||||
from .process_runner import ProcessRunner, resolve_python
|
||||
from .terminal_helper import TerminalHelper
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QGuiApplication(sys.argv)
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
runner = ProcessRunner()
|
||||
terminal = TerminalHelper()
|
||||
engine.rootContext().setContextProperty("runner", runner)
|
||||
engine.rootContext().setContextProperty("terminal", terminal)
|
||||
engine.rootContext().setContextProperty("pyExec", resolve_python())
|
||||
engine.rootContext().setContextProperty("toolScript", "jibo_updater.py")
|
||||
engine.rootContext().setContextProperty("toolTitle", "Updater")
|
||||
|
||||
qml_path = Path(__file__).resolve().parent / "qml" / "ToolRunner.qml"
|
||||
engine.load(QUrl.fromLocalFile(str(qml_path)))
|
||||
|
||||
if not engine.rootObjects():
|
||||
return 1
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user