Open source: https://github.com/gyx114/PowerBox | MIT License
Intro
A while ago I set myself a "practice" goal: write a C++ desktop app that I actually use every day — not another dead repo.
That's how PowerBox was born: a Windows toolbox that bundles an AI assistant, process manager, screen OCR, clipboard history, Git toolbox, batch rename, and 20+ utilities. Its most distinctive feature is this: the built-in AI assistant doesn't just answer questions — it can open a terminal, run commands by itself, and paste the results back into the conversation.
This post won't be a feature list. Instead, I want to share the three parts that matter most to developers:
- How I modernized an old MFC project (WebView2 migration, modular architecture)
- How the AI assistant executes commands through a built-in ConPTY terminal
- The "encoding consistency" trap that's easiest to overlook in a multi-language project
1. Tech Stack
| Item | Choice |
|---|---|
| Language / Standard | C++20 |
| UI Framework | MFC (Microsoft Foundation Classes) |
| AI Integration | WinHTTP + 6 AI providers |
| Markdown / AI Rendering | WebView2 (replacing WebBrowser) |
| Terminal | ConPTY (Windows Pseudo Console) |
| JSON | Nlohmann json.hpp |
| QR Code | Nayuki QR Code Generator |
| Localization | External INI files (UTF-16 LE) |
| Distribution | Static-linked MFC/CRT, single exe + lang + res |
Why MFC? It's the most mature and stable C++ desktop framework on Windows, and this project is a "small but strong" monolith — no need to drag in Qt's heavyweight dependency tree.
2. Architecture: Turning a 1,000-line Dialog into Clean Modules
The most common way MFC projects die: everything gets dumped into OnInitDialog and a few callbacks, and after a few thousand lines nobody dares to touch it. My approach was two things.
1. Split the main dialog by responsibility into separate source files
MFCApplication1Dlg_Buttons.cpp // quick-launch buttons
MFCApplication1Dlg_File.cpp // file / folder management
MFCApplication1Dlg_Process.cpp // process management
MFCApplication1Dlg_Startup.cpp // startup items
MFCApplication1Dlg_Terminal.cpp // terminal
MFCApplication1Dlg_Tray.cpp // tray
MFCApplication1Dlg_Window.cpp // window tools
2. Extract reusable capabilities into independent modules
Anything "UI-agnostic" — clipboard history, volume control, auto-clicker, OCR, process enumeration — lives in a separate Manager/Engine class. The main window just composes them. Each module can be tested in isolation, and the code is ready to be split into separate tools later.
ProcessManager / ClipboardManager / VolumeManager / AutoClicker / OcrEngine / LocalizationManager
3. Three Key Implementations
3.1 AI Assistant: Replacing WebBrowser with WebView2
Early versions rendered the AI panel and Markdown preview with WebBrowser (MSHTML) — poor rendering and painful state management. I migrated everything to WebView2.
The key design is two-way communication between HTML and C++. The "Run" button on a command card calls from HTML:
<script>
function execCmd(id) {
// send the command id to C++ via WebView2 postMessage
chrome.webview.postMessage(id);
}
</script>
On the C++ side, OnWebMessageReceived receives the message and forwards it to the main dialog as a custom message:
void CMFCApplication1Dlg::OnWebMessageReceived(
const std::wstring& msg)
{
// parse message → resolve command → trigger execution
PostMessage(WM_AI_EXECUTE_COMMAND, ...);
}
The benefit: rendering (HTML/CSS) and logic (C++) are fully decoupled. Font sizes, themes, and scroll positioning can be handled via ExecuteScript on the DOM without reloading the page — which is exactly what keeps the AI conversation state intact.
3.2 AI Executing Commands: Talking to a ConPTY Terminal
PowerBox ships a built-in ConPTY terminal with multiple sessions and tab switching. The AI command execution flow is:
AI generates command → confirmation dialog (shows purpose & risk level)
→ dispatched to a new terminal tab → async output read → result pasted back
The core of ConPTY is CreatePseudoConsole + CreateProcess attached to the pseudo console, with a background thread reading the pipe. The critical rule: always use a background thread + stop token, never block the UI thread waiting for output:
// std::jthread auto-joins; stop_token enables cancellation
std::jthread worker([stop = std::stop_source{}] {
while (!stop.get_token().stop_requested()) {
// read pipe output → notify UI to update
}
});
Security gets two layers: the confirmation dialog shows the command's purpose and risk level and requires explicit user approval, and AI-generated commands are never silently executed.
3.3 Localization: The Encoding-Consistency Trap Nobody Warns About
PowerBox supports 5 languages (CN/EN/JA/KO/RU) with language packs in external lang/*.ini. This is the area most likely to blow up, because the encoding requirements are strict:
-
.rcresource files = UTF-16 LE BOM -
lang/*.inilanguage packs = UTF-16 LE BOM
If you edit a .rc file with a regular editor, it silently converts to UTF-8 and the RC compiler starts throwing weird errors like RC4093 / RC2255. The safe way is to control the encoding explicitly with PowerShell:
# .rc or .ini edits must read/write as UTF-16 LE
$content = [System.IO.File]::ReadAllText("xxx.rc", [System.Text.Encoding]::Unicode)
$content = $content.Replace("old string", "new string")
[System.IO.File]::WriteAllText("xxx.rc", $content, [System.Text.Encoding]::Unicode)
This deserves emphasis: in a multi-language project, encoding consistency causes more damage than feature bugs — and it surfaces as garbled Chinese, the hardest kind of bug to debug.
4. Distribution: Static Linking, Portable Single Exe
MFC/CRT are fully statically linked, and the release artifact is only 4 items:
PowerBox.exe // main executable
WebView2Loader.dll // WebView2 loader
lang/ // 5 language packs
res/ // Markdown rendering resources
No runtime installation needed — unzip and run, no registry writes, which fits a "toolbox you can carry anywhere" positioning. The WebView2 Runtime is pre-installed on Win10/11 in most cases, so only the loader DLL needs to ship.
5. Pitfall Checklist (Save Yourself Three Months)
Ranked by how much they cost me:
-
Encoding consistency (most expensive):
.rc/lang/*.inimust be UTF-16 LE — wrong encoding equals garbled Chinese plus RC compile errors, the hardest thing to debug -
Blocking the UI thread: OCR, translation, and terminal reads must run on background threads (
std::jthread+ stop_token); calling.get()on the UI thread freezes the whole app -
Resource ID collisions: before adding any control, check
resource.h/.rc/ the message map — duplicate IDs plant landmines in both compile time and runtime -
Non-modal dialog destruction: calling
DestroyWindow()directly accesses a freedthis; usePostMessage(WM_CLOSE)for deferred destruction -
Tool windows minimized with the main window: tool windows must use
nullptras parent so they're independent - Screenshot overlay flicker: recreating a DIB and filling the whole screen on every mouse drag causes severe flicker; pre-create a DIB and do incremental double-buffered updates
- Don't hand-roll QR codes: error-correction logic is full of edge cases; just use a proven library like Nayuki's
-
.gitignoreencoding too: a UTF-16-encoded .gitignore makes Git misparse rules (e.g.*.apsbecomes*)
6. Open Source & What's Next
PowerBox is currently v4.1.0, MIT licensed, with source and releases on GitHub:
https://github.com/gyx114/PowerBox
The README is available in 5 languages with a full feature table and screenshots. If you're working on C++/MFC projects, or you're curious about the "AI assistant + terminal" interaction pattern, come check out the repo. A star is the biggest encouragement.
Questions, bug reports, or MFC development chat — feel free to reach out via Issue or comments.












