Skip to content

feat: complete standalone GUI for mtkclient - #1

Open
sudotsu wants to merge 3 commits into
mainfrom
feat/initial-gui
Open

feat: complete standalone GUI for mtkclient#1
sudotsu wants to merge 3 commits into
mainfrom
feat/initial-gui

Conversation

@sudotsu

@sudotsu sudotsu commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • 12-tab modular PySide6 GUI: Device, Read, Write, Erase, Keys, Bootloader, Memory, RPMB, IMEI, Exploit, eFuse, Server
  • All buttons wired to mtkclient backend via MtkWrapper facade with sys.exit() interception
  • GuiSignalProxy bridges mtkclient's logsetup() logging to Qt signals
  • USB polling and partition loading run on background threads (no UI freezes)
  • Button-disable-during-operations prevents concurrent conflicting ops
  • Destructive operations gated through ConfirmationDialog
  • Dark/light theme with QSS stylesheets
  • Plugin system: drop .py files in plugins/ to extend the GUI
  • PyInstaller spec for standalone Windows executable

Architecture

mtk_gui/
├── app.py                  # QApplication entry point
├── main_window.py          # Signal wiring, 15+ operation handlers
├── backend/
│   ├── device_manager.py   # Connection state machine, threaded USB poll
│   ├── log_interceptor.py  # GuiSignalProxy for mtkclient logging
│   ├── mtk_wrapper.py      # Clean facade over mtkclient DA operations
│   └── worker.py           # QThread worker with cancel support
├── ui/tabs/                # 12 tab widgets
├── ui/widgets/             # Reusable widgets (log panel, hex viewer, etc.)
├── plugins/                # Plugin base class and loader
└── theme/                  # QSS dark/light themes

Test plan

  • python -c "from mtk_gui.main_window import MainWindow" — no import errors
  • python run.py — all 12 tabs visible, dark theme, log panel works
  • Every button has a connected handler or is disabled
  • Tab switching is instant (no main-thread USB blocking)
  • Device connect/disconnect cycle (requires MTK hardware)

Summary by CodeRabbit

  • New Features
    • Introduced MTK-GUI, a desktop application for connecting to MediaTek devices and managing device operations.
    • Added tools for reading, writing, erasing, inspecting memory, handling partitions, RPMB, eFuses, IMEI/NV data, keys, exploits, and device controls.
    • Added device detection, connection status, progress tracking, logging, search/filtering, and export capabilities.
    • Added dark and light themes, plugin support, destructive-action confirmations, and a packaged standalone executable.
    • Added configurable device settings, serial-port selection, hex viewing, dismissible warnings, and example plugin support.

12-tab modular PySide6 GUI with plugin system:
- Device, Read, Write, Erase, Keys, Bootloader, Memory, RPMB, IMEI, Exploit, eFuse, Server
- All buttons wired to mtkclient backend via MtkWrapper facade
- GuiSignalProxy bridges mtkclient logging to Qt signals
- USB polling and partition loading run off the main thread
- Button-disable-during-operations prevents concurrent conflicts
- Destructive operations gated through ConfirmationDialog
- Dark/light theme support with QSS stylesheets
- Plugin system with hot-loadable MtkPlugin subclasses
- PyInstaller spec for Windows executable build

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds the first MTK-GUI desktop application. It includes startup, packaging, an mtkclient backend, USB device management, operation tabs, shared widgets, themes, plugin loading, and an example plugin.

Changes

MTK-GUI application

Layer / File(s) Summary
Bootstrap, theme, and packaging
mtk_gui/app.py, run.py, mtk_gui/constants.py, mtk_gui/theme/*, mtk-gui.spec, README.md
Adds application startup, platform plugin paths, metadata, theme loading, dark and light QSS resources, packaging configuration, and project documentation.
Backend session and connection flow
mtk_gui/backend/*
Adds the Qt-independent MtkWrapper, DeviceManager state machine, USB polling, worker execution, cancellation, shutdown handling, and GUI logging and progress signals.
Main window, tabs, and shared widgets
mtk_gui/main_window.py, mtk_gui/ui/tabs/*, mtk_gui/ui/widgets/*
Adds the main window, connection controls, operation parameter forms, validation, confirmation dialogs, log and progress panels, partition and hex viewers, serial-port selection, and persistent dismissal banners.
Flash, security, memory, and device operations
mtk_gui/backend/mtk_wrapper.py, mtk_gui/main_window.py, mtk_gui/ui/tabs/*
Adds asynchronous workflows for flash read/write/erase, bootloader and vbmeta actions, key generation, memory access, BROM dumps, RPMB, eFuse, IMEI, NVItem, exploit, modem, key-server, and device-control operations.
Plugin API and loading
mtk_gui/plugins/*, plugins/example_plugin.py, mtk_gui/main_window.py
Adds plugin metadata and lifecycle contracts, tab and menu registration, backend and worker access, dynamic discovery and loading, and an example plugin.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MainWindow
  participant DeviceManager
  participant Worker
  participant MtkWrapper

  User->>MainWindow: Start a device operation
  MainWindow->>DeviceManager: create_worker(operation)
  DeviceManager->>Worker: start(operation)
  Worker->>MtkWrapper: execute backend method
  MtkWrapper-->>Worker: progress and operation result
  Worker-->>MainWindow: finished(result) or error
  MainWindow-->>User: update panels and operation controls
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a complete standalone GUI for mtkclient.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/initial-gui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Standalone PySide6 GUI for mtkclient (tabs, plugins, themes, packaging)

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a 12-tab PySide6 desktop GUI wired to mtkclient operations.
• Introduce threaded backend (USB polling + workers) with mtkclient log/progress bridging.
• Add plugin loading, dark/light QSS themes, and a PyInstaller spec for standalone builds.
Diagram

graph TD
  user([User]) --> main["MainWindow"] --> dm(["DeviceManager"]) --> w(["Worker QThread"]) --> wrap["MtkWrapper"] --> mtk{{"mtkclient"}}
  main --> plug["Plugin loader"]
  main --> theme["QSS themes"]

  subgraph Legend
    direction LR
    _ui["UI component"] ~~~ _svc(["Background worker/service"]) ~~~ _ext{{"External library"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Isolate mtkclient behind a subprocess (CLI/IPC backend)
  • ➕ Hard isolation from sys.exit() and other process-global side effects
  • ➕ Backend crashes won’t take down the GUI process
  • ➕ Clear UI/backend boundary for future automation
  • ➖ More complexity (IPC protocol, file passing, structured progress/log streaming)
  • ➖ Harder to reuse in-memory objects (e.g., partition models) without serialization
  • ➖ Potentially higher latency and more error surface in IPC
2. Use QThreadPool/QRunnable (task queue) instead of dedicated QThread objects
  • ➕ Centralized concurrency control and easier throttling/queuing
  • ➕ Avoids accumulating many thread instances over time
  • ➖ More boilerplate to propagate progress/cancellation consistently
  • ➖ Harder to keep per-operation lifecycle explicit without a wrapper abstraction
3. Generate UI layouts via Qt Designer (.ui) and keep controllers in code
  • ➕ Faster UI iteration for complex multi-tab layouts
  • ➕ Cleaner separation between layout and controller logic
  • ➖ Adds tooling/build steps and potential merge friction on generated artifacts
  • ➖ Less ergonomic for highly dynamic plugin-driven UI composition

Recommendation: The chosen approach (in-process MtkWrapper + Qt signals + background workers) is a pragmatic way to deliver a complete first GUI with rich progress/log integration. If library process-global behavior becomes a recurring stability problem, moving the backend to a subprocess boundary is the most meaningful architectural upgrade; otherwise, consider a QThreadPool if you later add queuing or many concurrent operations.

Files changed (41) +5069 / -0 · 1 not counted

Enhancement (30) +4285 / -0
app.pyAdd QApplication entry point with theme and plugin discovery +54/-0

Add QApplication entry point with theme and plugin discovery

• Creates the GUI bootstrap: sets organization/application names, enables HiDPI rounding policy, constructs MainWindow, applies the initial theme, loads plugins from app and user plugin directories, and starts the Qt event loop.

mtk_gui/app.py

device_manager.pyImplement connection state machine with USB polling and worker tracking +206/-0

Implement connection state machine with USB polling and worker tracking

• Adds DeviceManager with explicit device states, a background USB polling thread (pyusb), and Qt signals for state/log/progress/status. Manages connect/disconnect via Worker threads and tracks/cancels running workers during disconnect.

mtk_gui/backend/device_manager.py

log_interceptor.pyBridge mtkclient logging/progress into Qt signals +57/-0

Bridge mtkclient logging/progress into Qt signals

• Adds LogSignalEmitter plus GuiSignalProxy implementing mtkclient’s expected gui/guiprogress/update_status_text interface. Enables forwarding of log lines, progress updates, and status messages into GUI widgets.

mtk_gui/backend/log_interceptor.py

mtk_wrapper.pyAdd MtkWrapper facade over mtkclient DA and device operations +716/-0

Add MtkWrapper facade over mtkclient DA and device operations

• Introduces a large, Qt-free wrapper exposing common actions (partition listing, read/write/erase, keys, bootloader, memory, RPMB, IMEI/NVItem, exploit helpers, eFuses, key server). Intercepts sys.exit() by monkey-patching it to raise an exception for GUI-safe error handling.

mtk_gui/backend/mtk_wrapper.py

worker.pyAdd QThread Worker for off-UI-thread operations +42/-0

Add QThread Worker for off-UI-thread operations

• Implements a simple QThread-based worker that executes a callable, supports cancellation, and emits finished/error signals. Converts intercepted sys.exit aborts into a user-facing error signal.

mtk_gui/backend/worker.py

constants.pyAdd app constants (USB IDs, polling interval, themes, defaults) +27/-0

Add app constants (USB IDs, polling interval, themes, defaults)

• Centralizes app identity/version, MediaTek USB VID/PIDs, USB polling interval, partition type names, and theme identifiers used across UI and backend.

mtk_gui/constants.py

main_window.pyBuild main window UI and wire all tab actions to backend operations +852/-0

Build main window UI and wire all tab actions to backend operations

• Creates the 12-tab layout with progress and log panels, adds menus (theme toggle, plugin reload, about), and connects every actionable control to DeviceManager/MtkWrapper operations via Workers. Loads partition metadata off-thread after connect, gates destructive operations through ConfirmationDialog, and enables/disables controls based on connection state.

mtk_gui/main_window.py

base_plugin.pyDefine plugin API and PluginContext helpers +74/-0

Define plugin API and PluginContext helpers

• Adds MtkPlugin base class and PluginContext utilities to add tabs/menu items, access the backend/device info, log into the GUI, and create tracked workers. Defines device connect/disconnect hooks and a cleanup hook.

mtk_gui/plugins/base_plugin.py

loader.pyImplement plugin discovery, dynamic import, and registration +62/-0

Implement plugin discovery, dynamic import, and registration

• Scans plugin directories for .py files, dynamically imports modules, instantiates MtkPlugin subclasses, and registers them into the app via PluginContext. Errors are logged without crashing the GUI.

mtk_gui/plugins/loader.py

__init__.pyAdd QSS theme loader helper +12/-0

Add QSS theme loader helper

• Implements load_stylesheet(theme_name) to read the corresponding .qss file for runtime theme application.

mtk_gui/theme/init.py

colors.pyAdd shared color constants for widgets +34/-0

Add shared color constants for widgets

• Defines common color constants used by widgets (e.g., status LEDs) to keep styling consistent between themes.

mtk_gui/theme/colors.py

bootloader_tab.pyAdd Bootloader tab (seccfg lock/unlock, vbmeta patch) +81/-0

Add Bootloader tab (seccfg lock/unlock, vbmeta patch)

• Implements the Bootloader UI for seccfg lock/unlock (including a critical mode option) and vbmeta patch mode selection. Buttons default disabled until a device is connected.

mtk_gui/ui/tabs/bootloader_tab.py

device_tab.pyAdd Device tab (status, connection settings, device info) +218/-0

Add Device tab (status, connection settings, device info)

• Creates the landing page with a status LED, connect/disconnect controls, device info fields, and connection settings (DA loader, preloader, serial port, work dir, modes). Exposes getters and update methods used by MainWindow.

mtk_gui/ui/tabs/device_tab.py

efuse_tab.pyAdd eFuse tab (read + table display) +45/-0

Add eFuse tab (read + table display)

• Implements a table-based eFuse viewer and a read button, with a setter to populate rows from backend results.

mtk_gui/ui/tabs/efuse_tab.py

erase_tab.pyAdd Erase tab (partition/sector erase) +105/-0

Add Erase tab (partition/sector erase)

• Implements erase UI with mode switching between partition selection and sector range input, plus a prominent warning banner and destructive button styling.

mtk_gui/ui/tabs/erase_tab.py

exploit_tab.pyAdd Exploit tab (crash/brute/payload/stage/meta/control) +168/-0

Add Exploit tab (crash/brute/payload/stage/meta/control)

• Implements UI for preloader crash/brute force, payload and stage2 execution, meta mode switching, and reset/shutdown controls. Buttons are enabled/disabled by connection state and invoked by MainWindow handlers.

mtk_gui/ui/tabs/exploit_tab.py

imei_tab.pyAdd IMEI tab (read/write IMEI, NVItem encrypt/decrypt, modem patch) +159/-0

Add IMEI tab (read/write IMEI, NVItem encrypt/decrypt, modem patch)

• Adds UI for reading/writing IMEIs (with seed/AES key inputs), patching modem, and encrypting/decrypting NVItem files. Provides parameter getters and display setters consumed by MainWindow.

mtk_gui/ui/tabs/imei_tab.py

keys_tab.pyAdd Keys tab (generate + display hwparam keys) +73/-0

Add Keys tab (generate + display hwparam keys)

• Implements output directory selection, a Generate Keys action, and a table to display generated key/value pairs. MainWindow drives generation and populates the table afterward.

mtk_gui/ui/tabs/keys_tab.py

memory_tab.pyAdd Memory tab (peek/poke + hex viewer + dump actions) +168/-0

Add Memory tab (peek/poke + hex viewer + dump actions)

• Adds memory peek/poke inputs, a HexViewer display, and dump controls (BROM/SRAM/DRAM/memdump) with output directory selection. Exposes getters and enable toggles for MainWindow.

mtk_gui/ui/tabs/memory_tab.py

read_tab.pyAdd Read tab (partitions/full flash/offset/sector) +194/-0

Add Read tab (partitions/full flash/offset/sector)

• Implements read modes with a stacked UI, partition selection list, GPT dump option, and file/directory pickers. Exposes a structured parameter dict used by MainWindow to start read operations.

mtk_gui/ui/tabs/read_tab.py

rpmb_tab.pyAdd RPMB tab (read/write/erase + auth key input) +123/-0

Add RPMB tab (read/write/erase + auth key input)

• Implements RPMB read/write controls with sector ranges and file selectors, plus destructive erase and an authentication key input field. Provides helpers to enable/disable controls based on connection state.

mtk_gui/ui/tabs/rpmb_tab.py

server_tab.pyAdd Server tab (key server runner + status/log) +70/-0

Add Server tab (key server runner + status/log)

• Implements UI to run the mtkclient key exchange server, show status, and display a dedicated server log view. MainWindow coordinates execution and status updates.

mtk_gui/ui/tabs/server_tab.py

write_tab.pyAdd Write tab (partition mapping/full flash/offset/directory) +205/-0

Add Write tab (partition mapping/full flash/offset/directory)

• Implements write modes including per-partition file mapping via a tree widget, full-flash image writing, offset writing, and directory-based writes. Includes a warning banner and parameter getters used by MainWindow.

mtk_gui/ui/tabs/write_tab.py

confirmation_dialog.pyAdd typed-YES confirmation dialog for destructive operations +71/-0

Add typed-YES confirmation dialog for destructive operations

• Implements a modal confirmation dialog requiring the user to type 'YES' before confirming destructive actions. Used throughout MainWindow for write/erase/bootloader/exploit operations.

mtk_gui/ui/widgets/confirmation_dialog.py

hex_viewer.pyAdd HexViewer widget for memory/binary inspection +47/-0

Add HexViewer widget for memory/binary inspection

• Introduces a reusable hex viewer widget used by the Memory tab to render byte buffers with an address base.

mtk_gui/ui/widgets/hex_viewer.py

log_panel.pyAdd filterable log panel with search and export +116/-0

Add filterable log panel with search and export

• Implements a GUI log viewer with severity filtering, text search, color-coded output, clear, and export-to-file. Consumes DeviceManager log signals.

mtk_gui/ui/widgets/log_panel.py

partition_list.pyAdd reusable partition selection list widget +109/-0

Add reusable partition selection list widget

• Implements a checkbox-based partition list with size and sector columns plus select/deselect-all controls. Used by Read/Erase tabs and populated after connection.

mtk_gui/ui/widgets/partition_list.py

progress_panel.pyAdd progress panel with speed and ETA +119/-0

Add progress panel with speed and ETA

• Implements a progress bar plus status/speed/ETA computation from progress callbacks, used to provide responsive feedback during long operations.

mtk_gui/ui/widgets/progress_panel.py

serial_port_dialog.pyAdd serial port selection dialog widget +59/-0

Add serial port selection dialog widget

• Introduces a dialog for selecting/configuring serial ports for connection scenarios where explicit selection is needed.

mtk_gui/ui/widgets/serial_port_dialog.py

run.pyAdd top-level entry script with import precedence safeguards +19/-0

Add top-level entry script with import precedence safeguards

• Adds a runnable entry point that ensures the local mtk_gui package takes precedence in sys.path (avoiding name collisions) and optionally appends a nearby mtkclient checkout. Delegates to mtk_gui.app.main().

run.py

Refactor (7) +6 / -0
__init__.pyAdjust mtk_gui package initializer +1/-0

Adjust mtk_gui package initializer

• Small package-level tweak to keep the standalone GUI package importable and consistent across entry points.

mtk_gui/init.py

__init__.pyAdjust backend package initializer +1/-0

Adjust backend package initializer

• Small initializer update to support clean imports of backend components.

mtk_gui/backend/init.py

__init__.pyAdjust plugins package initializer +1/-0

Adjust plugins package initializer

• Small initializer update to support plugin imports and discovery.

mtk_gui/plugins/init.py

__init__.pyAdjust resources package marker not counted

Adjust resources package marker

• Small change to the resources package initializer to keep imports and bundling behavior stable.

mtk_gui/resources/init.py

__init__.pyAdjust UI package initializer +1/-0

Adjust UI package initializer

• Small initializer update to support clean imports of UI components.

mtk_gui/ui/init.py

__init__.pyAdjust tabs package initializer +1/-0

Adjust tabs package initializer

• Small initializer update to support tab imports.

mtk_gui/ui/tabs/init.py

__init__.pyAdjust widgets package initializer +1/-0

Adjust widgets package initializer

• Small initializer update to support clean imports of reusable widgets.

mtk_gui/ui/widgets/init.py

Documentation (1) +48 / -0
example_plugin.pyAdd example plugin demonstrating the plugin API +48/-0

Add example plugin demonstrating the plugin API

• Adds a reference plugin that registers a new tab, logs messages, and demonstrates reading connected device info via PluginContext. Serves as sample code for plugin authors.

plugins/example_plugin.py

Other (3) +730 / -0
mtk-gui.specAdd PyInstaller spec for standalone builds +64/-0

Add PyInstaller spec for standalone builds

• Introduces a PyInstaller spec that bundles QSS themes and the plugins directory, and collects mtkclient/usb hidden imports and data files for dynamic loading. Configures a windowed executable for distribution.

mtk-gui.spec

dark.qssAdd dark mode stylesheet +349/-0

Add dark mode stylesheet

• Adds a comprehensive dark QSS theme, including styling hooks for destructive/success button properties and general widget polish.

mtk_gui/theme/dark.qss

light.qssAdd light mode stylesheet +317/-0

Add light mode stylesheet

• Adds a comprehensive light QSS theme mirroring the dark theme styling surface for consistent widget appearance.

mtk_gui/theme/light.qss

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mtk_gui/ui/tabs/exploit_tab.py`:
- Around line 155-159: Update the stage_addr method to parse every non-empty
entered address with base 16, including values without a 0x prefix; preserve the
existing default address for empty input.

In `@mtk_gui/ui/tabs/imei_tab.py`:
- Around line 51-60: The IMEI write handlers and both seed/AES-key operation
handlers currently start workers with unchecked input. Add pre-operation
validation in _on_imei_write and the corresponding seed/AES-key handlers:
require IMEI values to contain exactly 15 digits, and require hexadecimal inputs
to be valid, even-length strings before calling bytes.fromhex() or starting a
worker. Display the existing UI validation error mechanism and return
immediately on invalid input, preserving normal worker startup for valid values.

In `@mtk_gui/ui/tabs/memory_tab.py`:
- Around line 148-153: Update get_peek_params to reject empty or invalid
hexadecimal address and length fields instead of defaulting them to zero, while
preserving the registers flag. In mtk_gui/main_window.py._on_peek, catch and
handle validation errors before starting the worker or progress UI, presenting
the existing appropriate user-facing error feedback.

In `@mtk_gui/ui/tabs/read_tab.py`:
- Around line 177-193: Update get_read_params and the read-operation caller to
validate parameters before invoking the backend: catch invalid hexadecimal
offset/length values, reject empty output filenames for file-based reads, and
reject zero or otherwise invalid lengths. Show a user-facing validation error
and stop the operation when validation fails, while preserving valid
mode-specific parameter construction.

In `@mtk_gui/ui/tabs/rpmb_tab.py`:
- Around line 51-75: Update RpmbTab’s write flow to expose a validated getter
for the write sector, sector count, and input file, then have the write handler
pass those values to wrapper.write_rpmb. Ensure validation rejects invalid or
missing parameters and preserve compatibility with the backend method’s expected
argument format.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 84c79f03-f869-4b92-95b9-be0d795126b2

📥 Commits

Reviewing files that changed from the base of the PR and between 2509004 and f869154.

📒 Files selected for processing (41)
  • mtk-gui.spec
  • mtk_gui/__init__.py
  • mtk_gui/app.py
  • mtk_gui/backend/__init__.py
  • mtk_gui/backend/device_manager.py
  • mtk_gui/backend/log_interceptor.py
  • mtk_gui/backend/mtk_wrapper.py
  • mtk_gui/backend/worker.py
  • mtk_gui/constants.py
  • mtk_gui/main_window.py
  • mtk_gui/plugins/__init__.py
  • mtk_gui/plugins/base_plugin.py
  • mtk_gui/plugins/loader.py
  • mtk_gui/resources/__init__.py
  • mtk_gui/theme/__init__.py
  • mtk_gui/theme/colors.py
  • mtk_gui/theme/dark.qss
  • mtk_gui/theme/light.qss
  • mtk_gui/ui/__init__.py
  • mtk_gui/ui/tabs/__init__.py
  • mtk_gui/ui/tabs/bootloader_tab.py
  • mtk_gui/ui/tabs/device_tab.py
  • mtk_gui/ui/tabs/efuse_tab.py
  • mtk_gui/ui/tabs/erase_tab.py
  • mtk_gui/ui/tabs/exploit_tab.py
  • mtk_gui/ui/tabs/imei_tab.py
  • mtk_gui/ui/tabs/keys_tab.py
  • mtk_gui/ui/tabs/memory_tab.py
  • mtk_gui/ui/tabs/read_tab.py
  • mtk_gui/ui/tabs/rpmb_tab.py
  • mtk_gui/ui/tabs/server_tab.py
  • mtk_gui/ui/tabs/write_tab.py
  • mtk_gui/ui/widgets/__init__.py
  • mtk_gui/ui/widgets/confirmation_dialog.py
  • mtk_gui/ui/widgets/hex_viewer.py
  • mtk_gui/ui/widgets/log_panel.py
  • mtk_gui/ui/widgets/partition_list.py
  • mtk_gui/ui/widgets/progress_panel.py
  • mtk_gui/ui/widgets/serial_port_dialog.py
  • plugins/example_plugin.py
  • run.py
👮 Files not reviewed due to content moderation or server errors (15)
  • mtk_gui/ui/widgets/confirmation_dialog.py
  • mtk_gui/ui/widgets/log_panel.py
  • mtk_gui/ui/widgets/progress_panel.py
  • mtk_gui/ui/tabs/device_tab.py
  • mtk_gui/ui/tabs/bootloader_tab.py
  • mtk_gui/ui/tabs/write_tab.py
  • mtk_gui/ui/tabs/erase_tab.py
  • mtk_gui/main_window.py
  • mtk_gui/init.py
  • mtk_gui/theme/colors.py
  • mtk_gui/backend/init.py
  • mtk_gui/backend/log_interceptor.py
  • mtk_gui/backend/worker.py
  • mtk_gui/backend/mtk_wrapper.py
  • mtk_gui/backend/device_manager.py

Comment thread mtk_gui/ui/tabs/exploit_tab.py Outdated
Comment on lines +51 to +60
w1.addWidget(QLabel("IMEI 1:"))
self._imei1_input = QLineEdit()
self._imei1_input.setPlaceholderText("15-digit IMEI")
self._imei1_input.setMaxLength(15)
w1.addWidget(self._imei1_input, 1)
w1.addWidget(QLabel("IMEI 2:"))
self._imei2_input = QLineEdit()
self._imei2_input.setPlaceholderText("15-digit IMEI (optional)")
self._imei2_input.setMaxLength(15)
w1.addWidget(self._imei2_input, 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate IMEI and hexadecimal input before an operation starts.

setMaxLength(15) accepts short and non-numeric IMEIs. _on_imei_write forwards every non-empty value to wrapper.write_imei. Both IMEI handlers also pass these raw seed and AES-key strings to bytes.fromhex(), which raises ValueError for malformed or odd-length input.

Require a 15-digit IMEI and valid even-length hexadecimal values. Show a validation error before starting the worker.

Also applies to: 147-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/ui/tabs/imei_tab.py` around lines 51 - 60, The IMEI write handlers
and both seed/AES-key operation handlers currently start workers with unchecked
input. Add pre-operation validation in _on_imei_write and the corresponding
seed/AES-key handlers: require IMEI values to contain exactly 15 digits, and
require hexadecimal inputs to be valid, even-length strings before calling
bytes.fromhex() or starting a worker. Display the existing UI validation error
mechanism and return immediately on invalid input, preserving normal worker
startup for valid values.

Comment thread mtk_gui/ui/tabs/memory_tab.py Outdated
Comment thread mtk_gui/ui/tabs/read_tab.py Outdated
Comment thread mtk_gui/ui/tabs/rpmb_tab.py
@qodo-code-review

qodo-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Cancellation never completes ✓ Resolved 🐞 Bug ☼ Reliability
Description
Worker.cancel() only flips a flag and, when cancelled, Worker.run() suppresses finished emission
without emitting any other terminal signal; DeviceManager untracks workers only on finished/error.
Cancelling workers during disconnect therefore doesn’t reliably clean up threads/UI state and
doesn’t stop in-flight backend calls.
Code

mtk_gui/backend/worker.py[R33-37]

+    def run(self):
+        try:
+            result = self._func(*self._args, worker=self, **self._kwargs)
+            if not self._cancelled:
+                self.finished.emit(result)
Evidence
Worker.run only emits finished when not cancelled; DeviceManager only untracks on finished/error and
clears its worker list after calling cancel(), while disconnect tears down wrapper state
immediately.

mtk_gui/backend/worker.py[26-42]
mtk_gui/backend/device_manager.py[175-206]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Worker.cancel()` does not stop the running callable, and if the callable returns after cancellation, `Worker.run()` emits no completion signal. `DeviceManager` only removes workers from tracking on `finished`/`error`, so cancelled workers can leak and UI cleanup hooks tied to those signals may never run.

## Issue Context
`DeviceManager.disconnect_device()` cancels all workers and immediately clears wrapper state. If any worker continues running, it may race with wrapper teardown or interfere with a later reconnect.

## Fix (prompt to give LLM/AI)
1. Add a `cancelled` (or `finished`) terminal signal that is always emitted on thread completion.
2. In `Worker.run()`, ensure **exactly one** terminal signal is emitted in all cases: success, error, or cancellation.
3. In `DeviceManager._track_worker`, also connect to `QThread.finished` (thread completion) to untrack workers regardless of result.
4. Improve disconnect behavior:
  - either prevent disconnect while an operation is running,
  - or request cancellation then `wait()` for workers to finish (bounded timeout) before clearing `self._wrapper`.
5. (Optional) Add cooperative cancellation checks in long-running wrapper operations if the underlying library supports it.

## Fix Focus Areas
- mtk_gui/backend/worker.py[19-42]
- mtk_gui/backend/device_manager.py[175-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. sys.exit patch race ✓ Resolved 🐞 Bug ☼ Reliability
Description
MtkWrapper monkey-patches sys.exit globally per operation; if two Workers run wrapper operations
concurrently, one thread can restore sys.exit while another operation still expects it patched. A
library sys.exit() during that window can terminate the entire GUI process.
Code

mtk_gui/backend/mtk_wrapper.py[R56-60]

+    def _patch_sys_exit(self):
+        """Monkey-patch sys.exit() to raise OperationAborted instead."""
+        def patched_exit(code=0):
+            raise OperationAborted(f"sys.exit({code}) intercepted")
+        sys.exit = patched_exit
Evidence
The wrapper directly assigns to sys.exit, and the GUI can create overlapping workers (notably in the
per-partition read loop), making concurrent wrapper calls realistic.

mtk_gui/backend/mtk_wrapper.py[56-63]
mtk_gui/main_window.py[293-302]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MtkWrapper` replaces `sys.exit` process-wide during each operation. With concurrent operations (the GUI can start multiple `Worker`s), restoring `sys.exit` in one thread can unpatch it for another thread mid-operation.

## Issue Context
This is a classic race because `sys.exit` is a global mutable function. The GUI currently has code paths that can start multiple workers concurrently (e.g., per-partition reads).

## Fix (prompt to give LLM/AI)
Eliminate the global monkey-patch and intercept exits locally:
1. Remove `_patch_sys_exit()` / `_restore_sys_exit()` usage.
2. Wrap each underlying library call with `except SystemExit as e:` and convert it to a controlled failure (e.g., return False/None) while emitting an appropriate error message.
3. If patching is absolutely required, protect it with a **process-wide re-entrant lock + reference counter** so `sys.exit` is only restored when the last active operation finishes.
4. Add a unit/integration test that simulates two concurrent wrapper operations and asserts the process does not exit.

## Fix Focus Areas
- mtk_gui/backend/mtk_wrapper.py[56-106]
- mtk_gui/main_window.py[293-302]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Concurrent DA ops ✓ Resolved 🐞 Bug ☼ Reliability
Description
MainWindow._on_read starts one Worker per selected partition (sharing the same MtkWrapper/DA
connection) and does not serialize operations, so multiple DA commands can run concurrently. This
can destabilize the session or produce incorrect results, and wrapper.dump_gpt is also executed on
the GUI thread in the same path.
Code

mtk_gui/main_window.py[R295-298]

+            for part_name in params["partitions"]:
+                filename = os.path.join(directory, f"{part_name}.bin")
+                w = self.device_manager.create_worker(
+                    wrapper.read_partition, part_name, filename, params["parttype"])
Evidence
The read-partition mode explicitly loops partitions and starts a new Worker for each, while also
calling dump_gpt directly; there is no global busy/serialization and the button-disable helper isn’t
used in this code path.

mtk_gui/main_window.py[274-302]
mtk_gui/main_window.py[760-767]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MainWindow._on_read()` spawns multiple `Worker` threads (one per partition) that all call into the same `MtkWrapper`/`DaHandler` instance concurrently, and also calls `wrapper.dump_gpt()` synchronously on the UI thread. This allows overlapping DA operations and UI freezes.

## Issue Context
The backend wrapper represents a single device session; DA operations are typically not thread-safe and should be strictly serialized per connected device.

## Fix (prompt to give LLM/AI)
Implement a single-operation-at-a-time model for the connected device:
1. Add a per-device operation lock/queue in `DeviceManager` (e.g., a `QMutex`/`threading.Lock` + `busy_changed` signal).
2. Make `DeviceManager.create_worker(...)` either:
  - queue operations (preferred), or
  - reject creation while busy.
3. Update all operation handlers to use a single helper that:
  - disables all conflicting UI buttons/tabs while busy,
  - re-enables them when the operation completes.
4. For multi-partition reads, run a **single worker** that loops partitions sequentially, and move `dump_gpt` into that same worker.

## Fix Focus Areas
- mtk_gui/main_window.py[274-322]
- mtk_gui/main_window.py[760-767]
- mtk_gui/backend/device_manager.py[188-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. USB poll thread leaks ✓ Resolved 🐞 Bug ☼ Reliability
Description
DeviceManager.stop_polling() sets an event but never joins the daemon polling thread; during
shutdown, the thread may still be running and can emit signals while the UI is closing. This creates
a shutdown lifecycle race and makes thread state ambiguous across exit/restart flows.
Code

mtk_gui/backend/device_manager.py[R87-89]

+    def stop_polling(self):
+        """Stop USB device polling."""
+        self._poll_stop.set()
Evidence
stop_polling only sets the event; closeEvent triggers stop_polling and then proceeds with teardown,
without waiting for the polling thread to exit.

mtk_gui/backend/device_manager.py[78-90]
mtk_gui/main_window.py[835-840]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The USB polling thread is created as a daemon and stop_polling() does not wait for it to exit. closeEvent() calls stop_polling() and proceeds to teardown, so the poll loop can still run briefly.

## Issue Context
Even with Qt queued signals, emitting during shutdown can cause spurious state changes/logging and can become brittle if object lifetimes change.

## Fix (prompt to give LLM/AI)
1. In `stop_polling()`, after setting `_poll_stop`, join the thread with a small timeout (e.g., 1–2 seconds) and then clear `_poll_thread`.
2. Ensure `start_polling()` does not reuse/overlap an existing thread.
3. Optionally add a `_shutting_down` flag to ignore `_usb_detected` callbacks after close begins.
4. Consider replacing the manual thread with a Qt timer + non-blocking detection if feasible.

## Fix Focus Areas
- mtk_gui/backend/device_manager.py[78-90]
- mtk_gui/main_window.py[835-845]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Empty default AES key ✓ Resolved 🐞 Bug ≡ Correctness
Description
MtkWrapper.nvitem_crypt passes aeskey=b"" * 32, which evaluates to an empty bytes object rather than
a 32-byte value. This means NVItem encrypt/decrypt will run with an unintended aeskey parameter
value (behavior depends on the mtkclient nvitem API).
Code

mtk_gui/backend/mtk_wrapper.py[R588-591]

+            return self.mtk.daloader.nvitem(
+                data=data, encrypt=encrypt,
+                otp=self.mtk.config.get_otp(),
+                seed=b"", aeskey=b"" * 32, display=False,
Evidence
The wrapper constructs aeskey using b"" * 32, and the main window’s NVItem actions call this
method, so the expression is exercised by the UI feature.

mtk_gui/backend/mtk_wrapper.py[579-592]
mtk_gui/main_window.py[591-617]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In `nvitem_crypt`, `aeskey=b"" * 32` is always `b""`, not 32 bytes.

## Issue Context
The GUI exposes NVItem encrypt/decrypt actions that call `wrapper.nvitem_crypt(...)`.

## Fix (prompt to give LLM/AI)
1. Decide the correct default aeskey semantics for NVItem operations.
2. If the intended default is 32 zero bytes, replace with `aeskey=b"\x00" * 32`.
3. Alternatively, require a user-supplied key and validate its length before calling `nvitem`.
4. Add a small test asserting the aeskey passed to `nvitem` is 32 bytes.

## Fix Focus Areas
- mtk_gui/backend/mtk_wrapper.py[579-596]
- mtk_gui/main_window.py[591-617]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Untrusted plugins auto-run ✓ Resolved 🐞 Bug ⛨ Security
Description
The plugin loader executes arbitrary .py modules from plugin directories via exec_module at startup,
expanding the trust boundary to any code placed in those directories. On Windows, if APPDATA is
unset, the computed user-plugin directory becomes a relative path, increasing the chance of
unintentionally loading plugins from the current working directory tree.
Code

mtk_gui/plugins/loader.py[R33-36]

+                module = importlib.util.module_from_spec(spec)
+                sys.modules[module_name] = module
+                spec.loader.exec_module(module)
+
Evidence
discover_plugins loads each .py file and executes it with exec_module; app.py adds a user plugin
directory on Windows using APPDATA with a default empty string, which yields a relative path.

mtk_gui/plugins/loader.py[17-36]
mtk_gui/app.py[13-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Plugins are discovered by scanning directories and executing every matching `.py` file using `importlib`.

## Issue Context
This behavior is expected for a plugin system, but it needs an explicit trust model. Additionally, Windows `APPDATA` fallback can produce a relative path if the env var is missing.

## Fix (prompt to give LLM/AI)
1. Reject relative plugin directories:
  - if `APPDATA` is missing, skip the user-plugin dir entirely (or use a safer default from Qt/`QStandardPaths`).
2. Add an explicit user opt-in for loading *user* plugins (e.g., setting in QSettings + a warning dialog on first enable).
3. Log clearly which plugin directories are searched and which plugin files are loaded.
4. Consider restricting to a single trusted plugin dir for the packaged build, or requiring signed/packaged plugins (if that matches project goals).

## Fix Focus Areas
- mtk_gui/plugins/loader.py[13-45]
- mtk_gui/app.py[13-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread mtk_gui/main_window.py Outdated
Comment thread mtk_gui/backend/mtk_wrapper.py Outdated
Comment thread mtk_gui/backend/worker.py
Comment thread mtk_gui/plugins/loader.py
Comment thread mtk_gui/backend/mtk_wrapper.py Outdated
Comment thread mtk_gui/backend/device_manager.py
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch feat/initial-gui (commit: 16e491ebe8cb3a3dfabd3a3324d5d1ea38ebea26)

coderabbitai Bot and others added 2 commits August 6, 2026 17:03
Docstrings generation was requested by @sudotsu.

The following files were modified:

* `mtk_gui/app.py`
* `mtk_gui/backend/device_manager.py`
* `mtk_gui/backend/log_interceptor.py`
* `mtk_gui/backend/mtk_wrapper.py`
* `mtk_gui/backend/worker.py`
* `mtk_gui/main_window.py`
* `mtk_gui/plugins/base_plugin.py`
* `mtk_gui/plugins/loader.py`
* `mtk_gui/theme/__init__.py`
* `mtk_gui/ui/tabs/bootloader_tab.py`
* `mtk_gui/ui/tabs/device_tab.py`
* `mtk_gui/ui/tabs/efuse_tab.py`
* `mtk_gui/ui/tabs/erase_tab.py`
* `mtk_gui/ui/tabs/exploit_tab.py`
* `mtk_gui/ui/tabs/imei_tab.py`
* `mtk_gui/ui/tabs/keys_tab.py`
* `mtk_gui/ui/tabs/memory_tab.py`
* `mtk_gui/ui/tabs/read_tab.py`
* `mtk_gui/ui/tabs/rpmb_tab.py`
* `mtk_gui/ui/tabs/server_tab.py`
* `mtk_gui/ui/tabs/write_tab.py`
* `mtk_gui/ui/widgets/confirmation_dialog.py`
* `mtk_gui/ui/widgets/hex_viewer.py`
* `mtk_gui/ui/widgets/log_panel.py`
* `mtk_gui/ui/widgets/partition_list.py`
* `mtk_gui/ui/widgets/progress_panel.py`
* `mtk_gui/ui/widgets/serial_port_dialog.py`
* `plugins/example_plugin.py`

These file types are not supported:
* `mtk-gui.spec`
* `mtk_gui/theme/dark.qss`
* `mtk_gui/theme/light.qss`
Backend hardening:
- Replace sys.exit monkey-patching with SystemExit catch in all wrapper methods
- Single worker for multi-partition reads (no concurrent DA operations)
- Worker emits exactly one terminal signal (finished/error/cancelled)
- USB poll thread joined on shutdown, shutdown flag prevents post-close signals
- Fix b"" * 32 → b"\x00" * 32 in nvitem_crypt/read_imei/write_imei defaults
- Plugin loader rejects relative dirs when APPDATA is missing

Input validation:
- IMEI: 15-digit check, hex seed/aeskey even-length validation
- Memory peek/poke: reject empty/invalid hex addresses
- Read tab: validate offset/length/filename before backend call
- RPMB write: require input file, validate sector params
- Exploit stage_addr: always parse as hex

UX polish:
- Dismissable warning banners (erase/write tabs) with QSettings persistence
- 40 hover tooltips across all 12 tabs
- Expanded Help menu with mtkclient docs, wiki, XDA links
- Expanded About dialog with version and feature summary
- README.md with project rationale, install, build, and plugin docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
mtk_gui/ui/tabs/rpmb_tab.py (1)

93-103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Connect the RPMB authentication control to an operation.

The key field has no button, accessor, or main-window handler. MtkWrapper.auth_rpmb is therefore unreachable from the GUI. Add an Authenticate action that validates exactly 32 bytes of hexadecimal input and runs auth_rpmb in a tracked worker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/ui/tabs/rpmb_tab.py` around lines 93 - 103, Extend the RPMB
authentication UI around _auth_key with an Authenticate action, validate that
the entered value is exactly 32 bytes (64 hexadecimal characters), and expose
the action through the tab’s existing worker-tracking flow. Add the
corresponding main-window handler to read the key and invoke
MtkWrapper.auth_rpmb via a tracked worker, reporting validation or operation
errors through the established GUI feedback path.
mtk_gui/ui/tabs/erase_tab.py (1)

116-130: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate sector erase ranges before dispatch.

EraseTab.get_erase_params() turns empty sector fields into zero and accepts negative values or zero-length ranges. MtkWrapper.erase_sectors() forwards those values to da_handler.da_ess(). Validate start as nonnegative and sector count as positive before calling erase_sectors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/ui/tabs/erase_tab.py` around lines 116 - 130, Update
EraseTab.get_erase_params() to validate sector-mode inputs before returning
them: require a nonnegative starting sector and a strictly positive sector
count, rejecting empty, negative, or zero-length ranges. Ensure invalid values
cannot reach MtkWrapper.erase_sectors() or da_handler.da_ess(), while preserving
partition-mode behavior.
mtk_gui/plugins/base_plugin.py (1)

33-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Gate returned metadata on connected state.

DeviceInfo has no __bool__, so if dm.device_info is always true even for the empty pre-connection instance. Do not return populated fields when the backend state is not DeviceState.CONNECTED; this preserves the documented contract to return {} when device information is unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/plugins/base_plugin.py` around lines 33 - 52, Update get_device_info
to require the backend state to be DeviceState.CONNECTED before reading and
returning fields from dm.device_info; otherwise return {}. Preserve the existing
metadata mapping for connected devices and use the existing DeviceState symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mtk_gui/backend/mtk_wrapper.py`:
- Around line 183-187: Contain partition output paths in the loop that builds
filenames before calling da_read: resolve both the selected directory and the
candidate path, then reject any candidate not contained within the resolved
output directory. Preserve valid partition filenames and only invoke da_read for
validated paths.

In `@mtk_gui/backend/worker.py`:
- Around line 47-54: Make cancellation stop work before reporting it: in
mtk_gui/backend/worker.py lines 47-54, use a thread-safe cancellation primitive
and check it before invoking the worker function, while preserving the existing
cancellation signal behavior; in mtk_gui/backend/mtk_wrapper.py lines 177-190,
update read_selected_partitions to return immediately when worker.is_cancelled
is set, checking both before GPT dumping and before each partition read.

In `@mtk_gui/plugins/base_plugin.py`:
- Around line 20-27: The add_menu_item documentation in add_menu_item uses an
incorrect root-prefixed example, causing callers to target a nested Plugins
menu. Update the documented example to be relative to
MainWindow.add_plugin_menu_item’s existing Plugins root, and add a test covering
the documented menu path.
- Around line 16-18: Update PluginContext.add_tab and MainWindow.add_plugin_tab
to accept and forward the optional icon; when an icon is provided, pass it to
the underlying tab widget’s addTab call, while preserving the existing behavior
when no icon is supplied.

In `@mtk_gui/ui/tabs/read_tab.py`:
- Around line 204-224: Update the mode-specific input validation around the
offset and sector-start parsing to reject values below zero before assigning
them to params. In the mode handling logic, validate the parsed offset and start
sector alongside the existing length and sector-count checks, raising the
established ValueError pattern for negative values while preserving valid zero
and positive inputs.

In `@mtk_gui/ui/tabs/rpmb_tab.py`:
- Around line 155-165: Update get_write_params to validate the parsed sector and
sectors values before returning them: reject negative sector values and sector
counts that are zero or negative by raising ValueError, while preserving the
existing defaults for omitted fields.

In `@README.md`:
- Around line 55-70: Update the fenced directory-tree block in the README around
the mtk_gui structure to specify the text language identifier, changing the
opening fence to use text while leaving the tree content unchanged.

---

Outside diff comments:
In `@mtk_gui/plugins/base_plugin.py`:
- Around line 33-52: Update get_device_info to require the backend state to be
DeviceState.CONNECTED before reading and returning fields from dm.device_info;
otherwise return {}. Preserve the existing metadata mapping for connected
devices and use the existing DeviceState symbol.

In `@mtk_gui/ui/tabs/erase_tab.py`:
- Around line 116-130: Update EraseTab.get_erase_params() to validate
sector-mode inputs before returning them: require a nonnegative starting sector
and a strictly positive sector count, rejecting empty, negative, or zero-length
ranges. Ensure invalid values cannot reach MtkWrapper.erase_sectors() or
da_handler.da_ess(), while preserving partition-mode behavior.

In `@mtk_gui/ui/tabs/rpmb_tab.py`:
- Around line 93-103: Extend the RPMB authentication UI around _auth_key with an
Authenticate action, validate that the entered value is exactly 32 bytes (64
hexadecimal characters), and expose the action through the tab’s existing
worker-tracking flow. Add the corresponding main-window handler to read the key
and invoke MtkWrapper.auth_rpmb via a tracked worker, reporting validation or
operation errors through the established GUI feedback path.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 118ef602-016d-42b7-baf0-3af6c6b131ed

📥 Commits

Reviewing files that changed from the base of the PR and between f869154 and de39ee1.

📒 Files selected for processing (30)
  • README.md
  • mtk_gui/app.py
  • mtk_gui/backend/device_manager.py
  • mtk_gui/backend/log_interceptor.py
  • mtk_gui/backend/mtk_wrapper.py
  • mtk_gui/backend/worker.py
  • mtk_gui/main_window.py
  • mtk_gui/plugins/base_plugin.py
  • mtk_gui/plugins/loader.py
  • mtk_gui/theme/__init__.py
  • mtk_gui/ui/tabs/bootloader_tab.py
  • mtk_gui/ui/tabs/device_tab.py
  • mtk_gui/ui/tabs/efuse_tab.py
  • mtk_gui/ui/tabs/erase_tab.py
  • mtk_gui/ui/tabs/exploit_tab.py
  • mtk_gui/ui/tabs/imei_tab.py
  • mtk_gui/ui/tabs/keys_tab.py
  • mtk_gui/ui/tabs/memory_tab.py
  • mtk_gui/ui/tabs/read_tab.py
  • mtk_gui/ui/tabs/rpmb_tab.py
  • mtk_gui/ui/tabs/server_tab.py
  • mtk_gui/ui/tabs/write_tab.py
  • mtk_gui/ui/widgets/confirmation_dialog.py
  • mtk_gui/ui/widgets/dismissable_banner.py
  • mtk_gui/ui/widgets/hex_viewer.py
  • mtk_gui/ui/widgets/log_panel.py
  • mtk_gui/ui/widgets/partition_list.py
  • mtk_gui/ui/widgets/progress_panel.py
  • mtk_gui/ui/widgets/serial_port_dialog.py
  • plugins/example_plugin.py
🚧 Files skipped from review as they are similar to previous changes (22)
  • mtk_gui/theme/init.py
  • plugins/example_plugin.py
  • mtk_gui/plugins/loader.py
  • mtk_gui/backend/log_interceptor.py
  • mtk_gui/ui/widgets/hex_viewer.py
  • mtk_gui/app.py
  • mtk_gui/ui/tabs/server_tab.py
  • mtk_gui/ui/widgets/serial_port_dialog.py
  • mtk_gui/ui/widgets/confirmation_dialog.py
  • mtk_gui/ui/widgets/progress_panel.py
  • mtk_gui/ui/widgets/partition_list.py
  • mtk_gui/ui/tabs/memory_tab.py
  • mtk_gui/ui/tabs/bootloader_tab.py
  • mtk_gui/ui/tabs/efuse_tab.py
  • mtk_gui/ui/tabs/write_tab.py
  • mtk_gui/backend/device_manager.py
  • mtk_gui/ui/tabs/device_tab.py
  • mtk_gui/ui/tabs/imei_tab.py
  • mtk_gui/ui/tabs/keys_tab.py
  • mtk_gui/ui/tabs/exploit_tab.py
  • mtk_gui/ui/widgets/log_panel.py
  • mtk_gui/main_window.py

Comment on lines +183 to +187
for name in partitions:
filename = os.path.join(directory, f"{name}.bin")
try:
results[name] = bool(self.da_handler.da_read(
name, parttype, filename, display=True))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Contain partition output paths.

At Line 184, name originates from device GPT metadata. A partition name such as ../../target or /target escapes directory through os.path.join. A malicious connected device can make da_read write outside the user-selected directory.

Resolve the output path and reject paths outside the resolved output directory.

Proposed fix
+from pathlib import Path
+
+        output_dir = Path(directory).resolve()
         for name in partitions:
-            filename = os.path.join(directory, f"{name}.bin")
+            filename = (output_dir / f"{name}.bin").resolve()
+            try:
+                filename.relative_to(output_dir)
+            except ValueError:
+                results[name] = False
+                continue
             try:
                 results[name] = bool(self.da_handler.da_read(
-                    name, parttype, filename, display=True))
+                    name, parttype, str(filename), display=True))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for name in partitions:
filename = os.path.join(directory, f"{name}.bin")
try:
results[name] = bool(self.da_handler.da_read(
name, parttype, filename, display=True))
from pathlib import Path
output_dir = Path(directory).resolve()
for name in partitions:
filename = (output_dir / f"{name}.bin").resolve()
try:
filename.relative_to(output_dir)
except ValueError:
results[name] = False
continue
try:
results[name] = bool(self.da_handler.da_read(
name, parttype, str(filename), display=True))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/backend/mtk_wrapper.py` around lines 183 - 187, Contain partition
output paths in the loop that builds filenames before calling da_read: resolve
both the selected directory and the candidate path, then reject any candidate
not contained within the resolved output directory. Preserve valid partition
filenames and only invoke da_read for validated paths.

Comment thread mtk_gui/backend/worker.py
Comment on lines +47 to +54
def run(self):
"""Execute the worker function and emit its result or an error message."""
try:
result = self._func(*self._args, worker=self, **self._kwargs)
if self._cancelled:
self.cancelled.emit()
else:
self.finished.emit(result)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make cancellation stop work before reporting cancellation.

Worker.cancel() only changes the terminal signal after _func returns. read_selected_partitions then continues to start each remaining partition read. A cancelled operation can therefore continue device I/O while the UI reports cancellation.

  • mtk_gui/backend/worker.py#L47-L54: Check cancellation before invoking _func. Use a thread-safe cancellation primitive.
  • mtk_gui/backend/mtk_wrapper.py#L177-L190: Check worker.is_cancelled before GPT dumping and before each partition read. Return immediately when cancellation is requested.
📍 Affects 2 files
  • mtk_gui/backend/worker.py#L47-L54 (this comment)
  • mtk_gui/backend/mtk_wrapper.py#L177-L190
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/backend/worker.py` around lines 47 - 54, Make cancellation stop work
before reporting it: in mtk_gui/backend/worker.py lines 47-54, use a thread-safe
cancellation primitive and check it before invoking the worker function, while
preserving the existing cancellation signal behavior; in
mtk_gui/backend/mtk_wrapper.py lines 177-190, update read_selected_partitions to
return immediately when worker.is_cancelled is set, checking both before GPT
dumping and before each partition read.

Comment on lines +16 to +18
def add_tab(self, widget, title: str, icon=None):
"""Add a tab to the main window's tab widget."""
self._app.main_window.add_plugin_tab(widget, title)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 'def add_plugin_tab|add_plugin_tab\(|addTab\(' \
  mtk_gui/main_window.py mtk_gui/plugins/base_plugin.py

Repository: sudotsu/mtk_gui

Length of output: 3078


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant BasePlugin and MainWindow snippets:\n'
sed -n '1,40p' mtk_gui/plugins/base_plugin.py
printf '\n'
sed -n '850,862p' mtk_gui/main_window.py

printf '\nAll add_tab / add_plugin_tab / add_menu_item usages:\n'
rg -n 'add_tab\(|add_plugin_tab\(|add_menu_item\(' mtk_gui || true

printf '\nQt documentation availability in installed PySide6 (if present):\n'
python3 - <<'PY'
try:
    from PySide6 import QtWidgets
    import inspect
    method = QtWidgets.QTabWidget.addTab
    for name, method in inspect.getmembers(QtWidgets.QTabWidget):
        if name == "addTab":
            print(inspect.signature(method))
            print(inspect.getdoc(method))
except Exception as exc:
    print(f"PySide6 unavailable or introspection failed: {type(exc).__name__}: {exc}")
PY

Repository: sudotsu/mtk_gui

Length of output: 2544


🌐 Web query:

PySide6 QTabWidget addTab default overload append addTab widget icon label

💡 Result:

In PySide6, the QTabWidget.addTab method is overloaded to provide two primary ways to add a tab [1][2]. Both variants automatically append the new tab to the end of the tab list [3]. The available method signatures are: 1. addTab(widget, label): Adds a tab with the specified page widget and text label [1][2]. 2. addTab(widget, icon, label): Adds a tab with the specified page widget, icon, and text label [1][2]. In both cases, the widget passed to the method becomes owned by the QTabWidget [1][2]. If you need to insert a tab at a specific position rather than appending it to the end, you should use the insertTab method, which similarly supports both (widget, label) and (widget, icon, label) signatures [1][2].

Citations:


Forward the optional tab icon.

PluginContext.add_tab accepts an icon, but MainWindow.add_plugin_tab calls self._tabs.addTab(widget, title) and discards it. Add an icon parameter to MainWindow.add_plugin_tab, and pass it through when present to preserve plugin tab icons.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/plugins/base_plugin.py` around lines 16 - 18, Update
PluginContext.add_tab and MainWindow.add_plugin_tab to accept and forward the
optional icon; when an icon is provided, pass it to the underlying tab widget’s
addTab call, while preserving the existing behavior when no icon is supplied.

Comment on lines +20 to +27
def add_menu_item(self, menu_path: str, action):
"""Adds an action to the specified plugin menu path.

Parameters:
menu_path (str): Menu path for the action, such as ``"Plugins/My Action"``.
action: Action to add to the menu.
"""
self._app.main_window.add_plugin_menu_item(menu_path, action)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the documented menu path with the existing menu root.

At Line 24, the example uses "Plugins/My Action". MainWindow.add_plugin_menu_item already starts at the Plugins menu. The documented value therefore creates Plugins > Plugins > My Action. Document paths relative to the existing menu, or strip the leading Plugins component in the helper. Add a test for the documented example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/plugins/base_plugin.py` around lines 20 - 27, The add_menu_item
documentation in add_menu_item uses an incorrect root-prefixed example, causing
callers to target a nested Plugins menu. Update the documented example to be
relative to MainWindow.add_plugin_menu_item’s existing Plugins root, and add a
test covering the documented menu path.

Comment on lines +204 to +224
length = int(len_text, 16)
if length <= 0:
raise ValueError("Length must be greater than zero.")
params["offset"] = int(offset_text, 16)
params["length"] = length
params["filename"] = filename
elif mode == 3:
start_text = self._sector_start_input.text().strip()
count_text = self._sector_count_input.text().strip()
filename = self._sector_file_input.text().strip()
if not start_text:
raise ValueError("Start sector is required.")
if not count_text:
raise ValueError("Sector count is required.")
if not filename:
raise ValueError("Output filename is required.")
sectors = int(count_text)
if sectors <= 0:
raise ValueError("Sector count must be greater than zero.")
params["start"] = int(start_text)
params["sectors"] = sectors

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject negative read positions.

Lines 204 and 223 accept negative offsets and start sectors. These values reach the backend read operations. Reject values below zero before constructing params.

Proposed fix
             length = int(len_text, 16)
             if length <= 0:
                 raise ValueError("Length must be greater than zero.")
-            params["offset"] = int(offset_text, 16)
+            offset = int(offset_text, 16)
+            if offset < 0:
+                raise ValueError("Offset cannot be negative.")
+            params["offset"] = offset
             params["length"] = length
             params["filename"] = filename
@@
-            params["start"] = int(start_text)
+            start = int(start_text)
+            if start < 0:
+                raise ValueError("Start sector cannot be negative.")
+            params["start"] = start
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
length = int(len_text, 16)
if length <= 0:
raise ValueError("Length must be greater than zero.")
params["offset"] = int(offset_text, 16)
params["length"] = length
params["filename"] = filename
elif mode == 3:
start_text = self._sector_start_input.text().strip()
count_text = self._sector_count_input.text().strip()
filename = self._sector_file_input.text().strip()
if not start_text:
raise ValueError("Start sector is required.")
if not count_text:
raise ValueError("Sector count is required.")
if not filename:
raise ValueError("Output filename is required.")
sectors = int(count_text)
if sectors <= 0:
raise ValueError("Sector count must be greater than zero.")
params["start"] = int(start_text)
params["sectors"] = sectors
length = int(len_text, 16)
if length <= 0:
raise ValueError("Length must be greater than zero.")
offset = int(offset_text, 16)
if offset < 0:
raise ValueError("Offset cannot be negative.")
params["offset"] = offset
params["length"] = length
params["filename"] = filename
elif mode == 3:
start_text = self._sector_start_input.text().strip()
count_text = self._sector_count_input.text().strip()
filename = self._sector_file_input.text().strip()
if not start_text:
raise ValueError("Start sector is required.")
if not count_text:
raise ValueError("Sector count is required.")
if not filename:
raise ValueError("Output filename is required.")
sectors = int(count_text)
if sectors <= 0:
raise ValueError("Sector count must be greater than zero.")
start = int(start_text)
if start < 0:
raise ValueError("Start sector cannot be negative.")
params["start"] = start
params["sectors"] = sectors
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/ui/tabs/read_tab.py` around lines 204 - 224, Update the mode-specific
input validation around the offset and sector-start parsing to reject values
below zero before assigning them to params. In the mode handling logic, validate
the parsed offset and start sector alongside the existing length and
sector-count checks, raising the established ValueError pattern for negative
values while preserving valid zero and positive inputs.

Comment on lines +155 to +165
def get_write_params(self) -> dict:
"""Returns write params. Raises ValueError on invalid input."""
filename = self._write_file.text().strip()
if not filename:
raise ValueError("Input file is required for RPMB write.")
sector_text = self._write_sector.text().strip()
sectors_text = self._write_sectors.text().strip()
return {
"filename": filename,
"sector": int(sector_text) if sector_text else 0,
"sectors": int(sectors_text) if sectors_text else None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject invalid RPMB write ranges.

Negative sector values and nonpositive sector counts pass to write_rpmb. Reject them before the destructive operation starts.

Proposed fix
         sector_text = self._write_sector.text().strip()
         sectors_text = self._write_sectors.text().strip()
+        sector = int(sector_text) if sector_text else 0
+        sectors = int(sectors_text) if sectors_text else None
+        if sector < 0:
+            raise ValueError("Sector cannot be negative.")
+        if sectors is not None and sectors <= 0:
+            raise ValueError("Sector count must be greater than zero.")
         return {
             "filename": filename,
-            "sector": int(sector_text) if sector_text else 0,
-            "sectors": int(sectors_text) if sectors_text else None,
+            "sector": sector,
+            "sectors": sectors,
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_write_params(self) -> dict:
"""Returns write params. Raises ValueError on invalid input."""
filename = self._write_file.text().strip()
if not filename:
raise ValueError("Input file is required for RPMB write.")
sector_text = self._write_sector.text().strip()
sectors_text = self._write_sectors.text().strip()
return {
"filename": filename,
"sector": int(sector_text) if sector_text else 0,
"sectors": int(sectors_text) if sectors_text else None,
def get_write_params(self) -> dict:
"""Returns write params. Raises ValueError on invalid input."""
filename = self._write_file.text().strip()
if not filename:
raise ValueError("Input file is required for RPMB write.")
sector_text = self._write_sector.text().strip()
sectors_text = self._write_sectors.text().strip()
sector = int(sector_text) if sector_text else 0
sectors = int(sectors_text) if sectors_text else None
if sector < 0:
raise ValueError("Sector cannot be negative.")
if sectors is not None and sectors <= 0:
raise ValueError("Sector count must be greater than zero.")
return {
"filename": filename,
"sector": sector,
"sectors": sectors,
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mtk_gui/ui/tabs/rpmb_tab.py` around lines 155 - 165, Update get_write_params
to validate the parsed sector and sectors values before returning them: reject
negative sector values and sector counts that are zero or negative by raising
ValueError, while preserving the existing defaults for omitted fields.

Comment thread README.md
Comment on lines +55 to +70
```
mtk_gui/
├── app.py # QApplication entry point
├── main_window.py # Signal wiring, operation handlers
├── constants.py # App metadata, USB IDs, part types
├── backend/
│ ├── device_manager.py # Connection state machine, threaded USB poll
│ ├── log_interceptor.py # GuiSignalProxy for mtkclient logging bridge
│ ├── mtk_wrapper.py # Clean facade over mtkclient DA operations
│ └── worker.py # QThread worker with cancel support
├── ui/
│ ├── tabs/ # 12 tab widgets (one file each)
│ └── widgets/ # Reusable widgets (log panel, hex viewer, etc.)
├── plugins/ # Plugin base class and loader
└── theme/ # QSS dark/light stylesheets
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify a language for the fenced block.

Add a language identifier to satisfy Markdown rule MD040. Use text because this block contains a directory tree and comments.

Proposed fix
-```
+```text
 mtk_gui/
 ...
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
mtk_gui/
├── app.py # QApplication entry point
├── main_window.py # Signal wiring, operation handlers
├── constants.py # App metadata, USB IDs, part types
├── backend/
│ ├── device_manager.py # Connection state machine, threaded USB poll
│ ├── log_interceptor.py # GuiSignalProxy for mtkclient logging bridge
│ ├── mtk_wrapper.py # Clean facade over mtkclient DA operations
│ └── worker.py # QThread worker with cancel support
├── ui/
│ ├── tabs/ # 12 tab widgets (one file each)
│ └── widgets/ # Reusable widgets (log panel, hex viewer, etc.)
├── plugins/ # Plugin base class and loader
└── theme/ # QSS dark/light stylesheets
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 55-55: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 55 - 70, Update the fenced directory-tree block in
the README around the mtk_gui structure to specify the text language identifier,
changing the opening fence to use text while leaving the tree content unchanged.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant