gini-toolkit 6.0.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gini/__init__.py +12 -0
- gini/__main__.py +107 -0
- gini/_version.py +24 -0
- gini/agent/__init__.py +17 -0
- gini/agent/agent_gamemaster.py +140 -0
- gini/agent/api.py +291 -0
- gini/agent/ask.py +123 -0
- gini/agent/authoring.py +72 -0
- gini/agent/blackboard.py +114 -0
- gini/agent/contracts.py +142 -0
- gini/agent/domains.py +91 -0
- gini/agent/embed.py +123 -0
- gini/agent/gamemaster.py +256 -0
- gini/agent/kb.py +148 -0
- gini/agent/lesson_resolver.py +261 -0
- gini/agent/llm/__init__.py +5 -0
- gini/agent/llm/backend.py +43 -0
- gini/agent/llm/fake.py +25 -0
- gini/agent/llm/ollama.py +206 -0
- gini/agent/loop.py +258 -0
- gini/agent/mcp_server.py +86 -0
- gini/agent/meaning.py +225 -0
- gini/agent/mission.py +210 -0
- gini/agent/mission_controller.py +208 -0
- gini/agent/narration.py +116 -0
- gini/agent/notifier.py +86 -0
- gini/agent/personas.py +79 -0
- gini/agent/reasoning.py +172 -0
- gini/agent/recall.py +248 -0
- gini/agent/session.py +79 -0
- gini/agent/teaching_center.py +482 -0
- gini/agent/tools/__init__.py +3 -0
- gini/agent/tools/registry.py +193 -0
- gini/agent/twin/__init__.py +28 -0
- gini/agent/twin/authoring.py +71 -0
- gini/agent/twin/contracts.py +54 -0
- gini/agent/twin/dialectic.py +189 -0
- gini/agent/twin/harness.py +93 -0
- gini/agent/twin/justify.py +156 -0
- gini/agent/twin/learner.py +64 -0
- gini/agent/twin/mission.py +60 -0
- gini/agent/twin/os_coach.py +79 -0
- gini/agent/twin/salience.py +30 -0
- gini/agent/understand.py +250 -0
- gini/agent/verifiers.py +106 -0
- gini/agent/wizard.py +178 -0
- gini/agent/xv6_pack.py +74 -0
- gini/app/__init__.py +3 -0
- gini/app/context.py +368 -0
- gini/app/paths.py +121 -0
- gini/data/README.md +21 -0
- gini/domain/__init__.py +9 -0
- gini/domain/assembly.py +209 -0
- gini/domain/authoring.py +353 -0
- gini/domain/blueprints.py +5 -0
- gini/domain/capabilities.py +177 -0
- gini/domain/catalog.py +85 -0
- gini/domain/certify.py +201 -0
- gini/domain/compose.py +413 -0
- gini/domain/composition.py +88 -0
- gini/domain/concepts.py +383 -0
- gini/domain/connection_rules.py +269 -0
- gini/domain/constraints.py +153 -0
- gini/domain/content.py +59 -0
- gini/domain/cpu_journey.py +89 -0
- gini/domain/devices.py +747 -0
- gini/domain/diagnose.py +201 -0
- gini/domain/element_guide.py +327 -0
- gini/domain/explain.py +90 -0
- gini/domain/fingerprint.py +201 -0
- gini/domain/firewall.py +34 -0
- gini/domain/flowlog.py +61 -0
- gini/domain/flowtable.py +179 -0
- gini/domain/fragment_yaml.py +230 -0
- gini/domain/fragments.py +169 -0
- gini/domain/games/__init__.py +2 -0
- gini/domain/games/paging_games.py +119 -0
- gini/domain/games/policy_game.py +86 -0
- gini/domain/games/process_game.py +48 -0
- gini/domain/games/thrash_game.py +75 -0
- gini/domain/games/translate_game.py +60 -0
- gini/domain/games/trap_game.py +86 -0
- gini/domain/grader.py +155 -0
- gini/domain/grouping.py +67 -0
- gini/domain/legality.py +103 -0
- gini/domain/lesson.py +241 -0
- gini/domain/lexicon.py +150 -0
- gini/domain/machine_state.py +410 -0
- gini/domain/missions/networking/basic-lan.yaml +32 -0
- gini/domain/missions/networking/cache-in-front.yaml +23 -0
- gini/domain/missions/networking/decouple-with-queue.yaml +31 -0
- gini/domain/missions/networking/drive-load.yaml +20 -0
- gini/domain/missions/networking/fix-the-address.yaml +75 -0
- gini/domain/missions/networking/fix-the-lan.yaml +43 -0
- gini/domain/missions/networking/inspect-flows.yaml +16 -0
- gini/domain/missions/networking/k8s-autoscale.yaml +27 -0
- gini/domain/missions/networking/least-privilege.yaml +21 -0
- gini/domain/missions/networking/load-balanced-web.yaml +29 -0
- gini/domain/missions/networking/observe-it.yaml +24 -0
- gini/domain/missions/networking/put-in-vpc.yaml +30 -0
- gini/domain/missions/networking/reachability-boundary.yaml +56 -0
- gini/domain/missions/networking/sdn-reactive.yaml +35 -0
- gini/domain/missions/networking/send-request.yaml +19 -0
- gini/domain/missions/networking/serverless-api.yaml +25 -0
- gini/domain/missions/networking/service-chain.yaml +33 -0
- gini/domain/missions/os/lottery-fix.yaml +19 -0
- gini/domain/missions/os/priority-fix.yaml +24 -0
- gini/domain/missions.py +111 -0
- gini/domain/modulechain.py +36 -0
- gini/domain/objectives.py +488 -0
- gini/domain/os_zoo.py +79 -0
- gini/domain/paging_sim.py +141 -0
- gini/domain/pricing.py +199 -0
- gini/domain/probes.py +226 -0
- gini/domain/profile.py +142 -0
- gini/domain/recipes.py +738 -0
- gini/domain/riders.py +309 -0
- gini/domain/router_modules.py +224 -0
- gini/domain/routetable.py +67 -0
- gini/domain/scoring.py +76 -0
- gini/domain/staging.py +122 -0
- gini/domain/syscall_builder.py +144 -0
- gini/domain/topic_cloud.py +62 -0
- gini/domain/topology.py +213 -0
- gini/domain/vocabulary.py +51 -0
- gini/domain/xv6.py +808 -0
- gini/domain/xv6_fs.py +250 -0
- gini/domain/xv6_runner.py +113 -0
- gini/domain/xv6_vm.py +385 -0
- gini/gloader.py +17 -0
- gini/runtime/__init__.py +18 -0
- gini/runtime/cloudfabric_agent.py +370 -0
- gini/runtime/console.py +68 -0
- gini/runtime/control.py +70 -0
- gini/runtime/frame.py +138 -0
- gini/runtime/gbridge.py +638 -0
- gini/runtime/grouter.py +223 -0
- gini/runtime/hostsim.py +90 -0
- gini/runtime/shuttle.py +348 -0
- gini/runtime/switch.py +109 -0
- gini/runtime/transport.py +77 -0
- gini/runtime/xv6_bridge.py +312 -0
- gini/server/__init__.py +22 -0
- gini/server/__main__.py +74 -0
- gini/server/app.py +140 -0
- gini/server/auth.py +82 -0
- gini/server/policy.py +57 -0
- gini/server/session.py +23 -0
- gini/services/__init__.py +15 -0
- gini/services/boardflash.py +248 -0
- gini/services/boardsetup.py +374 -0
- gini/services/cloud_catalog.py +143 -0
- gini/services/compiler.py +1858 -0
- gini/services/discovery.py +324 -0
- gini/services/gloader.py +183 -0
- gini/services/orchestrator.py +1460 -0
- gini/services/persistence.py +28 -0
- gini/services/probe_runner.py +149 -0
- gini/services/project.py +217 -0
- gini/services/remote.py +93 -0
- gini/services/rider_runner.py +96 -0
- gini/services/rider_session.py +171 -0
- gini/services/shadow_store.py +52 -0
- gini/services/terminal.py +45 -0
- gini/setup/__init__.py +17 -0
- gini/setup/cli.py +109 -0
- gini/setup/images.py +33 -0
- gini/setup/marker.py +43 -0
- gini/setup/runtime.py +69 -0
- gini/ui/__init__.py +3 -0
- gini/ui/assets/app_icon.icns +0 -0
- gini/ui/assets/app_icon.ico +0 -0
- gini/ui/assets/app_icon.png +0 -0
- gini/ui/assets/app_icon_1024.png +0 -0
- gini/ui/assets/cue/_w.txt +1 -0
- gini/ui/assets/cue/ai.png +0 -0
- gini/ui/assets/cue/canvas.png +0 -0
- gini/ui/assets/cue/cloud.png +0 -0
- gini/ui/assets/cue/cost.png +0 -0
- gini/ui/assets/cue/dark/ai.png +0 -0
- gini/ui/assets/cue/dark/canvas.png +0 -0
- gini/ui/assets/cue/dark/cloud.png +0 -0
- gini/ui/assets/cue/dark/cost.png +0 -0
- gini/ui/assets/cue/dark/metrics.png +0 -0
- gini/ui/assets/cue/dark/router.png +0 -0
- gini/ui/assets/cue/dark/run.png +0 -0
- gini/ui/assets/cue/dark/serverless.png +0 -0
- gini/ui/assets/cue/dark/settings.png +0 -0
- gini/ui/assets/cue/dark/welcome.png +0 -0
- gini/ui/assets/cue/dark/wizard.png +0 -0
- gini/ui/assets/cue/ginibrand/ai.png +0 -0
- gini/ui/assets/cue/ginibrand/canvas.png +0 -0
- gini/ui/assets/cue/ginibrand/cloud.png +0 -0
- gini/ui/assets/cue/ginibrand/cost.png +0 -0
- gini/ui/assets/cue/ginibrand/metrics.png +0 -0
- gini/ui/assets/cue/ginibrand/router.png +0 -0
- gini/ui/assets/cue/ginibrand/run.png +0 -0
- gini/ui/assets/cue/ginibrand/serverless.png +0 -0
- gini/ui/assets/cue/ginibrand/settings.png +0 -0
- gini/ui/assets/cue/ginibrand/welcome.png +0 -0
- gini/ui/assets/cue/ginibrand/wizard.png +0 -0
- gini/ui/assets/cue/highcontrast/ai.png +0 -0
- gini/ui/assets/cue/highcontrast/canvas.png +0 -0
- gini/ui/assets/cue/highcontrast/cloud.png +0 -0
- gini/ui/assets/cue/highcontrast/cost.png +0 -0
- gini/ui/assets/cue/highcontrast/metrics.png +0 -0
- gini/ui/assets/cue/highcontrast/router.png +0 -0
- gini/ui/assets/cue/highcontrast/run.png +0 -0
- gini/ui/assets/cue/highcontrast/serverless.png +0 -0
- gini/ui/assets/cue/highcontrast/settings.png +0 -0
- gini/ui/assets/cue/highcontrast/welcome.png +0 -0
- gini/ui/assets/cue/highcontrast/wizard.png +0 -0
- gini/ui/assets/cue/light/ai.png +0 -0
- gini/ui/assets/cue/light/canvas.png +0 -0
- gini/ui/assets/cue/light/cloud.png +0 -0
- gini/ui/assets/cue/light/cost.png +0 -0
- gini/ui/assets/cue/light/metrics.png +0 -0
- gini/ui/assets/cue/light/router.png +0 -0
- gini/ui/assets/cue/light/run.png +0 -0
- gini/ui/assets/cue/light/serverless.png +0 -0
- gini/ui/assets/cue/light/settings.png +0 -0
- gini/ui/assets/cue/light/welcome.png +0 -0
- gini/ui/assets/cue/light/wizard.png +0 -0
- gini/ui/assets/cue/metrics.png +0 -0
- gini/ui/assets/cue/router.png +0 -0
- gini/ui/assets/cue/run.png +0 -0
- gini/ui/assets/cue/serverless.png +0 -0
- gini/ui/assets/cue/settings.png +0 -0
- gini/ui/assets/cue/welcome.png +0 -0
- gini/ui/assets/cue/wizard.png +0 -0
- gini/ui/assistant.py +2111 -0
- gini/ui/author_dialog.py +184 -0
- gini/ui/board_dialog.py +247 -0
- gini/ui/branding.py +21 -0
- gini/ui/canvas.py +2007 -0
- gini/ui/chat_panel.py +7 -0
- gini/ui/cpu_journey.py +212 -0
- gini/ui/cpu_lab.py +306 -0
- gini/ui/cue_cards.py +214 -0
- gini/ui/dashboard.py +222 -0
- gini/ui/diagnose_game.py +336 -0
- gini/ui/fingerprint_lab.py +219 -0
- gini/ui/flash_dialog.py +244 -0
- gini/ui/flow_layout.py +63 -0
- gini/ui/fragment_manager.py +1415 -0
- gini/ui/game_catalog.py +184 -0
- gini/ui/game_renderers.py +340 -0
- gini/ui/games_lab.py +90 -0
- gini/ui/inspector.py +1055 -0
- gini/ui/live_metrics.py +130 -0
- gini/ui/machine_lab.py +1412 -0
- gini/ui/main_window.py +3153 -0
- gini/ui/memory_lab.py +371 -0
- gini/ui/mission_panel.py +302 -0
- gini/ui/mode_indicator.py +227 -0
- gini/ui/palette.py +112 -0
- gini/ui/peripherals.py +218 -0
- gini/ui/process_tree.py +130 -0
- gini/ui/reset_dialog.py +179 -0
- gini/ui/router_lab.py +776 -0
- gini/ui/run_button.py +183 -0
- gini/ui/settings_dialog.py +234 -0
- gini/ui/signin_dialog.py +111 -0
- gini/ui/storage_lab.py +219 -0
- gini/ui/syscall_builder.py +235 -0
- gini/ui/syscall_lab.py +152 -0
- gini/ui/theme/__init__.py +5 -0
- gini/ui/theme/icons.py +145 -0
- gini/ui/theme/manager.py +291 -0
- gini/ui/theme/tokens.py +194 -0
- gini/ui/trap_lab.py +270 -0
- gini/ui/worker_host.py +102 -0
- gini/ui/zoo_lab.py +112 -0
- gini_toolkit-6.0.1.dev0.dist-info/METADATA +77 -0
- gini_toolkit-6.0.1.dev0.dist-info/RECORD +278 -0
- gini_toolkit-6.0.1.dev0.dist-info/WHEEL +5 -0
- gini_toolkit-6.0.1.dev0.dist-info/entry_points.txt +3 -0
- gini_toolkit-6.0.1.dev0.dist-info/top_level.txt +1 -0
gini/ui/flash_dialog.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Hardware → Flash a Board: put gBridge firmware on a GINI32 board over USB.
|
|
2
|
+
|
|
3
|
+
This is the step that was missing, and its absence made the rest of the Hardware menu
|
|
4
|
+
untrue. *Set Up a Board* talks to the board's `gini> ` console — which only exists once
|
|
5
|
+
firmware is on it. So a student holding a brand-new board could not begin in gBuilder at
|
|
6
|
+
all; they needed a terminal, ESP-IDF and the CLI. Now the menu covers a board's whole
|
|
7
|
+
life: flash it, set it up, reset it, list them.
|
|
8
|
+
|
|
9
|
+
Flashing does NOT erase the board. The images are written at their own offsets, which
|
|
10
|
+
leaves NVS at 0x9000 alone, so a board that was already set up keeps its id, its lab
|
|
11
|
+
Wi-Fi and its LED pin across a firmware update. See services/boardflash.py.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from PySide6.QtCore import QObject, Qt, Signal
|
|
16
|
+
from PySide6.QtWidgets import (
|
|
17
|
+
QComboBox, QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout, QLabel,
|
|
18
|
+
QMessageBox, QProgressBar, QPushButton, QVBoxLayout,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from ..services import boardflash as bf
|
|
22
|
+
from ..services import boardsetup as bs
|
|
23
|
+
from .worker_host import WorkerHost
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _note(text: str) -> QLabel:
|
|
27
|
+
lab = QLabel(text)
|
|
28
|
+
lab.setWordWrap(True)
|
|
29
|
+
lab.setStyleSheet("color: palette(mid);")
|
|
30
|
+
return lab
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _Detect(QObject):
|
|
34
|
+
"""Enumerate ports and ask the selected one what chip it is."""
|
|
35
|
+
done = Signal(list, str) # [PortInfo], chip target ("" = unknown)
|
|
36
|
+
failed = Signal(str)
|
|
37
|
+
|
|
38
|
+
def __init__(self, port: str = "") -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
self._port = port
|
|
41
|
+
|
|
42
|
+
def run(self) -> None:
|
|
43
|
+
try:
|
|
44
|
+
ports = bs.list_ports()
|
|
45
|
+
chip = bf.detect_chip(self._port) if self._port else ""
|
|
46
|
+
self.done.emit(ports, chip)
|
|
47
|
+
except Exception as e: # a USB oddity must never kill the UI
|
|
48
|
+
self.failed.emit(str(e))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _Flash(QObject):
|
|
52
|
+
"""Write the images, off the GUI thread — this takes ten to thirty seconds."""
|
|
53
|
+
done = Signal(bool, str)
|
|
54
|
+
progress = Signal(str)
|
|
55
|
+
|
|
56
|
+
def __init__(self, port: str, firmware) -> None:
|
|
57
|
+
super().__init__()
|
|
58
|
+
self._port, self._fw = port, firmware
|
|
59
|
+
|
|
60
|
+
def run(self) -> None:
|
|
61
|
+
try:
|
|
62
|
+
res = bf.flash(self._port, self._fw, on_progress=self.progress.emit)
|
|
63
|
+
self.done.emit(res.ok, res.message)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
self.done.emit(False, str(e))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FlashBoardDialog(WorkerHost, QDialog):
|
|
69
|
+
"""Pick a port, confirm the chip, write the firmware."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, parent=None) -> None:
|
|
72
|
+
super().__init__(parent)
|
|
73
|
+
self.setWindowTitle("Flash a Board")
|
|
74
|
+
self.setMinimumWidth(560)
|
|
75
|
+
self._alive = True
|
|
76
|
+
self._thread = None
|
|
77
|
+
self._worker = None
|
|
78
|
+
self._connections = []
|
|
79
|
+
self.flashed_port = "" # read by the caller to chain into Set Up
|
|
80
|
+
|
|
81
|
+
root = QVBoxLayout(self)
|
|
82
|
+
root.addWidget(_note(
|
|
83
|
+
"Puts the gBridge firmware on a new board. You only need this once per "
|
|
84
|
+
"board — afterwards use <b>Set Up a Board</b> to give it the lab Wi-Fi.<br><br>"
|
|
85
|
+
"Re-flashing an existing board is safe: its id, Wi-Fi and pairing are stored "
|
|
86
|
+
"in a separate area of flash that is not touched."))
|
|
87
|
+
|
|
88
|
+
form = QFormLayout()
|
|
89
|
+
self.port = QComboBox()
|
|
90
|
+
row = QHBoxLayout()
|
|
91
|
+
row.addWidget(self.port, 1)
|
|
92
|
+
self.rescan = QPushButton("Rescan")
|
|
93
|
+
self.rescan.clicked.connect(self.scan)
|
|
94
|
+
row.addWidget(self.rescan)
|
|
95
|
+
form.addRow("Board on", row)
|
|
96
|
+
|
|
97
|
+
self.chip = QLabel("—")
|
|
98
|
+
form.addRow("Chip", self.chip)
|
|
99
|
+
self.firmware = QLabel("—")
|
|
100
|
+
form.addRow("Firmware", self.firmware)
|
|
101
|
+
root.addLayout(form)
|
|
102
|
+
|
|
103
|
+
self.bar = QProgressBar()
|
|
104
|
+
self.bar.setRange(0, 0) # indeterminate: esptool's own % is not parsed
|
|
105
|
+
self.bar.setVisible(False)
|
|
106
|
+
root.addWidget(self.bar)
|
|
107
|
+
|
|
108
|
+
self.status = _note("")
|
|
109
|
+
root.addWidget(self.status)
|
|
110
|
+
|
|
111
|
+
self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel)
|
|
112
|
+
self.flash_btn = QPushButton("Flash")
|
|
113
|
+
self.flash_btn.setDefault(True)
|
|
114
|
+
self.buttons.addButton(self.flash_btn, QDialogButtonBox.AcceptRole)
|
|
115
|
+
self.flash_btn.clicked.connect(self._flash)
|
|
116
|
+
self.buttons.rejected.connect(self.reject)
|
|
117
|
+
root.addWidget(self.buttons)
|
|
118
|
+
|
|
119
|
+
self.port.currentIndexChanged.connect(self._port_changed)
|
|
120
|
+
self.scan()
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------- scanning
|
|
123
|
+
|
|
124
|
+
def scan(self) -> None:
|
|
125
|
+
if not bf.esptool_available():
|
|
126
|
+
# Say this once, plainly, instead of letting every flash fail with a
|
|
127
|
+
# traceback. esptool is a pure-Python package — no toolchain — so the fix
|
|
128
|
+
# really is a single pip install.
|
|
129
|
+
self._busy(False, "")
|
|
130
|
+
self.status.setText(
|
|
131
|
+
"<b>esptool is not installed.</b> It is a small Python package and "
|
|
132
|
+
"needs no toolchain:<br><tt>pip install esptool</tt>")
|
|
133
|
+
self.flash_btn.setEnabled(False)
|
|
134
|
+
return
|
|
135
|
+
self._busy(True, "looking for boards…")
|
|
136
|
+
self._run(_Detect(), self._scanned)
|
|
137
|
+
|
|
138
|
+
def _scanned(self, ports: list, chip: str) -> None:
|
|
139
|
+
if not self._alive:
|
|
140
|
+
return
|
|
141
|
+
self.port.blockSignals(True)
|
|
142
|
+
self.port.clear()
|
|
143
|
+
for p in ports:
|
|
144
|
+
self.port.addItem(p.label, p.device)
|
|
145
|
+
self.port.blockSignals(False)
|
|
146
|
+
if not ports:
|
|
147
|
+
self._busy(False, "")
|
|
148
|
+
self.status.setText(
|
|
149
|
+
"No board found. Plug one in with a <b>data</b> USB cable — charge-only "
|
|
150
|
+
"cables carry no data lines and look exactly like a dead board.")
|
|
151
|
+
self.flash_btn.setEnabled(False)
|
|
152
|
+
return
|
|
153
|
+
self._busy(False, "")
|
|
154
|
+
self._port_changed()
|
|
155
|
+
|
|
156
|
+
def _port_changed(self) -> None:
|
|
157
|
+
"""Identify the chip, then show which firmware would be written to it."""
|
|
158
|
+
if not self._alive or self.port.count() == 0:
|
|
159
|
+
return
|
|
160
|
+
self._busy(True, "asking the board what it is…")
|
|
161
|
+
self._run(_Detect(self.port.currentData()), self._identified)
|
|
162
|
+
|
|
163
|
+
def _identified(self, _ports: list, chip: str) -> None:
|
|
164
|
+
if not self._alive:
|
|
165
|
+
return
|
|
166
|
+
self._busy(False, "")
|
|
167
|
+
if not chip:
|
|
168
|
+
self.chip.setText("could not tell")
|
|
169
|
+
self.firmware.setText("—")
|
|
170
|
+
self.status.setText(
|
|
171
|
+
"The board did not answer. Hold <b>BOOT</b>, tap <b>RESET</b>, release "
|
|
172
|
+
"BOOT, then press Rescan — some boards cannot be reset over USB alone.")
|
|
173
|
+
self.flash_btn.setEnabled(False)
|
|
174
|
+
return
|
|
175
|
+
self.chip.setText(chip)
|
|
176
|
+
fw = bf.available(chip)
|
|
177
|
+
if fw is None:
|
|
178
|
+
have = ", ".join(bf.available_targets()) or "none"
|
|
179
|
+
self.firmware.setText("not available")
|
|
180
|
+
self.status.setText(
|
|
181
|
+
f"No firmware is shipped for <b>{chip}</b> (available: {have}). "
|
|
182
|
+
f"Build one with <tt>GINI32_TARGET={chip} ./gini32 build</tt>.")
|
|
183
|
+
self.flash_btn.setEnabled(False)
|
|
184
|
+
return
|
|
185
|
+
self._fw = fw
|
|
186
|
+
self.firmware.setText(f"{fw.build or 'gbridge'} · {fw.total_bytes // 1024} KB")
|
|
187
|
+
self.status.setText("")
|
|
188
|
+
self.flash_btn.setEnabled(True)
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------- flashing
|
|
191
|
+
|
|
192
|
+
def _flash(self) -> None:
|
|
193
|
+
fw = getattr(self, "_fw", None)
|
|
194
|
+
if fw is None or self.port.count() == 0:
|
|
195
|
+
return
|
|
196
|
+
port = self.port.currentData()
|
|
197
|
+
self.bar.setVisible(True)
|
|
198
|
+
self._busy(True, "flashing — do not unplug the board…")
|
|
199
|
+
self.flashed_port = port
|
|
200
|
+
self._run(_Flash(port, fw), self._flashed)
|
|
201
|
+
|
|
202
|
+
def _worker_progress(self, message: str) -> None:
|
|
203
|
+
if self._alive:
|
|
204
|
+
self.status.setText(message)
|
|
205
|
+
|
|
206
|
+
def _flashed(self, ok: bool, msg: str) -> None:
|
|
207
|
+
if not self._alive:
|
|
208
|
+
return
|
|
209
|
+
self.bar.setVisible(False)
|
|
210
|
+
self._busy(False, "")
|
|
211
|
+
if not ok:
|
|
212
|
+
self.flashed_port = ""
|
|
213
|
+
QMessageBox.warning(self, "Could not flash the board", msg)
|
|
214
|
+
self.status.setText(msg)
|
|
215
|
+
return
|
|
216
|
+
QMessageBox.information(
|
|
217
|
+
self, "Board flashed",
|
|
218
|
+
f"{msg}.\n\nNext: use Set Up a Board to give it the lab Wi-Fi and an id.")
|
|
219
|
+
self.accept()
|
|
220
|
+
|
|
221
|
+
def _worker_failed(self, message: str) -> None:
|
|
222
|
+
if not self._alive:
|
|
223
|
+
return
|
|
224
|
+
self.bar.setVisible(False)
|
|
225
|
+
self._busy(False, "")
|
|
226
|
+
self.status.setText(message)
|
|
227
|
+
|
|
228
|
+
# ------------------------------------------------------------------- utils
|
|
229
|
+
|
|
230
|
+
def closeEvent(self, event) -> None:
|
|
231
|
+
self._detach()
|
|
232
|
+
super().closeEvent(event)
|
|
233
|
+
|
|
234
|
+
def reject(self) -> None:
|
|
235
|
+
self._detach()
|
|
236
|
+
super().reject()
|
|
237
|
+
|
|
238
|
+
def _busy(self, busy: bool, message: str) -> None:
|
|
239
|
+
if not self._alive:
|
|
240
|
+
return
|
|
241
|
+
self.status.setText(message)
|
|
242
|
+
self.rescan.setEnabled(not busy)
|
|
243
|
+
self.flash_btn.setEnabled(not busy)
|
|
244
|
+
self.setCursor(Qt.WaitCursor if busy else Qt.ArrowCursor)
|
gini/ui/flow_layout.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""A wrapping flow layout — lays widgets left-to-right, wrapping to the next line when the
|
|
2
|
+
row is full (a tag/word-cloud arrangement). Standard Qt pattern, kept minimal."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import QPoint, QRect, QSize, Qt
|
|
6
|
+
from PySide6.QtWidgets import QLayout, QSizePolicy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FlowLayout(QLayout):
|
|
10
|
+
def __init__(self, parent=None, margin=0, spacing=6):
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
self._items = []
|
|
13
|
+
self.setContentsMargins(margin, margin, margin, margin)
|
|
14
|
+
self.setSpacing(spacing)
|
|
15
|
+
|
|
16
|
+
def addItem(self, item):
|
|
17
|
+
self._items.append(item)
|
|
18
|
+
|
|
19
|
+
def count(self):
|
|
20
|
+
return len(self._items)
|
|
21
|
+
|
|
22
|
+
def itemAt(self, i):
|
|
23
|
+
return self._items[i] if 0 <= i < len(self._items) else None
|
|
24
|
+
|
|
25
|
+
def takeAt(self, i):
|
|
26
|
+
return self._items.pop(i) if 0 <= i < len(self._items) else None
|
|
27
|
+
|
|
28
|
+
def expandingDirections(self):
|
|
29
|
+
return Qt.Orientations(Qt.Orientation(0))
|
|
30
|
+
|
|
31
|
+
def hasHeightForWidth(self):
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def heightForWidth(self, width):
|
|
35
|
+
return self._do_layout(QRect(0, 0, width, 0), True)
|
|
36
|
+
|
|
37
|
+
def setGeometry(self, rect):
|
|
38
|
+
super().setGeometry(rect)
|
|
39
|
+
self._do_layout(rect, False)
|
|
40
|
+
|
|
41
|
+
def sizeHint(self):
|
|
42
|
+
return self.minimumSize()
|
|
43
|
+
|
|
44
|
+
def minimumSize(self):
|
|
45
|
+
size = QSize()
|
|
46
|
+
for it in self._items:
|
|
47
|
+
size = size.expandedTo(it.minimumSize())
|
|
48
|
+
m = self.contentsMargins()
|
|
49
|
+
return size + QSize(m.left() + m.right(), m.top() + m.bottom())
|
|
50
|
+
|
|
51
|
+
def _do_layout(self, rect, test_only):
|
|
52
|
+
x, y, line_h = rect.x(), rect.y(), 0
|
|
53
|
+
sp = self.spacing()
|
|
54
|
+
for it in self._items:
|
|
55
|
+
hint = it.sizeHint()
|
|
56
|
+
w, h = hint.width(), hint.height()
|
|
57
|
+
if x + w > rect.right() and line_h > 0: # wrap to next row
|
|
58
|
+
x = rect.x(); y = y + line_h + sp; line_h = 0
|
|
59
|
+
if not test_only:
|
|
60
|
+
it.setGeometry(QRect(QPoint(x, y), hint))
|
|
61
|
+
x += w + sp
|
|
62
|
+
line_h = max(line_h, h)
|
|
63
|
+
return y + line_h - rect.y()
|