Repository navigation
Architecture
A technical overview of the GoodByeDPI GUI codebase β for contributors and anyone curious about how it's built.
The codebase follows three simple rules:
| Principle | Why |
|---|---|
| UI is pure | Widgets only emit signals. They never perform business logic. |
| Core is testable | No direct dependency on widgets. Logic can be unit-tested in isolation. |
| Controller wires everything | A single object connects UI signals to business logic to side-effects. |
This separation means you can redesign the UI without touching business logic β and vice versa.
GoodByeDPI-GUI/
βββ main.py β entry point
βββ requirements.txt
βββ README.md
βββ LICENSE
βββ .gitignore
β
βββ assets/ β visual resources
β βββ logo.ico
β βββ Screenshot.png
β
βββ bin/ β runtime binaries & drivers
β βββ goodbyedpi.exe
β βββ WinDivert.dll
β βββ WinDivert64.sys
β
βββ app/ β application package
βββ __init__.py
βββ constants.py β all app constants & mode presets
βββ settings.py β QSettings wrapper
βββ utils.py β helpers (paths, admin check, versions)
βββ validator.py β custom-arguments validator
β
βββ core/ β business logic
β βββ __init__.py
β βββ controller.py β wires UI, tray, settings, updater
β βββ updater.py β GitHub release checker (QThread)
β
βββ ui/ β presentation layer
βββ __init__.py
βββ main_window.py β MainWindow (signals only)
βββ dialogs.py β UpdateDialog, AboutDialog, Args Guide
βββ widgets.py β StatusDot, StatusPill, UptimeChip,
β ModeCard, ModeCardGroup,
β Toast, ToastManager
βββ theme.py β Catppuccin-inspired QSS
βββ tray.py β TrayManager (system tray)
- Creates
QApplication - Checks admin privileges β refuses to start if not elevated
- Instantiates
AppControllerand shows the window - Runs the Qt event loop
- App name, version, author
- GitHub repository info & release URL builder
- Mode presets as
NamedTuple(key, title, badge, icon, description, args, tooltip) - Bundled resource paths (
DPI_EXE_REL,LOGO_ICO_REL)
- Thin wrapper around
QSettings - Properties:
mode_index,custom_args,geometry - Uses Windows registry under the hood
-
resource_path()β resolves bundled paths for both dev & PyInstaller -
is_admin()β checks viactypes.windll.shell32.IsUserAnAdmin -
executable_path()β returns EXE path ormain.pyin dev -
parse_version(),is_newer_version()
- Pure function
validate_args(args_str) - Returns
ValidationResultwitherrors&warnings - No Qt imports β fully testable standalone
- Knows every GoodbyeDPI flag and cross-flag rules
The heart of the app. Wires everything together:
- Owns
MainWindow,TrayManager,UpdateChecker - Manages
QProcessfor launchinggoodbyedpi.exe - Persists state to
AppSettings - Handles autostart via Windows Task Scheduler
- Displays dialogs (About, Update, Custom Args Help)
Two classes:
-
_UpdateWorker(QThread)β scrapes the/releases/latestredirect (GitHub API is avoided because it's blocked in some regions) -
UpdateChecker(QObject)β public API with three signals:update_available(str),up_to_date(),error()
π‘ Why scrape instead of using the API? GitHub's
api.github.comis blocked in certain countries. Following the redirect of/releases/latestgives us the version number using a domain that's more likely to be reachable.
- Pure presentation β only emits signals
- Signals:
start_clicked,stop_clicked,mode_changed(int),custom_args_changed(str), etc. - Contains zero business logic β the controller decides what happens
- Manages the
ToastManageroverlay - Handles smooth window resize when switching modes
- Applies Windows dark title bar via
DwmSetWindowAttribute
Custom, reusable widgets:
| Widget | Purpose |
|---|---|
StatusDot |
Animated colored dot (pulses when running) |
StatusPill |
Pill combining dot + label |
UptimeChip |
Live HH:MM:SS session timer |
ModeCard |
Single selectable mode card |
ModeCardGroup |
Radio-style group of ModeCards |
Toast |
Self-dismissing notification card |
ToastManager |
Stacks & positions toasts |
-
UpdateDialogβ shows current vs. latest version cards -
AboutDialogβ app info, powered-by links, credits -
CustomArgsHelpDialogβ tabbed flag guide with live search
- Single global
DARK_QSSstring - Catppuccin-inspired palette
- Applied to
MainWindow; child widgets inherit
- Owns
QSystemTrayIconand its menu - Signals:
start_requested,stop_requested,exit_requested,restore_requested - Updates icon tooltip and menu state based on running status
Here's how a user click travels through the app:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User clicks "βΆ Start" β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MainWindow.start_clicked (Signal) β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AppController.start() (Slot) β
β β
β 1. Reads current mode from window β
β 2. Validates custom args (if Custom mode) β
β 3. Checks DPI_EXE exists β
β 4. Creates QProcess β
β 5. Emits updates β window.set_running_state(True) β
β 6. Emits updates β tray.set_running(True) β
β 7. Appends log separator β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe UI never decides what to do β it just says "the user clicked Start" and the controller figures out the rest.
Controller.start()
β
βΌ
QProcess.start(goodbyedpi.exe, args)
β
ββββ readyReadStandardOutput ββββ window.append_log(text)
β
ββββ finished(exit_code) ββββββββ _on_process_finished()
β β
β βββ window.set_running_state(False)
β βββ tray.set_running(False)
β βββ process.deleteLater()
β
ββββ errorOccurred(error) βββββββ _on_process_error()
β
βββ window.append_log("[error] ...")
Controller.stop()
β
βΌ
QProcess.kill() + waitForFinished(2000)
β
βΌ
finished signal fires β same path as natural exitSay you want to add a "Restart" button next to Start/Stop. Here's the workflow:
In app/ui/main_window.py:
- Add
restart_clicked = Signal() - Create the button, add to layout
- Wire:
self.restart_btn.clicked.connect(self.restart_clicked.emit)
In app/core/controller.py:
- Connect:
self.window.restart_clicked.connect(self.restart) - Implement
restart(self)slot: callself.stop()thenself.start()
In app/ui/theme.py:
- Add QSS for
QPushButton#restartBtn
No UI code needs to know how restart works. No core code needs to know the button exists.
| Layer | How to Test |
|---|---|
validator.py |
Unit tests β pure function, no Qt needed |
utils.py |
Unit tests β version parsing, path resolution |
core/ |
Integration β mock the UI, verify signal wiring |
ui/ |
Manual β run python main.py and interact |
Because the UI and core are decoupled, you can test business logic without ever showing a window.
The entire visual style is one QSS string in app/ui/theme.py:
DARK_QSS = """
QMainWindow { background: ...; }
QPushButton#startBtn { background-color: #a6e3a1; ... }
QFrame#modeCard[selected="true"] { border: 1px solid #89b4fa; ... }
..."""
# Applied once at window construction:
self.setStyleSheet(DARK_QSS)Dynamic states (like a selected mode card) use Qt properties:
# In the widget:
self.setProperty("selected", True)
self.style().unpolish(self)
self.style().polish(self)QSS then matches via [selected="true"] selector.
utils.resource_path() handles both development and PyInstaller builds:
def resource_path(relative_path: str) -> str:
if getattr(sys, "frozen", False):
base_path = sys._MEIPASS # PyInstaller temp dir
else:
base_path = project_root # parent of app/
return os.path.join(base_path, relative_path)
This means resource_path("bin/goodbyedpi.exe") works
identically whether you're running from source or from a bundled EXE.
Three layers enforce admin:
-
main.pyβ exits with error dialog if not admin -
PyInstaller
--uac-adminβ triggers UAC on launch -
Task Scheduler β created with
/rl highestfor autostart
| I want to... | Look at |
|---|---|
| Change the visual style | app/ui/theme.py |
| Add a button |
app/ui/main_window.py + app/core/controller.py
|
| Add a GoodbyeDPI flag |
app/validator.py + app/ui/dialogs.py
|
| Tweak update behavior | app/core/updater.py |
| Add a setting | app/settings.py |
| Add a mode preset |
app/constants.py (append to MODE_PRESETS) |
- Fork the repo
- Create a feature branch:
git checkout -b feature/amazing-feature - Follow the UI β Core separation β no business logic in widgets
- Comment non-obvious code
- Test manually with
python main.py - Commit:
git commit -m "feat: add amazing feature" - Push and open a Pull Request
π‘ Prefer Conventional Commits:
feat:,fix:,refactor:,docs:,style:.
Ready to build? See Build from Source π
Happy hacking π§βπ»
Everything you need to know about bypassing DPI on Windows β in one place.
This wiki is the complete documentation for GoodByeDPI GUI β a clean, powerful graphical interface for GoodbyeDPI.
If you're new here, start with Getting Started. If you're looking for something specific, use the navigation below.
|
|
GoodByeDPI GUI wraps the powerful GoodbyeDPI command-line tool in a modern, easy-to-use desktop application for Windows.
It helps you bypass Deep Packet Inspection (DPI) β the technology some ISPs use to block or throttle websites.
π‘ No terminal. No config files. Just click and browse.
| Feature | What it does | |
|---|---|---|
| π | Mode Cards | Pick Fast, Compatible, or Custom visually |
| β‘ | One-Click Start/Stop | Toggle DPI circumvention instantly |
| π | Toast Notifications | Friendly, non-intrusive feedback |
| π | Arguments Guide | Built-in reference for every GoodbyeDPI flag |
| β | Live Validator | Catches typos before you start |
| π | System Tray | Runs quietly in the background |
| π¨ | Modern Dark UI | Catppuccin-inspired design |
| π | Auto Update Checker | Knows when a new version is out |
- π¦ Download the latest release
- π Report a bug
- π¬ Ask a question
- β Star the project
π Documentation
π©Ί Support
π Development
π Links