From 360c1eae36290ca10ab451b84e4813fd48b2556f Mon Sep 17 00:00:00 2001 From: KaiUR Date: Tue, 2 Jun 2026 23:06:12 +0100 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20resolve=20CodeQL=20alerts=20?= =?UTF-8?q?=E2=80=94=20tautological=20comparison,=20variable=20shadowing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - meta.c: remove redundant cur < DESC_MAX (always true after early return) - settings.c: rename local msg[] to warn[]/dup_msg[] to stop shadowing msg param - sources.c: rename local msg[] to confirm[] to stop shadowing msg param Co-Authored-By: Claude Sonnet 4.6 --- src/meta.c | 2 +- src/settings.c | 12 ++++++------ src/sources.c | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/meta.c b/src/meta.c index f1e046b..3059ae9 100644 --- a/src/meta.c +++ b/src/meta.c @@ -79,7 +79,7 @@ static void AppendDesc(WCHAR *buf, const WCHAR *text) int cur = (int)wcslen(buf); if (cur >= DESC_MAX) return; /* Add a space separator if buffer not empty */ - if (cur > 0 && cur < DESC_MAX) { buf[cur++]=L' '; buf[cur]=L'\0'; } + if (cur > 0) { buf[cur++]=L' '; buf[cur]=L'\0'; } /* cur < DESC_MAX guaranteed by guard above */ wcsncat_s(buf, DESC_MAX + 1, text, _TRUNCATE); } diff --git a/src/settings.c b/src/settings.c index 15f32d0..e11d27e 100644 --- a/src/settings.c +++ b/src/settings.c @@ -988,13 +988,13 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) if (GetFileAttributes(venv_python) != INVALID_FILE_ATTRIBUTES) { SetDlgItemText(hwnd, IDC_EDIT_PYTHON, venv_python); } else { - WCHAR msg[MAX_APPPATH + 128]; - _snwprintf_s(msg, MAX_APPPATH + 127, _TRUNCATE, + WCHAR warn[MAX_APPPATH + 128]; + _snwprintf_s(warn, MAX_APPPATH + 127, _TRUNCATE, L"No Python executable found at:\n%s\n\n" L"Make sure you selected the root folder of a valid " L"virtual environment (it must contain Scripts\\python.exe).", venv_python); - MessageBox(hwnd, msg, L"Browse Virtual Environment", + MessageBox(hwnd, warn, L"Browse Virtual Environment", MB_ICONWARNING | MB_OK); } break; @@ -1245,14 +1245,14 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) if (dup >= 0) { /* Duplicate: only prompt when the user selected this repo's token */ if (tp.repo_token_selected[i] && tp.repos[i].token[0]) { - WCHAR msg[720]; - _snwprintf_s(msg, 719, _TRUNCATE, + WCHAR dup_msg[720]; + _snwprintf_s(dup_msg, 719, _TRUNCATE, L"This repository is already in your sources:\n%s\n\n" L"Which token do you want to keep?\n\n" L"Yes → Keep existing token\n" L"No → Use imported token", tp.repos[i].url); - if (MessageBox(hwnd, msg, L"Duplicate Repository", + if (MessageBox(hwnd, dup_msg, L"Duplicate Repository", MB_ICONQUESTION | MB_YESNO) == IDNO) wcsncpy_s(g.cfg.extra_repos[dup].token, 256, tp.repos[i].token, _TRUNCATE); diff --git a/src/sources.c b/src/sources.c index 34e5bb8..d8ff9bf 100644 --- a/src/sources.c +++ b/src/sources.c @@ -299,20 +299,20 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, bool has_cache = GitHub_ParseOwnerRepo(repo->url, owner, reponame); /* Build confirmation message */ - WCHAR msg[512]; + WCHAR confirm[512]; if (has_cache) { - _snwprintf_s(msg, 511, _TRUNCATE, L"Remove repository:\n%s\n\n" + _snwprintf_s(confirm, 511, _TRUNCATE, L"Remove repository:\n%s\n\n" L"Delete cached scripts and requirements from:\n" L"%s\\%s_%s\n\n" L"This cannot be undone.", repo->url, g.cfg.cache_dir, owner, reponame); } else { - _snwprintf_s(msg, 511, _TRUNCATE, L"Remove repository:\n%s\n\n" + _snwprintf_s(confirm, 511, _TRUNCATE, L"Remove repository:\n%s\n\n" L"No cached files found to delete.", repo->url); } - int res = MessageBox(hwnd, msg, L"Remove Repository", + int res = MessageBox(hwnd, confirm, L"Remove Repository", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); /* MB_DEFBUTTON2 = default to "No" for safety */ if (res != IDYES) break; /* user cancelled — leave the repo list unchanged */ From 0e2bf7357275b8721ab26dc5401b167c2b73d1b7 Mon Sep 17 00:00:00 2001 From: KaiUR Date: Tue, 2 Jun 2026 23:09:14 +0100 Subject: [PATCH 2/5] ci: add clang-tidy workflow, PR template, and .clang-tidy config Co-Authored-By: Claude Sonnet 4.6 --- .clang-tidy | 31 +++++++++++++++++++++++ .github/pull_request_template.md | 12 +++++++++ .github/workflows/clang-tidy.yml | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 .clang-tidy create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/clang-tidy.yml diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..8b5981f --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,31 @@ +--- +# clang-tidy configuration for CatiaMenuWin32 (C11 / Win32) +# Targets real bugs and security issues only. +# Style and modernise checks are excluded — the codebase has its own conventions. + +Checks: > + -*, + bugprone-implicit-widening-of-multiplication-result, + bugprone-integer-division, + bugprone-misplaced-widening-cast, + bugprone-not-null-terminated-result, + bugprone-signed-char-misuse, + bugprone-sizeof-expression, + bugprone-suspicious-memset-usage, + bugprone-suspicious-missing-comma, + bugprone-too-small-loop-variable, + bugprone-undefined-memory-manipulation, + cert-err33-c, + cert-err34-c, + misc-redundant-expression, + clang-analyzer-security.insecureAPI.UncheckedReturn, + clang-analyzer-security.insecureAPI.rand, + clang-analyzer-security.insecureAPI.strcpy + +WarningsAsErrors: '' + +# Only report diagnostics from project source files — suppresses noise from +# Windows SDK, MSVC CRT, and third-party headers. +HeaderFilterRegex: 'src/.*\.h$' + +FormatStyle: none diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..5c7099f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Summary + + +## Changes + + +## Test plan + +- [ ] Build passes (LLVM Clang MSVC Release) +- [ ] Feature / fix tested manually +- [ ] No regressions in adjacent features +- [ ] Docs / wiki updated if user-visible behaviour changed diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml new file mode 100644 index 0000000..78d2d38 --- /dev/null +++ b/.github/workflows/clang-tidy.yml @@ -0,0 +1,43 @@ +name: clang-tidy + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + tidy: + name: clang-tidy (C/C++) + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Add LLVM to PATH + run: echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Configure CMake (generate compile_commands.json) + run: | + cmake -S . -B build -G "Ninja" ` + -DCMAKE_BUILD_TYPE=Debug ` + -DCMAKE_C_COMPILER=clang-cl ` + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Run clang-tidy + shell: pwsh + run: | + $files = Get-ChildItem -Path src -Filter "*.c" | Select-Object -ExpandProperty FullName + $failed = $false + foreach ($f in $files) { + Write-Host "-- $f" + clang-tidy -p build $f + if ($LASTEXITCODE -ne 0) { $failed = $true } + } + if ($failed) { exit 1 } From 3b22d3292d531f3ca0817fbd5b7cabb62b52cf67 Mon Sep 17 00:00:00 2001 From: KaiUR Date: Tue, 2 Jun 2026 23:14:48 +0100 Subject: [PATCH 3/5] ci: enforce memory-safe functions rule via clang-tidy Add clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling to flag use of strcpy/strcat/sprintf/memcpy/gets etc. as CI failures, enforcing the 'Memory-safe functions only' rule from the developer guide. Co-Authored-By: Claude Sonnet 4.6 --- .clang-tidy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 8b5981f..5fc1790 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -20,7 +20,8 @@ Checks: > misc-redundant-expression, clang-analyzer-security.insecureAPI.UncheckedReturn, clang-analyzer-security.insecureAPI.rand, - clang-analyzer-security.insecureAPI.strcpy + clang-analyzer-security.insecureAPI.strcpy, + clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling WarningsAsErrors: '' From 144d1f1587516e9d21cc38fa1ec56acd3520565c Mon Sep 17 00:00:00 2001 From: KaiUR Date: Tue, 2 Jun 2026 23:17:07 +0100 Subject: [PATCH 4/5] ci: add clang-format config and format check workflow Co-Authored-By: Claude Sonnet 4.6 --- .clang-format | 41 ++++++++++++++++++++++++++++++++++++ .github/workflows/format.yml | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/format.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a671c95 --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +--- +Language: C +BasedOnStyle: LLVM + +# --- Indentation --- +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +IndentCaseLabels: false # case labels flush with switch, not indented further + +# --- Braces --- +BreakBeforeBraces: Allman # opening brace always on its own line + +# --- Lines --- +ColumnLimit: 0 # no wrapping — code uses long lines for comment alignment + +# --- Pointers --- +PointerAlignment: Right # WCHAR *out, not WCHAR* out +DerivePointerAlignment: false + +# --- Short constructs --- +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: WithoutElse # if (!x) return; stays on one line +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never + +# --- Spacing --- +SpaceBeforeParens: ControlStatements # space before if/for/while, not before foo() +SpacesInParentheses: false + +# --- Includes --- +SortIncludes: Never # include order is intentional + +# --- Alignment --- +# Disabled — the code uses manual alignment for readability, clang-format +# would fight it and produce worse output. +AlignConsecutiveAssignments: None +AlignConsecutiveDeclarations: None +AlignTrailingComments: + Kind: Never +... diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000..77b75f8 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,41 @@ +name: Format + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + clang-format: + name: clang-format check + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Add LLVM to PATH + run: echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Check formatting + shell: pwsh + run: | + $files = Get-ChildItem -Path src -Include "*.c","*.h" -Recurse | + Select-Object -ExpandProperty FullName + $failed = $false + foreach ($f in $files) { + $result = & clang-format --dry-run --Werror $f 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "NEEDS FORMAT: $f" + $failed = $true + } + } + if ($failed) { + Write-Host "" + Write-Host "Run 'clang-format -i src/*.c src/*.h' locally to fix formatting." + exit 1 + } + Write-Host "All files formatted correctly." From 9deb1f8b9e19d16c023ff190211ef9e1630cb2bd Mon Sep 17 00:00:00 2001 From: KaiUR Date: Tue, 2 Jun 2026 23:19:42 +0100 Subject: [PATCH 5/5] style: apply clang-format baseline across all source files One-time reformatting pass to establish a clean baseline that the format.yml CI workflow can enforce going forward. Co-Authored-By: Claude Sonnet 4.6 --- .clang-format | 1 - src/github.c | 223 +++++++---- src/help.c | 653 +++++++++++++++--------------- src/log.c | 226 +++++++---- src/main.c | 514 ++++++++++++++++-------- src/meta.c | 188 +++++---- src/paint.c | 383 +++++++++++------- src/prefs.c | 128 +++--- src/quickbar.c | 620 +++++++++++++++++------------ src/runner.c | 287 +++++++++----- src/settings.c | 1031 ++++++++++++++++++++++++++---------------------- src/sources.c | 160 ++++---- src/sync.c | 894 ++++++++++++++++++++++++----------------- src/tabs.c | 184 +++++---- src/updater.c | 126 ++++-- src/window.c | 391 +++++++++--------- 16 files changed, 3561 insertions(+), 2448 deletions(-) diff --git a/.clang-format b/.clang-format index a671c95..9d58cc3 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,4 @@ --- -Language: C BasedOnStyle: LLVM # --- Indentation --- diff --git a/src/github.c b/src/github.c index 336d714..cb54f0f 100644 --- a/src/github.c +++ b/src/github.c @@ -39,17 +39,20 @@ static bool GitHub_VerifyCert(HINTERNET hReq, const WCHAR *expected_host) bool ok = false; /* ── Check 1: subject must contain the expected host ─────────── */ - if (ci.lpszSubjectInfo) { + if (ci.lpszSubjectInfo) + { const char *subj = (const char *)ci.lpszSubjectInfo; - if (strstr(subj, host_a) || - strstr(subj, "github.com") || - strstr(subj, "github.io") || - strstr(subj, "githubusercontent.com")) { + if (strstr(subj, host_a) || + strstr(subj, "github.com") || + strstr(subj, "github.io") || + strstr(subj, "githubusercontent.com")) + { ok = true; } } - if (!ok) { + if (!ok) + { Util_Log(L"CertCheck: subject mismatch for %s", expected_host); LocalFree(ci.lpszSubjectInfo); LocalFree(ci.lpszIssuerInfo); @@ -61,12 +64,14 @@ static bool GitHub_VerifyCert(HINTERNET hReq, const WCHAR *expected_host) /* ── Check 2: issuer must be a known GitHub CA ────────────────── */ ok = false; - if (ci.lpszIssuerInfo) { + if (ci.lpszIssuerInfo) + { const char *issr = (const char *)ci.lpszIssuerInfo; - if (strstr(issr, "DigiCert") || - strstr(issr, "Sectigo") || - strstr(issr, "GlobalSign") || - strstr(issr, "Encrypt")) { + if (strstr(issr, "DigiCert") || + strstr(issr, "Sectigo") || + strstr(issr, "GlobalSign") || + strstr(issr, "Encrypt")) + { ok = true; } if (!ok) @@ -109,39 +114,47 @@ bool GitHub_HttpGet(const WCHAR *host, const WCHAR *path, DWORD timeout_ms = 15000; InternetSetOption(hInet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeout_ms, sizeof(timeout_ms)); InternetSetOption(hInet, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeout_ms, sizeof(timeout_ms)); - InternetSetOption(hInet, INTERNET_OPTION_SEND_TIMEOUT, &timeout_ms, sizeof(timeout_ms)); + InternetSetOption(hInet, INTERNET_OPTION_SEND_TIMEOUT, &timeout_ms, sizeof(timeout_ms)); HINTERNET hConn = InternetConnect( hInet, host, INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); - if (!hConn) { InternetCloseHandle(hInet); return false; } + if (!hConn) + { + InternetCloseHandle(hInet); + return false; + } - DWORD flags = INTERNET_FLAG_SECURE /* require HTTPS — plaintext is rejected */ - | INTERNET_FLAG_RELOAD /* bypass WinINet cache, always fetch fresh */ - | INTERNET_FLAG_NO_CACHE_WRITE; /* don't write the response into the WinINet cache */ + DWORD flags = INTERNET_FLAG_SECURE /* require HTTPS — plaintext is rejected */ + | INTERNET_FLAG_RELOAD /* bypass WinINet cache, always fetch fresh */ + | INTERNET_FLAG_NO_CACHE_WRITE; /* don't write the response into the WinINet cache */ HINTERNET hReq = HttpOpenRequest( hConn, L"GET", path, NULL, NULL, NULL, flags, 0); - if (!hReq) { + if (!hReq) + { InternetCloseHandle(hConn); InternetCloseHandle(hInet); return false; } /* GitHub REST API requires the v3 JSON accept header; raw file downloads must NOT send it */ - if (wcsstr(host, L"api.github.com")) { + if (wcsstr(host, L"api.github.com")) + { HttpAddRequestHeaders(hReq, - L"Accept: application/vnd.github.v3+json\r\n", - (DWORD)-1L, HTTP_ADDREQ_FLAG_ADD); /* -1 = let WinINet measure the string length */ + L"Accept: application/vnd.github.v3+json\r\n", + (DWORD)-1L, HTTP_ADDREQ_FLAG_ADD); /* -1 = let WinINet measure the string length */ } - if (token && token[0]) { + if (token && token[0]) + { WCHAR auth[300]; _snwprintf_s(auth, 299, _TRUNCATE, L"Authorization: token %s\r\n", token); HttpAddRequestHeaders(hReq, auth, (DWORD)-1L, HTTP_ADDREQ_FLAG_ADD); } - if (!HttpSendRequest(hReq, NULL, 0, NULL, 0)) { + if (!HttpSendRequest(hReq, NULL, 0, NULL, 0)) + { InternetCloseHandle(hReq); InternetCloseHandle(hConn); InternetCloseHandle(hInet); @@ -149,7 +162,8 @@ bool GitHub_HttpGet(const WCHAR *host, const WCHAR *path, } /* ── Certificate validation ──────────────────────────────────── */ - if (!GitHub_VerifyCert(hReq, host)) { + if (!GitHub_VerifyCert(hReq, host)) + { Util_Log(L"CertCheck: FAILED for %s - aborting", host); InternetCloseHandle(hReq); InternetCloseHandle(hConn); @@ -162,7 +176,8 @@ bool GitHub_HttpGet(const WCHAR *host, const WCHAR *path, HttpQueryInfo(hReq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &ssz, NULL); - if (status != 200) { + if (status != 200) + { /* 404 = not found, 401/403 = auth failure, 429 = rate-limited, etc. */ Util_Log(L"GitHub_HttpGet: HTTP %d for %s%s", status, host, path); InternetCloseHandle(hReq); @@ -174,7 +189,9 @@ bool GitHub_HttpGet(const WCHAR *host, const WCHAR *path, DWORD total = 0, read = 0; DWORD max_read = (*len > 0) ? *len : HTTP_BUF_SIZE; /* use caller-supplied capacity when provided */ while (InternetReadFile(hReq, buf + total, - max_read - total - 1, &read) && read) { /* -1 reserves space for the null terminator */ + max_read - total - 1, &read) && + read) + { /* -1 reserves space for the null terminator */ total += read; if (total >= max_read - 1) break; /* buffer full — stop reading */ } @@ -199,7 +216,7 @@ bool GitHub_HttpGet(const WCHAR *host, const WCHAR *path, /* Out: true and sha_out filled if successful; false on error */ /* ================================================================== */ bool GitHub_ComputeFileSHA1(const WCHAR *local_path, - WCHAR *sha_out, int sha_max) + WCHAR *sha_out, int sha_max) { sha_out[0] = L'\0'; @@ -208,17 +225,25 @@ bool GitHub_ComputeFileSHA1(const WCHAR *local_path, if (hf == INVALID_HANDLE_VALUE) return false; DWORD file_size = GetFileSize(hf, NULL); - char *file_buf = (char *)malloc(file_size + 1); - if (!file_buf) { CloseHandle(hf); return false; } + char *file_buf = (char *)malloc(file_size + 1); + if (!file_buf) + { + CloseHandle(hf); + return false; + } DWORD read_bytes = 0; ReadFile(hf, file_buf, file_size, &read_bytes, NULL); CloseHandle(hf); - if (read_bytes != file_size) { free(file_buf); return false; } + if (read_bytes != file_size) + { + free(file_buf); + return false; + } /* Build the git blob header: "blob \0" */ char header[64]; - int header_len = _snprintf_s(header, sizeof(header), _TRUNCATE, "blob %lu", (unsigned long)file_size); + int header_len = _snprintf_s(header, sizeof(header), _TRUNCATE, "blob %lu", (unsigned long)file_size); /* header_len does NOT include the NUL - but the NUL IS part of the hash input */ /* Hash = SHA1(header + NUL + file_content) */ @@ -227,19 +252,23 @@ bool GitHub_ComputeFileSHA1(const WCHAR *local_path, bool ok = false; if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) { /* CRYPT_VERIFYCONTEXT = key-less context, sufficient for hashing */ - if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { /* CALG_SHA1 = SHA-1 algorithm ID */ + CRYPT_VERIFYCONTEXT)) + { /* CRYPT_VERIFYCONTEXT = key-less context, sufficient for hashing */ + if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) + { /* CALG_SHA1 = SHA-1 algorithm ID */ /* Feed the three-part input to match GitHub's blob SHA formula */ CryptHashData(hHash, (BYTE *)header, header_len, 0); /* "blob " (no NUL yet) */ BYTE nul = 0; - CryptHashData(hHash, &nul, 1, 0); /* the mandatory NUL separator */ - CryptHashData(hHash, (BYTE *)file_buf, file_size, 0);/* raw file bytes */ + CryptHashData(hHash, &nul, 1, 0); /* the mandatory NUL separator */ + CryptHashData(hHash, (BYTE *)file_buf, file_size, 0); /* raw file bytes */ - BYTE hash[20]; /* SHA-1 produces 160 bits = 20 bytes */ + BYTE hash[20]; /* SHA-1 produces 160 bits = 20 bytes */ DWORD hash_len = sizeof(hash); - if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &hash_len, 0)) { + if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &hash_len, 0)) + { WCHAR *p = sha_out; - for (int i = 0; i < 20 && p < sha_out + sha_max - 2; i++) { + for (int i = 0; i < 20 && p < sha_out + sha_max - 2; i++) + { _snwprintf_s(p, 3, _TRUNCATE, L"%02x", hash[i]); /* 3 = 2 hex chars + null; advance p by 2 */ p += 2; } @@ -266,7 +295,7 @@ bool GitHub_ComputeFileSHA1(const WCHAR *local_path, /* ================================================================== */ bool GitHub_VerifyScriptSHA(const Script *s) { - if (!s->sha[0]) return true; /* no SHA in the manifest — skip verification (e.g. local dir scripts) */ + if (!s->sha[0]) return true; /* no SHA in the manifest — skip verification (e.g. local dir scripts) */ WCHAR computed[MAX_SHA] = {0}; if (!GitHub_ComputeFileSHA1(s->local, computed, MAX_SHA)) @@ -295,18 +324,20 @@ bool GitHub_DownloadRaw(const WCHAR *gh_path, const WCHAR *local_path, { WCHAR raw_url[MAX_APPPATH * 2]; _snwprintf_s(raw_url, MAX_APPPATH * 2 - 1, _TRUNCATE, L"/%s/%s/%s/%s", - GITHUB_OWNER, GITHUB_REPO, GITHUB_BRANCH, gh_path); + GITHUB_OWNER, GITHUB_REPO, GITHUB_BRANCH, gh_path); /* Retry up to 3 times with increasing delays: 0 ms, 1000 ms, 2000 ms */ - for (int attempt = 0; attempt < 3; attempt++) { + for (int attempt = 0; attempt < 3; attempt++) + { if (attempt > 0) Sleep(1000 * attempt); /* back off: attempt 1 = 1 s, attempt 2 = 2 s */ char *buf = (char *)malloc(HTTP_BUF_SIZE); if (!buf) return false; DWORD len = 0; - bool ok = GitHub_HttpGet(GITHUB_RAW_HOST, raw_url, token, buf, &len); - if (ok && len > 0) { + bool ok = GitHub_HttpGet(GITHUB_RAW_HOST, raw_url, token, buf, &len); + if (ok && len > 0) + { WCHAR dir[MAX_APPPATH]; wcsncpy_s(dir, MAX_APPPATH, local_path, _TRUNCATE); PathRemoveFileSpec(dir); @@ -314,17 +345,22 @@ bool GitHub_DownloadRaw(const WCHAR *gh_path, const WCHAR *local_path, HANDLE hf = CreateFile(local_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (hf != INVALID_HANDLE_VALUE) { + if (hf != INVALID_HANDLE_VALUE) + { DWORD written = 0; WriteFile(hf, buf, len, &written, NULL); CloseHandle(hf); free(buf); if (written == len) return true; /* all bytes written — success */ /* partial write: fall through to retry */ - } else { + } + else + { free(buf); /* couldn't create the file — retry */ } - } else { + } + else + { free(buf); /* HTTP GET failed — retry */ } } @@ -375,14 +411,15 @@ bool GitHub_ParseOwnerRepo(const WCHAR *url, WCHAR *owner, WCHAR *repo) /* Out: true if the file was written successfully; false otherwise */ /* ================================================================== */ bool GitHub_DownloadRawFull(const WCHAR *host, const WCHAR *path, - const WCHAR *local_path, const WCHAR *token) + const WCHAR *local_path, const WCHAR *token) { char *buf = (char *)malloc(HTTP_BUF_SIZE); if (!buf) return false; DWORD len = 0; - bool ok = GitHub_HttpGet(host, path, token, buf, &len); - if (ok && len > 0) { + bool ok = GitHub_HttpGet(host, path, token, buf, &len); + if (ok && len > 0) + { WCHAR dir[MAX_APPPATH]; wcsncpy_s(dir, MAX_APPPATH, local_path, _TRUNCATE); PathRemoveFileSpec(dir); @@ -390,12 +427,15 @@ bool GitHub_DownloadRawFull(const WCHAR *host, const WCHAR *path, HANDLE hf = CreateFile(local_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (hf != INVALID_HANDLE_VALUE) { + if (hf != INVALID_HANDLE_VALUE) + { DWORD written = 0; WriteFile(hf, buf, len, &written, NULL); CloseHandle(hf); ok = (written == len); - } else ok = false; + } + else + ok = false; } free(buf); return ok; @@ -415,7 +455,7 @@ bool GitHub_DownloadRawFull(const WCHAR *host, const WCHAR *path, /* NULL if key not found or value is not a string */ /* ================================================================== */ static const char *json_str(const char *p, const char *key, - char *out, int max) + char *out, int max) { char needle[MAX_NAME]; _snprintf_s(needle, sizeof(needle), _TRUNCATE, "\"%s\"", key); @@ -423,14 +463,23 @@ static const char *json_str(const char *p, const char *key, p = strstr(p, needle); if (!p) return NULL; p += strlen(needle); - while (*p == ' ' || *p == ':') p++; + while (*p == ' ' || *p == ':') + p++; if (*p != '"') return NULL; p++; int i = 0; - while (*p && *p != '"' && i < max - 1) { - if (*p == '\\') { p++; if (*p) out[i++] = *p++; } /* handle JSON escape: skip '\', copy next char literally */ - else { out[i++] = *p++; } + while (*p && *p != '"' && i < max - 1) + { + if (*p == '\\') + { + p++; + if (*p) out[i++] = *p++; + } /* handle JSON escape: skip '\', copy next char literally */ + else + { + out[i++] = *p++; + } } out[i] = '\0'; return (*p == '"') ? p + 1 : NULL; /* return position after closing quote, or NULL if string was unterminated */ @@ -451,27 +500,38 @@ void GitHub_ParseRoot(const char *json) g.folders[] struct fields, and Folder_Free is called on existing entries. */ EnterCriticalSection(&g.cs_folders); - for (int _fi = 0; _fi < g.folder_count; _fi++) Folder_Free(&g.folders[_fi]); + for (int _fi = 0; _fi < g.folder_count; _fi++) + Folder_Free(&g.folders[_fi]); g.folder_count = 0; const char *p = json; - while ((p = strstr(p, "\"type\"")) != NULL - && g.folder_count < MAX_FOLDERS) { + while ((p = strstr(p, "\"type\"")) != NULL && g.folder_count < MAX_FOLDERS) + { char type_val[32] = {0}; const char *after = json_str(p, "type", type_val, sizeof(type_val)); - if (!after) { p++; continue; } + if (!after) + { + p++; + continue; + } - if (strcmp(type_val, "dir") == 0) { + if (strcmp(type_val, "dir") == 0) + { /* Walk back to the opening '{' of this JSON object to read other fields */ const char *obj = p; - while (obj > json && *obj != '{') obj--; + while (obj > json && *obj != '{') + obj--; char name_a[MAX_NAME] = {0}; json_str(obj, "name", name_a, sizeof(name_a)); /* Skip the setup folder (build files, not scripts) and hidden dot-folders */ - if (_stricmp(name_a, "setup") == 0 || name_a[0] == '.') { p = after; continue; } + if (_stricmp(name_a, "setup") == 0 || name_a[0] == '.') + { + p = after; + continue; + } ScriptFolder *f = &g.folders[g.folder_count++]; ZeroMemory(f, sizeof(*f)); @@ -508,36 +568,45 @@ void GitHub_ParseFolder(const char *json, int fi) Folder_Alloc(f, 64); const char *p = json; - while ((p = strstr(p, "\"type\"")) != NULL) { + while ((p = strstr(p, "\"type\"")) != NULL) + { char type_val[32] = {0}; const char *after = json_str(p, "type", type_val, sizeof(type_val)); - if (!after) { p++; continue; } + if (!after) + { + p++; + continue; + } - if (strcmp(type_val, "file") == 0) { + if (strcmp(type_val, "file") == 0) + { const char *obj = p; - while (obj > json && *obj != '{') obj--; + while (obj > json && *obj != '{') + obj--; - char name_a[MAX_NAME] = {0}; - char path_a[MAX_APPPATH] = {0}; - char sha_a[MAX_SHA] = {0}; - json_str(obj, "name", name_a, sizeof(name_a)); - json_str(obj, "path", path_a, sizeof(path_a)); - json_str(obj, "sha", sha_a, sizeof(sha_a)); + char name_a[MAX_NAME] = {0}; + char path_a[MAX_APPPATH] = {0}; + char sha_a[MAX_SHA] = {0}; + json_str(obj, "name", name_a, sizeof(name_a)); + json_str(obj, "path", path_a, sizeof(path_a)); + json_str(obj, "sha", sha_a, sizeof(sha_a)); size_t nl = strlen(name_a); - if (nl < 4 || strcmp(name_a + nl - 3, ".py") != 0) { + if (nl < 4 || strcmp(name_a + nl - 3, ".py") != 0) + { /* nl < 4 rejects anything shorter than "x.py"; the strcmp checks the extension */ - p = after; continue; + p = after; + continue; } Script *s = Folder_Push(f); if (!s) break; ZeroMemory(s, sizeof(*s)); - MultiByteToWideChar(CP_UTF8, 0, name_a, -1, s->name, MAX_NAME); + MultiByteToWideChar(CP_UTF8, 0, name_a, -1, s->name, MAX_NAME); MultiByteToWideChar(CP_UTF8, 0, path_a, -1, s->gh_path, MAX_APPPATH); - MultiByteToWideChar(CP_UTF8, 0, sha_a, -1, s->sha, MAX_SHA); + MultiByteToWideChar(CP_UTF8, 0, sha_a, -1, s->sha, MAX_SHA); /* Strip .py from display name before building local path */ Util_StripExt(s->name); @@ -547,7 +616,7 @@ void GitHub_ParseFolder(const char *json, int fi) WCHAR fname_w[MAX_NAME] = {0}; MultiByteToWideChar(CP_UTF8, 0, name_a, -1, fname_w, MAX_NAME); _snwprintf_s(s->local, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\%s", - g.cfg.cache_dir, g.folders[fi].name, fname_w); + g.cfg.cache_dir, g.folders[fi].name, fname_w); } p = after; } diff --git a/src/help.c b/src/help.c index 41f5719..e651c4b 100644 --- a/src/help.c +++ b/src/help.c @@ -18,7 +18,8 @@ /* In: (enum — no parameters) */ /* Out: (values used as TreeView item lParam and switch keys) */ /* ================================================================== */ -typedef enum { +typedef enum +{ HELP_GETTING_STARTED = 0, HELP_INTERFACE, HELP_RUNNING_SCRIPTS, @@ -48,73 +49,74 @@ static const char *Help_GetRTF(HelpTopic topic) { case HELP_GETTING_STARTED: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Getting Started}\\par\\par" - "{\\b\\cf1 Requirements}\\par\\par" - "\\bullet Windows 10 or later\\par" - "\\bullet Python 3.9+ installed and in your PATH\\par" - "\\bullet PyCATIA: {\\f1\\cf3 pip install pycatia}\\par" - "\\bullet CATIA V5 must be running for scripts that interact with it\\par\\par" - "{\\b\\cf1 Installation}\\par\\par" - "1. Download {\\b CatiaMenuWin32.exe} from the GitHub releases page.\\par" - "2. Place it anywhere on your machine \\emdash no installer required.\\par" - "3. Double-click to run.\\par\\par" - "{\\b\\cf1 First Launch}\\par\\par" - "On first launch the app will:\\par" - "\\bullet Create {\\f1\\cf3 %APPDATA%\\\\CatiaMenuWin32\\\\} to store settings and scripts.\\par" - "\\bullet Auto-detect your Python installation.\\par" - "\\bullet Sync scripts from the built-in KaiUR/Pycatia_Scripts repository.\\par" - "\\bullet Display the scripts as clickable buttons organised by tab.\\par\\par" - "By default the app starts with Windows and minimizes to the system tray. " - "Double-click the tray icon to restore it.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Getting Started}\\par\\par" + "{\\b\\cf1 Requirements}\\par\\par" + "\\bullet Windows 10 or later\\par" + "\\bullet Python 3.9+ installed and in your PATH\\par" + "\\bullet PyCATIA: {\\f1\\cf3 pip install pycatia}\\par" + "\\bullet CATIA V5 must be running for scripts that interact with it\\par\\par" + "{\\b\\cf1 Installation}\\par\\par" + "1. Download {\\b CatiaMenuWin32.exe} from the GitHub releases page.\\par" + "2. Place it anywhere on your machine \\emdash no installer required.\\par" + "3. Double-click to run.\\par\\par" + "{\\b\\cf1 First Launch}\\par\\par" + "On first launch the app will:\\par" + "\\bullet Create {\\f1\\cf3 %APPDATA%\\\\CatiaMenuWin32\\\\} to store settings and scripts.\\par" + "\\bullet Auto-detect your Python installation.\\par" + "\\bullet Sync scripts from the built-in KaiUR/Pycatia_Scripts repository.\\par" + "\\bullet Display the scripts as clickable buttons organised by tab.\\par\\par" + "By default the app starts with Windows and minimizes to the system tray. " + "Double-click the tray icon to restore it.\\par" + "}"; case HELP_INTERFACE: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 The Interface}\\par\\par" - "{\\b\\cf1 Toolbar}\\par\\par" - "\\bullet {\\b \\u9776? Menu} \\emdash Access all app functions\\par" - "\\bullet {\\b \\u8635? Refresh} \\emdash Re-sync scripts from all sources\\par" - "\\bullet {\\b \\u9881? Settings} \\emdash Open the settings dialog\\par" - "\\bullet {\\b \\u8615? Deps} \\emdash Install/update Python dependencies\\par" - "\\bullet {\\b \\u9632? Stop} \\emdash Terminate the running background script " - "(grayed out when idle, red when active)\\par" - "\\bullet {\\b \\u8801? Log} \\emdash Open the script output log window to view stdout/stderr " - "captured from background script runs\\par\\par" - "{\\b\\cf1 Script Button Tinting}\\par\\par" - "When enabled in Settings (Window tab), script buttons are tinted based on their source:\\par" - "\\bullet {\\b Warm tint} (subtle rose) \\emdash script is from a local folder source\\par" - "\\bullet {\\b Cool tint} (subtle green) \\emdash script is from an extra GitHub repository\\par" - "\\bullet No tint \\emdash script is from the built-in KaiUR/Pycatia_Scripts repository\\par\\par" - "{\\b\\cf1 Search Bar}\\par\\par" - "The filter bar below the toolbar filters scripts in real time by name or purpose. " - "Type any text to filter, clear it to show all scripts.\\par\\par" - "{\\b\\cf1 Tab Bar}\\par\\par" - "Each tab corresponds to a script folder. Click a tab to switch categories. " - "When more tabs exist than fit, {\\b \\u9668? } and {\\b \\u9658? } arrows appear. " - "Use the mouse wheel over the tab bar to scroll.\\par\\par" - "{\\b\\cf1 Script Buttons}\\par\\par" - "\\bullet {\\b Left-click} the main area to run the script.\\par" - "\\bullet {\\b Hover} the {\\b i} badge on the right to see script info in a tooltip.\\par" - "\\bullet {\\b Right-click} any button to see the full context menu.\\par\\par" - "{\\b\\cf1 Status Bar}\\par\\par" - "Shows sync progress, script launch status, and exit codes.\\par\\par" - "{\\b\\cf1 Quick Launch Bar}\\par\\par" - "An optional floating toolbar that pins your favourite scripts as icon buttons outside " - "the main window. Enable it in {\\b Settings \\u8594? Quick Bar} or " - "{\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Enable Quick Bar}. " - "See the {\\b Quick Launch Bar} help topic for details.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 The Interface}\\par\\par" + "{\\b\\cf1 Toolbar}\\par\\par" + "\\bullet {\\b \\u9776? Menu} \\emdash Access all app functions\\par" + "\\bullet {\\b \\u8635? Refresh} \\emdash Re-sync scripts from all sources\\par" + "\\bullet {\\b \\u9881? Settings} \\emdash Open the settings dialog\\par" + "\\bullet {\\b \\u8615? Deps} \\emdash Install/update Python dependencies\\par" + "\\bullet {\\b \\u9632? Stop} \\emdash Terminate the running background script " + "(grayed out when idle, red when active)\\par" + "\\bullet {\\b \\u8801? Log} \\emdash Open the script output log window to view stdout/stderr " + "captured from background script runs\\par\\par" + "{\\b\\cf1 Script Button Tinting}\\par\\par" + "When enabled in Settings (Window tab), script buttons are tinted based on their source:\\par" + "\\bullet {\\b Warm tint} (subtle rose) \\emdash script is from a local folder source\\par" + "\\bullet {\\b Cool tint} (subtle green) \\emdash script is from an extra GitHub repository\\par" + "\\bullet No tint \\emdash script is from the built-in KaiUR/Pycatia_Scripts repository\\par\\par" + "{\\b\\cf1 Search Bar}\\par\\par" + "The filter bar below the toolbar filters scripts in real time by name or purpose. " + "Type any text to filter, clear it to show all scripts.\\par\\par" + "{\\b\\cf1 Tab Bar}\\par\\par" + "Each tab corresponds to a script folder. Click a tab to switch categories. " + "When more tabs exist than fit, {\\b \\u9668? } and {\\b \\u9658? } arrows appear. " + "Use the mouse wheel over the tab bar to scroll.\\par\\par" + "{\\b\\cf1 Script Buttons}\\par\\par" + "\\bullet {\\b Left-click} the main area to run the script.\\par" + "\\bullet {\\b Hover} the {\\b i} badge on the right to see script info in a tooltip.\\par" + "\\bullet {\\b Right-click} any button to see the full context menu.\\par\\par" + "{\\b\\cf1 Status Bar}\\par\\par" + "Shows sync progress, script launch status, and exit codes.\\par\\par" + "{\\b\\cf1 Quick Launch Bar}\\par\\par" + "An optional floating toolbar that pins your favourite scripts as icon buttons outside " + "the main window. Enable it in {\\b Settings \\u8594? Quick Bar} or " + "{\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Enable Quick Bar}. " + "See the {\\b Quick Launch Bar} help topic for details.\\par" + "}"; case HELP_RUNNING_SCRIPTS: { static char s_run[8000] = {0}; - if (!s_run[0]) { + if (!s_run[0]) + { static const char rp1[] = "{\\rtf1\\ansi\\deff0" "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" @@ -184,7 +186,8 @@ static const char *Help_GetRTF(HelpTopic topic) /* Split across two static arrays to stay within the ISO C99 4095-char string literal limit; concatenated once into s_buf on first call. */ static char s_buf[8000] = {0}; - if (!s_buf[0]) { + if (!s_buf[0]) + { static const char part1[] = "{\\rtf1\\ansi\\deff0" "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" @@ -274,222 +277,222 @@ static const char *Help_GetRTF(HelpTopic topic) case HELP_SOURCES: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Script Sources}\\par\\par" - "Open via {\\b Menu \\u8594? File \\u8594? Sources...}\\par\\par" - "The app can load scripts from three types of sources simultaneously:\\par\\par" - "{\\b\\cf1 Built-in Repository}\\par\\par" - "The KaiUR/Pycatia_Scripts repository is always the primary source. " - "Use the checkbox at the top of the Sources dialog to enable or disable it.\\par\\par" - "{\\b\\cf1 Additional GitHub Repositories}\\par\\par" - "Add any GitHub repository that uses the same folder structure:\\par" - "1. Click {\\b Add...} under Additional GitHub Repositories.\\par" - "2. Enter the full URL: {\\f1\\cf3 https://github.com/owner/repo}\\par" - "3. Enter the branch name (defaults to main).\\par" - "4. Optionally add a token for private repos or higher rate limits.\\par" - "5. Click OK.\\par\\par" - "If two repositories have a folder with the same name, their scripts are merged into one tab.\\par\\par" - "{\\b\\cf1 Local Script Folders}\\par\\par" - "Add a folder on your machine that contains subfolders with .py files:\\par" - "{\\f1\\cf3 My_Scripts\\\\}\\par" - "{\\f1\\cf3 Any_Document_Scripts\\\\my_script.py}\\par" - "{\\f1\\cf3 Part_Document_Scripts\\\\another.py}\\par\\par" - "Local scripts run directly from disk \\emdash no downloading or SHA checking.\\par\\par" - "{\\b\\cf1 Setup Folder}\\par\\par" - "If a source has a {\\f1\\cf3 setup/requirements.txt} file, clicking {\\b Deps} " - "will install those dependencies. The {\\f1\\cf3 setup/} folder never becomes a tab.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Script Sources}\\par\\par" + "Open via {\\b Menu \\u8594? File \\u8594? Sources...}\\par\\par" + "The app can load scripts from three types of sources simultaneously:\\par\\par" + "{\\b\\cf1 Built-in Repository}\\par\\par" + "The KaiUR/Pycatia_Scripts repository is always the primary source. " + "Use the checkbox at the top of the Sources dialog to enable or disable it.\\par\\par" + "{\\b\\cf1 Additional GitHub Repositories}\\par\\par" + "Add any GitHub repository that uses the same folder structure:\\par" + "1. Click {\\b Add...} under Additional GitHub Repositories.\\par" + "2. Enter the full URL: {\\f1\\cf3 https://github.com/owner/repo}\\par" + "3. Enter the branch name (defaults to main).\\par" + "4. Optionally add a token for private repos or higher rate limits.\\par" + "5. Click OK.\\par\\par" + "If two repositories have a folder with the same name, their scripts are merged into one tab.\\par\\par" + "{\\b\\cf1 Local Script Folders}\\par\\par" + "Add a folder on your machine that contains subfolders with .py files:\\par" + "{\\f1\\cf3 My_Scripts\\\\}\\par" + "{\\f1\\cf3 Any_Document_Scripts\\\\my_script.py}\\par" + "{\\f1\\cf3 Part_Document_Scripts\\\\another.py}\\par\\par" + "Local scripts run directly from disk \\emdash no downloading or SHA checking.\\par\\par" + "{\\b\\cf1 Setup Folder}\\par\\par" + "If a source has a {\\f1\\cf3 setup/requirements.txt} file, clicking {\\b Deps} " + "will install those dependencies. The {\\f1\\cf3 setup/} folder never becomes a tab.\\par" + "}"; case HELP_FAVOURITES: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Favourites & Search}\\par\\par" - "{\\b\\cf1 Favourites}\\par\\par" - "Right-click any script \\u8594? {\\b Add to Favourites} to add it to a dedicated " - "{\\b \\u9733? Favourites} tab at the top of the tab bar.\\par\\par" - "\\bullet The tab only appears when you have at least one favourite.\\par" - "\\bullet It disappears automatically when all favourites are removed.\\par" - "\\bullet Favourites are stored in {\\f1\\cf3 %APPDATA%\\\\CatiaMenuWin32\\\\prefs.ini}.\\par\\par" - "To remove a favourite: right-click the script \\u8594? {\\b Remove from Favourites}.\\par\\par" - "{\\b\\cf1 Search / Filter}\\par\\par" - "The filter bar below the toolbar filters scripts in the current tab in real time.\\par\\par" - "\\bullet Filters by both script name and purpose line.\\par" - "\\bullet Case-insensitive \\emdash searching {\\f1\\cf3 iges} matches {\\b IGES Export Curve AXIS}.\\par" - "\\bullet Clear the box to show all scripts again.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Favourites & Search}\\par\\par" + "{\\b\\cf1 Favourites}\\par\\par" + "Right-click any script \\u8594? {\\b Add to Favourites} to add it to a dedicated " + "{\\b \\u9733? Favourites} tab at the top of the tab bar.\\par\\par" + "\\bullet The tab only appears when you have at least one favourite.\\par" + "\\bullet It disappears automatically when all favourites are removed.\\par" + "\\bullet Favourites are stored in {\\f1\\cf3 %APPDATA%\\\\CatiaMenuWin32\\\\prefs.ini}.\\par\\par" + "To remove a favourite: right-click the script \\u8594? {\\b Remove from Favourites}.\\par\\par" + "{\\b\\cf1 Search / Filter}\\par\\par" + "The filter bar below the toolbar filters scripts in the current tab in real time.\\par\\par" + "\\bullet Filters by both script name and purpose line.\\par" + "\\bullet Case-insensitive \\emdash searching {\\f1\\cf3 iges} matches {\\b IGES Export Curve AXIS}.\\par" + "\\bullet Clear the box to show all scripts again.\\par" + "}"; case HELP_DETAILS_NOTES: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Script Details & Notes}\\par\\par" - "{\\b\\cf1 Script Details}\\par\\par" - "Right-click any script \\u8594? {\\b Script Details...} opens a full details dialog showing:\\par" - "\\bullet Name, Purpose, Author, Version, Date\\par" - "\\bullet Code environment (e.g. Python 3.10.4, PyCATIA 0.8.3)\\par" - "\\bullet CATIA release (e.g. V5R32)\\par" - "\\bullet Full description and requirements\\par" - "\\bullet Local cache path\\par" - "\\bullet Your personal note\\par" - "\\bullet Favourite and Hidden toggles\\par\\par" - "Changes to the note, favourite, and hidden state are saved when you click OK.\\par\\par" - "{\\b\\cf1 Script Notes}\\par\\par" - "Right-click any script \\u8594? {\\b Add Note...} (or {\\b Edit Note...}) to attach " - "a personal note to a script.\\par\\par" - "Notes are visible in the Script Details dialog and stored in " - "{\\f1\\cf3 prefs.ini}.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Script Details & Notes}\\par\\par" + "{\\b\\cf1 Script Details}\\par\\par" + "Right-click any script \\u8594? {\\b Script Details...} opens a full details dialog showing:\\par" + "\\bullet Name, Purpose, Author, Version, Date\\par" + "\\bullet Code environment (e.g. Python 3.10.4, PyCATIA 0.8.3)\\par" + "\\bullet CATIA release (e.g. V5R32)\\par" + "\\bullet Full description and requirements\\par" + "\\bullet Local cache path\\par" + "\\bullet Your personal note\\par" + "\\bullet Favourite and Hidden toggles\\par\\par" + "Changes to the note, favourite, and hidden state are saved when you click OK.\\par\\par" + "{\\b\\cf1 Script Notes}\\par\\par" + "Right-click any script \\u8594? {\\b Add Note...} (or {\\b Edit Note...}) to attach " + "a personal note to a script.\\par\\par" + "Notes are visible in the Script Details dialog and stored in " + "{\\f1\\cf3 prefs.ini}.\\par" + "}"; case HELP_SORT_HIDE: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Sort & Hide Scripts}\\par\\par" - "{\\b\\cf1 Sorting}\\par\\par" - "{\\b Menu \\u8594? View \\u8594? Sort Scripts} offers four sort modes:\\par\\par" - "\\bullet {\\b Default Order} \\emdash Order from GitHub API or disk\\par" - "\\bullet {\\b Alphabetical} \\emdash A to Z by script name\\par" - "\\bullet {\\b By Date} \\emdash Most recent scripts first (from script header Date field)\\par" - "\\bullet {\\b Most Used} \\emdash Scripts you run most often appear first\\par\\par" - "The sort mode is saved in Settings and applied to all tabs.\\par\\par" - "{\\b\\cf1 Hiding Scripts}\\par\\par" - "Right-click any script \\u8594? {\\b Hide Script} removes it from view.\\par\\par" - "\\bullet Hidden scripts are not deleted \\emdash They remain in the cache.\\par" - "\\bullet Hidden scripts will not reappear after a sync.\\par" - "\\bullet To restore: {\\b Menu \\u8594? File \\u8594? Manage Hidden Scripts}\\par" - "\\bullet Select a script and click {\\b Unhide}, or click {\\b Unhide All}.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Sort & Hide Scripts}\\par\\par" + "{\\b\\cf1 Sorting}\\par\\par" + "{\\b Menu \\u8594? View \\u8594? Sort Scripts} offers four sort modes:\\par\\par" + "\\bullet {\\b Default Order} \\emdash Order from GitHub API or disk\\par" + "\\bullet {\\b Alphabetical} \\emdash A to Z by script name\\par" + "\\bullet {\\b By Date} \\emdash Most recent scripts first (from script header Date field)\\par" + "\\bullet {\\b Most Used} \\emdash Scripts you run most often appear first\\par\\par" + "The sort mode is saved in Settings and applied to all tabs.\\par\\par" + "{\\b\\cf1 Hiding Scripts}\\par\\par" + "Right-click any script \\u8594? {\\b Hide Script} removes it from view.\\par\\par" + "\\bullet Hidden scripts are not deleted \\emdash They remain in the cache.\\par" + "\\bullet Hidden scripts will not reappear after a sync.\\par" + "\\bullet To restore: {\\b Menu \\u8594? File \\u8594? Manage Hidden Scripts}\\par" + "\\bullet Select a script and click {\\b Unhide}, or click {\\b Unhide All}.\\par" + "}"; case HELP_UPDATE_DEPS: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Update Dependencies}\\par\\par" - "Click {\\b \\u8615? Deps} (or {\\b Menu \\u8594? Run \\u8594? Update Dependencies}) " - "to install Python packages required by the scripts.\\par\\par" - "The app runs {\\f1\\cf3 pip install -r requirements.txt} for each source that has a " - "{\\f1\\cf3 setup/requirements.txt} file, in order:\\par" - "1. Main repository requirements\\par" - "2. Each extra GitHub repository's requirements\\par" - "3. Each local folder's requirements\\par\\par" - "Each source runs in its own console window sequentially.\\par\\par" - "{\\b\\cf1 Keep Deps Console Open}\\par\\par" - "Enable this in Settings to keep each console window visible until you close it manually. " - "Useful to read pip output and check for errors.\\par\\par" - "{\\b\\cf1 Adding Dependencies to Your Scripts}\\par\\par" - "Place a {\\f1\\cf3 requirements.txt} file in the {\\f1\\cf3 setup/} subfolder of " - "your script repository or local folder. The app will find and run it automatically.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Update Dependencies}\\par\\par" + "Click {\\b \\u8615? Deps} (or {\\b Menu \\u8594? Run \\u8594? Update Dependencies}) " + "to install Python packages required by the scripts.\\par\\par" + "The app runs {\\f1\\cf3 pip install -r requirements.txt} for each source that has a " + "{\\f1\\cf3 setup/requirements.txt} file, in order:\\par" + "1. Main repository requirements\\par" + "2. Each extra GitHub repository's requirements\\par" + "3. Each local folder's requirements\\par\\par" + "Each source runs in its own console window sequentially.\\par\\par" + "{\\b\\cf1 Keep Deps Console Open}\\par\\par" + "Enable this in Settings to keep each console window visible until you close it manually. " + "Useful to read pip output and check for errors.\\par\\par" + "{\\b\\cf1 Adding Dependencies to Your Scripts}\\par\\par" + "Place a {\\f1\\cf3 requirements.txt} file in the {\\f1\\cf3 setup/} subfolder of " + "your script repository or local folder. The app will find and run it automatically.\\par" + "}"; case HELP_QUICK_BAR: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Quick Launch Bar}\\par\\par" - "The Quick Launch Bar is a small floating toolbar that displays your favourite scripts as " - "icon buttons, always within reach outside the main window.\\par\\par" - "{\\b\\cf1 Enabling}\\par\\par" - "\\bullet Open {\\b \\u2699? Settings \\u8594? Quick Bar tab} and check {\\b Enable Quick Launch Bar}.\\par" - "\\bullet Or use {\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Enable Quick Bar}.\\par\\par" - "{\\b\\cf1 Populating the Bar}\\par\\par" - "Right-click any script \\u8594? {\\b Add to Favourites}. " - "Every script you mark as a favourite automatically appears on the Quick Bar.\\par\\par" - "{\\b\\cf1 Orientation}\\par\\par" - "Switch between {\\b Vertical} (stacked column) and {\\b Horizontal} (row) layouts " - "from {\\b Settings \\u8594? Quick Bar} or {\\b Menu \\u8594? View \\u8594? Quick Bar}.\\par\\par" - "{\\b\\cf1 Moving the Bar}\\par\\par" - "Click and drag the bar to reposition it anywhere on screen. " - "The position is saved automatically.\\par" - "Use {\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Reset Position} to move it back to the default location.\\par\\par" - "{\\b\\cf1 Stay on Top with Target Application}\\par\\par" - "When a {\\b Target App} is configured, the Quick Bar automatically elevates to topmost " - "when that application\\u8217?s window is in the foreground (e.g. CATIA V5), and lowers " - "itself when a different window becomes active.\\par\\par" - "\\bullet Set the target in {\\b Settings \\u8594? Quick Bar \\u8594? Target App field}.\\par" - "\\bullet Leave the field empty to keep the bar always visible on top.\\par" - "\\bullet Toggle the behaviour with {\\b Settings \\u8594? Quick Bar \\u8594? Stay on Top with Target App}.\\par\\par" - "{\\b\\cf1 Repeat on Double-Click}\\par\\par" - "{\\b Double-click} any Quick Bar button to enter repeat mode: the script re-runs after each " - "completion until you stop it. The button turns amber with a \\u8635? loop symbol.\\par\\par" - "\\bullet Press {\\b Escape} to cancel repeat and stop the running background script.\\par" - "\\bullet Single-click any button to cancel (same = no re-run, different = run that script).\\par" - "\\bullet Toggle via right-click \\u8594? {\\b Repeat on Double-Click} or " - "{\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Repeat on Double-Click}.\\par\\par" - "{\\b\\cf1 Process Executable Filter}\\par\\par" - "If another application has the same window-title substring as your target, the bar will " - "incorrectly respond to it. The {\\b Target Exe} field adds a second filter: the window\\u8217?s " - "owning process must also match the given executable filename.\\par\\par" - "\\bullet Set {\\b Settings \\u8594? Quick Bar \\u8594? Target Exe} to the executable " - "filename (e.g. {\\f1\\cf3 CNEXT.exe} for CATIA V5).\\par" - "\\bullet The check is case-insensitive and matches the filename only, not the full path.\\par" - "\\bullet Leave the field empty (default) to match any process — identical to the previous behaviour.\\par" - "\\bullet {\\b Repeat on double-click (Quick Bar)} \\emdash Enable repeat mode for Quick Bar buttons " - "(see {\\b Running Scripts} topic for details).\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Quick Launch Bar}\\par\\par" + "The Quick Launch Bar is a small floating toolbar that displays your favourite scripts as " + "icon buttons, always within reach outside the main window.\\par\\par" + "{\\b\\cf1 Enabling}\\par\\par" + "\\bullet Open {\\b \\u2699? Settings \\u8594? Quick Bar tab} and check {\\b Enable Quick Launch Bar}.\\par" + "\\bullet Or use {\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Enable Quick Bar}.\\par\\par" + "{\\b\\cf1 Populating the Bar}\\par\\par" + "Right-click any script \\u8594? {\\b Add to Favourites}. " + "Every script you mark as a favourite automatically appears on the Quick Bar.\\par\\par" + "{\\b\\cf1 Orientation}\\par\\par" + "Switch between {\\b Vertical} (stacked column) and {\\b Horizontal} (row) layouts " + "from {\\b Settings \\u8594? Quick Bar} or {\\b Menu \\u8594? View \\u8594? Quick Bar}.\\par\\par" + "{\\b\\cf1 Moving the Bar}\\par\\par" + "Click and drag the bar to reposition it anywhere on screen. " + "The position is saved automatically.\\par" + "Use {\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Reset Position} to move it back to the default location.\\par\\par" + "{\\b\\cf1 Stay on Top with Target Application}\\par\\par" + "When a {\\b Target App} is configured, the Quick Bar automatically elevates to topmost " + "when that application\\u8217?s window is in the foreground (e.g. CATIA V5), and lowers " + "itself when a different window becomes active.\\par\\par" + "\\bullet Set the target in {\\b Settings \\u8594? Quick Bar \\u8594? Target App field}.\\par" + "\\bullet Leave the field empty to keep the bar always visible on top.\\par" + "\\bullet Toggle the behaviour with {\\b Settings \\u8594? Quick Bar \\u8594? Stay on Top with Target App}.\\par\\par" + "{\\b\\cf1 Repeat on Double-Click}\\par\\par" + "{\\b Double-click} any Quick Bar button to enter repeat mode: the script re-runs after each " + "completion until you stop it. The button turns amber with a \\u8635? loop symbol.\\par\\par" + "\\bullet Press {\\b Escape} to cancel repeat and stop the running background script.\\par" + "\\bullet Single-click any button to cancel (same = no re-run, different = run that script).\\par" + "\\bullet Toggle via right-click \\u8594? {\\b Repeat on Double-Click} or " + "{\\b Menu \\u8594? View \\u8594? Quick Bar \\u8594? Repeat on Double-Click}.\\par\\par" + "{\\b\\cf1 Process Executable Filter}\\par\\par" + "If another application has the same window-title substring as your target, the bar will " + "incorrectly respond to it. The {\\b Target Exe} field adds a second filter: the window\\u8217?s " + "owning process must also match the given executable filename.\\par\\par" + "\\bullet Set {\\b Settings \\u8594? Quick Bar \\u8594? Target Exe} to the executable " + "filename (e.g. {\\f1\\cf3 CNEXT.exe} for CATIA V5).\\par" + "\\bullet The check is case-insensitive and matches the filename only, not the full path.\\par" + "\\bullet Leave the field empty (default) to match any process — identical to the previous behaviour.\\par" + "\\bullet {\\b Repeat on double-click (Quick Bar)} \\emdash Enable repeat mode for Quick Bar buttons " + "(see {\\b Running Scripts} topic for details).\\par" + "}"; case HELP_KEYBOARD: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Keyboard Shortcuts}\\par\\par" - "\\pard\\tx2160" - "{\\b Shortcut}\\tab{\\b Action}\\par" - "{\\f1\\cf3 F5}\\tab Refresh + Sync\\par" - "{\\f1\\cf3 F9}\\tab Run last script\\par" - "{\\f1\\cf3 F1}\\tab Open Help\\par" - "{\\f1\\cf3 Ctrl+Tab}\\tab Next tab\\par" - "{\\f1\\cf3 Ctrl+Shift+Tab}\\tab Previous tab\\par" - "{\\f1\\cf3 Escape}\\tab Cancel repeat mode and stop running script (when active)\\par" - "\\pard\\par" - "{\\b\\cf1 Right-click Menu}\\par\\par" - "Right-clicking any script button shows:\\par" - "\\bullet Script Details...\\par" - "\\bullet Run with Arguments...\\par" - "\\bullet Open Script Location \\emdash opens the folder containing the cached script in Explorer.\\par" - "\\bullet Open with Default App \\emdash opens the script file with the Windows default application for its file type.\\par" - "\\bullet Open in Editor \\emdash opens the script in the registered editor (e.g. VS Code, Notepad++). " - "Shown only for local folder scripts.\\par" - "\\bullet Add to Favourites / Remove from Favourites\\par" - "\\bullet Add Note... / Edit Note...\\par" - "\\bullet Hide Script\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Keyboard Shortcuts}\\par\\par" + "\\pard\\tx2160" + "{\\b Shortcut}\\tab{\\b Action}\\par" + "{\\f1\\cf3 F5}\\tab Refresh + Sync\\par" + "{\\f1\\cf3 F9}\\tab Run last script\\par" + "{\\f1\\cf3 F1}\\tab Open Help\\par" + "{\\f1\\cf3 Ctrl+Tab}\\tab Next tab\\par" + "{\\f1\\cf3 Ctrl+Shift+Tab}\\tab Previous tab\\par" + "{\\f1\\cf3 Escape}\\tab Cancel repeat mode and stop running script (when active)\\par" + "\\pard\\par" + "{\\b\\cf1 Right-click Menu}\\par\\par" + "Right-clicking any script button shows:\\par" + "\\bullet Script Details...\\par" + "\\bullet Run with Arguments...\\par" + "\\bullet Open Script Location \\emdash opens the folder containing the cached script in Explorer.\\par" + "\\bullet Open with Default App \\emdash opens the script file with the Windows default application for its file type.\\par" + "\\bullet Open in Editor \\emdash opens the script in the registered editor (e.g. VS Code, Notepad++). " + "Shown only for local folder scripts.\\par" + "\\bullet Add to Favourites / Remove from Favourites\\par" + "\\bullet Add Note... / Edit Note...\\par" + "\\bullet Hide Script\\par" + "}"; case HELP_TROUBLESHOOTING: return "{\\rtf1\\ansi\\deff0" - "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" - "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" - "\\f0\\fs22\\cf2" - "{\\b\\fs32\\cf1 Troubleshooting}\\par\\par" - "{\\b\\cf1 \\u8220? Connect to internet to sync\\u8221? }\\par" - "\\bullet Check your internet connection.\\par" - "\\bullet You may have hit the GitHub API rate limit (60 req/hr without a token). " - "Add a GitHub token in Settings.\\par" - "\\bullet Corporate firewalls may block {\\f1\\cf3 api.github.com}.\\par\\par" - "{\\b\\cf1 Tooltips or Script Details are blank}\\par" - "This happens on first launch before scripts have downloaded. " - "Wait for the sync to complete then hover the script again.\\par\\par" - "{\\b\\cf1 Script fails with \\u8220? Python Not Found\\u8221? }\\par" - "Open Settings and click Browse next to Python Interpreter to locate " - "{\\f1\\cf3 python.exe}.\\par\\par" - "{\\b\\cf1 SHA mismatch warning}\\par" - "The local cached script differs from GitHub. Click {\\b Yes} to re-download. " - "If it persists, delete the cache folder in Settings and re-sync.\\par\\par" - "{\\b\\cf1 A script has disappeared}\\par" - "It may have been hidden. Check {\\b Menu \\u8594? File \\u8594? Manage Hidden Scripts }. " - "Also check if the search filter box has text in it.\\par\\par" - "{\\b\\cf1 Update prompt appears on local builds }\\par" - "Run {\\f1\\cf3 git fetch --tags} and {\\f1\\cf3 git pull origin main} then delete " - "the build folder and rebuild.\\par" - "}"; + "{\\fonttbl{\\f0 Segoe UI;}{\\f1 Consolas;}}" + "{\\colortbl ;\\red82\\green155\\blue245;\\red210\\green215\\blue240;\\red110\\green116\\blue148;}" + "\\f0\\fs22\\cf2" + "{\\b\\fs32\\cf1 Troubleshooting}\\par\\par" + "{\\b\\cf1 \\u8220? Connect to internet to sync\\u8221? }\\par" + "\\bullet Check your internet connection.\\par" + "\\bullet You may have hit the GitHub API rate limit (60 req/hr without a token). " + "Add a GitHub token in Settings.\\par" + "\\bullet Corporate firewalls may block {\\f1\\cf3 api.github.com}.\\par\\par" + "{\\b\\cf1 Tooltips or Script Details are blank}\\par" + "This happens on first launch before scripts have downloaded. " + "Wait for the sync to complete then hover the script again.\\par\\par" + "{\\b\\cf1 Script fails with \\u8220? Python Not Found\\u8221? }\\par" + "Open Settings and click Browse next to Python Interpreter to locate " + "{\\f1\\cf3 python.exe}.\\par\\par" + "{\\b\\cf1 SHA mismatch warning}\\par" + "The local cached script differs from GitHub. Click {\\b Yes} to re-download. " + "If it persists, delete the cache folder in Settings and re-sync.\\par\\par" + "{\\b\\cf1 A script has disappeared}\\par" + "It may have been hidden. Check {\\b Menu \\u8594? File \\u8594? Manage Hidden Scripts }. " + "Also check if the search filter box has text in it.\\par\\par" + "{\\b\\cf1 Update prompt appears on local builds }\\par" + "Run {\\f1\\cf3 git fetch --tags} and {\\f1\\cf3 git pull origin main} then delete " + "the build folder and rebuild.\\par" + "}"; default: return "{\\rtf1\\ansi Hello}"; /* fallback — minimal valid RTF so the control is never empty */ @@ -505,20 +508,34 @@ static const char *Help_GetRTF(HelpTopic topic) /* ================================================================== */ static const WCHAR *Help_TopicLabel(HelpTopic t) { - switch (t) { - case HELP_GETTING_STARTED: return L"Getting Started"; - case HELP_INTERFACE: return L"The Interface"; - case HELP_RUNNING_SCRIPTS: return L"Running Scripts"; - case HELP_SETTINGS: return L"Settings"; - case HELP_SOURCES: return L"Script Sources"; - case HELP_FAVOURITES: return L"Favourites & Search"; - case HELP_DETAILS_NOTES: return L"Script Details & Notes"; - case HELP_SORT_HIDE: return L"Sort & Hide Scripts"; - case HELP_UPDATE_DEPS: return L"Update Dependencies"; - case HELP_QUICK_BAR: return L"Quick Launch Bar"; - case HELP_KEYBOARD: return L"Keyboard Shortcuts"; - case HELP_TROUBLESHOOTING: return L"Troubleshooting"; - default: return L""; + switch (t) + { + case HELP_GETTING_STARTED: + return L"Getting Started"; + case HELP_INTERFACE: + return L"The Interface"; + case HELP_RUNNING_SCRIPTS: + return L"Running Scripts"; + case HELP_SETTINGS: + return L"Settings"; + case HELP_SOURCES: + return L"Script Sources"; + case HELP_FAVOURITES: + return L"Favourites & Search"; + case HELP_DETAILS_NOTES: + return L"Script Details & Notes"; + case HELP_SORT_HIDE: + return L"Sort & Hide Scripts"; + case HELP_UPDATE_DEPS: + return L"Update Dependencies"; + case HELP_QUICK_BAR: + return L"Quick Launch Bar"; + case HELP_KEYBOARD: + return L"Keyboard Shortcuts"; + case HELP_TROUBLESHOOTING: + return L"Troubleshooting"; + default: + return L""; } } @@ -531,7 +548,11 @@ static const WCHAR *Help_TopicLabel(HelpTopic t) /* pos — current byte offset into data (advanced by callback) */ /* Out: (struct value — consumed by RtfCallback via cookie pointer) */ /* ================================================================== */ -typedef struct { const char *data; DWORD pos; } RtfStream; +typedef struct +{ + const char *data; + DWORD pos; +} RtfStream; /* ================================================================== */ /* RtfCallback (static) */ @@ -544,12 +565,18 @@ typedef struct { const char *data; DWORD pos; } RtfStream; /* Out: 0 always (non-zero signals a streaming error to RichEdit) */ /* ================================================================== */ static DWORD CALLBACK RtfCallback(DWORD_PTR cookie, LPBYTE buf, - LONG cb, LONG *read) + LONG cb, LONG *read) { RtfStream *s = (RtfStream *)cookie; DWORD avail = (DWORD)strlen(s->data) - s->pos; DWORD n = (avail < (DWORD)cb) ? avail : (DWORD)cb; /* take whichever is smaller: bytes available vs bytes requested */ - if (n) { if (memmove_s(buf, n, s->data + s->pos, n) == 0) s->pos += n; else n = 0; } /* advance only if copy succeeded */ + if (n) + { + if (memmove_s(buf, n, s->data + s->pos, n) == 0) + s->pos += n; + else + n = 0; + } /* advance only if copy succeeded */ *read = (LONG)n; /* 0 signals end of stream to RichEdit */ return 0; /* non-zero would abort streaming with an error */ } @@ -566,8 +593,8 @@ static DWORD CALLBACK RtfCallback(DWORD_PTR cookie, LPBYTE buf, static void Help_LoadTopic(HWND hEdit, HelpTopic topic) { const char *rtf = Help_GetRTF(topic); - RtfStream stream = { rtf, 0 }; - EDITSTREAM es = { (DWORD_PTR)&stream, 0, RtfCallback }; + RtfStream stream = {rtf, 0}; + EDITSTREAM es = {(DWORD_PTR)&stream, 0, RtfCallback}; SendMessage(hEdit, EM_STREAMIN, SF_RTF, (LPARAM)&es); /* Scroll to top */ SendMessage(hEdit, EM_SETSEL, 0, 0); @@ -590,12 +617,12 @@ static void Help_LoadTopic(HWND hEdit, HelpTopic topic) static HWND s_hwnd_help = NULL; static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { static HWND hTree = NULL, hEdit = NULL; - static int splitter = 200; /* 200 px — TreeView pane width */ + static int splitter = 200; /* 200 px — TreeView pane width */ static WCHAR s_header_text[64]; /* current topic label shown in header strip */ - static const int HDR_H = 40; /* header strip height in pixels */ + static const int HDR_H = 40; /* header strip height in pixels */ switch (msg) { @@ -608,32 +635,32 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_HELP_ICON)); if (!hIcon) hIcon = LoadIcon(NULL, IDI_QUESTION); SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); - SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); /* Clip children so the dialog background fill doesn't paint over controls */ SetWindowLongPtr(hwnd, GWL_STYLE, - GetWindowLongPtr(hwnd, GWL_STYLE) | WS_CLIPCHILDREN); + GetWindowLongPtr(hwnd, GWL_STYLE) | WS_CLIPCHILDREN); /* Create TreeView — TVS_FULLROWSELECT highlights full row; TVS_NOHSCROLL keeps it tidy */ hTree = CreateWindowEx(0, WC_TREEVIEW, L"", - WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | - TVS_SHOWSELALWAYS | TVS_FULLROWSELECT | TVS_NOHSCROLL, - 0, 0, splitter, 400, - hwnd, (HMENU)(UINT_PTR)IDC_HELP_TREE, - GetModuleHandle(NULL), NULL); + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | + TVS_SHOWSELALWAYS | TVS_FULLROWSELECT | TVS_NOHSCROLL, + 0, 0, splitter, 400, + hwnd, (HMENU)(UINT_PTR)IDC_HELP_TREE, + GetModuleHandle(NULL), NULL); /* Set TreeView colours to match app theme */ - TreeView_SetBkColor(hTree, COL_BG()); + TreeView_SetBkColor(hTree, COL_BG()); TreeView_SetTextColor(hTree, COL_TEXT()); SendMessage(hTree, WM_SETFONT, (WPARAM)g.font_ui, FALSE); /* Create RichEdit — top is offset by HDR_H to leave room for the header strip */ hEdit = CreateWindowEx(0, RICHEDIT_CLASS, L"", - WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_CLIPSIBLINGS | - ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL, - splitter + 1, HDR_H, 400, 400, - hwnd, (HMENU)(UINT_PTR)IDC_HELP_RICHEDIT, - GetModuleHandle(NULL), NULL); + WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_CLIPSIBLINGS | + ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL, + splitter + 1, HDR_H, 400, 400, + hwnd, (HMENU)(UINT_PTR)IDC_HELP_RICHEDIT, + GetModuleHandle(NULL), NULL); /* Style RichEdit */ SendMessage(hEdit, EM_SETBKGNDCOLOR, 0, (LPARAM)COL_BG()); @@ -641,19 +668,21 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, MAKELONG(16, 16)); /* 16 px padding on left and right edges */ /* Populate TreeView */ - for (int i = 0; i < HELP_TOPIC_COUNT; i++) { + for (int i = 0; i < HELP_TOPIC_COUNT; i++) + { TVINSERTSTRUCTW tvi = {0}; - tvi.hParent = TVI_ROOT; + tvi.hParent = TVI_ROOT; tvi.hInsertAfter = TVI_LAST; - tvi.item.mask = TVIF_TEXT | TVIF_PARAM; + tvi.item.mask = TVIF_TEXT | TVIF_PARAM; tvi.item.pszText = (LPWSTR)Help_TopicLabel((HelpTopic)i); - tvi.item.lParam = (LPARAM)i; /* store topic index so TVN_SELCHANGED can retrieve it */ + tvi.item.lParam = (LPARAM)i; /* store topic index so TVN_SELCHANGED can retrieve it */ TreeView_InsertItem(hTree, &tvi); } /* Select first item and initialise header text */ HTREEITEM first = TreeView_GetRoot(hTree); - if (first) { + if (first) + { TreeView_SelectItem(hTree, first); Help_LoadTopic(hEdit, HELP_GETTING_STARTED); } @@ -664,7 +693,8 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, Window_ApplyDarkMode(hwnd); /* Trigger WM_SIZE to position controls for the actual client area */ - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); SendMessage(hwnd, WM_SIZE, 0, MAKELPARAM(rc.right, rc.bottom)); return TRUE; } @@ -672,7 +702,8 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, case WM_ERASEBKGND: { /* Fill dialog background with app theme colour */ - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); FillRect((HDC)wp, &rc, g.br_bg); return TRUE; /* returning TRUE signals the background was erased, preventing flicker */ } @@ -681,10 +712,11 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); /* Header strip — accent-coloured bar above the RichEdit showing the current topic name */ - RECT hdr = { splitter + 1, 0, rc.right, HDR_H }; + RECT hdr = {splitter + 1, 0, rc.right, HDR_H}; HBRUSH br_hdr = CreateSolidBrush(COL_ACCENT); FillRect(hdc, &hdr, br_hdr); DeleteObject(br_hdr); @@ -693,14 +725,14 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, HFONT old_font = (HFONT)SelectObject(hdc, g.font_ui); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, RGB(255, 255, 255)); - RECT text_rc = { splitter + 18, 0, rc.right - 8, HDR_H }; + RECT text_rc = {splitter + 18, 0, rc.right - 8, HDR_H}; DrawTextW(hdc, s_header_text, -1, &text_rc, DT_SINGLELINE | DT_VCENTER | DT_LEFT | DT_END_ELLIPSIS); SelectObject(hdc, old_font); /* Vertical divider between tree and content pane — full height including header */ HPEN pen = CreatePen(PS_SOLID, 1, COL_DIVIDER()); - HPEN op = SelectObject(hdc, pen); + HPEN op = SelectObject(hdc, pen); MoveToEx(hdc, splitter, 0, NULL); LineTo(hdc, splitter, rc.bottom); SelectObject(hdc, op); @@ -721,9 +753,9 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, w - splitter - 1, h - HDR_H, SWP_NOZORDER | SWP_NOACTIVATE); /* Invalidate the divider strip and header area to force a repaint */ - RECT div = { splitter - 1, 0, splitter + 2, h }; + RECT div = {splitter - 1, 0, splitter + 2, h}; InvalidateRect(hwnd, &div, FALSE); - RECT hdr_inv = { splitter + 1, 0, w, HDR_H }; + RECT hdr_inv = {splitter + 1, 0, w, HDR_H}; InvalidateRect(hwnd, &hdr_inv, FALSE); return 0; } @@ -732,14 +764,16 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, { NMHDR *nm = (NMHDR *)lp; if (nm->idFrom == IDC_HELP_TREE && - nm->code == TVN_SELCHANGED) { /* fires when user clicks a new topic */ + nm->code == TVN_SELCHANGED) + { /* fires when user clicks a new topic */ NMTREEVIEW *ntv = (NMTREEVIEW *)lp; HelpTopic t = (HelpTopic)ntv->itemNew.lParam; /* lParam is the topic index stored at insert time */ if (hEdit) Help_LoadTopic(hEdit, t); /* Update header strip label and repaint it */ wcsncpy_s(s_header_text, 64, Help_TopicLabel(t), _TRUNCATE); - RECT rc; GetClientRect(hwnd, &rc); - RECT hdr_inv = { splitter + 1, 0, rc.right, HDR_H }; + RECT rc; + GetClientRect(hwnd, &rc); + RECT hdr_inv = {splitter + 1, 0, rc.right, HDR_H}; InvalidateRect(hwnd, &hdr_inv, FALSE); } return 0; @@ -775,7 +809,8 @@ static INT_PTR CALLBACK HelpDlgProc(HWND hwnd, UINT msg, /* ================================================================== */ void Help_Show(void) { - if (s_hwnd_help) { /* single-instance guard — focus the existing window instead of opening a second */ + if (s_hwnd_help) + { /* single-instance guard — focus the existing window instead of opening a second */ SetForegroundWindow(s_hwnd_help); return; } diff --git a/src/log.c b/src/log.c index 37c5e53..2787ae2 100644 --- a/src/log.c +++ b/src/log.c @@ -33,39 +33,45 @@ /* ------------------------------------------------------------------ */ /* Module-level state */ /* ------------------------------------------------------------------ */ -#define LOG_WNDCLASS L"CMW32LogWnd" -static HMODULE s_rich = NULL; /* RICHED20.DLL handle */ -static HFONT s_font = NULL; /* Consolas font for the RichEdit */ +#define LOG_WNDCLASS L"CMW32LogWnd" +static HMODULE s_rich = NULL; /* RICHED20.DLL handle */ +static HFONT s_font = NULL; /* Consolas font for the RichEdit */ /* Session text buffer — always appended even when the window is closed */ -static WCHAR *s_buf = NULL; /* heap-allocated accumulated log text */ -static size_t s_buf_len = 0; /* chars used (not including null terminator) */ -static size_t s_buf_cap = 0; /* chars allocated (including null terminator) */ +static WCHAR *s_buf = NULL; /* heap-allocated accumulated log text */ +static size_t s_buf_len = 0; /* chars used (not including null terminator) */ +static size_t s_buf_cap = 0; /* chars allocated (including null terminator) */ /* ------------------------------------------------------------------ */ /* Child control IDs and layout */ /* ------------------------------------------------------------------ */ -#define LOG_ID_RICHEDIT 1 /* RichEdit control */ -#define LOG_ID_BTN_SAVE 2 /* "Save log..." button */ -#define LOG_ID_BTN_CLEAR 3 /* "Clear" button */ -#define LOG_BTN_H 28 /* height of the button bar at the bottom */ +#define LOG_ID_RICHEDIT 1 /* RichEdit control */ +#define LOG_ID_BTN_SAVE 2 /* "Save log..." button */ +#define LOG_ID_BTN_CLEAR 3 /* "Clear" button */ +#define LOG_BTN_H 28 /* height of the button bar at the bottom */ /* ------------------------------------------------------------------ */ /* Right-click context menu item IDs (local to RichEdit subclass) */ /* ------------------------------------------------------------------ */ -#define CTX_COPY 1 +#define CTX_COPY 1 #define CTX_SEL_ALL 2 -#define CTX_CLEAR 3 +#define CTX_CLEAR 3 /* ------------------------------------------------------------------ */ /* Highlight colours (theme-aware) */ /* ------------------------------------------------------------------ */ static inline COLORREF LOG_COL_ERROR(void) - { return g.dark_mode ? RGB(255, 100, 100) : RGB(180, 0, 0); } +{ + return g.dark_mode ? RGB(255, 100, 100) : RGB(180, 0, 0); +} static inline COLORREF LOG_COL_WARN(void) - { return g.dark_mode ? RGB(255, 200, 60) : RGB(150, 90, 0); } +{ + return g.dark_mode ? RGB(255, 200, 60) : RGB(150, 90, 0); +} static inline COLORREF LOG_COL_SUCCESS(void) - { return g.dark_mode ? RGB(100, 220, 100) : RGB( 0,128, 0); } +{ + return g.dark_mode ? RGB(100, 220, 100) : RGB(0, 128, 0); +} /* ================================================================== */ /* Buf_Append (static) */ @@ -81,12 +87,13 @@ static void Buf_Append(const WCHAR *text) if (add == 0) return; size_t need = s_buf_len + add + 1; - if (need > s_buf_cap) { + if (need > s_buf_cap) + { size_t new_cap = need * 2; if (new_cap < 4096) new_cap = 4096; WCHAR *nb = (WCHAR *)realloc(s_buf, new_cap * sizeof(WCHAR)); if (!nb) return; - s_buf = nb; + s_buf = nb; s_buf_cap = new_cap; } memcpy(s_buf + s_buf_len, text, (add + 1) * sizeof(WCHAR)); @@ -115,7 +122,8 @@ static HWND Log_GetRichEdit(void) static bool LineContainsI(const WCHAR *line, const WCHAR *kw) { size_t klen = wcslen(kw); - for (; *line; line++) { + for (; *line; line++) + { if (_wcsnicmp(line, kw, klen) == 0) return true; } return false; @@ -134,18 +142,19 @@ static COLORREF Log_LineColor(const WCHAR *line) if (_wcsnicmp(line, L"===", 3) == 0) return COL_ACCENT; /* Completion footer lines produced by main.c WM_SCRIPT_STOPPED */ - if (_wcsnicmp(line, L"---", 3) == 0) { + if (_wcsnicmp(line, L"---", 3) == 0) + { if (LineContainsI(line, L"successfully")) return LOG_COL_SUCCESS(); - if (LineContainsI(line, L"stopped")) return LOG_COL_WARN(); + if (LineContainsI(line, L"stopped")) return LOG_COL_WARN(); return LOG_COL_ERROR(); /* "Exited with code N" and any other --- lines */ } /* Python runtime output patterns */ if (LineContainsI(line, L"traceback")) return LOG_COL_ERROR(); if (LineContainsI(line, L"exception")) return LOG_COL_ERROR(); - if (LineContainsI(line, L"fatal")) return LOG_COL_ERROR(); - if (LineContainsI(line, L"error")) return LOG_COL_ERROR(); - if (LineContainsI(line, L"warning")) return LOG_COL_WARN(); + if (LineContainsI(line, L"fatal")) return LOG_COL_ERROR(); + if (LineContainsI(line, L"error")) return LOG_COL_ERROR(); + if (LineContainsI(line, L"warning")) return LOG_COL_WARN(); return 0; /* 0 = no override; caller skips EM_SETCHARFORMAT */ } @@ -163,7 +172,8 @@ static void Log_ColorizeNewLines(HWND hRE, int first_line) { int total = (int)SendMessage(hRE, EM_GETLINECOUNT, 0, 0); - for (int ln = first_line; ln < total; ln++) { + for (int ln = first_line; ln < total; ln++) + { int idx = (int)SendMessage(hRE, EM_LINEINDEX, (WPARAM)ln, 0); if (idx < 0) continue; int len = (int)SendMessage(hRE, EM_LINELENGTH, (WPARAM)idx, 0); @@ -181,10 +191,10 @@ static void Log_ColorizeNewLines(HWND hRE, int first_line) SendMessage(hRE, EM_SETSEL, (WPARAM)idx, (LPARAM)(idx + len)); CHARFORMAT2W cf = {0}; - cf.cbSize = sizeof(cf); - cf.dwMask = CFM_COLOR; + cf.cbSize = sizeof(cf); + cf.dwMask = CFM_COLOR; cf.crTextColor = color; - cf.dwEffects = 0; /* clear CFE_AUTOCOLOR so crTextColor is used */ + cf.dwEffects = 0; /* clear CFE_AUTOCOLOR so crTextColor is used */ SendMessage(hRE, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); } @@ -209,10 +219,10 @@ static void Log_ApplyColors(HWND hRE) /* Reset all text to the current theme colour first */ CHARFORMAT2W cf = {0}; - cf.cbSize = sizeof(cf); - cf.dwMask = CFM_COLOR; + cf.cbSize = sizeof(cf); + cf.dwMask = CFM_COLOR; cf.crTextColor = COL_TEXT(); - cf.dwEffects = 0; /* clear CFE_AUTOCOLOR */ + cf.dwEffects = 0; /* clear CFE_AUTOCOLOR */ SendMessage(hRE, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf); /* Re-apply per-line highlights over the reset baseline */ @@ -237,7 +247,7 @@ void Log_Append(const WCHAR *text) if (!hRE) return; /* Record insertion point so colorizer knows which lines are new */ - int old_len = (int)SendMessage(hRE, WM_GETTEXTLENGTH, 0, 0); + int old_len = (int)SendMessage(hRE, WM_GETTEXTLENGTH, 0, 0); int first_ln = (int)SendMessage(hRE, EM_LINEFROMCHAR, (WPARAM)old_len, 0); SendMessage(hRE, EM_SETSEL, (WPARAM)old_len, (LPARAM)old_len); @@ -261,9 +271,9 @@ void Log_AddHeader(const WCHAR *script_name) WCHAR header[256]; _snwprintf_s(header, 255, _TRUNCATE, - L"=== %s (%02d:%02d:%02d) ===\r\n", - script_name ? script_name : L"Script", - st.wHour, st.wMinute, st.wSecond); + L"=== %s (%02d:%02d:%02d) ===\r\n", + script_name ? script_name : L"Script", + st.wHour, st.wMinute, st.wSecond); Log_Append(header); } @@ -308,7 +318,8 @@ void Log_OnThemeChange(void) /* ================================================================== */ static void Log_SaveToFile(HWND hwnd) { - if (!s_buf || s_buf_len == 0) { + if (!s_buf || s_buf_len == 0) + { MessageBox(hwnd, L"The log is empty — nothing to save.", L"Save Log", MB_ICONINFORMATION | MB_OK); return; @@ -320,36 +331,42 @@ static void Log_SaveToFile(HWND hwnd) OPENFILENAMEW ofn; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = hwnd; + ofn.hwndOwner = hwnd; ofn.lpstrFilter = L"Text Files\0*.txt\0Log Files\0*.log\0All Files\0*.*\0"; - ofn.lpstrFile = path; - ofn.nMaxFile = MAX_APPPATH; + ofn.lpstrFile = path; + ofn.nMaxFile = MAX_APPPATH; ofn.lpstrDefExt = L"txt"; - ofn.lpstrTitle = L"Save Log"; - ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; + ofn.lpstrTitle = L"Save Log"; + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (!GetSaveFileNameW(&ofn)) return; /* Convert buffer to UTF-8 */ int utf8_len = WideCharToMultiByte(CP_UTF8, 0, s_buf, (int)s_buf_len, - NULL, 0, NULL, NULL); + NULL, 0, NULL, NULL); char *utf8 = (char *)malloc((size_t)utf8_len + 3); /* +3 for UTF-8 BOM */ - if (!utf8) { + if (!utf8) + { MessageBox(hwnd, L"Out of memory.", L"Save Log", MB_ICONERROR | MB_OK); return; } /* UTF-8 BOM (EF BB BF) so the file opens correctly in Notepad etc. */ - utf8[0] = (char)0xEF; utf8[1] = (char)0xBB; utf8[2] = (char)0xBF; + utf8[0] = (char)0xEF; + utf8[1] = (char)0xBB; + utf8[2] = (char)0xBF; WideCharToMultiByte(CP_UTF8, 0, s_buf, (int)s_buf_len, utf8 + 3, utf8_len, NULL, NULL); FILE *f = NULL; - if (_wfopen_s(&f, path, L"wb") == 0 && f) { + if (_wfopen_s(&f, path, L"wb") == 0 && f) + { fwrite(utf8, 1, (size_t)utf8_len + 3, f); fclose(f); MessageBox(hwnd, L"Log saved successfully.", L"Save Log", MB_ICONINFORMATION | MB_OK); - } else { + } + else + { MessageBox(hwnd, L"Could not write to the selected file.", L"Save Log", MB_ICONERROR | MB_OK); } @@ -372,11 +389,13 @@ static void Log_SaveToFile(HWND hwnd) static LRESULT CALLBACK RichEditSubclassProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR uid, DWORD_PTR data) { - (void)uid; (void)data; + (void)uid; + (void)data; - if (msg == WM_CONTEXTMENU) { + if (msg == WM_CONTEXTMENU) + { /* Resolve screen coordinates (keyboard menu sends -1,-1) */ - POINT pt = { (int)(short)LOWORD(lp), (int)(short)HIWORD(lp) }; + POINT pt = {(int)(short)LOWORD(lp), (int)(short)HIWORD(lp)}; if (pt.x == -1 && pt.y == -1) GetCursorPos(&pt); /* Enable Copy only when there is an active selection */ @@ -386,20 +405,27 @@ static LRESULT CALLBACK RichEditSubclassProc( HMENU hm = CreatePopupMenu(); AppendMenuW(hm, MF_STRING | (has_sel ? 0 : MF_GRAYED), - CTX_COPY, L"Copy\tCtrl+C"); + CTX_COPY, L"Copy\tCtrl+C"); AppendMenuW(hm, MF_STRING, CTX_SEL_ALL, L"Select All\tCtrl+A"); AppendMenuW(hm, MF_SEPARATOR, 0, NULL); AppendMenuW(hm, MF_STRING, CTX_CLEAR, L"Clear log"); int cmd = TrackPopupMenu(hm, - TPM_RETURNCMD | TPM_RIGHTBUTTON, - pt.x, pt.y, 0, GetParent(hwnd), NULL); + TPM_RETURNCMD | TPM_RIGHTBUTTON, + pt.x, pt.y, 0, GetParent(hwnd), NULL); DestroyMenu(hm); - switch (cmd) { - case CTX_COPY: SendMessage(hwnd, WM_COPY, 0, 0); break; - case CTX_SEL_ALL: SendMessage(hwnd, EM_SETSEL, 0, -1); break; - case CTX_CLEAR: Log_Clear(); break; + switch (cmd) + { + case CTX_COPY: + SendMessage(hwnd, WM_COPY, 0, 0); + break; + case CTX_SEL_ALL: + SendMessage(hwnd, EM_SETSEL, 0, -1); + break; + case CTX_CLEAR: + Log_Clear(); + break; } return 0; } @@ -422,7 +448,8 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { case WM_CREATE: { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int w = rc.right, h = rc.bottom; HINSTANCE hinst = GetModuleHandle(NULL); @@ -434,7 +461,8 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) 0, 0, w, h - LOG_BTN_H, hwnd, (HMENU)LOG_ID_RICHEDIT, hinst, NULL); - if (hRE) { + if (hRE) + { if (s_font) SendMessage(hRE, WM_SETFONT, (WPARAM)s_font, FALSE); SendMessage(hRE, EM_AUTOURLDETECT, FALSE, 0); Log_ApplyColors(hRE); @@ -446,14 +474,14 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) int btn_y = h - LOG_BTN_H + 4; HWND hSave = CreateWindowExW(0, L"BUTTON", L"Save log...", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, - 6, btn_y, 90, 20, hwnd, (HMENU)LOG_ID_BTN_SAVE, hinst, NULL); - HWND hClr = CreateWindowExW(0, L"BUTTON", L"Clear", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, - 100, btn_y, 56, 20, hwnd, (HMENU)LOG_ID_BTN_CLEAR, hinst, NULL); + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, + 6, btn_y, 90, 20, hwnd, (HMENU)LOG_ID_BTN_SAVE, hinst, NULL); + HWND hClr = CreateWindowExW(0, L"BUTTON", L"Clear", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, + 100, btn_y, 56, 20, hwnd, (HMENU)LOG_ID_BTN_CLEAR, hinst, NULL); if (hSave) SendMessage(hSave, WM_SETFONT, (WPARAM)hUiFont, FALSE); - if (hClr) SendMessage(hClr, WM_SETFONT, (WPARAM)hUiFont, FALSE); + if (hClr) SendMessage(hClr, WM_SETFONT, (WPARAM)hUiFont, FALSE); (void)hUiFont; /* font set above; owner-draw uses g.font_ui via Paint_ToolbarButton */ return 0; @@ -462,7 +490,8 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case WM_ERASEBKGND: { /* Fill any area not yet covered by the RichEdit (visible briefly during resize) */ - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); HBRUSH br = CreateSolidBrush(COL_BG()); FillRect((HDC)wp, &rc, br); DeleteObject(br); @@ -472,15 +501,15 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case WM_SIZE: { int w = (int)LOWORD(lp), h = (int)HIWORD(lp); - HWND hRE = GetDlgItem(hwnd, LOG_ID_RICHEDIT); + HWND hRE = GetDlgItem(hwnd, LOG_ID_RICHEDIT); HWND hSave = GetDlgItem(hwnd, LOG_ID_BTN_SAVE); - HWND hClr = GetDlgItem(hwnd, LOG_ID_BTN_CLEAR); + HWND hClr = GetDlgItem(hwnd, LOG_ID_BTN_CLEAR); if (hRE) SetWindowPos(hRE, NULL, 0, 0, w, h - LOG_BTN_H, SWP_NOZORDER | SWP_NOACTIVATE); int btn_y = h - LOG_BTN_H + 4; - if (hSave) SetWindowPos(hSave, NULL, 6, btn_y, 90, 20, SWP_NOZORDER | SWP_NOACTIVATE); - if (hClr) SetWindowPos(hClr, NULL, 100, btn_y, 56, 20, SWP_NOZORDER | SWP_NOACTIVATE); + if (hSave) SetWindowPos(hSave, NULL, 6, btn_y, 90, 20, SWP_NOZORDER | SWP_NOACTIVATE); + if (hClr) SetWindowPos(hClr, NULL, 100, btn_y, 56, 20, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } @@ -493,9 +522,14 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) } case WM_COMMAND: - switch (LOWORD(wp)) { - case LOG_ID_BTN_SAVE: Log_SaveToFile(hwnd); break; - case LOG_ID_BTN_CLEAR: Log_Clear(); break; + switch (LOWORD(wp)) + { + case LOG_ID_BTN_SAVE: + Log_SaveToFile(hwnd); + break; + case LOG_ID_BTN_CLEAR: + Log_Clear(); + break; } return 0; @@ -523,18 +557,21 @@ static LRESULT CALLBACK LogWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* ================================================================== */ void Log_Show(void) { - if (g.hwnd_log && IsWindow(g.hwnd_log)) { + if (g.hwnd_log && IsWindow(g.hwnd_log)) + { ShowWindow(g.hwnd_log, SW_RESTORE); SetForegroundWindow(g.hwnd_log); return; } - if (!s_rich) { + if (!s_rich) + { s_rich = LoadLibraryW(L"RICHED20.DLL"); if (!s_rich) return; } - if (!s_font) { + if (!s_font) + { s_font = CreateFont( -12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, @@ -542,11 +579,11 @@ void Log_Show(void) } WNDCLASSEX wc = {0}; - wc.cbSize = sizeof(wc); - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = LogWndProc; - wc.hInstance = GetModuleHandle(NULL); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.cbSize = sizeof(wc); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = LogWndProc; + wc.hInstance = GetModuleHandle(NULL); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); /* WM_ERASEBKGND fills theme colour */ wc.lpszClassName = LOG_WNDCLASS; RegisterClassEx(&wc); /* subsequent calls fail silently — safe to call every time */ @@ -562,15 +599,18 @@ void Log_Show(void) Window_ApplyDarkMode(g.hwnd_log); /* match title bar chrome to app theme */ HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_APP_ICON)); - if (hIcon) { + if (hIcon) + { SendMessage(g.hwnd_log, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); - SendMessage(g.hwnd_log, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(g.hwnd_log, WM_SETICON, ICON_BIG, (LPARAM)hIcon); } /* Pre-populate from buffer and apply syntax highlighting */ - if (s_buf && s_buf_len > 0) { + if (s_buf && s_buf_len > 0) + { HWND hRE = GetDlgItem(g.hwnd_log, 1); - if (hRE) { + if (hRE) + { SendMessage(hRE, WM_SETTEXT, 0, (LPARAM)s_buf); Log_ColorizeNewLines(hRE, 0); /* colorize the pre-loaded history */ SendMessage(hRE, WM_VSCROLL, SB_BOTTOM, 0); @@ -594,7 +634,21 @@ void Log_Destroy(void) DestroyWindow(g.hwnd_log); g.hwnd_log = NULL; - if (s_buf) { free(s_buf); s_buf = NULL; s_buf_len = 0; s_buf_cap = 0; } - if (s_font) { DeleteObject(s_font); s_font = NULL; } - if (s_rich) { FreeLibrary(s_rich); s_rich = NULL; } + if (s_buf) + { + free(s_buf); + s_buf = NULL; + s_buf_len = 0; + s_buf_cap = 0; + } + if (s_font) + { + DeleteObject(s_font); + s_font = NULL; + } + if (s_rich) + { + FreeLibrary(s_rich); + s_rich = NULL; + } } diff --git a/src/main.c b/src/main.c index 8dff3c0..095bc88 100644 --- a/src/main.c +++ b/src/main.c @@ -29,9 +29,11 @@ static bool SystemIsDark(void); /* ================================================================== */ static LRESULT CALLBACK GlobalKbdHookProc(int nCode, WPARAM wp, LPARAM lp) { - if (nCode == HC_ACTION && wp == WM_KEYDOWN) { /* HC_ACTION = hook must process this event */ + if (nCode == HC_ACTION && wp == WM_KEYDOWN) + { /* HC_ACTION = hook must process this event */ KBDLLHOOKSTRUCT *kb = (KBDLLHOOKSTRUCT *)lp; - if (GetForegroundWindow() != g.hwnd) { /* only forward when our window doesn't have focus */ + if (GetForegroundWindow() != g.hwnd) + { /* only forward when our window doesn't have focus */ if (kb->vkCode == VK_ESCAPE && (g.repeat_mode || (!g.cfg.show_console && g.run_process))) PostMessage(g.hwnd, WM_KEYDOWN, VK_ESCAPE, 0); @@ -50,8 +52,8 @@ static LRESULT CALLBACK GlobalKbdHookProc(int nCode, WPARAM wp, LPARAM lp) /* ================================================================== */ void Repeat_Start(int fi, int si) { - g.repeat_fi = fi; - g.repeat_si = si; + g.repeat_fi = fi; + g.repeat_si = si; g.repeat_mode = true; } @@ -66,7 +68,7 @@ void Repeat_Stop(void) if (!g.repeat_mode) return; g.repeat_mode = false; HWND hBtn = GetDlgItem(g.hwnd_scroll, - IDC_SCRIPT_BTN_BASE + g.repeat_si); + IDC_SCRIPT_BTN_BASE + g.repeat_si); if (hBtn) InvalidateRect(hBtn, NULL, FALSE); if (g.hwnd_qbar) InvalidateRect(g.hwnd_qbar, NULL, FALSE); } @@ -92,10 +94,12 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, /* If another instance is already running, bring it to the front (or show it from tray) and exit this new instance. */ HANDLE hMutex = CreateMutex(NULL, TRUE, L"CatiaMenuWin32_SingleInstance"); - if (GetLastError() == ERROR_ALREADY_EXISTS) { /* mutex already held — another instance is running */ + if (GetLastError() == ERROR_ALREADY_EXISTS) + { /* mutex already held — another instance is running */ /* Find the existing window and restore it */ HWND hExisting = FindWindow(APP_CLASS, NULL); - if (hExisting) { + if (hExisting) + { /* If hidden (minimized to tray), show it */ if (!IsWindowVisible(hExisting)) ShowWindow(hExisting, SW_RESTORE); @@ -107,7 +111,7 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, INITCOMMONCONTROLSEX icc = { .dwSize = sizeof(icc), - .dwICC = ICC_TAB_CLASSES | ICC_BAR_CLASSES | ICC_STANDARD_CLASSES /* register common controls used by dialogs */ + .dwICC = ICC_TAB_CLASSES | ICC_BAR_CLASSES | ICC_STANDARD_CLASSES /* register common controls used by dialogs */ }; InitCommonControlsEx(&icc); @@ -124,15 +128,20 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, /* honor either cfg flag or /minimized command-line arg from autorun registry entry */ bool launch_minimized = g.cfg.start_minimized || - (lpCmd && wcsstr(lpCmd, L"/minimized")); + (lpCmd && wcsstr(lpCmd, L"/minimized")); /* Show window first, THEN apply topmost - SetWindowPos needs a visible window */ - if (launch_minimized && g.cfg.minimize_to_tray) { /* start hidden in tray */ + if (launch_minimized && g.cfg.minimize_to_tray) + { /* start hidden in tray */ ShowWindow(g.hwnd, SW_HIDE); Window_AddTrayIcon(); - } else if (launch_minimized) { /* start minimized to taskbar */ + } + else if (launch_minimized) + { /* start minimized to taskbar */ ShowWindow(g.hwnd, SW_SHOWMINIMIZED); - } else { + } + else + { ShowWindow(g.hwnd, nShow); UpdateWindow(g.hwnd); } @@ -149,37 +158,46 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, Prefs_Load(); Prefs_ApplyToFolders(); Tabs_BuildFavourites(); - if (g.folder_count > 0) { + if (g.folder_count > 0) + { Tabs_Build(); Tabs_Switch(0); } /* Start auto-refresh timer if configured */ - if (g.cfg.refresh_interval > 0) { + if (g.cfg.refresh_interval > 0) + { SetTimer(g.hwnd, TIMER_AUTO_REFRESH, g.cfg.refresh_interval * 3600 * 1000, NULL); /* 3600 * 1000 = convert hours to ms */ } - if (g.cfg.auto_sync) { + if (g.cfg.auto_sync) + { g.syncing = true; HANDLE hT = CreateThread(NULL, 0, Sync_Thread, NULL, 0, NULL); if (hT) CloseHandle(hT); - } else { + } + else + { SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)L"Auto-sync disabled. Press Refresh to sync."); } /* Check for app updates in background */ - if (g.cfg.check_updates) { + if (g.cfg.check_updates) + { HANDLE hU = CreateThread(NULL, 0, Updater_CheckThread, NULL, 0, NULL); if (hU) CloseHandle(hU); - } else if (!g.cfg.auto_sync) { + } + else if (!g.cfg.auto_sync) + { SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)L"Auto-sync disabled. Press Refresh to load scripts."); } MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) { + while (GetMessage(&msg, NULL, 0, 0) > 0) + { TranslateMessage(&msg); DispatchMessage(&msg); } @@ -199,11 +217,11 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, static bool SystemIsDark(void) { DWORD val = 1; /* default 1 = light; unchanged if the registry key is missing */ - DWORD sz = sizeof(val); + DWORD sz = sizeof(val); RegGetValue(HKEY_CURRENT_USER, - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", - L"AppsUseLightTheme", - RRF_RT_DWORD, NULL, &val, &sz); + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"AppsUseLightTheme", + RRF_RT_DWORD, NULL, &val, &sz); return (val == 0); /* AppsUseLightTheme = 0 means dark mode is on */ } @@ -217,10 +235,17 @@ static bool SystemIsDark(void) /* ================================================================== */ void App_ResolveTheme(void) { - switch (g.cfg.theme) { - case THEME_DARK: g.dark_mode = true; break; - case THEME_LIGHT: g.dark_mode = false; break; - default: g.dark_mode = SystemIsDark(); break; + switch (g.cfg.theme) + { + case THEME_DARK: + g.dark_mode = true; + break; + case THEME_LIGHT: + g.dark_mode = false; + break; + default: + g.dark_mode = SystemIsDark(); + break; } } @@ -236,7 +261,7 @@ void App_BuildAppDataPath(void) { WCHAR appdata[MAX_APPPATH] = {0}; SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, appdata); - _snwprintf_s(g.appdata_dir, MAX_APPPATH , _TRUNCATE, L"%s\\%s", appdata, APP_APPDATA_DIR); + _snwprintf_s(g.appdata_dir, MAX_APPPATH, _TRUNCATE, L"%s\\%s", appdata, APP_APPDATA_DIR); SHCreateDirectoryEx(NULL, g.appdata_dir, NULL); } @@ -250,24 +275,24 @@ void App_BuildAppDataPath(void) /* ================================================================== */ void App_InitGDI(void) { - g.font_ui = CreateFont(-13,0,0,0,FW_NORMAL, 0,0,0,DEFAULT_CHARSET, /* -13 logical units ≈ 10 pt at 96 DPI */ - OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS, - CLEARTYPE_QUALITY,DEFAULT_PITCH|FF_SWISS,L"Segoe UI"); - g.font_bold = CreateFont(-13,0,0,0,FW_SEMIBOLD,0,0,0,DEFAULT_CHARSET, - OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS, - CLEARTYPE_QUALITY,DEFAULT_PITCH|FF_SWISS,L"Segoe UI"); - g.font_small = CreateFont(-11,0,0,0,FW_NORMAL, 0,0,0,DEFAULT_CHARSET, /* -11 logical units ≈ 8 pt at 96 DPI */ - OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS, - CLEARTYPE_QUALITY,DEFAULT_PITCH|FF_SWISS,L"Segoe UI"); - - g.br_bg = CreateSolidBrush(COL_BG()); + g.font_ui = CreateFont(-13, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, /* -13 logical units ≈ 10 pt at 96 DPI */ + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI"); + g.font_bold = CreateFont(-13, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI"); + g.font_small = CreateFont(-11, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, /* -11 logical units ≈ 8 pt at 96 DPI */ + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI"); + + g.br_bg = CreateSolidBrush(COL_BG()); g.br_toolbar = CreateSolidBrush(COL_TOOLBAR()); - g.br_btn = CreateSolidBrush(COL_BTN_NORM()); + g.br_btn = CreateSolidBrush(COL_BTN_NORM()); g.br_btn_hot = CreateSolidBrush(COL_BTN_HOT()); - g.br_accent = CreateSolidBrush(COL_ACCENT); - g.br_status = CreateSolidBrush(COL_TOOLBAR()); - g.hot_btn = -1; /* -1 = no button is currently hot */ - g.tip_btn = -1; /* -1 = no tooltip is currently shown */ + g.br_accent = CreateSolidBrush(COL_ACCENT); + g.br_status = CreateSolidBrush(COL_TOOLBAR()); + g.hot_btn = -1; /* -1 = no button is currently hot */ + g.tip_btn = -1; /* -1 = no tooltip is currently shown */ } /* ================================================================== */ @@ -280,11 +305,15 @@ void App_InitGDI(void) /* ================================================================== */ void App_FreeGDI(void) { - DeleteObject(g.font_ui); DeleteObject(g.font_bold); + DeleteObject(g.font_ui); + DeleteObject(g.font_bold); DeleteObject(g.font_small); - DeleteObject(g.br_bg); DeleteObject(g.br_toolbar); - DeleteObject(g.br_btn); DeleteObject(g.br_btn_hot); - DeleteObject(g.br_accent); DeleteObject(g.br_status); + DeleteObject(g.br_bg); + DeleteObject(g.br_toolbar); + DeleteObject(g.br_btn); + DeleteObject(g.br_btn_hot); + DeleteObject(g.br_accent); + DeleteObject(g.br_status); } /* ================================================================== */ @@ -299,16 +328,19 @@ void App_FreeGDI(void) void App_RebuildGDI(void) { /* Delete brushes (not fonts) and recreate with new colours */ - DeleteObject(g.br_bg); DeleteObject(g.br_toolbar); - DeleteObject(g.br_btn); DeleteObject(g.br_btn_hot); - DeleteObject(g.br_accent); DeleteObject(g.br_status); - - g.br_bg = CreateSolidBrush(COL_BG()); + DeleteObject(g.br_bg); + DeleteObject(g.br_toolbar); + DeleteObject(g.br_btn); + DeleteObject(g.br_btn_hot); + DeleteObject(g.br_accent); + DeleteObject(g.br_status); + + g.br_bg = CreateSolidBrush(COL_BG()); g.br_toolbar = CreateSolidBrush(COL_TOOLBAR()); - g.br_btn = CreateSolidBrush(COL_BTN_NORM()); + g.br_btn = CreateSolidBrush(COL_BTN_NORM()); g.br_btn_hot = CreateSolidBrush(COL_BTN_HOT()); - g.br_accent = CreateSolidBrush(COL_ACCENT); - g.br_status = CreateSolidBrush(COL_TOOLBAR()); + g.br_accent = CreateSolidBrush(COL_ACCENT); + g.br_status = CreateSolidBrush(COL_TOOLBAR()); } /* ================================================================== */ @@ -329,11 +361,17 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { case WM_KEYDOWN: /* F1 opens help */ - if (wp == VK_F1) { Help_Show(); return 0; } + if (wp == VK_F1) + { + Help_Show(); + return 0; + } /* Escape cancels repeat mode and/or stops a running background script */ if (wp == VK_ESCAPE && - (g.repeat_mode || (!g.cfg.show_console && g.run_process))) { - if (g.repeat_mode) { + (g.repeat_mode || (!g.cfg.show_console && g.run_process))) + { + if (g.repeat_mode) + { Repeat_Stop(); PostStatus(L"Repeat cancelled."); } @@ -342,18 +380,28 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) return 0; } /* Ctrl+Tab / Ctrl+Shift+Tab to switch tabs */ - if (GetKeyState(VK_CONTROL) & 0x8000) { /* high bit set = key is held down */ - if (wp == VK_TAB) { + if (GetKeyState(VK_CONTROL) & 0x8000) + { /* high bit set = key is held down */ + if (wp == VK_TAB) + { bool shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; int next = g.active_tab + (shift ? -1 : 1); if (next < 0) next = g.folder_count - 1; /* wrap left */ - if (next >= g.folder_count) next = 0; /* wrap right */ + if (next >= g.folder_count) next = 0; /* wrap right */ Tabs_Switch(next); return 0; } } - if (wp == VK_F5) { SendMessage(hwnd, WM_COMMAND, IDM_REFRESH, 0); return 0; } - if (wp == VK_F9) { SendMessage(hwnd, WM_COMMAND, IDM_RUN_LAST, 0); return 0; } + if (wp == VK_F5) + { + SendMessage(hwnd, WM_COMMAND, IDM_REFRESH, 0); + return 0; + } + if (wp == VK_F9) + { + SendMessage(hwnd, WM_COMMAND, IDM_RUN_LAST, 0); + return 0; + } break; case WM_ERASEBKGND: @@ -369,7 +417,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) } case WM_SIZE: - if (wp == SIZE_MINIMIZED && g.cfg.minimize_to_tray) { /* intercept minimize → hide to tray instead */ + if (wp == SIZE_MINIMIZED && g.cfg.minimize_to_tray) + { /* intercept minimize → hide to tray instead */ Window_AddTrayIcon(); ShowWindow(hwnd, SW_HIDE); return 0; @@ -388,7 +437,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* System colour/theme change */ case WM_TIMER: - if (wp == TIMER_AUTO_REFRESH && !g.syncing) { + if (wp == TIMER_AUTO_REFRESH && !g.syncing) + { g.syncing = true; HANDLE hT = CreateThread(NULL, 0, Sync_Thread, NULL, 0, NULL); if (hT) CloseHandle(hT); @@ -396,7 +446,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) break; case WM_SETTINGCHANGE: - if (g.cfg.theme == THEME_SYSTEM) { + if (g.cfg.theme == THEME_SYSTEM) + { App_ResolveTheme(); App_RebuildGDI(); Window_ApplyDarkMode(hwnd); @@ -408,7 +459,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) break; case WM_COMMAND: - if (LOWORD(wp) == IDC_SEARCH && HIWORD(wp) == EN_CHANGE) { /* EN_CHANGE fires as the user types */ + if (LOWORD(wp) == IDC_SEARCH && HIWORD(wp) == EN_CHANGE) + { /* EN_CHANGE fires as the user types */ GetWindowText(g.hwnd_search, g.filter_text, MAX_NAME - 1); Tabs_ApplyFilter(); return 0; @@ -423,14 +475,16 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case WM_DRAWITEM: { DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lp; - if (dis->CtlType == ODT_BUTTON) { /* ODT_BUTTON = owner-draw button; skip list views, static controls etc. */ + if (dis->CtlType == ODT_BUTTON) + { /* ODT_BUTTON = owner-draw button; skip list views, static controls etc. */ int id = (int)dis->CtlID; if (id == IDC_BTN_MENU || id == IDC_BTN_REFRESH || id == IDC_BTN_SETTINGS || id == IDC_BTN_UPDATE_DEPS || id == IDC_BTN_STOP || - id == IDC_BTN_LOG) { + id == IDC_BTN_LOG) + { Paint_ToolbarButton(dis); return TRUE; } @@ -438,8 +492,6 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) break; } - - /* Dark-mode aware child colouring */ case WM_CTLCOLORSTATIC: { @@ -466,10 +518,13 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* System tray */ case WM_TRAYICON: - if (lp == WM_LBUTTONDBLCLK) { /* double-click restores from tray */ + if (lp == WM_LBUTTONDBLCLK) + { /* double-click restores from tray */ ShowWindow(hwnd, SW_RESTORE); SetForegroundWindow(hwnd); - } else if (lp == WM_RBUTTONUP) { /* right-click shows tray context menu */ + } + else if (lp == WM_RBUTTONUP) + { /* right-click shows tray context menu */ Window_ShowTrayMenu(); } return 0; @@ -489,7 +544,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case WM_STATUS_SET: { WCHAR *text = (WCHAR *)lp; - if (text) { + if (text) + { SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)text); free(text); } @@ -500,7 +556,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { /* lp = heap-allocated WCHAR* from LogReader_Thread; we own it and must free it */ WCHAR *text = (WCHAR *)lp; - if (text) { + if (text) + { Log_Append(text); free(text); } @@ -520,7 +577,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) const WCHAR *sname = (g.run_fi >= 0 && g.run_fi < g.folder_count && g.run_si >= 0 && g.run_si < g.folders[g.run_fi].count) - ? g.folders[g.run_fi].scripts[g.run_si].name : L"Script"; + ? g.folders[g.run_fi].scripts[g.run_si].name + : L"Script"; Log_AddHeader(sname); } return 0; @@ -536,23 +594,32 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* Append a completion footer to the log. lp=1 = Runner_Stop was called (user terminated); lp=0 = natural exit; wp = exit code. */ - if (lp) { + if (lp) + { Log_Append(L"--- Stopped by user. ---\r\n\r\n"); - } else if (wp == 0) { + } + else if (wp == 0) + { Log_Append(L"--- Finished successfully. ---\r\n\r\n"); - } else { + } + else + { WCHAR _lf[80]; _snwprintf_s(_lf, 79, _TRUNCATE, L"--- Exited with code %llu. ---\r\n\r\n", (unsigned long long)wp); Log_Append(_lf); } - if (g.repeat_mode) { - if (wp != 0) { /* non-zero exit code = script failed; stop repeating */ + if (g.repeat_mode) + { + if (wp != 0) + { /* non-zero exit code = script failed; stop repeating */ Repeat_Stop(); PostStatus(L"Repeat cancelled: script exited with error (code %llu).", (unsigned long long)wp); - } else { + } + else + { Runner_Run(g.repeat_fi, g.repeat_si); } } @@ -565,7 +632,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) break; case WM_CLOSE: - if (g.cfg.minimize_to_tray && wp == 0) { /* wp == 0 = normal close; intercept and hide to tray instead */ + if (g.cfg.minimize_to_tray && wp == 0) + { /* wp == 0 = normal close; intercept and hide to tray instead */ Window_AddTrayIcon(); ShowWindow(hwnd, SW_HIDE); return 0; @@ -576,7 +644,8 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) return 0; case WM_DESTROY: - if (g.kbd_repeat_hook) { + if (g.kbd_repeat_hook) + { UnhookWindowsHookEx(g.kbd_repeat_hook); g.kbd_repeat_hook = NULL; } @@ -605,10 +674,12 @@ static void Handle_Command(WPARAM wp) { int id = LOWORD(wp); - if (id >= IDC_SCRIPT_BTN_BASE && id < IDC_SCRIPT_BTN_BASE + MAX_SCRIPTS) { + if (id >= IDC_SCRIPT_BTN_BASE && id < IDC_SCRIPT_BTN_BASE + MAX_SCRIPTS) + { int fi = g.active_tab; int si = id - IDC_SCRIPT_BTN_BASE; - if (g.repeat_mode) { + if (g.repeat_mode) + { bool same = (g.repeat_fi == fi && g.repeat_si == si); /* true if clicking the script that is repeating */ Repeat_Stop(); if (same) return; /* single-click repeating script = cancel without re-run */ @@ -625,7 +696,8 @@ static void Handle_Command(WPARAM wp) case IDM_REFRESH: case IDC_BTN_REFRESH: - if (!g.syncing) { + if (!g.syncing) + { g.syncing = true; for (int i = 0; i < g.folder_count; i++) g.folders[i].loaded = false; /* mark stale so Sync_Thread fetches fresh data */ @@ -642,9 +714,11 @@ static void Handle_Command(WPARAM wp) case IDM_SOURCES: if (DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_SOURCES), - g.hwnd, SourcesDlgProc) == IDOK) { + g.hwnd, SourcesDlgProc) == IDOK) + { /* Re-sync with new sources */ - if (!g.syncing) { + if (!g.syncing) + { g.syncing = true; HANDLE hT = CreateThread(NULL, 0, Sync_Thread, NULL, 0, NULL); if (hT) CloseHandle(hT); @@ -656,21 +730,21 @@ static void Handle_Command(WPARAM wp) g.cfg.always_on_top = !g.cfg.always_on_top; Window_ApplyAlwaysOnTop(); CheckMenuItem(GetMenu(g.hwnd), IDM_ALWAYS_ON_TOP, - g.cfg.always_on_top ? MF_CHECKED : MF_UNCHECKED); + g.cfg.always_on_top ? MF_CHECKED : MF_UNCHECKED); Settings_Save(&g.cfg); break; case IDM_MINIMIZE_TO_TRAY: g.cfg.minimize_to_tray = !g.cfg.minimize_to_tray; CheckMenuItem(GetMenu(g.hwnd), IDM_MINIMIZE_TO_TRAY, - g.cfg.minimize_to_tray ? MF_CHECKED : MF_UNCHECKED); + g.cfg.minimize_to_tray ? MF_CHECKED : MF_UNCHECKED); Settings_Save(&g.cfg); break; case IDM_START_WITH_WINDOWS: g.cfg.start_with_windows = !g.cfg.start_with_windows; CheckMenuItem(GetMenu(g.hwnd), IDM_START_WITH_WINDOWS, - g.cfg.start_with_windows ? MF_CHECKED : MF_UNCHECKED); + g.cfg.start_with_windows ? MF_CHECKED : MF_UNCHECKED); Settings_ApplyAutorun(g.cfg.start_with_windows, g.cfg.start_minimized); Settings_Save(&g.cfg); break; @@ -678,7 +752,7 @@ static void Handle_Command(WPARAM wp) case IDM_START_MINIMIZED: g.cfg.start_minimized = !g.cfg.start_minimized; CheckMenuItem(GetMenu(g.hwnd), IDM_START_MINIMIZED, - g.cfg.start_minimized ? MF_CHECKED : MF_UNCHECKED); + g.cfg.start_minimized ? MF_CHECKED : MF_UNCHECKED); if (g.cfg.start_with_windows) Settings_ApplyAutorun(true, g.cfg.start_minimized); Settings_Save(&g.cfg); @@ -692,7 +766,7 @@ static void Handle_Command(WPARAM wp) goto apply_theme; case IDM_THEME_SYSTEM: g.cfg.theme = THEME_SYSTEM; -apply_theme: /* shared apply: resolve, rebuild GDI, repaint */ + apply_theme: /* shared apply: resolve, rebuild GDI, repaint */ App_ResolveTheme(); App_RebuildGDI(); Window_ApplyDarkMode(g.hwnd); @@ -700,22 +774,24 @@ static void Handle_Command(WPARAM wp) QuickBar_OnThemeChange(); /* Update menu checkmarks */ CheckMenuItem(GetMenu(g.hwnd), IDM_THEME_DARK, - g.cfg.theme == THEME_DARK ? MF_CHECKED : MF_UNCHECKED); + g.cfg.theme == THEME_DARK ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(GetMenu(g.hwnd), IDM_THEME_LIGHT, - g.cfg.theme == THEME_LIGHT ? MF_CHECKED : MF_UNCHECKED); + g.cfg.theme == THEME_LIGHT ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(GetMenu(g.hwnd), IDM_THEME_SYSTEM, - g.cfg.theme == THEME_SYSTEM ? MF_CHECKED : MF_UNCHECKED); + g.cfg.theme == THEME_SYSTEM ? MF_CHECKED : MF_UNCHECKED); Tabs_RebuildButtons(); InvalidateRect(g.hwnd, NULL, TRUE); Settings_Save(&g.cfg); break; case IDM_RUN_LAST: - if (g.last_run_path[0]) { + if (g.last_run_path[0]) + { for (int fi = 0; fi < g.folder_count; fi++) for (int si = 0; si < g.folders[fi].count; si++) if (wcscmp(g.folders[fi].scripts[si].gh_path, - g.last_run_path) == 0) { + g.last_run_path) == 0) + { Runner_Run(fi, si); return; } @@ -728,22 +804,22 @@ static void Handle_Command(WPARAM wp) case IDM_GITHUB: ShellExecute(NULL, L"open", - L"https://github.com/KaiUR/CatiaMenuWin32", NULL, NULL, SW_SHOW); + L"https://github.com/KaiUR/CatiaMenuWin32", NULL, NULL, SW_SHOW); break; case IDM_GITHUB_PAGES: ShellExecute(NULL, L"open", - L"https://kaiur.github.io/CatiaMenuWin32/", NULL, NULL, SW_SHOW); + L"https://kaiur.github.io/CatiaMenuWin32/", NULL, NULL, SW_SHOW); break; case IDM_WIKI: ShellExecute(NULL, L"open", - L"https://github.com/KaiUR/CatiaMenuWin32/wiki", NULL, NULL, SW_SHOW); + L"https://github.com/KaiUR/CatiaMenuWin32/wiki", NULL, NULL, SW_SHOW); break; case IDM_WIKI_SCRIPTS: ShellExecute(NULL, L"open", - L"https://github.com/KaiUR/Pycatia_Scripts/wiki", NULL, NULL, SW_SHOW); + L"https://github.com/KaiUR/Pycatia_Scripts/wiki", NULL, NULL, SW_SHOW); break; case IDM_OPEN_EXE_FOLDER: @@ -780,7 +856,6 @@ static void Handle_Command(WPARAM wp) Tabs_RebuildButtons(); break; - /* ── Quick Launch Bar ──────────────────────────────────────────── */ case IDM_QBAR_TOGGLE: g.cfg.qbar_enabled = !g.cfg.qbar_enabled; @@ -789,7 +864,8 @@ static void Handle_Command(WPARAM wp) break; case IDM_QBAR_HORIZONTAL: - if (!g.cfg.qbar_horizontal) { + if (!g.cfg.qbar_horizontal) + { g.cfg.qbar_horizontal = true; /* Recreate bar with new orientation */ QuickBar_Destroy(); @@ -801,7 +877,8 @@ static void Handle_Command(WPARAM wp) break; case IDM_QBAR_VERTICAL: - if (g.cfg.qbar_horizontal) { + if (g.cfg.qbar_horizontal) + { g.cfg.qbar_horizontal = false; QuickBar_Destroy(); QuickBar_Register(GetModuleHandle(NULL)); @@ -820,13 +897,14 @@ static void Handle_Command(WPARAM wp) case IDM_QBAR_RESET_POS: { - RECT work; SystemParametersInfo(SPI_GETWORKAREA, 0, &work, 0); + RECT work; + SystemParametersInfo(SPI_GETWORKAREA, 0, &work, 0); g.cfg.qbar_x = work.right - (QBAR_BTN_SIZE + 2 * QBAR_PAD + 2) - 20; /* 20 px inset from right edge */ g.cfg.qbar_y = work.top + 60; /* 60 px from top of work area */ if (g.hwnd_qbar) SetWindowPos(g.hwnd_qbar, NULL, - g.cfg.qbar_x, g.cfg.qbar_y, 0, 0, - SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + g.cfg.qbar_x, g.cfg.qbar_y, 0, 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); Settings_Save(&g.cfg); break; } @@ -849,7 +927,7 @@ static void Handle_Command(WPARAM wp) case IDM_GITHUB_SCRIPTS: ShellExecute(NULL, L"open", - L"https://github.com/KaiUR/Pycatia_Scripts", NULL, NULL, SW_SHOW); + L"https://github.com/KaiUR/Pycatia_Scripts", NULL, NULL, SW_SHOW); break; case IDM_CHECK_UPDATES: @@ -868,10 +946,15 @@ static void Handle_Command(WPARAM wp) WCHAR win_ver[64] = {0}; OSVERSIONINFOEXW osvi = {0}; osvi.dwOSVersionInfoSize = sizeof(osvi); - typedef LONG (WINAPI *RtlGetVersion_t)(OSVERSIONINFOEXW *); + typedef LONG(WINAPI * RtlGetVersion_t)(OSVERSIONINFOEXW *); HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); - if (hNtdll) { - union { FARPROC proc; RtlGetVersion_t fn; } u; + if (hNtdll) + { + union + { + FARPROC proc; + RtlGetVersion_t fn; + } u; u.proc = GetProcAddress(hNtdll, "RtlGetVersion"); /* RtlGetVersion returns the true version; GetVersionEx caps at 8.1 */ if (u.fn) u.fn(&osvi); } @@ -884,58 +967,121 @@ static void Handle_Command(WPARAM wp) /* Build body - all on one line to avoid newline corruption */ WCHAR body[2048] = {0}; _snwprintf_s(body, 2047, _TRUNCATE, - L"## Description\n" - L"A clear description of the bug.\n\n" - L"## Steps to Reproduce\n" - L"1. \n2. \n3. \n\n" - L"## Expected Behaviour\n\n" - L"## Actual Behaviour\n\n" - L"## Environment\n" - L"- App version: v%s\n" - L"- Windows: %s\n" - L"- Python: %s\n" - L"- Theme: %s\n\n" - L"## Additional Context\n", - VERSION_STRING_W, - win_ver, - python[0] ? python : L"Not found", - g.dark_mode ? L"Dark" : L"Light"); + L"## Description\n" + L"A clear description of the bug.\n\n" + L"## Steps to Reproduce\n" + L"1. \n2. \n3. \n\n" + L"## Expected Behaviour\n\n" + L"## Actual Behaviour\n\n" + L"## Environment\n" + L"- App version: v%s\n" + L"- Windows: %s\n" + L"- Python: %s\n" + L"- Theme: %s\n\n" + L"## Additional Context\n", + VERSION_STRING_W, + win_ver, + python[0] ? python : L"Not found", + g.dark_mode ? L"Dark" : L"Light"); /* URL-encode the body (percent-encode all chars that are unsafe in a query value) */ WCHAR encoded[3072] = {0}; WCHAR *dst = encoded; - for (const WCHAR *src = body; *src && dst < encoded + 3067; src++) { /* 3067 = 3072 - 5 = room for one 3-char %XX sequence plus null */ + for (const WCHAR *src = body; *src && dst < encoded + 3067; src++) + { /* 3067 = 3072 - 5 = room for one 3-char %XX sequence plus null */ WCHAR ch = *src; - /* Encode each unsafe character as %XX (using 3 output slots). */ - #define PENC(hi, lo) do { *dst++=L'%'; *dst++=(hi); *dst++=(lo); } while(0) - if (ch == L' ') { *dst++ = L'+'; } - else if (ch == 13) { /* skip CR — LF is encoded as %0A below */ } - else if (ch == 10) { PENC(L'0',L'A'); } - else if (ch == L'%') { PENC(L'2',L'5'); } - else if (ch == L'"') { PENC(L'2',L'2'); } - else if (ch == L'#') { PENC(L'2',L'3'); } - else if (ch == L'&') { PENC(L'2',L'6'); } - else if (ch == L'+') { PENC(L'2',L'B'); } - else if (ch == L'/') { PENC(L'2',L'F'); } - else if (ch == L':') { PENC(L'3',L'A'); } - else if (ch == L'<') { PENC(L'3',L'C'); } - else if (ch == L'=') { PENC(L'3',L'D'); } - else if (ch == L'>') { PENC(L'3',L'E'); } - else if (ch == L'?') { PENC(L'3',L'F'); } - else if (ch == L'[') { PENC(L'5',L'B'); } - else if (ch == L']') { PENC(L'5',L'D'); } - else if (ch == L'*') { PENC(L'2',L'A'); } - else if (ch == L'|') { PENC(L'7',L'C'); } - else { *dst++ = ch; } - #undef PENC +/* Encode each unsafe character as %XX (using 3 output slots). */ +#define PENC(hi, lo) \ + do \ + { \ + *dst++ = L'%'; \ + *dst++ = (hi); \ + *dst++ = (lo); \ + } while (0) + if (ch == L' ') + { + *dst++ = L'+'; + } + else if (ch == 13) + { /* skip CR — LF is encoded as %0A below */ + } + else if (ch == 10) + { + PENC(L'0', L'A'); + } + else if (ch == L'%') + { + PENC(L'2', L'5'); + } + else if (ch == L'"') + { + PENC(L'2', L'2'); + } + else if (ch == L'#') + { + PENC(L'2', L'3'); + } + else if (ch == L'&') + { + PENC(L'2', L'6'); + } + else if (ch == L'+') + { + PENC(L'2', L'B'); + } + else if (ch == L'/') + { + PENC(L'2', L'F'); + } + else if (ch == L':') + { + PENC(L'3', L'A'); + } + else if (ch == L'<') + { + PENC(L'3', L'C'); + } + else if (ch == L'=') + { + PENC(L'3', L'D'); + } + else if (ch == L'>') + { + PENC(L'3', L'E'); + } + else if (ch == L'?') + { + PENC(L'3', L'F'); + } + else if (ch == L'[') + { + PENC(L'5', L'B'); + } + else if (ch == L']') + { + PENC(L'5', L'D'); + } + else if (ch == L'*') + { + PENC(L'2', L'A'); + } + else if (ch == L'|') + { + PENC(L'7', L'C'); + } + else + { + *dst++ = ch; + } +#undef PENC } *dst = L'\0'; WCHAR url[4096] = {0}; _snwprintf_s(url, 4095, _TRUNCATE, - L"https://github.com/KaiUR/CatiaMenuWin32/issues/new" - L"?template=bug_report.md&title=&body=%s", - encoded); + L"https://github.com/KaiUR/CatiaMenuWin32/issues/new" + L"?template=bug_report.md&title=&body=%s", + encoded); ShellExecute(NULL, L"open", url, NULL, NULL, SW_SHOW); break; @@ -982,16 +1128,20 @@ static void Handle_SyncDone(SyncResult *sr) g.syncing = false; if (!sr) return; - if (sr->status == SR_NO_INTERNET) { + if (sr->status == SR_NO_INTERNET) + { g.status_offline = true; InvalidateRect(g.hwnd_status, NULL, FALSE); SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)sr->message); - if (!g.cfg.offline_use_cache) { /* user opted out — clear UI */ + if (!g.cfg.offline_use_cache) + { /* user opted out — clear UI */ Tabs_Build(); Tabs_DestroyButtons(); - InvalidateRect(g.hwnd_tab, NULL, TRUE); + InvalidateRect(g.hwnd_tab, NULL, TRUE); InvalidateRect(g.hwnd_scroll, NULL, TRUE); - } else { + } + else + { /* Rebuild UI from the in-memory cached state. Guarantees buttons are visible even if they were destroyed during the sync, picks up any local-dir scripts merged by Sync_LocalDir, and triggers the red @@ -1004,22 +1154,30 @@ static void Handle_SyncDone(SyncResult *sr) Prefs_ApplyToFolders(); Tabs_BuildFavourites(); Tabs_Build(); - if (g.folder_count > 0) { + if (g.folder_count > 0) + { int restore = 0; - if (g.active_folder_name[0]) { - for (int fi = 0; fi < g.folder_count; fi++) { - if (wcscmp(g.folders[fi].name, g.active_folder_name) == 0) { + if (g.active_folder_name[0]) + { + for (int fi = 0; fi < g.folder_count; fi++) + { + if (wcscmp(g.folders[fi].name, g.active_folder_name) == 0) + { restore = fi; break; } } - } else { + } + else + { restore = (g.active_tab < g.folder_count) ? g.active_tab : 0; } Tabs_Switch(restore); - } else { + } + else + { Tabs_DestroyButtons(); - InvalidateRect(g.hwnd_tab, NULL, TRUE); + InvalidateRect(g.hwnd_tab, NULL, TRUE); InvalidateRect(g.hwnd_scroll, NULL, TRUE); } InvalidateRect(g.hwnd, NULL, FALSE); @@ -1038,22 +1196,30 @@ static void Handle_SyncDone(SyncResult *sr) Prefs_ApplyToFolders(); Tabs_BuildFavourites(); Tabs_Build(); - if (g.folder_count == 0) { + if (g.folder_count == 0) + { Tabs_DestroyButtons(); - InvalidateRect(g.hwnd_tab, NULL, TRUE); + InvalidateRect(g.hwnd_tab, NULL, TRUE); InvalidateRect(g.hwnd_scroll, NULL, TRUE); - } else { + } + else + { /* Restore the previously active folder by name so the tab doesn't drift when Tabs_BuildFavourites inserts/removes the Favourites tab at index 0. */ int restore = 0; - if (g.active_folder_name[0]) { - for (int fi = 0; fi < g.folder_count; fi++) { - if (wcscmp(g.folders[fi].name, g.active_folder_name) == 0) { + if (g.active_folder_name[0]) + { + for (int fi = 0; fi < g.folder_count; fi++) + { + if (wcscmp(g.folders[fi].name, g.active_folder_name) == 0) + { restore = fi; break; } } - } else { + } + else + { restore = (g.active_tab < g.folder_count) ? g.active_tab : 0; /* fallback: keep current tab if valid */ } Tabs_Switch(restore); diff --git a/src/meta.c b/src/meta.c index 3059ae9..4484ddd 100644 --- a/src/meta.c +++ b/src/meta.c @@ -8,7 +8,7 @@ #include "main.h" -#define DESC_MAX 1023 /* ScriptMeta.description is [1024], so max writable index is 1023 */ +#define DESC_MAX 1023 /* ScriptMeta.description is [1024], so max writable index is 1023 */ /* ================================================================== */ /* TrimRight (static) */ @@ -20,8 +20,8 @@ static void TrimRight(WCHAR *s) { int n = (int)wcslen(s); - while (n > 0 && (s[n-1]==L'\r'||s[n-1]==L'\n'|| - s[n-1]==L' ' ||s[n-1]==L'\t')) + while (n > 0 && (s[n - 1] == L'\r' || s[n - 1] == L'\n' || + s[n - 1] == L' ' || s[n - 1] == L'\t')) s[--n] = L'\0'; } @@ -36,9 +36,10 @@ static void TrimRight(WCHAR *s) static void StripLeading(WCHAR *s) { WCHAR *p = s; - while (*p==L' '||*p==L'\t'||*p==L'#'|| - *p==L'\''||*p==L'"'||*p==L'-') p++; - if (p != s) memmove_s(s, (wcslen(p)+1)*sizeof(WCHAR), p, (wcslen(p)+1)*sizeof(WCHAR)); + while (*p == L' ' || *p == L'\t' || *p == L'#' || + *p == L'\'' || *p == L'"' || *p == L'-') + p++; + if (p != s) memmove_s(s, (wcslen(p) + 1) * sizeof(WCHAR), p, (wcslen(p) + 1) * sizeof(WCHAR)); } /* ================================================================== */ @@ -57,10 +58,12 @@ static const WCHAR *MatchKey(const WCHAR *line, const WCHAR *key) size_t klen = wcslen(key); if (_wcsnicmp(line, key, klen) != 0) return NULL; const WCHAR *p = line + klen; - while (*p==L' '||*p==L'\t') p++; + while (*p == L' ' || *p == L'\t') + p++; if (*p != L':') return NULL; p++; - while (*p==L' '||*p==L'\t') p++; + while (*p == L' ' || *p == L'\t') + p++; return (*p) ? p : NULL; } @@ -79,7 +82,11 @@ static void AppendDesc(WCHAR *buf, const WCHAR *text) int cur = (int)wcslen(buf); if (cur >= DESC_MAX) return; /* Add a space separator if buffer not empty */ - if (cur > 0) { buf[cur++]=L' '; buf[cur]=L'\0'; } /* cur < DESC_MAX guaranteed by guard above */ + if (cur > 0) + { + buf[cur++] = L' '; + buf[cur] = L'\0'; + } /* cur < DESC_MAX guaranteed by guard above */ wcsncat_s(buf, DESC_MAX + 1, text, _TRUNCATE); } @@ -95,123 +102,163 @@ static void AppendDesc(WCHAR *buf, const WCHAR *text) /* ================================================================== */ void Meta_Parse(Script *s) { - if (s->meta_loaded) return; /* already parsed — nothing to do */ - if (!s->local[0]) return; /* no local path yet (not downloaded)*/ + if (s->meta_loaded) return; /* already parsed — nothing to do */ + if (!s->local[0]) return; /* no local path yet (not downloaded)*/ if (GetFileAttributes(s->local) == INVALID_FILE_ATTRIBUTES) return; /* file missing — INVALID_FILE_ATTRIBUTES is the sentinel for "not found" */ FILE *f = _wfopen(s->local, L"r, ccs=UTF-8"); /* try UTF-8 first (script headers are ASCII-safe) */ - if (!f) f = _wfopen(s->local, L"r"); /* fall back to system default encoding if UTF-8 open fails */ - if (!f) return; /* file exists but cannot be opened (e.g. locked) */ + if (!f) f = _wfopen(s->local, L"r"); /* fall back to system default encoding if UTF-8 open fails */ + if (!f) return; /* file exists but cannot be opened (e.g. locked) */ ScriptMeta m; ZeroMemory(&m, sizeof(m)); WCHAR raw[1024], line[1024]; - int lineno = 0; - bool in_header = false; - bool in_desc = false; - bool found_any = false; + int lineno = 0; + bool in_header = false; + bool in_desc = false; + bool found_any = false; while (lineno < 200 && fgetws(raw, 1024, f)) /* 200-line cap: header is always near the top; avoids reading whole file */ { lineno++; TrimRight(raw); wcsncpy_s(line, 1024, raw, _TRUNCATE); - StripLeading(line); /* strip comment chars (#, ', ", -) from the raw line copy */ + StripLeading(line); /* strip comment chars (#, ', ", -) from the raw line copy */ TrimRight(line); /* Detect dashed separator line — a run of 10+ dashes with no other content */ bool is_dashes = false; { int dash_count = 0, other = 0; - for (WCHAR *p = raw; *p && *p!=L'\r' && *p!=L'\n'; p++) { - if (*p==L'-') dash_count++; - else if (*p!=L' '&&*p!=L'\t'&&*p!=L'\''&&*p!=L'"') other++; + for (WCHAR *p = raw; *p && *p != L'\r' && *p != L'\n'; p++) + { + if (*p == L'-') + dash_count++; + else if (*p != L' ' && *p != L'\t' && *p != L'\'' && *p != L'"') + other++; } is_dashes = (dash_count >= 10 && other == 0); /* 10 = minimum dash run to be a separator */ } - if (is_dashes) { - if (!in_header) { in_header = true; } /* first separator = start of header block */ - else { break; } /* second separator = end of header; stop */ + if (is_dashes) + { + if (!in_header) + { + in_header = true; + } /* first separator = start of header block */ + else + { + break; + } /* second separator = end of header; stop */ continue; } if (!in_header) continue; /* skip lines before the opening separator */ /* Stop if we reach actual Python code — the header is over */ - if (_wcsnicmp(line, L"import ", 7)==0 || /* length includes the trailing space */ - _wcsnicmp(line, L"from ", 5)==0 || - _wcsnicmp(line, L"def ", 4)==0 || - _wcsnicmp(line, L"class ", 6)==0 || - _wcsnicmp(line, L"Change", 6)==0) break; + if (_wcsnicmp(line, L"import ", 7) == 0 || /* length includes the trailing space */ + _wcsnicmp(line, L"from ", 5) == 0 || + _wcsnicmp(line, L"def ", 4) == 0 || + _wcsnicmp(line, L"class ", 6) == 0 || + _wcsnicmp(line, L"Change", 6) == 0) break; /* Skip blank lines */ if (line[0] == L'\0') continue; const WCHAR *val = NULL; - if (MatchKey(line, L"Script name") != NULL) { + if (MatchKey(line, L"Script name") != NULL) + { in_desc = false; /* "Script name:" has no display field; recognise it so it doesn't bleed into description */ - - } else if ((val = MatchKey(line, L"Version")) != NULL) { + } + else if ((val = MatchKey(line, L"Version")) != NULL) + { wcsncpy_s(m.version, 32, val, _TRUNCATE); - found_any = true; in_desc = false; - - } else if ((val = MatchKey(line, L"Author")) != NULL) { + found_any = true; + in_desc = false; + } + else if ((val = MatchKey(line, L"Author")) != NULL) + { wcsncpy_s(m.author, 64, val, _TRUNCATE); - found_any = true; in_desc = false; - - } else if ((val = MatchKey(line, L"Date")) != NULL) { + found_any = true; + in_desc = false; + } + else if ((val = MatchKey(line, L"Date")) != NULL) + { wcsncpy_s(m.date, 32, val, _TRUNCATE); - found_any = true; in_desc = false; - - } else if ((val = MatchKey(line, L"Purpose")) != NULL) { + found_any = true; + in_desc = false; + } + else if ((val = MatchKey(line, L"Purpose")) != NULL) + { wcsncpy_s(m.purpose, 128, val, _TRUNCATE); - found_any = true; in_desc = false; - - } else if ((val = MatchKey(line, L"Code")) != NULL) { + found_any = true; + in_desc = false; + } + else if ((val = MatchKey(line, L"Code")) != NULL) + { wcsncpy_s(m.code, 64, val, _TRUNCATE); - found_any = true; in_desc = false; - - } else if ((val = MatchKey(line, L"Release")) != NULL) { + found_any = true; + in_desc = false; + } + else if ((val = MatchKey(line, L"Release")) != NULL) + { wcsncpy_s(m.release, 32, val, _TRUNCATE); - found_any = true; in_desc = false; - - } else if ((val = MatchKey(line, L"Description")) != NULL) { + found_any = true; + in_desc = false; + } + else if ((val = MatchKey(line, L"Description")) != NULL) + { wcsncpy_s(m.description, DESC_MAX + 1, val, _TRUNCATE); - found_any = true; in_desc = true; - - } else if (_wcsnicmp(line, L"requirements", 12) == 0) { + found_any = true; + in_desc = true; + } + else if (_wcsnicmp(line, L"requirements", 12) == 0) + { /* "requirements:" may have content on the same line or span multiple indented lines */ val = MatchKey(line, L"requirements"); if (val) wcsncpy_s(m.requirements, 512, val, _TRUNCATE); - found_any = true; in_desc = false; + found_any = true; + in_desc = false; /* Continuation lines are collected in the m.requirements[0] branch below */ - - } else if (_wcsnicmp(line, L"dependencies", 12) == 0) { + } + else if (_wcsnicmp(line, L"dependencies", 12) == 0) + { in_desc = false; /* "dependencies:" block is recognised but not stored */ - - } else if (in_desc) { + } + else if (in_desc) + { /* Description continuation: collect indented or comment-prefixed lines */ - bool indented = (raw[0]==L' '||raw[0]==L'\t'|| - raw[0]==L'\''||raw[0]==L'"'); - if (line[0]==L'['||line[0]==L']') { in_desc = false; continue; } /* INI-style section marker ends description */ - if (indented && line[0]) { + bool indented = (raw[0] == L' ' || raw[0] == L'\t' || + raw[0] == L'\'' || raw[0] == L'"'); + if (line[0] == L'[' || line[0] == L']') + { + in_desc = false; + continue; + } /* INI-style section marker ends description */ + if (indented && line[0]) + { AppendDesc(m.description, line); - } else if (!indented) { + } + else if (!indented) + { in_desc = false; /* unindented non-key line means description block is over */ } - } else if (m.requirements[0] && !in_desc) { + } + else if (m.requirements[0] && !in_desc) + { /* Requirements continuation — collect indented lines after the "requirements:" key */ - bool indented = (raw[0]==L' '||raw[0]==L'\t'); + bool indented = (raw[0] == L' ' || raw[0] == L'\t'); if (indented && line[0] && - line[0] != L'[' && line[0] != L']') { + line[0] != L'[' && line[0] != L']') + { int cur = (int)wcslen(m.requirements); - if (cur < 508) { /* 508 = 512 buffer - 4 reserved for \r\n\0 appended below */ + if (cur < 508) + { /* 508 = 512 buffer - 4 reserved for \r\n\0 appended below */ m.requirements[cur++] = L'\r'; m.requirements[cur++] = L'\n'; - m.requirements[cur] = L'\0'; + m.requirements[cur] = L'\0'; wcsncat_s(m.requirements, 512, line, _TRUNCATE); } } @@ -220,9 +267,10 @@ void Meta_Parse(Script *s) fclose(f); - if (found_any) { + if (found_any) + { /* At least one recognised key was parsed — commit the result */ - s->meta = m; + s->meta = m; s->meta_loaded = true; } /* If found_any is false the file exists but has no recognised header. diff --git a/src/paint.c b/src/paint.c index 10ed746..9a4a484 100644 --- a/src/paint.c +++ b/src/paint.c @@ -30,15 +30,16 @@ /* Out: (void) */ /* ================================================================== */ static void DrawRoundRect(HDC hdc, int x, int y, int w, int h, int r, - COLORREF fill, COLORREF border) + COLORREF fill, COLORREF border) { - HBRUSH br = CreateSolidBrush(fill); - HPEN pen = (border == fill) ? (HPEN)GetStockObject(NULL_PEN) /* no border wanted — use stock pen to avoid double-drawing */ - : CreatePen(PS_SOLID, 1, border); + HBRUSH br = CreateSolidBrush(fill); + HPEN pen = (border == fill) ? (HPEN)GetStockObject(NULL_PEN) /* no border wanted — use stock pen to avoid double-drawing */ + : CreatePen(PS_SOLID, 1, border); HBRUSH obr = SelectObject(hdc, br); - HPEN ope = SelectObject(hdc, pen); + HPEN ope = SelectObject(hdc, pen); RoundRect(hdc, x, y, x + w, y + h, r, r); - SelectObject(hdc, obr); SelectObject(hdc, ope); + SelectObject(hdc, obr); + SelectObject(hdc, ope); DeleteObject(br); if (border != fill) DeleteObject(pen); /* don't delete the stock NULL_PEN — it is not owned by us */ } @@ -55,10 +56,11 @@ static void DrawRoundRect(HDC hdc, int x, int y, int w, int h, int r, /* ================================================================== */ void Paint_MainWindow(HWND hwnd, HDC hdc_paint) { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int w = rc.right, h = rc.bottom; - HDC mem = CreateCompatibleDC(hdc_paint); + HDC mem = CreateCompatibleDC(hdc_paint); HBITMAP bmp = CreateCompatibleBitmap(hdc_paint, w, h); HBITMAP old = SelectObject(mem, bmp); @@ -66,7 +68,7 @@ void Paint_MainWindow(HWND hwnd, HDC hdc_paint) FillRect(mem, &rc, bg); DeleteObject(bg); - RECT tb = { 0, 0, w, TOOLBAR_H }; + RECT tb = {0, 0, w, TOOLBAR_H}; HBRUSH tb_br = CreateSolidBrush(COL_TOOLBAR()); FillRect(mem, &tb, tb_br); DeleteObject(tb_br); @@ -75,7 +77,8 @@ void Paint_MainWindow(HWND hwnd, HDC hdc_paint) HPEN op = SelectObject(mem, dp); MoveToEx(mem, 0, TOOLBAR_H - 1, NULL); /* TOOLBAR_H - 1 = one px below toolbar band = divider line */ LineTo(mem, w, TOOLBAR_H - 1); - SelectObject(mem, op); DeleteObject(dp); + SelectObject(mem, op); + DeleteObject(dp); SetBkMode(mem, TRANSPARENT); HFONT of = SelectObject(mem, g.font_bold); @@ -84,36 +87,43 @@ void Paint_MainWindow(HWND hwnd, HDC hdc_paint) WCHAR title[128]; _snwprintf_s(title, 127, _TRUNCATE, L"%s v%s", APP_TITLE, VERSION_STRING_W); - if (g.latest_version[0]) { /* update badge pending — split toolbar into two text rows */ + if (g.latest_version[0]) + { /* update badge pending — split toolbar into two text rows */ SetTextColor(mem, COL_ACCENT); SelectObject(mem, g.font_bold); - RECT tr = { 440, 0, w - 8, TOOLBAR_H / 2 }; /* 440 = starts right of toolbar buttons; top half of bar */ + RECT tr = {440, 0, w - 8, TOOLBAR_H / 2}; /* 440 = starts right of toolbar buttons; top half of bar */ DrawText(mem, title, -1, &tr, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); WCHAR upd[64]; _snwprintf_s(upd, 63, _TRUNCATE, L"\u2B06 Update %s available", g.latest_version); SelectObject(mem, g.font_small); SetTextColor(mem, COL_WARN); - RECT ur = { 444, TOOLBAR_H / 2, w - 8, TOOLBAR_H - 2 }; + RECT ur = {444, TOOLBAR_H / 2, w - 8, TOOLBAR_H - 2}; DrawText(mem, upd, -1, &ur, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - } else if (g.syncing) { /* no update pending, but sync is in progress */ + } + else if (g.syncing) + { /* no update pending, but sync is in progress */ SetTextColor(mem, COL_ACCENT); - RECT tr = { 440, 0, w - 8, TOOLBAR_H / 2 }; + RECT tr = {440, 0, w - 8, TOOLBAR_H / 2}; DrawText(mem, title, -1, &tr, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); SelectObject(mem, g.font_small); SetTextColor(mem, COL_WARN); - RECT sr = { 444, TOOLBAR_H / 2, w - 8, TOOLBAR_H - 2 }; + RECT sr = {444, TOOLBAR_H / 2, w - 8, TOOLBAR_H - 2}; DrawText(mem, L"\u25CF Syncing\u2026", -1, &sr, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - } else { + } + else + { SetTextColor(mem, COL_ACCENT); - RECT tr = { 440, 0, w - 8, TOOLBAR_H }; + RECT tr = {440, 0, w - 8, TOOLBAR_H}; DrawText(mem, title, -1, &tr, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); } SelectObject(mem, of); BitBlt(hdc_paint, 0, 0, w, h, mem, 0, 0, SRCCOPY); - SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem); + SelectObject(mem, old); + DeleteObject(bmp); + DeleteDC(mem); } /* ================================================================== */ @@ -128,34 +138,34 @@ void Paint_MainWindow(HWND hwnd, HDC hdc_paint) /* ================================================================== */ void Paint_ToolbarButton(DRAWITEMSTRUCT *dis) { - HDC hdc = dis->hDC; - RECT rc = dis->rcItem; - int w = rc.right - rc.left; - int h = rc.bottom - rc.top; + HDC hdc = dis->hDC; + RECT rc = dis->rcItem; + int w = rc.right - rc.left; + int h = rc.bottom - rc.top; - HDC mem = CreateCompatibleDC(hdc); + HDC mem = CreateCompatibleDC(hdc); HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h); HBITMAP old = SelectObject(mem, bmp); - bool pressed = (dis->itemState & ODS_SELECTED) != 0; /* ODS_SELECTED = button is being clicked */ - bool hot = (dis->itemState & ODS_HOTLIGHT) != 0; - bool focused = (dis->itemState & ODS_FOCUS) != 0; - bool disabled = (dis->itemState & ODS_DISABLED) != 0; - bool is_stop = ((int)dis->CtlID == IDC_BTN_STOP); /* Stop button gets red colour treatment */ - -#define COL_STOP_ACTIVE RGB(200, 60, 60) -#define COL_STOP_HOT RGB(230, 90, 90) - - COLORREF bg = (pressed && !disabled) ? COL_BTN_PRESS() - : (hot && !disabled) ? COL_BTN_HOT() - : COL_BTN_NORM(); - COLORREF bdr = disabled ? COL_DIVIDER() - : (hot || focused) ? (is_stop ? COL_STOP_ACTIVE : COL_ACCENT) - : COL_DIVIDER(); - COLORREF txt = disabled ? COL_SUBTEXT() - : (hot || pressed) ? (is_stop ? COL_STOP_HOT : COL_ACCENT) - : is_stop ? COL_STOP_ACTIVE - : COL_TEXT(); + bool pressed = (dis->itemState & ODS_SELECTED) != 0; /* ODS_SELECTED = button is being clicked */ + bool hot = (dis->itemState & ODS_HOTLIGHT) != 0; + bool focused = (dis->itemState & ODS_FOCUS) != 0; + bool disabled = (dis->itemState & ODS_DISABLED) != 0; + bool is_stop = ((int)dis->CtlID == IDC_BTN_STOP); /* Stop button gets red colour treatment */ + +#define COL_STOP_ACTIVE RGB(200, 60, 60) +#define COL_STOP_HOT RGB(230, 90, 90) + + COLORREF bg = (pressed && !disabled) ? COL_BTN_PRESS() + : (hot && !disabled) ? COL_BTN_HOT() + : COL_BTN_NORM(); + COLORREF bdr = disabled ? COL_DIVIDER() + : (hot || focused) ? (is_stop ? COL_STOP_ACTIVE : COL_ACCENT) + : COL_DIVIDER(); + COLORREF txt = disabled ? COL_SUBTEXT() + : (hot || pressed) ? (is_stop ? COL_STOP_HOT : COL_ACCENT) + : is_stop ? COL_STOP_ACTIVE + : COL_TEXT(); #undef COL_STOP_ACTIVE #undef COL_STOP_HOT @@ -169,13 +179,15 @@ void Paint_ToolbarButton(DRAWITEMSTRUCT *dis) SetBkMode(mem, TRANSPARENT); SetTextColor(mem, txt); HFONT of = SelectObject(mem, g.font_ui); - RECT tr = { 0, 0, w, h }; + RECT tr = {0, 0, w, h}; DrawText(mem, text, -1, &tr, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(mem, of); BitBlt(hdc, 0, 0, w, h, mem, 0, 0, SRCCOPY); - SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem); + SelectObject(mem, old); + DeleteObject(bmp); + DeleteDC(mem); } /* ================================================================== */ @@ -195,62 +207,82 @@ void Paint_ToolbarButton(DRAWITEMSTRUCT *dis) /* Out: (void) */ /* ================================================================== */ void Paint_ScriptButton(HWND hwnd_btn, HDC hdc, - bool hot, bool pressed, bool info_hot, bool repeat, - bool running, const Script *s) + bool hot, bool pressed, bool info_hot, bool repeat, + bool running, const Script *s) { - RECT rc; GetClientRect(hwnd_btn, &rc); + RECT rc; + GetClientRect(hwnd_btn, &rc); int w = rc.right, h = rc.bottom; int main_w = w - INFO_BTN_W; /* rightmost INFO_BTN_W px is the "i" info zone; rest is the run area */ - HDC mem = CreateCompatibleDC(hdc); + HDC mem = CreateCompatibleDC(hdc); HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h); HBITMAP old = SelectObject(mem, bmp); /* Idle background: offline tint takes priority over source tint; local scripts are never tinted red */ COLORREF idle_bg = COL_BTN_NORM(); - if (!pressed && !hot && s) { - if (g.status_offline && g.cfg.offline_use_cache) { - if (s->source == SCRIPT_SRC_MAIN) idle_bg = COL_BTN_OFFLINE_MAIN(); - else if (s->source == SCRIPT_SRC_EXTRA) idle_bg = COL_BTN_OFFLINE_EXTRA(); - else if (g.cfg.tint_script_sources) idle_bg = COL_BTN_LOCAL(); /* SCRIPT_SRC_LOCAL unchanged */ - } else if (g.cfg.tint_script_sources) { - if (s->source == SCRIPT_SRC_LOCAL) idle_bg = COL_BTN_LOCAL(); - else if (s->source == SCRIPT_SRC_EXTRA) idle_bg = COL_BTN_EXTRA(); + if (!pressed && !hot && s) + { + if (g.status_offline && g.cfg.offline_use_cache) + { + if (s->source == SCRIPT_SRC_MAIN) + idle_bg = COL_BTN_OFFLINE_MAIN(); + else if (s->source == SCRIPT_SRC_EXTRA) + idle_bg = COL_BTN_OFFLINE_EXTRA(); + else if (g.cfg.tint_script_sources) + idle_bg = COL_BTN_LOCAL(); /* SCRIPT_SRC_LOCAL unchanged */ + } + else if (g.cfg.tint_script_sources) + { + if (s->source == SCRIPT_SRC_LOCAL) + idle_bg = COL_BTN_LOCAL(); + else if (s->source == SCRIPT_SRC_EXTRA) + idle_bg = COL_BTN_EXTRA(); } } - COLORREF bg = pressed ? COL_BTN_PRESS() : hot ? COL_BTN_HOT() : idle_bg; - COLORREF bdr = repeat ? COL_WARN : running ? COL_SUCCESS : hot ? COL_ACCENT : COL_DIVIDER(); /* border priority: repeat > running > hot > idle */ - DrawRoundRect(mem, 0, 0, main_w, h, 7, bg, bdr); + COLORREF bg = pressed ? COL_BTN_PRESS() : hot ? COL_BTN_HOT() + : idle_bg; + COLORREF bdr = repeat ? COL_WARN : running ? COL_SUCCESS + : hot ? COL_ACCENT + : COL_DIVIDER(); /* border priority: repeat > running > hot > idle */ + DrawRoundRect(mem, 0, 0, main_w, h, 7, bg, bdr); DrawRoundRect(mem, main_w, 0, INFO_BTN_W, h, 7, info_hot ? COL_ACCENT_DIM : COL_INFO_ZONE(), bdr); SetBkMode(mem, TRANSPARENT); SetTextColor(mem, info_hot ? COL_TEXT() : COL_SUBTEXT()); HFONT of = SelectObject(mem, g.font_bold); - RECT ir = { main_w, 0, w, h }; + RECT ir = {main_w, 0, w, h}; DrawText(mem, L"i", -1, &ir, DT_CENTER | DT_VCENTER | DT_SINGLELINE); /* Left accent bar — 4 px wide, 5 px inset top and bottom */ - if (repeat) { + if (repeat) + { HBRUSH ab = CreateSolidBrush(COL_WARN); - RECT ar = { 0, 5, 4, h - 5 }; + RECT ar = {0, 5, 4, h - 5}; FillRect(mem, &ar, ab); DeleteObject(ab); - } else if (running) { + } + else if (running) + { HBRUSH ab = CreateSolidBrush(COL_SUCCESS); - RECT ar = { 0, 5, 4, h - 5 }; + RECT ar = {0, 5, 4, h - 5}; FillRect(mem, &ar, ab); DeleteObject(ab); - } else if (hot || pressed) { + } + else if (hot || pressed) + { HBRUSH ab = CreateSolidBrush(pressed ? COL_ACCENT_DIM : COL_ACCENT); - RECT ar = { 0, 5, 4, h - 5 }; + RECT ar = {0, 5, 4, h - 5}; FillRect(mem, &ar, ab); DeleteObject(ab); } - SetTextColor(mem, repeat ? COL_WARN : running ? COL_SUCCESS : hot ? COL_ACCENT : COL_SUBTEXT()); + SetTextColor(mem, repeat ? COL_WARN : running ? COL_SUCCESS + : hot ? COL_ACCENT + : COL_SUBTEXT()); SelectObject(mem, g.font_ui); - RECT arr = { 8, 0, 28, h }; /* columns 8–28 = small arrow/loop icon area */ + RECT arr = {8, 0, 28, h}; /* columns 8–28 = small arrow/loop icon area */ /* Show repeat loop symbol (\u21BB) when in repeat mode, arrow otherwise */ DrawText(mem, repeat ? L"\u21BB" : L"\u25BA", -1, &arr, DT_CENTER | DT_VCENTER | DT_SINGLELINE); @@ -259,30 +291,38 @@ void Paint_ScriptButton(HWND hwnd_btn, HDC hdc, if (!s || !s->name[0]) GetWindowText(hwnd_btn, fallback, MAX_NAME - 1); - const WCHAR *label = (s && s->name[0]) ? s->name : fallback; + const WCHAR *label = (s && s->name[0]) ? s->name : fallback; const WCHAR *purpose = (s && s->meta_loaded && s->meta.purpose[0]) - ? s->meta.purpose : NULL; + ? s->meta.purpose + : NULL; - SetTextColor(mem, repeat ? COL_WARN : running ? COL_SUCCESS : hot ? COL_ACCENT : COL_TEXT()); + SetTextColor(mem, repeat ? COL_WARN : running ? COL_SUCCESS + : hot ? COL_ACCENT + : COL_TEXT()); SelectObject(mem, g.font_bold); - if (purpose) { - RECT lr = { 30, 3, main_w - 6, h / 2 + 2 }; /* name in top half; 30 = past the arrow icon */ + if (purpose) + { + RECT lr = {30, 3, main_w - 6, h / 2 + 2}; /* name in top half; 30 = past the arrow icon */ DrawText(mem, label, -1, &lr, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); SetTextColor(mem, COL_SUBTEXT()); SelectObject(mem, g.font_small); - RECT pr = { 30, h / 2, main_w - 6, h - 4 }; /* purpose in bottom half */ + RECT pr = {30, h / 2, main_w - 6, h - 4}; /* purpose in bottom half */ DrawText(mem, purpose, -1, &pr, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); - } else { - RECT lr = { 30, 0, main_w - 6, h }; + } + else + { + RECT lr = {30, 0, main_w - 6, h}; DrawText(mem, label, -1, &lr, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); } SelectObject(mem, of); BitBlt(hdc, 0, 0, w, h, mem, 0, 0, SRCCOPY); - SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem); + SelectObject(mem, old); + DeleteObject(bmp); + DeleteDC(mem); } /* ================================================================== */ @@ -298,14 +338,15 @@ static int Tip_ComputeHeight(const Script *s) /* Fixed rows: Script + Purpose + Author + Version + Date; 8 = top padding */ int h = 8 + TIP_HEADER_ROWS * TIP_ROW_H; - if (s && s->meta.description[0]) { + if (s && s->meta.description[0]) + { h += 6; /* separator + gap */ /* Use a memory DC with the exact font to get accurate word-wrap height */ HDC screen = GetDC(NULL); - HDC mem = CreateCompatibleDC(screen); - HFONT of = SelectObject(mem, g.font_small); + HDC mem = CreateCompatibleDC(screen); + HFONT of = SelectObject(mem, g.font_small); /* Width must match what Paint_Tooltip draws into; height 0 because DT_CALCRECT fills it in */ - RECT dr = { 0, 0, TIP_W - 14, 0 }; /* TIP_W - 14 = TIP_W minus 8+6 side margins */ + RECT dr = {0, 0, TIP_W - 14, 0}; /* TIP_W - 14 = TIP_W minus 8+6 side margins */ int text_h = DrawText(mem, s->meta.description, -1, &dr, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); SelectObject(mem, of); @@ -328,7 +369,8 @@ static int Tip_ComputeHeight(const Script *s) /* ================================================================== */ void Paint_Tooltip(HWND hwnd) { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int w = rc.right, h = rc.bottom; PAINTSTRUCT ps; @@ -345,14 +387,15 @@ void Paint_Tooltip(HWND hwnd) HPEN op = SelectObject(mem, bp); HBRUSH nb = SelectObject(mem, GetStockObject(NULL_BRUSH)); Rectangle(mem, 0, 0, w, h); - SelectObject(mem, op); SelectObject(mem, nb); + SelectObject(mem, op); + SelectObject(mem, nb); DeleteObject(bp); int fi = g.active_tab; int si = g.tip_btn - IDC_SCRIPT_BTN_BASE; /* recover script index from stored button ID */ /* Copy the script under cs_folders — the sync thread may realloc f->scripts. */ - Script s_copy = {0}; + Script s_copy = {0}; EnterCriticalSection(&g.cs_folders); bool tip_valid = (fi >= 0 && fi < g.folder_count && si >= 0 && si < g.folders[fi].count); @@ -364,20 +407,24 @@ void Paint_Tooltip(HWND hwnd) { Script *s = &s_copy; - struct { const WCHAR *lbl; const WCHAR *val; } rows[] = { - { L"Script:", s->name }, - { L"Purpose:", s->meta.purpose[0] ? s->meta.purpose : L"--" }, - { L"Author:", s->meta.author[0] ? s->meta.author : L"--" }, - { L"Version:", s->meta.version[0] ? s->meta.version : L"--" }, - { L"Date:", s->meta.date[0] ? s->meta.date : L"--" }, - { NULL, NULL } - }; + struct + { + const WCHAR *lbl; + const WCHAR *val; + } rows[] = { + {L"Script:", s->name}, + {L"Purpose:", s->meta.purpose[0] ? s->meta.purpose : L"--"}, + {L"Author:", s->meta.author[0] ? s->meta.author : L"--"}, + {L"Version:", s->meta.version[0] ? s->meta.version : L"--"}, + {L"Date:", s->meta.date[0] ? s->meta.date : L"--"}, + {NULL, NULL}}; SetBkMode(mem, TRANSPARENT); int y = 8; - for (int i = 0; rows[i].lbl; i++) { - RECT lr = { 8, y, 76, y + 16 }; /* 76 px = label column right edge */ - RECT vr = { 80, y, w - 6, y + 16 }; /* 80 = value column start; w-6 = right margin */ + for (int i = 0; rows[i].lbl; i++) + { + RECT lr = {8, y, 76, y + 16}; /* 76 px = label column right edge */ + RECT vr = {80, y, w - 6, y + 16}; /* 80 = value column start; w-6 = right margin */ SelectObject(mem, g.font_bold); SetTextColor(mem, COL_ACCENT); DrawText(mem, rows[i].lbl, -1, &lr, DT_LEFT | DT_TOP | DT_SINGLELINE); @@ -388,14 +435,17 @@ void Paint_Tooltip(HWND hwnd) y += TIP_ROW_H; } - if (s->meta.description[0]) { + if (s->meta.description[0]) + { HPEN sep = CreatePen(PS_SOLID, 1, COL_DIVIDER()); - HPEN os = SelectObject(mem, sep); - MoveToEx(mem, 8, y, NULL); LineTo(mem, w - 8, y); - SelectObject(mem, os); DeleteObject(sep); + HPEN os = SelectObject(mem, sep); + MoveToEx(mem, 8, y, NULL); + LineTo(mem, w - 8, y); + SelectObject(mem, os); + DeleteObject(sep); y += 5; /* Full word-wrapped description - no ellipsis, no clipping */ - RECT dr = { 8, y, w - 6, h - 6 }; + RECT dr = {8, y, w - 6, h - 6}; SelectObject(mem, g.font_small); SetTextColor(mem, COL_SUBTEXT()); DrawText(mem, s->meta.description, -1, &dr, @@ -405,7 +455,9 @@ void Paint_Tooltip(HWND hwnd) done: BitBlt(hdc, 0, 0, w, h, mem, 0, 0, SRCCOPY); - SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem); + SelectObject(mem, old); + DeleteObject(bmp); + DeleteDC(mem); EndPaint(hwnd, &ps); } @@ -425,29 +477,31 @@ void Paint_Tooltip(HWND hwnd) /* Out: LRESULT — 0 for suppressed messages; DefSubclassProc otherwise*/ /* ================================================================== */ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, - LPARAM lp, UINT_PTR uid, DWORD_PTR data) + LPARAM lp, UINT_PTR uid, DWORD_PTR data) { (void)data; switch (msg) { case WM_MOUSEMOVE: { - if (g.hot_btn != (int)uid && g.hot_btn >= IDC_SCRIPT_BTN_BASE) { + if (g.hot_btn != (int)uid && g.hot_btn >= IDC_SCRIPT_BTN_BASE) + { /* repaint the button that is losing hot state */ HWND hp = GetDlgItem(GetParent(hwnd), g.hot_btn); if (hp) InvalidateRect(hp, NULL, FALSE); } g.hot_btn = (int)uid; - TRACKMOUSEEVENT tme = { .cbSize=sizeof(tme), .dwFlags=TME_LEAVE, - .hwndTrack=hwnd }; + TRACKMOUSEEVENT tme = {.cbSize = sizeof(tme), .dwFlags = TME_LEAVE, .hwndTrack = hwnd}; TrackMouseEvent(&tme); /* subscribe so WM_MOUSELEAVE clears the hot state */ InvalidateRect(hwnd, NULL, FALSE); - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int mx = GET_X_LPARAM(lp); bool over_info = (mx >= rc.right - INFO_BTN_W); /* true when cursor is in the "i" zone */ - if (over_info && g.tip_btn != (int)uid) { + if (over_info && g.tip_btn != (int)uid) + { g.tip_btn = (int)uid; int fi = g.active_tab; int si = (int)uid - IDC_SCRIPT_BTN_BASE; @@ -468,30 +522,32 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, /* Get work area of the monitor the window is on (accounts for taskbar, multi-monitor, maximized state) */ - MONITORINFO mi; mi.cbSize = sizeof(mi); + MONITORINFO mi; + mi.cbSize = sizeof(mi); HMONITOR hmon = MonitorFromWindow(g.hwnd, MONITOR_DEFAULTTONEAREST); GetMonitorInfo(hmon, &mi); RECT wa = mi.rcWork; /* Position tooltip to right of button; flip to left side if it would go off screen */ - POINT pt = { rc.right, 0 }; + POINT pt = {rc.right, 0}; ClientToScreen(hwnd, &pt); int tx = (pt.x + TIP_W > wa.right) - ? pt.x - rc.right - TIP_W /* flip: place left of the button */ - : pt.x; + ? pt.x - rc.right - TIP_W /* flip: place left of the button */ + : pt.x; int ty = pt.y; /* Clamp within work area on all four sides; 4 px gap from edge */ if (ty + th > wa.bottom) ty = wa.bottom - th - 4; - if (ty < wa.top) ty = wa.top + 4; - if (tx < wa.left) tx = wa.left + 4; + if (ty < wa.top) ty = wa.top + 4; + if (tx < wa.left) tx = wa.left + 4; if (tx + TIP_W > wa.right) tx = wa.right - TIP_W - 4; SetWindowPos(g.hwnd_tip, HWND_TOPMOST, tx, ty, TIP_W, th, SWP_NOACTIVATE | SWP_SHOWWINDOW); InvalidateRect(g.hwnd_tip, NULL, TRUE); - - } else if (!over_info && g.tip_btn == (int)uid) { + } + else if (!over_info && g.tip_btn == (int)uid) + { g.tip_btn = -1; ShowWindow(g.hwnd_tip, SW_HIDE); } @@ -499,18 +555,28 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, } case WM_MOUSELEAVE: - if (g.hot_btn == (int)uid) { g.hot_btn = -1; InvalidateRect(hwnd, NULL, FALSE); } - if (g.tip_btn == (int)uid) { g.tip_btn = -1; ShowWindow(g.hwnd_tip, SW_HIDE); } + if (g.hot_btn == (int)uid) + { + g.hot_btn = -1; + InvalidateRect(hwnd, NULL, FALSE); + } + if (g.tip_btn == (int)uid) + { + g.tip_btn = -1; + ShowWindow(g.hwnd_tip, SW_HIDE); + } break; case WM_LBUTTONDBLCLK: { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); /* Ignore double-clicks on the info zone */ if (GET_X_LPARAM(lp) >= rc.right - INFO_BTN_W) return 0; if (!g.cfg.repeat_on_dblclick) return 0; /* Console mode doesn't notify us when the script finishes */ - if (g.cfg.show_console) { + if (g.cfg.show_console) + { PostStatus(L"Repeat mode not available in console mode."); return 0; } @@ -518,7 +584,8 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, int si = (int)uid - IDC_SCRIPT_BTN_BASE; if (fi < 0 || fi >= g.folder_count || si < 0 || si >= g.folders[fi].count) return 0; - if (g.repeat_mode && g.repeat_fi == fi && g.repeat_si == si) { /* double-click same script while repeating — toggle off */ + if (g.repeat_mode && g.repeat_fi == fi && g.repeat_si == si) + { /* double-click same script while repeating — toggle off */ Repeat_Stop(); g.suppress_lbuttonup = false; PostStatus(L"Repeat cancelled."); @@ -537,10 +604,12 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, case WM_LBUTTONUP: { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); if (GET_X_LPARAM(lp) >= rc.right - INFO_BTN_W) return 0; /* Suppress the trailing LBUTTONUP generated after a double-click */ - if (g.suppress_lbuttonup) { + if (g.suppress_lbuttonup) + { g.suppress_lbuttonup = false; return 0; } @@ -585,16 +654,19 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, AppendMenu(hm, MF_SEPARATOR, 0, NULL); AppendMenu(hm, MF_STRING, IDM_SCRIPT_HIDE, L"Hide Script"); - POINT pt; GetCursorPos(&pt); + POINT pt; + GetCursorPos(&pt); int cmd = TrackPopupMenu(hm, TPM_RETURNCMD | TPM_RIGHTBUTTON, - pt.x, pt.y, 0, g.hwnd, NULL); + pt.x, pt.y, 0, g.hwnd, NULL); DestroyMenu(hm); - switch (cmd) { + switch (cmd) + { case IDM_SCRIPT_DETAILS: if (DialogBoxParam(GetModuleHandle(NULL), - MAKEINTRESOURCE(IDD_SCRIPT_DETAILS), - g.hwnd, ScriptDetailsDlgProc, (LPARAM)s) == IDOK) { + MAKEINTRESOURCE(IDD_SCRIPT_DETAILS), + g.hwnd, ScriptDetailsDlgProc, (LPARAM)s) == IDOK) + { Tabs_BuildFavourites(); Tabs_Build(); Tabs_RebuildButtons(); @@ -603,8 +675,9 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, case IDM_SCRIPT_RUN_ARGS: { if (DialogBoxParam(GetModuleHandle(NULL), - MAKEINTRESOURCE(IDD_RUN_ARGS), - g.hwnd, RunWithArgsDlgProc, (LPARAM)s) == IDOK) { + MAKEINTRESOURCE(IDD_RUN_ARGS), + g.hwnd, RunWithArgsDlgProc, (LPARAM)s) == IDOK) + { /* Get args from a static buffer set by the dialog */ Runner_Run(fi, si); } @@ -616,10 +689,13 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, bool was_on_favourites = (wcscmp(g.folders[fi].name, L"Favourites") == 0); /* remember so we can return to Favourites after rebuild */ Prefs_SetFavourite(s->gh_path, new_fav); /* Update the script in its real folder (not the Favourites copy) */ - for (int f2 = 0; f2 < g.folder_count; f2++) { + for (int f2 = 0; f2 < g.folder_count; f2++) + { if (wcscmp(g.folders[f2].name, L"Favourites") == 0) continue; - for (int s2 = 0; s2 < g.folders[f2].count; s2++) { - if (wcscmp(g.folders[f2].scripts[s2].gh_path, s->gh_path) == 0) { + for (int s2 = 0; s2 < g.folders[f2].count; s2++) + { + if (wcscmp(g.folders[f2].scripts[s2].gh_path, s->gh_path) == 0) + { g.folders[f2].scripts[s2].is_favourite = new_fav; } } @@ -659,30 +735,31 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, SHELLEXECUTEINFOW sei; ZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); - sei.fMask = SEE_MASK_FLAG_NO_UI; - sei.nShow = SW_SHOWNORMAL; + sei.fMask = SEE_MASK_FLAG_NO_UI; + sei.nShow = SW_SHOWNORMAL; sei.lpVerb = L"edit"; sei.lpFile = s->local; - if (!ShellExecuteExW(&sei)) { + if (!ShellExecuteExW(&sei)) + { wchar_t editor[MAX_PATH] = {0}; DWORD len = MAX_PATH; if (AssocQueryStringW(0, ASSOCSTR_EXECUTABLE, L".txt", L"open", - editor, &len) == S_OK && editor[0]) + editor, &len) == S_OK && + editor[0]) ShellExecuteW(NULL, L"open", editor, s->local, NULL, SW_SHOWNORMAL); } break; } case IDM_SCRIPT_NOTE: DialogBoxParam(GetModuleHandle(NULL), - MAKEINTRESOURCE(IDD_SCRIPT_NOTE), - g.hwnd, ScriptNoteDlgProc, (LPARAM)s); + MAKEINTRESOURCE(IDD_SCRIPT_NOTE), + g.hwnd, ScriptNoteDlgProc, (LPARAM)s); break; case IDM_SCRIPT_HIDE: s->is_hidden = true; Prefs_SetHidden(s->gh_path, true); Tabs_RebuildButtons(); break; - } return 0; } @@ -703,8 +780,8 @@ LRESULT CALLBACK BtnSubclassProc(HWND hwnd, UINT msg, WPARAM wp, /* ================================================================== */ void Paint_Tab(DRAWITEMSTRUCT *dis) { - HDC hdc = dis->hDC; - RECT rc = dis->rcItem; + HDC hdc = dis->hDC; + RECT rc = dis->rcItem; bool sel = (dis->itemState & ODS_SELECTED) != 0; if (sel) rc.bottom += 2; /* expand 2 px downward so selected tab overlaps and hides the tab-bar border line */ @@ -718,22 +795,27 @@ void Paint_Tab(DRAWITEMSTRUCT *dis) /* Border: draw on left, top, right - NOT bottom for selected tab */ COLORREF bdr = sel ? COL_ACCENT : COL_DIVIDER(); HPEN pen = CreatePen(PS_SOLID, 1, bdr); - HPEN op = SelectObject(hdc, pen); + HPEN op = SelectObject(hdc, pen); - if (sel) { + if (sel) + { /* Top accent line (2px) */ HPEN ap = CreatePen(PS_SOLID, 2, COL_ACCENT); SelectObject(hdc, ap); - MoveToEx(hdc, rc.left, rc.top, NULL); - LineTo (hdc, rc.right, rc.top); + MoveToEx(hdc, rc.left, rc.top, NULL); + LineTo(hdc, rc.right, rc.top); SelectObject(hdc, op); DeleteObject(ap); /* Left and right edges */ SelectObject(hdc, pen); - MoveToEx(hdc, rc.left, rc.top, NULL); LineTo(hdc, rc.left, rc.bottom); - MoveToEx(hdc, rc.right - 1, rc.top, NULL); LineTo(hdc, rc.right - 1, rc.bottom); - } else { + MoveToEx(hdc, rc.left, rc.top, NULL); + LineTo(hdc, rc.left, rc.bottom); + MoveToEx(hdc, rc.right - 1, rc.top, NULL); + LineTo(hdc, rc.right - 1, rc.bottom); + } + else + { /* Unselected: full border + bottom line */ RECT border = rc; HBRUSH tb = CreateSolidBrush(bdr); @@ -746,8 +828,7 @@ void Paint_Tab(DRAWITEMSTRUCT *dis) /* Text */ WCHAR text[MAX_NAME] = {0}; - TCITEM ti = { .mask = TCIF_TEXT, .pszText = text, - .cchTextMax = MAX_NAME - 1 }; + TCITEM ti = {.mask = TCIF_TEXT, .pszText = text, .cchTextMax = MAX_NAME - 1}; TabCtrl_GetItem(g.hwnd_tab, dis->itemID, &ti); SetBkMode(hdc, TRANSPARENT); diff --git a/src/prefs.c b/src/prefs.c index cada8db..17e4adf 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -34,7 +34,8 @@ static void Prefs_GetPath(WCHAR *out, int max) static void PathToKey(const WCHAR *path, WCHAR *key, int max) { wcsncpy_s(key, max, path, _TRUNCATE); - for (WCHAR *p = key; *p; p++) { + for (WCHAR *p = key; *p; p++) + { if (*p == L'\\' || *p == L'/') *p = L'_'; } } @@ -172,8 +173,12 @@ void Prefs_SetNote(const WCHAR *gh_path, const WCHAR *note) /* In: (none) */ /* Out: (void) */ /* ================================================================== */ -void Prefs_Load(void) { /* INI reads are lazy - no bulk load needed */ } -void Prefs_Save(void) { /* INI writes are immediate - no bulk save needed */ } +void Prefs_Load(void) +{ /* INI reads are lazy - no bulk load needed */ +} +void Prefs_Save(void) +{ /* INI writes are immediate - no bulk save needed */ +} /* ================================================================== */ /* Prefs_ApplyToFolders */ @@ -188,13 +193,15 @@ void Prefs_Save(void) { /* INI writes are immediate - no bulk save needed */ } /* ================================================================== */ void Prefs_ApplyToFolders(void) { - for (int fi = 0; fi < g.folder_count; fi++) { + for (int fi = 0; fi < g.folder_count; fi++) + { ScriptFolder *f = &g.folders[fi]; - for (int si = 0; si < f->count; si++) { + for (int si = 0; si < f->count; si++) + { Script *s = &f->scripts[si]; s->is_favourite = Prefs_IsFavourite(s->gh_path); - s->is_hidden = Prefs_IsHidden(s->gh_path); - s->run_count = Prefs_GetRunCount(s->gh_path); + s->is_hidden = Prefs_IsHidden(s->gh_path); + s->run_count = Prefs_GetRunCount(s->gh_path); Prefs_GetNote(s->gh_path, s->note, MAX_NOTE_LEN); } } @@ -213,16 +220,20 @@ void Prefs_ApplyToFolders(void) void Tabs_BuildFavourites(void) { /* First remove any existing Favourites tab to avoid duplicates */ - for (int fi = 0; fi < g.folder_count; fi++) { - if (wcscmp(g.folders[fi].name, L"Favourites") == 0) { + for (int fi = 0; fi < g.folder_count; fi++) + { + if (wcscmp(g.folders[fi].name, L"Favourites") == 0) + { Folder_Free(&g.folders[fi]); /* Shift remaining folders left to fill the gap */ for (int i = fi; i < g.folder_count - 1; i++) g.folders[i] = g.folders[i + 1]; g.folder_count--; ZeroMemory(&g.folders[g.folder_count], sizeof(ScriptFolder)); /* clear now-unused last slot to avoid dangling pointer */ - if (g.active_tab > fi) g.active_tab--; /* shift active index to compensate for removed slot */ - else if (g.active_tab == fi) g.active_tab = 0; /* active tab was the favourites tab — reset to first */ + if (g.active_tab > fi) + g.active_tab--; /* shift active index to compensate for removed slot */ + else if (g.active_tab == fi) + g.active_tab = 0; /* active tab was the favourites tab — reset to first */ break; } } @@ -235,7 +246,7 @@ void Tabs_BuildFavourites(void) !g.folders[fi].scripts[si].is_hidden) fav_count++; - if (fav_count == 0) return; /* no favourited scripts — don't create an empty tab */ + if (fav_count == 0) return; /* no favourited scripts — don't create an empty tab */ if (g.folder_count >= MAX_FOLDERS) return; /* no room to insert a new tab */ /* Shift all existing folders one position right to free slot 0 */ @@ -252,7 +263,7 @@ void Tabs_BuildFavourites(void) Do NOT free it — g.folders[1] still owns that memory. Just zero out this slot and allocate fresh for the favourites. */ ZeroMemory(fav, sizeof(*fav)); - wcsncpy_s(fav->name, MAX_NAME, L"Favourites", _TRUNCATE); + wcsncpy_s(fav->name, MAX_NAME, L"Favourites", _TRUNCATE); wcsncpy_s(fav->display, MAX_NAME, L"\u2605 Favourites", _TRUNCATE); fav->loaded = true; Folder_Alloc(fav, fav_count > 0 ? fav_count : 8); @@ -261,7 +272,8 @@ void Tabs_BuildFavourites(void) for (int fi = 1; fi < g.folder_count; fi++) for (int si = 0; si < g.folders[fi].count; si++) if (g.folders[fi].scripts[si].is_favourite && - !g.folders[fi].scripts[si].is_hidden) { + !g.folders[fi].scripts[si].is_hidden) + { Script *dst = Folder_Push(fav); if (dst) *dst = g.folders[fi].scripts[si]; } @@ -283,42 +295,51 @@ void Tabs_BuildFavourites(void) /* Out: INT_PTR — IDOK / IDCANCEL via EndDialog */ /* ================================================================== */ INT_PTR CALLBACK ScriptDetailsDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { static Script *s = NULL; /* static: persists across messages for this dialog instance */ switch (msg) { case WM_INITDIALOG: s = (Script *)lp; - if (!s) { EndDialog(hwnd, 0); return FALSE; } /* safety: should never be NULL */ + if (!s) + { + EndDialog(hwnd, 0); + return FALSE; + } /* safety: should never be NULL */ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)s); /* stash for WM_COMMAND */ /* Ensure meta is loaded */ Meta_Parse(s); - SetDlgItemText(hwnd, IDC_DETAIL_NAME, s->name); + SetDlgItemText(hwnd, IDC_DETAIL_NAME, s->name); SetDlgItemText(hwnd, IDC_DETAIL_PURPOSE, s->meta.purpose); - SetDlgItemText(hwnd, IDC_DETAIL_AUTHOR, s->meta.author); + SetDlgItemText(hwnd, IDC_DETAIL_AUTHOR, s->meta.author); SetDlgItemText(hwnd, IDC_DETAIL_VERSION, s->meta.version); - SetDlgItemText(hwnd, IDC_DETAIL_DATE, s->meta.date); - SetDlgItemText(hwnd, IDC_DETAIL_CODE, s->meta.code); + SetDlgItemText(hwnd, IDC_DETAIL_DATE, s->meta.date); + SetDlgItemText(hwnd, IDC_DETAIL_CODE, s->meta.code); SetDlgItemText(hwnd, IDC_DETAIL_RELEASE, s->meta.release); - SetDlgItemText(hwnd, IDC_DETAIL_DESC, s->meta.description); - SetDlgItemText(hwnd, IDC_DETAIL_REQS, s->meta.requirements); - SetDlgItemText(hwnd, IDC_DETAIL_NOTE, s->note); - SetDlgItemText(hwnd, IDC_DETAIL_PATH, s->local); + SetDlgItemText(hwnd, IDC_DETAIL_DESC, s->meta.description); + SetDlgItemText(hwnd, IDC_DETAIL_REQS, s->meta.requirements); + SetDlgItemText(hwnd, IDC_DETAIL_NOTE, s->note); + SetDlgItemText(hwnd, IDC_DETAIL_PATH, s->local); CheckDlgButton(hwnd, IDC_CHK_FAVOURITE, - s->is_favourite ? BST_CHECKED : BST_UNCHECKED); + s->is_favourite ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwnd, IDC_CHK_HIDDEN, - s->is_hidden ? BST_CHECKED : BST_UNCHECKED); + s->is_hidden ? BST_CHECKED : BST_UNCHECKED); return TRUE; case WM_COMMAND: s = (Script *)GetWindowLongPtr(hwnd, GWLP_USERDATA); /* retrieve Script pointer stored in WM_INITDIALOG */ - if (!s) { EndDialog(hwnd, 0); return TRUE; } /* defensive: close if pointer was lost */ + if (!s) + { + EndDialog(hwnd, 0); + return TRUE; + } /* defensive: close if pointer was lost */ - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDOK: { /* Save note */ @@ -361,7 +382,7 @@ INT_PTR CALLBACK ScriptDetailsDlgProc(HWND hwnd, UINT msg, /* Out: INT_PTR — IDOK / IDCANCEL via EndDialog */ /* ================================================================== */ INT_PTR CALLBACK RunWithArgsDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { switch (msg) { @@ -369,7 +390,8 @@ INT_PTR CALLBACK RunWithArgsDlgProc(HWND hwnd, UINT msg, { Script *s = (Script *)lp; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)s); /* stash for WM_COMMAND */ - if (s) { + if (s) + { WCHAR title[MAX_NAME + 20]; /* +20 = "Run: " prefix (5) + null terminator + headroom */ _snwprintf_s(title, MAX_NAME + 19, _TRUNCATE, L"Run: %s", s->name); SetWindowText(hwnd, title); @@ -377,7 +399,8 @@ INT_PTR CALLBACK RunWithArgsDlgProc(HWND hwnd, UINT msg, return TRUE; } case WM_COMMAND: - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDOK: case IDCANCEL: EndDialog(hwnd, LOWORD(wp)); /* both cases close; caller reads IDC_EDIT_ARGS after IDOK */ @@ -400,7 +423,7 @@ INT_PTR CALLBACK RunWithArgsDlgProc(HWND hwnd, UINT msg, /* Out: INT_PTR — IDOK / IDCANCEL via EndDialog */ /* ================================================================== */ INT_PTR CALLBACK ScriptNoteDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { switch (msg) { @@ -414,9 +437,11 @@ INT_PTR CALLBACK ScriptNoteDlgProc(HWND hwnd, UINT msg, case WM_COMMAND: { Script *s = (Script *)GetWindowLongPtr(hwnd, GWLP_USERDATA); /* retrieve Script pointer stashed in WM_INITDIALOG */ - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDOK: - if (s) { + if (s) + { GetDlgItemText(hwnd, IDC_EDIT_NOTE, s->note, MAX_NOTE_LEN - 1); /* -1 leaves room for null terminator */ Prefs_SetNote(s->gh_path, s->note); } @@ -446,7 +471,7 @@ INT_PTR CALLBACK ScriptNoteDlgProc(HWND hwnd, UINT msg, /* Out: INT_PTR — IDOK / IDCANCEL via EndDialog */ /* ================================================================== */ INT_PTR CALLBACK HiddenScriptsDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { (void)lp; switch (msg) @@ -456,23 +481,27 @@ INT_PTR CALLBACK HiddenScriptsDlgProc(HWND hwnd, UINT msg, HWND hList = GetDlgItem(hwnd, IDC_LST_HIDDEN); LVCOLUMN lvc = {0}; lvc.mask = LVCF_TEXT | LVCF_WIDTH; - lvc.pszText = L"Script"; lvc.cx = 220; + lvc.pszText = L"Script"; + lvc.cx = 220; ListView_InsertColumn(hList, 0, &lvc); - lvc.pszText = L"Folder"; lvc.cx = 150; + lvc.pszText = L"Folder"; + lvc.cx = 150; ListView_InsertColumn(hList, 1, &lvc); /* Populate with hidden scripts */ int row = 0; - for (int fi = 0; fi < g.folder_count; fi++) { + for (int fi = 0; fi < g.folder_count; fi++) + { if (wcscmp(g.folders[fi].name, L"Favourites") == 0) continue; /* skip synthetic Favourites tab; its scripts live in real folders */ - for (int si = 0; si < g.folders[fi].count; si++) { + for (int si = 0; si < g.folders[fi].count; si++) + { Script *s = &g.folders[fi].scripts[si]; if (!s->is_hidden) continue; LVITEM lvi = {0}; - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = row; + lvi.mask = LVIF_TEXT | LVIF_PARAM; + lvi.iItem = row; lvi.pszText = s->name; - lvi.lParam = (LPARAM)s; /* store Script pointer so Unhide handler can retrieve it via GetItem */ + lvi.lParam = (LPARAM)s; /* store Script pointer so Unhide handler can retrieve it via GetItem */ ListView_InsertItem(hList, &lvi); ListView_SetItemText(hList, row, 1, g.folders[fi].display); row++; @@ -490,11 +519,12 @@ INT_PTR CALLBACK HiddenScriptsDlgProc(HWND hwnd, UINT msg, int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); /* -1 starts from beginning; returns -1 if nothing selected */ if (sel < 0) break; /* nothing selected — nothing to do */ LVITEM lvi = {0}; - lvi.mask = LVIF_PARAM; - lvi.iItem = sel; + lvi.mask = LVIF_PARAM; + lvi.iItem = sel; ListView_GetItem(hList, &lvi); Script *s = (Script *)lvi.lParam; - if (s) { + if (s) + { s->is_hidden = false; Prefs_SetHidden(s->gh_path, false); ListView_DeleteItem(hList, sel); @@ -506,13 +536,15 @@ INT_PTR CALLBACK HiddenScriptsDlgProc(HWND hwnd, UINT msg, { HWND hList = GetDlgItem(hwnd, IDC_LST_HIDDEN); int count = ListView_GetItemCount(hList); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; i++) + { LVITEM lvi = {0}; - lvi.mask = LVIF_PARAM; + lvi.mask = LVIF_PARAM; lvi.iItem = i; ListView_GetItem(hList, &lvi); Script *s = (Script *)lvi.lParam; - if (s) { + if (s) + { s->is_hidden = false; Prefs_SetHidden(s->gh_path, false); } diff --git a/src/quickbar.c b/src/quickbar.c index a37a523..1ce7435 100644 --- a/src/quickbar.c +++ b/src/quickbar.c @@ -12,27 +12,28 @@ /* ------------------------------------------------------------------ */ /* Internal constants */ /* ------------------------------------------------------------------ */ -#define QBAR_WNDCLASS L"CMW32QuickBar" -#define QBAR_TIPCLASS L"CMW32QBarTip" -#define HIT_NONE (-1) /* background / drag area */ -#define HIT_ARROW_PREV (-2) /* scroll-back arrow (▲ or ◄) */ -#define HIT_ARROW_NEXT (-3) /* scroll-forward arrow (▼ or ►) */ +#define QBAR_WNDCLASS L"CMW32QuickBar" +#define QBAR_TIPCLASS L"CMW32QBarTip" +#define HIT_NONE (-1) /* background / drag area */ +#define HIT_ARROW_PREV (-2) /* scroll-back arrow (▲ or ◄) */ +#define HIT_ARROW_NEXT (-3) /* scroll-forward arrow (▼ or ►) */ /* ------------------------------------------------------------------ */ /* Module-level state */ /* ------------------------------------------------------------------ */ -static HWINEVENTHOOK s_event_hook; /* EVENT_SYSTEM_FOREGROUND hook */ +static HWINEVENTHOOK s_event_hook; /* EVENT_SYSTEM_FOREGROUND hook */ static HWINEVENTHOOK s_minimize_hook; /* MINIMIZESTART..END hook */ -static HFONT s_font_label; /* large bold font for 2-letter abbrev */ +static HFONT s_font_label; /* large bold font for 2-letter abbrev */ /* ------------------------------------------------------------------ */ /* CatiaState */ /* Describes the aggregate visibility of all CATIA V5 windows. */ /* ------------------------------------------------------------------ */ -typedef enum { - CATIA_NONE = 0, /* no CATIA window found at all */ - CATIA_MINIMIZED = 1, /* CATIA is running but all windows minimized */ - CATIA_VISIBLE = 2 /* at least one CATIA window is visible */ +typedef enum +{ + CATIA_NONE = 0, /* no CATIA window found at all */ + CATIA_MINIMIZED = 1, /* CATIA is running but all windows minimized */ + CATIA_VISIBLE = 2 /* at least one CATIA window is visible */ } CatiaState; /* ------------------------------------------------------------------ */ @@ -42,9 +43,9 @@ static LRESULT CALLBACK QuickBarProc(HWND, UINT, WPARAM, LPARAM); static LRESULT CALLBACK QBarTipProc(HWND, UINT, WPARAM, LPARAM); static INT_PTR CALLBACK QB_TargetAppDlgProc(HWND, UINT, WPARAM, LPARAM); static void QB_Paint(HWND hwnd, HDC hdc); -static int QB_HitTest(HWND hwnd, int x, int y); +static int QB_HitTest(HWND hwnd, int x, int y); static void QB_BtnRect(HWND hwnd, int idx, RECT *r); -static int QB_FavCount(void); +static int QB_FavCount(void); static Script *QB_GetFav(int idx, int *fi_out, int *si_out); static void QB_UpdateGeometry(void); static void QB_UpdateScrollMax(HWND hwnd); @@ -56,7 +57,7 @@ static bool QB_IsTarget(HWND hwnd); static CatiaState QB_CatiaState(void); static void QB_UpdateVisibility(HWND fg_hwnd); static void CALLBACK QB_WinEventProc(HWINEVENTHOOK, DWORD, HWND, - LONG, LONG, DWORD, DWORD); + LONG, LONG, DWORD, DWORD); /* ================================================================== */ /* QB_FavCount */ @@ -64,12 +65,12 @@ static void CALLBACK QB_WinEventProc(HWINEVENTHOOK, DWORD, HWND, /* ================================================================== */ static int QB_FavCount(void) { - if (g.folder_count == 0) return 0; /* no data loaded yet */ - if (wcscmp(g.folders[0].name, L"Favourites") != 0) return 0; /* first folder must be Favourites */ + if (g.folder_count == 0) return 0; /* no data loaded yet */ + if (wcscmp(g.folders[0].name, L"Favourites") != 0) return 0; /* first folder must be Favourites */ ScriptFolder *f = &g.folders[0]; int n = 0; for (int i = 0; i < f->count; i++) - if (!f->scripts[i].is_hidden) n++; /* count only visible scripts */ + if (!f->scripts[i].is_hidden) n++; /* count only visible scripts */ return n; } @@ -83,9 +84,11 @@ static Script *QB_GetFav(int idx, int *fi_out, int *si_out) if (wcscmp(g.folders[0].name, L"Favourites") != 0) return NULL; ScriptFolder *f = &g.folders[0]; int n = 0; - for (int i = 0; i < f->count; i++) { + for (int i = 0; i < f->count; i++) + { if (f->scripts[i].is_hidden) continue; - if (n == idx) { + if (n == idx) + { if (fi_out) *fi_out = 0; /* Favourites folder is always at index 0 */ if (si_out) *si_out = i; return &f->scripts[i]; @@ -130,14 +133,14 @@ static bool QB_GetProcessExeName(HWND hwnd, WCHAR *buf, int len) /* ================================================================== */ static bool QB_IsTarget(HWND hwnd) { - if (!hwnd || !g.cfg.qbar_target_app[0]) return false; /* no target configured — nothing can match */ + if (!hwnd || !g.cfg.qbar_target_app[0]) return false; /* no target configured — nothing can match */ WCHAR title[256] = {0}; GetWindowTextW(hwnd, title, 255); - if (!wcsstr(title, g.cfg.qbar_target_app)) return false; /* title doesn't contain filter substring */ - if (!g.cfg.qbar_target_exe[0]) return true; /* no exe filter — title match is sufficient */ + if (!wcsstr(title, g.cfg.qbar_target_app)) return false; /* title doesn't contain filter substring */ + if (!g.cfg.qbar_target_exe[0]) return true; /* no exe filter — title match is sufficient */ WCHAR exe[MAX_NAME] = {0}; if (!QB_GetProcessExeName(hwnd, exe, MAX_NAME)) return false; /* can't query owning process */ - return _wcsicmp(exe, g.cfg.qbar_target_exe) == 0; /* case-insensitive exe filename comparison */ + return _wcsicmp(exe, g.cfg.qbar_target_exe) == 0; /* case-insensitive exe filename comparison */ } /* ================================================================== */ @@ -154,18 +157,22 @@ static BOOL CALLBACK CatiaCheckProc(HWND hwnd, LPARAM lp) if (!IsWindowVisible(hwnd)) return TRUE; WCHAR title[256] = {0}; GetWindowTextW(hwnd, title, 255); - if (!wcsstr(title, g.cfg.qbar_target_app)) return TRUE; /* title doesn't match filter; continue enum */ + if (!wcsstr(title, g.cfg.qbar_target_app)) return TRUE; /* title doesn't match filter; continue enum */ /* Optional exe filter — skip window if it belongs to a different process */ - if (g.cfg.qbar_target_exe[0]) { + if (g.cfg.qbar_target_exe[0]) + { WCHAR exe[MAX_NAME] = {0}; if (!QB_GetProcessExeName(hwnd, exe, MAX_NAME)) return TRUE; /* can't determine exe; skip */ if (_wcsicmp(exe, g.cfg.qbar_target_exe) != 0) return TRUE; /* exe mismatch; skip */ } /* Passed all filters — categorise by window state */ - if (IsIconic(hwnd)) { + if (IsIconic(hwnd)) + { /* IsIconic = minimized; upgrade state but don't downgrade if VISIBLE already found */ if (*result < CATIA_MINIMIZED) *result = CATIA_MINIMIZED; - } else { + } + else + { *result = CATIA_VISIBLE; return FALSE; /* found a visible one — stop EnumWindows early */ } @@ -195,7 +202,8 @@ static void QB_UpdateVisibility(HWND fg_hwnd) /* No target app configured: bar is always visible when enabled, no topmost behaviour. */ - if (!g.cfg.qbar_target_app[0]) { + if (!g.cfg.qbar_target_app[0]) + { if (g.cfg.qbar_enabled && !IsWindowVisible(g.hwnd_qbar)) /* only show if currently hidden */ ShowWindow(g.hwnd_qbar, SW_SHOWNOACTIVATE); return; @@ -204,7 +212,8 @@ static void QB_UpdateVisibility(HWND fg_hwnd) CatiaState state = QB_CatiaState(); /* Hide the bar when the target app is not open or is fully minimised. */ - if (state != CATIA_VISIBLE) { + if (state != CATIA_VISIBLE) + { QB_HideTip(); ShowWindow(g.hwnd_qbar, SW_HIDE); return; /* nothing more to do when target isn't visible */ @@ -220,7 +229,8 @@ static void QB_UpdateVisibility(HWND fg_hwnd) /* Go topmost only when CATIA is visible AND the new foreground window is the target */ HWND ins = (state == CATIA_VISIBLE && QB_IsTarget(fg_hwnd)) - ? HWND_TOPMOST : HWND_NOTOPMOST; + ? HWND_TOPMOST + : HWND_NOTOPMOST; SetWindowPos(g.hwnd_qbar, ins, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } @@ -231,9 +241,13 @@ static void QB_UpdateVisibility(HWND fg_hwnd) /* events. Runs in the main thread via WINEVENT_OUTOFCONTEXT. */ /* ================================================================== */ static void CALLBACK QB_WinEventProc(HWINEVENTHOOK hook, DWORD event, - HWND hwnd, LONG idObj, LONG idChild, DWORD thread, DWORD time) + HWND hwnd, LONG idObj, LONG idChild, DWORD thread, DWORD time) { - (void)hook; (void)idObj; (void)idChild; (void)thread; (void)time; + (void)hook; + (void)idObj; + (void)idChild; + (void)thread; + (void)time; /* Pass hwnd only for foreground events so QB_UpdateVisibility can use it for the topmost decision. Minimize/restore events carry the window being minimised, not the new foreground window. */ @@ -251,20 +265,23 @@ static void QB_BtnRect(HWND hwnd, int idx, RECT *r) (void)hwnd; bool horiz = g.cfg.qbar_horizontal; bool has_arrows = (g.qbar_scroll_max > 0); - int arrow_off = has_arrows ? QBAR_ARROW_W : 0; /* shift origin past the scroll arrow zone */ - int step = QBAR_BTN_SIZE + QBAR_GAP; /* distance between button origins */ + int arrow_off = has_arrows ? QBAR_ARROW_W : 0; /* shift origin past the scroll arrow zone */ + int step = QBAR_BTN_SIZE + QBAR_GAP; /* distance between button origins */ - if (horiz) { + if (horiz) + { int x = QBAR_PAD + arrow_off + idx * step - g.qbar_scroll; /* subtract scroll offset so buttons shift left */ - r->left = x; - r->top = QBAR_PAD; - r->right = x + QBAR_BTN_SIZE; + r->left = x; + r->top = QBAR_PAD; + r->right = x + QBAR_BTN_SIZE; r->bottom = QBAR_PAD + QBAR_BTN_SIZE; - } else { + } + else + { int y = QBAR_PAD + arrow_off + idx * step - g.qbar_scroll; /* subtract scroll offset so buttons shift up */ - r->left = QBAR_PAD; - r->top = y; - r->right = QBAR_PAD + QBAR_BTN_SIZE; + r->left = QBAR_PAD; + r->top = y; + r->right = QBAR_PAD + QBAR_BTN_SIZE; r->bottom = y + QBAR_BTN_SIZE; } } @@ -275,23 +292,30 @@ static void QB_BtnRect(HWND hwnd, int idx, RECT *r) /* ================================================================== */ static int QB_HitTest(HWND hwnd, int x, int y) { - RECT rc; GetClientRect(hwnd, &rc); - bool horiz = g.cfg.qbar_horizontal; + RECT rc; + GetClientRect(hwnd, &rc); + bool horiz = g.cfg.qbar_horizontal; bool has_arrows = (g.qbar_scroll_max > 0); - if (has_arrows) { - if (horiz) { - if (x < QBAR_ARROW_W) return HIT_ARROW_PREV; - if (x > rc.right - QBAR_ARROW_W) return HIT_ARROW_NEXT; - } else { - if (y < QBAR_ARROW_W) return HIT_ARROW_PREV; - if (y > rc.bottom - QBAR_ARROW_W) return HIT_ARROW_NEXT; + if (has_arrows) + { + if (horiz) + { + if (x < QBAR_ARROW_W) return HIT_ARROW_PREV; + if (x > rc.right - QBAR_ARROW_W) return HIT_ARROW_NEXT; + } + else + { + if (y < QBAR_ARROW_W) return HIT_ARROW_PREV; + if (y > rc.bottom - QBAR_ARROW_W) return HIT_ARROW_NEXT; } } int n = QB_FavCount(); - for (int i = 0; i < n; i++) { - RECT br; QB_BtnRect(hwnd, i, &br); + for (int i = 0; i < n; i++) + { + RECT br; + QB_BtnRect(hwnd, i, &br); if (x >= br.left && x < br.right && y >= br.top && y < br.bottom) return i; } @@ -304,10 +328,11 @@ static int QB_HitTest(HWND hwnd, int x, int y) /* ================================================================== */ static void QB_UpdateScrollMax(HWND hwnd) { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); bool horiz = g.cfg.qbar_horizontal; - int n = QB_FavCount(); - int step = QBAR_BTN_SIZE + QBAR_GAP; + int n = QB_FavCount(); + int step = QBAR_BTN_SIZE + QBAR_GAP; /* Total logical content size: 2*PAD for both ends; last button has no trailing gap; minimum = one button when n=0 */ @@ -317,11 +342,11 @@ static void QB_UpdateScrollMax(HWND hwnd) int visible = horiz ? (rc.right - rc.left) : (rc.bottom - rc.top); /* Arrow areas eat into the drawable space */ - bool need_arrows = content > visible; /* arrows appear only when content overflows */ - int drawable = need_arrows ? visible - 2 * QBAR_ARROW_W : visible; /* each arrow zone subtracts QBAR_ARROW_W */ - if (drawable < 0) drawable = 0; /* safety guard for windows narrower than two arrows */ + bool need_arrows = content > visible; /* arrows appear only when content overflows */ + int drawable = need_arrows ? visible - 2 * QBAR_ARROW_W : visible; /* each arrow zone subtracts QBAR_ARROW_W */ + if (drawable < 0) drawable = 0; /* safety guard for windows narrower than two arrows */ - g.qbar_scroll_max = max(0, content - drawable); /* px of content hidden beyond the visible edge */ + g.qbar_scroll_max = max(0, content - drawable); /* px of content hidden beyond the visible edge */ if (g.qbar_scroll > g.qbar_scroll_max) g.qbar_scroll = g.qbar_scroll_max; /* clamp current offset after resize */ } @@ -333,24 +358,25 @@ static void QB_UpdateGeometry(void) { if (!g.hwnd_qbar) return; - MONITORINFO mi = {0}; mi.cbSize = sizeof(mi); + MONITORINFO mi = {0}; + mi.cbSize = sizeof(mi); HMONITOR hm = MonitorFromWindow(g.hwnd_qbar, MONITOR_DEFAULTTONEAREST); GetMonitorInfo(hm, &mi); RECT work = mi.rcWork; - int n = QB_FavCount(); + int n = QB_FavCount(); int step = QBAR_BTN_SIZE + QBAR_GAP; bool horiz = g.cfg.qbar_horizontal; int content = 2 * QBAR_PAD + (n > 0 ? (n * step - QBAR_GAP) : QBAR_BTN_SIZE); int max_vis = horiz - ? (work.right - work.left) * 4 / 5 /* cap at 80% of screen width */ - : (work.bottom - work.top) * 4 / 5; /* cap at 80% of screen height */ + ? (work.right - work.left) * 4 / 5 /* cap at 80% of screen width */ + : (work.bottom - work.top) * 4 / 5; /* cap at 80% of screen height */ if (max_vis < QBAR_BTN_SIZE + 2 * QBAR_PAD) max_vis = QBAR_BTN_SIZE + 2 * QBAR_PAD; /* floor: at least one button must fit */ - int vis = min(content, max_vis); /* actual client length in scroll direction */ - int fixed_dim = QBAR_BTN_SIZE + 2 * QBAR_PAD; /* client dimension perpendicular to scroll */ + int vis = min(content, max_vis); /* actual client length in scroll direction */ + int fixed_dim = QBAR_BTN_SIZE + 2 * QBAR_PAD; /* client dimension perpendicular to scroll */ /* WS_BORDER adds 1px each side — window = client + 2 */ int window_w = horiz ? (vis + 2) : (fixed_dim + 2); @@ -358,10 +384,10 @@ static void QB_UpdateGeometry(void) /* Clamp saved position to monitor work area */ int x = g.cfg.qbar_x, y = g.cfg.qbar_y; - if (x + window_w > work.right) x = work.right - window_w; /* clamp right edge */ + if (x + window_w > work.right) x = work.right - window_w; /* clamp right edge */ if (y + window_h > work.bottom) y = work.bottom - window_h; /* clamp bottom edge */ if (x < work.left) x = work.left; /* clamp left edge */ - if (y < work.top) y = work.top; /* clamp top edge */ + if (y < work.top) y = work.top; /* clamp top edge */ SetWindowPos(g.hwnd_qbar, NULL, x, y, window_w, window_h, SWP_NOZORDER | SWP_NOACTIVATE); @@ -375,7 +401,8 @@ static void QB_UpdateGeometry(void) static void QB_SavePos(void) { if (!g.hwnd_qbar) return; - RECT rc; GetWindowRect(g.hwnd_qbar, &rc); + RECT rc; + GetWindowRect(g.hwnd_qbar, &rc); if (rc.left == g.cfg.qbar_x && rc.top == g.cfg.qbar_y) return; g.cfg.qbar_x = rc.left; g.cfg.qbar_y = rc.top; @@ -388,10 +415,11 @@ static void QB_SavePos(void) /* ================================================================== */ static void QB_Paint(HWND hwnd, HDC hdc) { - RECT rc; GetClientRect(hwnd, &rc); - bool horiz = g.cfg.qbar_horizontal; + RECT rc; + GetClientRect(hwnd, &rc); + bool horiz = g.cfg.qbar_horizontal; bool has_arrows = (g.qbar_scroll_max > 0); - int n = QB_FavCount(); + int n = QB_FavCount(); /* ── Background ──────────────────────────────────────────────── */ HBRUSH br_bg = CreateSolidBrush(COL_TOOLBAR()); @@ -408,7 +436,8 @@ static void QB_Paint(HWND hwnd, HDC hdc) DeleteObject(pen_brd); /* ── Empty state hint ─────────────────────────────────────────── */ - if (n == 0) { /* no favourites — show placeholder text instead of buttons */ + if (n == 0) + { /* no favourites — show placeholder text instead of buttons */ SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, COL_SUBTEXT()); HFONT of = SelectObject(hdc, g.font_small); @@ -419,41 +448,55 @@ static void QB_Paint(HWND hwnd, HDC hdc) } /* ── Scroll arrows ─────────────────────────────────────────────── */ - if (has_arrows) { /* content overflows window — draw scroll controls */ + if (has_arrows) + { /* content overflows window — draw scroll controls */ COLORREF arrow_col = COL_SUBTEXT(); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, arrow_col); HFONT of = SelectObject(hdc, g.font_bold); RECT ar; - if (!horiz) { + if (!horiz) + { /* ▲ at top */ - ar.left = rc.left; ar.right = rc.right; - ar.top = 0; ar.bottom = QBAR_ARROW_W; - if (g.qbar_scroll > 0) { /* not at start — back arrow is active */ + ar.left = rc.left; + ar.right = rc.right; + ar.top = 0; + ar.bottom = QBAR_ARROW_W; + if (g.qbar_scroll > 0) + { /* not at start — back arrow is active */ SetTextColor(hdc, COL_ACCENT); DrawTextW(hdc, L"▲", -1, &ar, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SetTextColor(hdc, arrow_col); } /* ▼ at bottom */ - ar.top = rc.bottom - QBAR_ARROW_W; ar.bottom = rc.bottom; - if (g.qbar_scroll < g.qbar_scroll_max) { /* not at end — forward arrow is active */ + ar.top = rc.bottom - QBAR_ARROW_W; + ar.bottom = rc.bottom; + if (g.qbar_scroll < g.qbar_scroll_max) + { /* not at end — forward arrow is active */ SetTextColor(hdc, COL_ACCENT); DrawTextW(hdc, L"▼", -1, &ar, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SetTextColor(hdc, arrow_col); } - } else { + } + else + { /* ◄ at left */ - ar.left = 0; ar.right = QBAR_ARROW_W; - ar.top = rc.top; ar.bottom = rc.bottom; - if (g.qbar_scroll > 0) { /* not at start — back arrow is active */ + ar.left = 0; + ar.right = QBAR_ARROW_W; + ar.top = rc.top; + ar.bottom = rc.bottom; + if (g.qbar_scroll > 0) + { /* not at start — back arrow is active */ SetTextColor(hdc, COL_ACCENT); DrawTextW(hdc, L"◄", -1, &ar, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SetTextColor(hdc, arrow_col); } /* ► at right */ - ar.left = rc.right - QBAR_ARROW_W; ar.right = rc.right; - if (g.qbar_scroll < g.qbar_scroll_max) { /* not at end — forward arrow is active */ + ar.left = rc.right - QBAR_ARROW_W; + ar.right = rc.right; + if (g.qbar_scroll < g.qbar_scroll_max) + { /* not at end — forward arrow is active */ SetTextColor(hdc, COL_ACCENT); DrawTextW(hdc, L"►", -1, &ar, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } @@ -464,26 +507,32 @@ static void QB_Paint(HWND hwnd, HDC hdc) /* ── Buttons ──────────────────────────────────────────────────── */ /* Clip to avoid drawing into arrow areas */ HRGN clip_rgn = NULL; - if (has_arrows) { + if (has_arrows) + { RECT clip = rc; - if (!horiz) { - clip.top = QBAR_ARROW_W; + if (!horiz) + { + clip.top = QBAR_ARROW_W; clip.bottom = rc.bottom - QBAR_ARROW_W; - } else { - clip.left = QBAR_ARROW_W; + } + else + { + clip.left = QBAR_ARROW_W; clip.right = rc.right - QBAR_ARROW_W; } clip_rgn = CreateRectRgnIndirect(&clip); SelectClipRgn(hdc, clip_rgn); } - for (int i = 0; i < n; i++) { - RECT br; QB_BtnRect(hwnd, i, &br); + for (int i = 0; i < n; i++) + { + RECT br; + QB_BtnRect(hwnd, i, &br); /* Skip fully off-screen buttons (scrolled out of view) */ if (br.right < 0 || br.bottom < 0) continue; if (!horiz && (br.top > rc.bottom || br.bottom < 0)) continue; - if ( horiz && (br.left > rc.right || br.right < 0)) continue; + if (horiz && (br.left > rc.right || br.right < 0)) continue; bool hot = (i == g.qbar_hot); @@ -499,23 +548,33 @@ static void QB_Paint(HWND hwnd, HDC hdc) /* Button background — offline tint takes priority over source tint; local scripts never tinted red */ COLORREF idle_bg = COL_BTN_NORM(); - if (!hot) { - if (g.status_offline && g.cfg.offline_use_cache) { - if (s->source == SCRIPT_SRC_MAIN) idle_bg = COL_BTN_OFFLINE_MAIN(); - else if (s->source == SCRIPT_SRC_EXTRA) idle_bg = COL_BTN_OFFLINE_EXTRA(); - else if (g.cfg.tint_script_sources) idle_bg = COL_BTN_LOCAL(); - } else if (g.cfg.tint_script_sources) { - if (s->source == SCRIPT_SRC_LOCAL) idle_bg = COL_BTN_LOCAL(); - else if (s->source == SCRIPT_SRC_EXTRA) idle_bg = COL_BTN_EXTRA(); + if (!hot) + { + if (g.status_offline && g.cfg.offline_use_cache) + { + if (s->source == SCRIPT_SRC_MAIN) + idle_bg = COL_BTN_OFFLINE_MAIN(); + else if (s->source == SCRIPT_SRC_EXTRA) + idle_bg = COL_BTN_OFFLINE_EXTRA(); + else if (g.cfg.tint_script_sources) + idle_bg = COL_BTN_LOCAL(); + } + else if (g.cfg.tint_script_sources) + { + if (s->source == SCRIPT_SRC_LOCAL) + idle_bg = COL_BTN_LOCAL(); + else if (s->source == SCRIPT_SRC_EXTRA) + idle_bg = COL_BTN_EXTRA(); } } - COLORREF bg = hot ? COL_BTN_HOT() : idle_bg; - COLORREF brd = rep ? COL_WARN : run ? COL_SUCCESS : (hot ? COL_ACCENT : COL_DIVIDER()); + COLORREF bg = hot ? COL_BTN_HOT() : idle_bg; + COLORREF brd = rep ? COL_WARN : run ? COL_SUCCESS + : (hot ? COL_ACCENT : COL_DIVIDER()); /* border priority: repeating > running > hovered > idle */ HBRUSH br_btn = CreateSolidBrush(bg); - HPEN pen = CreatePen(PS_SOLID, (rep || run) ? 2 : 1, brd); /* 2px border for active states */ - HPEN op = SelectObject(hdc, pen); - HBRUSH ob = SelectObject(hdc, br_btn); + HPEN pen = CreatePen(PS_SOLID, (rep || run) ? 2 : 1, brd); /* 2px border for active states */ + HPEN op = SelectObject(hdc, pen); + HBRUSH ob = SelectObject(hdc, br_btn); RoundRect(hdc, br.left, br.top, br.right, br.bottom, 10, 10); /* corner radius 10 */ SelectObject(hdc, op); SelectObject(hdc, ob); @@ -523,21 +582,26 @@ static void QB_Paint(HWND hwnd, HDC hdc) DeleteObject(br_btn); /* Accent edge bar when hot, repeating, or running */ - if (hot || rep || run) { - HBRUSH ba = CreateSolidBrush(rep ? COL_WARN : run ? COL_SUCCESS : COL_ACCENT); - RECT acc; - if (!horiz) { + if (hot || rep || run) + { + HBRUSH ba = CreateSolidBrush(rep ? COL_WARN : run ? COL_SUCCESS + : COL_ACCENT); + RECT acc; + if (!horiz) + { /* Vertical bar: accent strip on the left edge */ - acc.left = br.left; - acc.top = br.top + 8; /* 8px inset from button top */ - acc.right = br.left + 3; /* 3px wide */ + acc.left = br.left; + acc.top = br.top + 8; /* 8px inset from button top */ + acc.right = br.left + 3; /* 3px wide */ acc.bottom = br.bottom - 8; /* 8px inset from button bottom */ - } else { + } + else + { /* Horizontal bar: accent strip on the top edge */ - acc.left = br.left + 8; /* 8px inset from button left */ - acc.top = br.top; - acc.right = br.right - 8; /* 8px inset from button right */ - acc.bottom = br.top + 3; /* 3px tall */ + acc.left = br.left + 8; /* 8px inset from button left */ + acc.top = br.top; + acc.right = br.right - 8; /* 8px inset from button right */ + acc.bottom = br.top + 3; /* 3px tall */ } FillRect(hdc, &acc, ba); DeleteObject(ba); @@ -551,21 +615,24 @@ static void QB_Paint(HWND hwnd, HDC hdc) /* Extract the first 2 non-separator characters as the button label */ WCHAR label[3] = {0}; - int li = 0; - for (int j = 0; tmp[j] && li < 2; j++) { + int li = 0; + for (int j = 0; tmp[j] && li < 2; j++) + { WCHAR ch = tmp[j]; - if (ch != L'_' && ch != L' ' && ch != L'-') /* skip separator characters */ - label[li++] = (WCHAR)towupper(ch); /* accumulate up to 2 uppercase characters */ + if (ch != L'_' && ch != L' ' && ch != L'-') /* skip separator characters */ + label[li++] = (WCHAR)towupper(ch); /* accumulate up to 2 uppercase characters */ } SetBkMode(hdc, TRANSPARENT); - SetTextColor(hdc, rep ? COL_WARN : run ? COL_SUCCESS : (hot ? COL_ACCENT : COL_TEXT())); + SetTextColor(hdc, rep ? COL_WARN : run ? COL_SUCCESS + : (hot ? COL_ACCENT : COL_TEXT())); HFONT of = SelectObject(hdc, s_font_label); DrawTextW(hdc, label, -1, &br, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, of); } - if (clip_rgn) { + if (clip_rgn) + { SelectClipRgn(hdc, NULL); DeleteObject(clip_rgn); } @@ -583,36 +650,41 @@ static void QB_ShowTip(HWND hwnd, int idx) g.qbar_tip_idx = idx; if (!s->meta_loaded) Meta_Parse(s); - int rows = 1 + (s->meta.purpose[0] ? 1 : 0); /* 1 name row + optional purpose row */ - int tip_h = 2 * QBAR_TIP_PAD + rows * QBAR_TIP_ROW; /* height scales with row count */ - int tip_w = QBAR_TIP_W; + int rows = 1 + (s->meta.purpose[0] ? 1 : 0); /* 1 name row + optional purpose row */ + int tip_h = 2 * QBAR_TIP_PAD + rows * QBAR_TIP_ROW; /* height scales with row count */ + int tip_w = QBAR_TIP_W; - RECT br; QB_BtnRect(hwnd, idx, &br); + RECT br; + QB_BtnRect(hwnd, idx, &br); POINT tl = {br.left, br.top}; POINT tr = {br.right, br.bottom}; ClientToScreen(hwnd, &tl); ClientToScreen(hwnd, &tr); - MONITORINFO mi = {0}; mi.cbSize = sizeof(mi); + MONITORINFO mi = {0}; + mi.cbSize = sizeof(mi); HMONITOR hm = MonitorFromPoint(tl, MONITOR_DEFAULTTONEAREST); GetMonitorInfo(hm, &mi); RECT work = mi.rcWork; int x, y; - if (!g.cfg.qbar_horizontal) { + if (!g.cfg.qbar_horizontal) + { /* Vertical bar: show to the right, fallback left */ - x = tr.x + 4; /* 4px gap from bar right edge */ + x = tr.x + 4; /* 4px gap from bar right edge */ y = tl.y; if (x + tip_w > work.right) x = tl.x - tip_w - 4; /* flip left if clipping right edge */ if (y + tip_h > work.bottom) y = work.bottom - tip_h; /* clamp to bottom of work area */ - if (y < work.top) y = work.top; /* clamp to top of work area */ - } else { + if (y < work.top) y = work.top; /* clamp to top of work area */ + } + else + { /* Horizontal bar: show below, fallback above */ x = tl.x; - y = tr.y + 4; /* 4px gap below bar bottom edge */ + y = tr.y + 4; /* 4px gap below bar bottom edge */ if (y + tip_h > work.bottom) y = tl.y - tip_h - 4; /* flip above bar if clipping bottom */ - if (x + tip_w > work.right) x = work.right - tip_w; /* clamp right edge */ - if (x < work.left) x = work.left; /* clamp left edge */ + if (x + tip_w > work.right) x = work.right - tip_w; /* clamp right edge */ + if (x < work.left) x = work.left; /* clamp left edge */ } SetWindowPos(g.hwnd_qbar_tip, HWND_TOPMOST, x, y, tip_w, tip_h, @@ -636,8 +708,8 @@ static void QB_HideTip(void) static void QB_Scroll(HWND hwnd, int delta) { g.qbar_scroll += delta; - if (g.qbar_scroll < 0) g.qbar_scroll = 0; /* clamp at start */ - if (g.qbar_scroll > g.qbar_scroll_max) g.qbar_scroll = g.qbar_scroll_max; /* clamp at end */ + if (g.qbar_scroll < 0) g.qbar_scroll = 0; /* clamp at start */ + if (g.qbar_scroll > g.qbar_scroll_max) g.qbar_scroll = g.qbar_scroll_max; /* clamp at end */ InvalidateRect(hwnd, NULL, FALSE); } @@ -649,32 +721,33 @@ static void QB_ShowContextMenu(HWND hwnd) bool has_target = g.cfg.qbar_target_app[0] != L'\0'; HMENU hm = CreatePopupMenu(); - AppendMenu(hm, MF_STRING, IDM_QBAR_TOGGLE, L"Enable Quick Bar"); + AppendMenu(hm, MF_STRING, IDM_QBAR_TOGGLE, L"Enable Quick Bar"); AppendMenu(hm, MF_SEPARATOR, 0, NULL); - AppendMenu(hm, MF_STRING, IDM_QBAR_HORIZONTAL, L"Horizontal"); - AppendMenu(hm, MF_STRING, IDM_QBAR_VERTICAL, L"Vertical"); + AppendMenu(hm, MF_STRING, IDM_QBAR_HORIZONTAL, L"Horizontal"); + AppendMenu(hm, MF_STRING, IDM_QBAR_VERTICAL, L"Vertical"); AppendMenu(hm, MF_SEPARATOR, 0, NULL); AppendMenu(hm, has_target ? MF_STRING : (MF_STRING | MF_GRAYED), IDM_QBAR_TOPMOST, L"On Top with Target App"); - AppendMenu(hm, MF_STRING, IDM_QBAR_SET_TARGET, L"Set Target App..."); + AppendMenu(hm, MF_STRING, IDM_QBAR_SET_TARGET, L"Set Target App..."); AppendMenu(hm, MF_SEPARATOR, 0, NULL); - AppendMenu(hm, MF_STRING, IDM_QBAR_RESET_POS, L"Reset Position"); + AppendMenu(hm, MF_STRING, IDM_QBAR_RESET_POS, L"Reset Position"); AppendMenu(hm, MF_SEPARATOR, 0, NULL); - AppendMenu(hm, MF_STRING, IDM_REPEAT_QBAR, L"Repeat on Double-Click"); + AppendMenu(hm, MF_STRING, IDM_REPEAT_QBAR, L"Repeat on Double-Click"); CheckMenuItem(hm, IDM_QBAR_TOGGLE, - g.cfg.qbar_enabled ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_enabled ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hm, IDM_QBAR_HORIZONTAL, - g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hm, IDM_QBAR_VERTICAL, - !g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); + !g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); if (has_target) CheckMenuItem(hm, IDM_QBAR_TOPMOST, - g.cfg.qbar_topmost_with_catia ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_topmost_with_catia ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hm, IDM_REPEAT_QBAR, - g.cfg.qbar_repeat_on_dblclick ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_repeat_on_dblclick ? MF_CHECKED : MF_UNCHECKED); - POINT pt; GetCursorPos(&pt); + POINT pt; + GetCursorPos(&pt); /* SetForegroundWindow ensures the menu closes properly */ SetForegroundWindow(hwnd); TrackPopupMenu(hm, TPM_RIGHTBUTTON | TPM_LEFTALIGN, @@ -687,9 +760,10 @@ static void QB_ShowContextMenu(HWND hwnd) /* Tooltip popup window procedure. */ /* ================================================================== */ static LRESULT CALLBACK QBarTipProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { - (void)wp; (void)lp; + (void)wp; + (void)lp; switch (msg) { case WM_ERASEBKGND: @@ -699,7 +773,8 @@ static LRESULT CALLBACK QBarTipProc(HWND hwnd, UINT msg, { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); /* Background */ HBRUSH br = CreateSolidBrush(COL_TIP_BG()); @@ -708,16 +783,24 @@ static LRESULT CALLBACK QBarTipProc(HWND hwnd, UINT msg, /* Border */ HPEN pen = CreatePen(PS_SOLID, 1, COL_TIP_BORDER); - HPEN op = SelectObject(hdc, pen); + HPEN op = SelectObject(hdc, pen); HBRUSH ob = SelectObject(hdc, GetStockObject(NULL_BRUSH)); Rectangle(hdc, rc.left, rc.top, rc.right - 1, rc.bottom - 1); SelectObject(hdc, op); SelectObject(hdc, ob); DeleteObject(pen); - if (g.qbar_tip_idx < 0) { EndPaint(hwnd, &ps); return 0; } /* no tip active — nothing to draw */ + if (g.qbar_tip_idx < 0) + { + EndPaint(hwnd, &ps); + return 0; + } /* no tip active — nothing to draw */ Script *s = QB_GetFav(g.qbar_tip_idx, NULL, NULL); - if (!s) { EndPaint(hwnd, &ps); return 0; } /* stale index; script was removed */ + if (!s) + { + EndPaint(hwnd, &ps); + return 0; + } /* stale index; script was removed */ SetBkMode(hdc, TRANSPARENT); int y = QBAR_TIP_PAD; /* start drawing below top padding */ @@ -729,9 +812,9 @@ static LRESULT CALLBACK QBarTipProc(HWND hwnd, UINT msg, Util_StripExt(disp); Util_SnakeToTitle(disp); - tr.left = QBAR_TIP_PAD; - tr.right = rc.right - QBAR_TIP_PAD; - tr.top = y; + tr.left = QBAR_TIP_PAD; + tr.right = rc.right - QBAR_TIP_PAD; + tr.top = y; tr.bottom = y + QBAR_TIP_ROW; SetTextColor(hdc, COL_TEXT()); HFONT of = SelectObject(hdc, g.font_bold); @@ -740,8 +823,9 @@ static LRESULT CALLBACK QBarTipProc(HWND hwnd, UINT msg, y += QBAR_TIP_ROW; /* advance to next row */ /* Purpose line in small text */ - if (s->meta.purpose[0]) { /* show purpose only when metadata has been parsed */ - tr.top = y; + if (s->meta.purpose[0]) + { /* show purpose only when metadata has been parsed */ + tr.top = y; tr.bottom = y + QBAR_TIP_ROW; SetTextColor(hdc, COL_SUBTEXT()); of = SelectObject(hdc, g.font_small); @@ -764,13 +848,13 @@ static LRESULT CALLBACK QBarTipProc(HWND hwnd, UINT msg, /* cancels both repeat mode and any running background script. */ /* ================================================================== */ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: - g.qbar_hot = -1; /* -1 = no button is hovered */ - g.qbar_tip_idx = -1; /* -1 = no tooltip displayed */ + g.qbar_hot = -1; /* -1 = no button is hovered */ + g.qbar_tip_idx = -1; /* -1 = no tooltip displayed */ g.qbar_dragging = false; return 0; @@ -781,10 +865,11 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, { PAINTSTRUCT ps; HDC hdc_raw = BeginPaint(hwnd, &ps); - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); /* Double-buffered to prevent flicker */ - HDC hdc = CreateCompatibleDC(hdc_raw); + HDC hdc = CreateCompatibleDC(hdc_raw); HBITMAP bmp = CreateCompatibleBitmap(hdc_raw, rc.right, rc.bottom); HBITMAP old = SelectObject(hdc, bmp); @@ -801,8 +886,11 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, /* Make the background area act as a drag handle */ case WM_SETCURSOR: { - POINT pt; GetCursorPos(&pt); ScreenToClient(hwnd, &pt); - if (QB_HitTest(hwnd, pt.x, pt.y) == HIT_NONE) { /* pointer is on background, not a button */ + POINT pt; + GetCursorPos(&pt); + ScreenToClient(hwnd, &pt); + if (QB_HitTest(hwnd, pt.x, pt.y) == HIT_NONE) + { /* pointer is on background, not a button */ SetCursor(LoadCursor(NULL, IDC_SIZEALL)); return TRUE; /* handled; suppress default cursor */ } @@ -814,20 +902,27 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, int x = GET_X_LPARAM(lp), y = GET_Y_LPARAM(lp); int hit = QB_HitTest(hwnd, x, y); - if (hit == HIT_NONE) { + if (hit == HIT_NONE) + { /* Start drag */ QB_HideTip(); g.qbar_hot = -1; InvalidateRect(hwnd, NULL, FALSE); SetCapture(hwnd); g.qbar_dragging = true; - POINT sp; GetCursorPos(&sp); - RECT wr; GetWindowRect(hwnd, &wr); + POINT sp; + GetCursorPos(&sp); + RECT wr; + GetWindowRect(hwnd, &wr); g.qbar_drag_ox = sp.x - wr.left; g.qbar_drag_oy = sp.y - wr.top; - } else if (hit == HIT_ARROW_PREV) { + } + else if (hit == HIT_ARROW_PREV) + { QB_Scroll(hwnd, -(QBAR_BTN_SIZE + QBAR_GAP)); /* scroll back by one button step */ - } else if (hit == HIT_ARROW_NEXT) { + } + else if (hit == HIT_ARROW_NEXT) + { QB_Scroll(hwnd, +(QBAR_BTN_SIZE + QBAR_GAP)); /* scroll forward by one button step */ } /* Button presses are handled on LBUTTONUP to avoid false triggers */ @@ -837,22 +932,27 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, case WM_LBUTTONUP: { bool was_drag = g.qbar_dragging; /* capture before clearing so post-drag clicks are skipped */ - if (g.qbar_dragging) { + if (g.qbar_dragging) + { ReleaseCapture(); g.qbar_dragging = false; QB_SavePos(); } - if (g.suppress_lbuttonup) { /* eat the LBUTTONUP that follows a double-click */ + if (g.suppress_lbuttonup) + { /* eat the LBUTTONUP that follows a double-click */ g.suppress_lbuttonup = false; return 0; } - if (!was_drag) { - int x = GET_X_LPARAM(lp), y = GET_Y_LPARAM(lp); + if (!was_drag) + { + int x = GET_X_LPARAM(lp), y = GET_Y_LPARAM(lp); int hit = QB_HitTest(hwnd, x, y); - if (hit >= 0) { + if (hit >= 0) + { int fi = 0, si = 0; QB_GetFav(hit, &fi, &si); - if (g.repeat_mode) { + if (g.repeat_mode) + { bool same = (g.repeat_fi == fi && g.repeat_si == si); Repeat_Stop(); if (same) return 0; /* clicking the repeating script cancels repeat; don't re-run */ @@ -865,17 +965,19 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, case WM_LBUTTONDBLCLK: { - int x = GET_X_LPARAM(lp), y = GET_Y_LPARAM(lp); + int x = GET_X_LPARAM(lp), y = GET_Y_LPARAM(lp); int hit = QB_HitTest(hwnd, x, y); - if (hit < 0) return 0; /* clicked arrow or background — not a button */ - if (!g.cfg.qbar_repeat_on_dblclick) return 0; /* feature disabled in settings */ - if (g.cfg.show_console) { /* repeat mode uses background process; incompatible with console mode */ + if (hit < 0) return 0; /* clicked arrow or background — not a button */ + if (!g.cfg.qbar_repeat_on_dblclick) return 0; /* feature disabled in settings */ + if (g.cfg.show_console) + { /* repeat mode uses background process; incompatible with console mode */ PostStatus(L"Repeat mode not available in console mode."); return 0; } int fi = 0, si = 0; QB_GetFav(hit, &fi, &si); - if (g.repeat_mode && g.repeat_fi == fi && g.repeat_si == si) { + if (g.repeat_mode && g.repeat_fi == fi && g.repeat_si == si) + { /* Double-click on the already-repeating script — toggle repeat off */ Repeat_Stop(); g.suppress_lbuttonup = false; @@ -894,8 +996,10 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, } case WM_KEYDOWN: - if (wp == VK_ESCAPE) { - if (g.repeat_mode) { + if (wp == VK_ESCAPE) + { + if (g.repeat_mode) + { Repeat_Stop(); PostStatus(L"Repeat cancelled."); } @@ -908,21 +1012,26 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, { int x = GET_X_LPARAM(lp), y = GET_Y_LPARAM(lp); - if (g.qbar_dragging) { - POINT sp; GetCursorPos(&sp); + if (g.qbar_dragging) + { + POINT sp; + GetCursorPos(&sp); SetWindowPos(hwnd, NULL, - sp.x - g.qbar_drag_ox, sp.y - g.qbar_drag_oy, - 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + sp.x - g.qbar_drag_ox, sp.y - g.qbar_drag_oy, + 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); return 0; } int hit = QB_HitTest(hwnd, x, y); int btn = (hit >= 0) ? hit : HIT_NONE; - if (btn != g.qbar_hot) { + if (btn != g.qbar_hot) + { g.qbar_hot = btn; InvalidateRect(hwnd, NULL, FALSE); - if (btn >= 0) QB_ShowTip(hwnd, btn); - else QB_HideTip(); + if (btn >= 0) + QB_ShowTip(hwnd, btn); + else + QB_HideTip(); } TRACKMOUSEEVENT tme = {sizeof(tme), TME_LEAVE, hwnd, 0}; @@ -965,25 +1074,23 @@ static LRESULT CALLBACK QuickBarProc(HWND hwnd, UINT msg, void QuickBar_Register(HINSTANCE hInst) { WNDCLASSEX wc = { - .cbSize = sizeof(wc), - .style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, /* CS_DBLCLKS required for WM_LBUTTONDBLCLK */ - .lpfnWndProc = QuickBarProc, - .hInstance = hInst, - .hCursor = LoadCursor(NULL, IDC_ARROW), + .cbSize = sizeof(wc), + .style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, /* CS_DBLCLKS required for WM_LBUTTONDBLCLK */ + .lpfnWndProc = QuickBarProc, + .hInstance = hInst, + .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), - .lpszClassName = QBAR_WNDCLASS - }; + .lpszClassName = QBAR_WNDCLASS}; RegisterClassEx(&wc); WNDCLASSEX wct = { - .cbSize = sizeof(wct), - .style = CS_HREDRAW | CS_VREDRAW, - .lpfnWndProc = QBarTipProc, - .hInstance = hInst, - .hCursor = LoadCursor(NULL, IDC_ARROW), + .cbSize = sizeof(wct), + .style = CS_HREDRAW | CS_VREDRAW, + .lpfnWndProc = QBarTipProc, + .hInstance = hInst, + .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), - .lpszClassName = QBAR_TIPCLASS - }; + .lpszClassName = QBAR_TIPCLASS}; RegisterClassEx(&wct); /* Large bold font for 2-letter button labels; -22 ≈ 16pt */ @@ -1004,8 +1111,10 @@ void QuickBar_Create(void) HINSTANCE hInst = GetModuleHandle(NULL); /* Default position: near the right edge below the taskbar */ - if (!g.cfg.qbar_x && !g.cfg.qbar_y) { /* both zero = first launch or settings cleared */ - RECT work; SystemParametersInfo(SPI_GETWORKAREA, 0, &work, 0); + if (!g.cfg.qbar_x && !g.cfg.qbar_y) + { /* both zero = first launch or settings cleared */ + RECT work; + SystemParametersInfo(SPI_GETWORKAREA, 0, &work, 0); g.cfg.qbar_x = work.right - (QBAR_BTN_SIZE + 2 * QBAR_PAD + 2) - 20; /* 20px from right edge */ g.cfg.qbar_y = work.top + 60; /* 60px below top of work area (below taskbar with a gap) */ } @@ -1065,12 +1174,32 @@ void QuickBar_Create(void) /* ================================================================== */ void QuickBar_Destroy(void) { - if (s_event_hook) { UnhookWinEvent(s_event_hook); s_event_hook = NULL; } - if (s_minimize_hook){ UnhookWinEvent(s_minimize_hook); s_minimize_hook = NULL; } + if (s_event_hook) + { + UnhookWinEvent(s_event_hook); + s_event_hook = NULL; + } + if (s_minimize_hook) + { + UnhookWinEvent(s_minimize_hook); + s_minimize_hook = NULL; + } QB_HideTip(); - if (g.hwnd_qbar_tip) { DestroyWindow(g.hwnd_qbar_tip); g.hwnd_qbar_tip = NULL; } - if (g.hwnd_qbar) { DestroyWindow(g.hwnd_qbar); g.hwnd_qbar = NULL; } - if (s_font_label) { DeleteObject(s_font_label); s_font_label = NULL; } + if (g.hwnd_qbar_tip) + { + DestroyWindow(g.hwnd_qbar_tip); + g.hwnd_qbar_tip = NULL; + } + if (g.hwnd_qbar) + { + DestroyWindow(g.hwnd_qbar); + g.hwnd_qbar = NULL; + } + if (s_font_label) + { + DeleteObject(s_font_label); + s_font_label = NULL; + } } /* ================================================================== */ @@ -1079,13 +1208,16 @@ void QuickBar_Destroy(void) void QuickBar_Show(bool show) { if (!g.hwnd_qbar) return; - if (show) { + if (show) + { /* When no target is configured, always show. Otherwise only show if the target has a visible window. */ if (!g.cfg.qbar_target_app[0] || QB_CatiaState() == CATIA_VISIBLE) ShowWindow(g.hwnd_qbar, SW_SHOWNOACTIVATE); /* else: bar will appear automatically when the target app becomes visible */ - } else { + } + else + { QB_HideTip(); ShowWindow(g.hwnd_qbar, SW_HIDE); } @@ -1099,8 +1231,8 @@ void QuickBar_Show(bool show) void QuickBar_Rebuild(void) { if (!g.hwnd_qbar) return; - g.qbar_scroll = 0; /* reset to start after content changes */ - g.qbar_hot = -1; /* clear highlight; button indices may have shifted */ + g.qbar_scroll = 0; /* reset to start after content changes */ + g.qbar_hot = -1; /* clear highlight; button indices may have shifted */ QB_HideTip(); QB_UpdateGeometry(); } @@ -1110,11 +1242,13 @@ void QuickBar_Rebuild(void) /* ================================================================== */ void QuickBar_OnThemeChange(void) { - if (g.hwnd_qbar) { + if (g.hwnd_qbar) + { Window_ApplyDarkMode(g.hwnd_qbar); InvalidateRect(g.hwnd_qbar, NULL, FALSE); } - if (g.hwnd_qbar_tip) { + if (g.hwnd_qbar_tip) + { Window_ApplyDarkMode(g.hwnd_qbar_tip); InvalidateRect(g.hwnd_qbar_tip, NULL, FALSE); } @@ -1137,27 +1271,25 @@ void QuickBar_SetTopmost(bool topmost) /* and the optional process executable name (qbar_target_exe). */ /* ================================================================== */ static INT_PTR CALLBACK QB_TargetAppDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { (void)lp; - switch (msg) { + switch (msg) + { case WM_INITDIALOG: - SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET, g.cfg.qbar_target_app); + SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET, g.cfg.qbar_target_app); SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE, g.cfg.qbar_target_exe); return TRUE; /* let Windows set default keyboard focus */ case WM_COMMAND: - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDC_BTN_BROWSE_TARGET_EXE: { WCHAR path[MAX_APPPATH] = {0}; OPENFILENAME ofn = { - .lStructSize = sizeof(ofn), .hwndOwner = hwnd, - .lpstrFilter = L"Executables\0*.exe\0All Files\0*.*\0", - .lpstrFile = path, .nMaxFile = MAX_APPPATH, - .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, - .lpstrTitle = L"Select Target Application" - }; - if (GetOpenFileName(&ofn)) { + .lStructSize = sizeof(ofn), .hwndOwner = hwnd, .lpstrFilter = L"Executables\0*.exe\0All Files\0*.*\0", .lpstrFile = path, .nMaxFile = MAX_APPPATH, .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, .lpstrTitle = L"Select Target Application"}; + if (GetOpenFileName(&ofn)) + { /* Strip the directory path — store only the executable filename */ WCHAR *slash = wcsrchr(path, L'\\'); SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE, slash ? slash + 1 : path); @@ -1166,15 +1298,19 @@ static INT_PTR CALLBACK QB_TargetAppDlgProc(HWND hwnd, UINT msg, } case IDOK: { - /* Helper lambda-equivalent: trim spaces from a buffer in-place */ - #define TRIM(buf) do { \ - WCHAR *_s = (buf); \ - while (*_s == L' ') _s++; \ - WCHAR *_e = _s + wcslen(_s); \ - while (_e > _s && *(_e-1) == L' ') _e--; \ - *_e = L'\0'; \ - wcsncpy_s((buf), MAX_NAME, _s, _TRUNCATE); \ - } while (0) +/* Helper lambda-equivalent: trim spaces from a buffer in-place */ +#define TRIM(buf) \ + do \ + { \ + WCHAR *_s = (buf); \ + while (*_s == L' ') \ + _s++; \ + WCHAR *_e = _s + wcslen(_s); \ + while (_e > _s && *(_e - 1) == L' ') \ + _e--; \ + *_e = L'\0'; \ + wcsncpy_s((buf), MAX_NAME, _s, _TRUNCATE); \ + } while (0) WCHAR buf[MAX_NAME] = {0}; GetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET, buf, MAX_NAME); @@ -1184,7 +1320,7 @@ static INT_PTR CALLBACK QB_TargetAppDlgProc(HWND hwnd, UINT msg, GetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE, buf, MAX_NAME); TRIM(buf); wcsncpy_s(g.cfg.qbar_target_exe, MAX_NAME, buf, _TRUNCATE); - #undef TRIM +#undef TRIM Settings_Save(&g.cfg); QB_UpdateVisibility(NULL); /* re-evaluate visibility; NULL = no specific fg window to use */ diff --git a/src/runner.c b/src/runner.c index 122efc5..52c6614 100644 --- a/src/runner.c +++ b/src/runner.c @@ -16,14 +16,15 @@ /* In: (allocated and populated by Runner_Run before CreateThread) */ /* Out: (freed by Runner_Thread on completion) */ /* ================================================================== */ -typedef struct { +typedef struct +{ WCHAR python[MAX_APPPATH]; WCHAR script[MAX_APPPATH]; - bool show_console; - bool keep_open; + bool show_console; + bool keep_open; /* Pipe handles for background-mode stdout/stderr capture (NULL when unused) */ - HANDLE hPipe_read; /* parent's read end; passed to LogReader_Thread */ - HANDLE hPipe_write; /* child's write end; set in STARTUPINFO, closed after CreateProcess */ + HANDLE hPipe_read; /* parent's read end; passed to LogReader_Thread */ + HANDLE hPipe_write; /* child's write end; set in STARTUPINFO, closed after CreateProcess */ } RunArg; /* ================================================================== */ @@ -39,13 +40,15 @@ bool Runner_FindPython(WCHAR *out, int max) { /* 1. User-configured path: if set and the file actually exists on disk, use it */ if (g.cfg.python_exe[0] && - GetFileAttributes(g.cfg.python_exe) != INVALID_FILE_ATTRIBUTES) { + GetFileAttributes(g.cfg.python_exe) != INVALID_FILE_ATTRIBUTES) + { wcsncpy_s(out, max, g.cfg.python_exe, _TRUNCATE); return true; } /* 2. PATH search: lets the system find whatever "python" the user has activated */ WCHAR found[MAX_APPPATH]; - if (SearchPath(NULL, L"python.exe", NULL, MAX_APPPATH, found, NULL)) { + if (SearchPath(NULL, L"python.exe", NULL, MAX_APPPATH, found, NULL)) + { wcsncpy_s(out, max, found, _TRUNCATE); return true; } @@ -60,10 +63,12 @@ bool Runner_FindPython(WCHAR *out, int max) L"C:\\Program Files\\Python313\\python.exe", L"C:\\Program Files\\Python312\\python.exe", L"C:\\Program Files\\Python311\\python.exe", - NULL /* sentinel — terminates the loop */ + NULL /* sentinel — terminates the loop */ }; - for (int i = 0; candidates[i]; i++) { - if (GetFileAttributes(candidates[i]) != INVALID_FILE_ATTRIBUTES) { + for (int i = 0; candidates[i]; i++) + { + if (GetFileAttributes(candidates[i]) != INVALID_FILE_ATTRIBUTES) + { wcsncpy_s(out, max, candidates[i], _TRUNCATE); return true; } @@ -84,11 +89,11 @@ bool Runner_FindPython(WCHAR *out, int max) /* ================================================================== */ static void Runner_BuildLocalPath(int fi, int si, WCHAR *out, int max) { - const WCHAR *gh = g.folders[fi].scripts[si].gh_path; - const WCHAR *sep = wcsrchr(gh, L'/'); + const WCHAR *gh = g.folders[fi].scripts[si].gh_path; + const WCHAR *sep = wcsrchr(gh, L'/'); const WCHAR *fname = sep ? sep + 1 : gh; /* advance past '/' to get filename; use whole string if no '/' found */ _snwprintf_s(out, max, _TRUNCATE, L"%s\\%s\\%s", - g.cfg.cache_dir, g.folders[fi].name, fname); + g.cfg.cache_dir, g.folders[fi].name, fname); out[max - 1] = L'\0'; } @@ -106,10 +111,11 @@ static void Runner_BuildLocalPath(int fi, int si, WCHAR *out, int max) static DWORD WINAPI LogReader_Thread(LPVOID arg) { HANDLE hRead = (HANDLE)arg; - char buf[1024]; - DWORD nRead; + char buf[1024]; + DWORD nRead; - while (ReadFile(hRead, buf, sizeof(buf) - 1, &nRead, NULL) && nRead > 0) { + while (ReadFile(hRead, buf, sizeof(buf) - 1, &nRead, NULL) && nRead > 0) + { /* Convert the ANSI/OEM bytes the child wrote to the console into wide chars */ int wlen = MultiByteToWideChar(CP_ACP, 0, buf, (int)nRead, NULL, 0); if (wlen <= 0) continue; @@ -144,15 +150,19 @@ DWORD WINAPI Runner_Thread(LPVOID arg) STARTUPINFOW si; PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); WCHAR cmd[MAX_APPPATH * 2]; - if (ra->show_console && ra->keep_open) { + if (ra->show_console && ra->keep_open) + { /* Wrap in cmd.exe /k so the console window stays open after the script exits */ _snwprintf_s(cmd, MAX_APPPATH * 2 - 1, _TRUNCATE, L"cmd.exe /k \"\"%s\" \"%s\"\"", - ra->python, ra->script); - } else { + ra->python, ra->script); + } + else + { /* Run Python directly without a shell wrapper */ _snwprintf_s(cmd, MAX_APPPATH * 2 - 1, _TRUNCATE, L"\"%s\" \"%s\"", ra->python, ra->script); } @@ -162,14 +172,16 @@ DWORD WINAPI Runner_Thread(LPVOID arg) DWORD flags = ra->show_console ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW; /* Background mode: create pipe so stdout/stderr are captured for the log window */ - if (!ra->show_console) { + if (!ra->show_console) + { SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; /* TRUE = child can inherit write end */ - if (CreatePipe(&ra->hPipe_read, &ra->hPipe_write, &sa, 0)) { + if (CreatePipe(&ra->hPipe_read, &ra->hPipe_write, &sa, 0)) + { /* Make the read end non-inheritable so the child cannot hold it open */ SetHandleInformation(ra->hPipe_read, HANDLE_FLAG_INHERIT, 0); si.hStdOutput = ra->hPipe_write; - si.hStdError = ra->hPipe_write; - si.dwFlags |= STARTF_USESTDHANDLES; + si.hStdError = ra->hPipe_write; + si.dwFlags |= STARTF_USESTDHANDLES; } } @@ -177,23 +189,29 @@ DWORD WINAPI Runner_Thread(LPVOID arg) BOOL inherit = (ra->hPipe_write != NULL); if (CreateProcessW(NULL, cmd, NULL, NULL, inherit, - flags, NULL, NULL, &si, &pi)) { - if (!ra->show_console) { + flags, NULL, NULL, &si, &pi)) + { + if (!ra->show_console) + { /* Close our copy of the write end — only the child keeps one open now. When the child exits the last write end closes and ReadFile in the reader thread returns 0 (EOF), naturally ending the reader loop. */ - if (ra->hPipe_write) { + if (ra->hPipe_write) + { CloseHandle(ra->hPipe_write); ra->hPipe_write = NULL; } /* Launch log reader; it owns hPipe_read and closes it on EOF */ - if (ra->hPipe_read) { + if (ra->hPipe_read) + { HANDLE hRT = CreateThread(NULL, 0, LogReader_Thread, ra->hPipe_read, 0, NULL); - if (hRT) CloseHandle(hRT); - else CloseHandle(ra->hPipe_read); /* CreateThread failed — release handle */ - ra->hPipe_read = NULL; /* thread (or cleanup above) now owns it */ + if (hRT) + CloseHandle(hRT); + else + CloseHandle(ra->hPipe_read); /* CreateThread failed — release handle */ + ra->hPipe_read = NULL; /* thread (or cleanup above) now owns it */ } /* Background run: duplicate the process handle into g.run_process so Runner_Stop can terminate it */ @@ -207,7 +225,8 @@ DWORD WINAPI Runner_Thread(LPVOID arg) /* Atomically swap g.run_process to NULL and take ownership of the handle */ HANDLE old = (HANDLE)InterlockedExchangePointer((void **)&g.run_process, NULL); - if (old) { + if (old) + { /* We won the race — Runner_Stop has not claimed the handle */ CloseHandle(old); DWORD exit_code = 0; @@ -219,15 +238,27 @@ DWORD WINAPI Runner_Thread(LPVOID arg) PostStatus(L"Script exited with code %lu.", exit_code); } /* else NULL: Runner_Stop already claimed the handle, closed it, and posted WM_SCRIPT_STOPPED */ - } else { + } + else + { PostStatus(L"Script launched in console."); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - } else { + } + else + { /* CreateProcess failed — release any pipe handles we may have opened */ - if (ra->hPipe_write) { CloseHandle(ra->hPipe_write); ra->hPipe_write = NULL; } - if (ra->hPipe_read) { CloseHandle(ra->hPipe_read); ra->hPipe_read = NULL; } + if (ra->hPipe_write) + { + CloseHandle(ra->hPipe_write); + ra->hPipe_write = NULL; + } + if (ra->hPipe_read) + { + CloseHandle(ra->hPipe_read); + ra->hPipe_read = NULL; + } DWORD err = GetLastError(); PostStatus(L"Failed to launch Python. Error %lu.", err); } @@ -251,7 +282,7 @@ DWORD WINAPI Runner_Thread(LPVOID arg) bool Runner_Run(int fi, int si) { /* Guard against stale indices if folders were rebuilt since the button was drawn */ - if (fi < 0 || fi >= g.folder_count) return false; + if (fi < 0 || fi >= g.folder_count) return false; if (si < 0 || si >= g.folders[fi].count) return false; Script *s = &g.folders[fi].scripts[si]; @@ -259,7 +290,8 @@ bool Runner_Run(int fi, int si) WCHAR local[MAX_APPPATH] = {0}; WCHAR python[MAX_APPPATH] = {0}; - if (!Runner_FindPython(python, MAX_APPPATH)) { + if (!Runner_FindPython(python, MAX_APPPATH)) + { MessageBox(g.hwnd, L"Python executable not found.\n\n" L"Please set the path in File \u2192 Settings.", @@ -267,24 +299,30 @@ bool Runner_Run(int fi, int si) return false; } - if (s->source == SCRIPT_SRC_LOCAL) { + if (s->source == SCRIPT_SRC_LOCAL) + { /* Local scripts are already on disk \u2014 use the stored path directly; no download or SHA check */ wcsncpy_s(local, MAX_APPPATH, s->local, _TRUNCATE); - if (GetFileAttributes(local) == INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(local) == INVALID_FILE_ATTRIBUTES) + { MessageBox(g.hwnd, L"Local script file not found.\n\nThe file may have been moved or deleted.", L"File Not Found", MB_ICONERROR | MB_OK); return false; } - } else { + } + else + { Runner_BuildLocalPath(fi, si, local, MAX_APPPATH); bool missing = (GetFileAttributes(local) == INVALID_FILE_ATTRIBUTES); - if (missing || g.cfg.download_before_run) { + if (missing || g.cfg.download_before_run) + { /* Download if the cached file is absent or if the user wants a fresh copy every run */ PostStatus(L"Downloading %s\u2026", s->name); const WCHAR *tok = g.cfg.github_token[0] ? g.cfg.github_token : NULL; - if (!GitHub_DownloadRaw(s->gh_path, local, tok)) { + if (!GitHub_DownloadRaw(s->gh_path, local, tok)) + { MessageBox(g.hwnd, L"Failed to download script from GitHub.", L"Download Error", MB_ICONERROR | MB_OK); @@ -294,29 +332,35 @@ bool Runner_Run(int fi, int si) } /* SHA verification: only run if the manifest has a known SHA for this script */ - if (s->sha[0] && !GitHub_VerifyScriptSHA(s)) { + if (s->sha[0] && !GitHub_VerifyScriptSHA(s)) + { int res = MessageBox(g.hwnd, - L"Warning: Script SHA does not match the expected value from GitHub.\n\n" - L"The local file may have been tampered with.\n\n" - L"Do you want to re-download and run the script?", - L"Security Warning", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); - if (res == IDYES) { + L"Warning: Script SHA does not match the expected value from GitHub.\n\n" + L"The local file may have been tampered with.\n\n" + L"Do you want to re-download and run the script?", + L"Security Warning", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); + if (res == IDYES) + { /* User chose to trust GitHub \u2014 force a fresh download */ const WCHAR *tok = g.cfg.github_token[0] ? g.cfg.github_token : NULL; - if (!GitHub_DownloadRaw(s->gh_path, local, tok)) { + if (!GitHub_DownloadRaw(s->gh_path, local, tok)) + { MessageBox(g.hwnd, L"Failed to re-download script.", L"Error", MB_ICONERROR | MB_OK); return false; } /* Verify once more; a second mismatch means the repo itself is suspect */ - if (!GitHub_VerifyScriptSHA(s)) { + if (!GitHub_VerifyScriptSHA(s)) + { MessageBox(g.hwnd, - L"SHA still does not match after re-download.\n" - L"Script will not run.", - L"Security Error", MB_ICONERROR | MB_OK); + L"SHA still does not match after re-download.\n" + L"Script will not run.", + L"Security Error", MB_ICONERROR | MB_OK); return false; } - } else { + } + else + { return false; /* user declined to run \u2014 abort silently */ } } @@ -327,7 +371,8 @@ bool Runner_Run(int fi, int si) s->run_count++; /* Clear the green highlight on the previously running button before we reassign run_fi/run_si */ - if (g.script_running) { + if (g.script_running) + { HWND hOldBtn = GetDlgItem(g.hwnd_scroll, IDC_SCRIPT_BTN_BASE + g.run_si); if (hOldBtn) InvalidateRect(hOldBtn, NULL, FALSE); if (g.hwnd_qbar) InvalidateRect(g.hwnd_qbar, NULL, FALSE); @@ -339,14 +384,18 @@ bool Runner_Run(int fi, int si) RunArg *ra = (RunArg *)malloc(sizeof(RunArg)); if (!ra) return false; /* OOM \u2014 thread cannot be launched */ wcsncpy_s(ra->python, MAX_APPPATH, python, _TRUNCATE); - wcsncpy_s(ra->script, MAX_APPPATH, local, _TRUNCATE); + wcsncpy_s(ra->script, MAX_APPPATH, local, _TRUNCATE); ra->show_console = g.cfg.show_console; - ra->keep_open = g.cfg.show_console && g.cfg.console_keep_open; - ra->hPipe_read = NULL; - ra->hPipe_write = NULL; + ra->keep_open = g.cfg.show_console && g.cfg.console_keep_open; + ra->hPipe_read = NULL; + ra->hPipe_write = NULL; HANDLE hT = CreateThread(NULL, 0, Runner_Thread, ra, 0, NULL); - if (hT) { CloseHandle(hT); return true; } /* thread owns ra now; close our copy of the thread handle */ + if (hT) + { + CloseHandle(hT); + return true; + } /* thread owns ra now; close our copy of the thread handle */ free(ra); /* CreateThread failed \u2014 free the block we allocated */ return false; } @@ -384,28 +433,35 @@ void Runner_Stop(void) /* Out: (void) */ /* ================================================================== */ static void RunPipInstall(const WCHAR *python, const WCHAR *req, - const WCHAR *work_dir, bool keep_open) + const WCHAR *work_dir, bool keep_open) { WCHAR cmd[MAX_APPPATH * 4]; - if (keep_open) { + if (keep_open) + { /* /k keeps the console open after pip finishes so the user can read output */ _snwprintf_s(cmd, MAX_APPPATH * 4 - 1, _TRUNCATE, - L"cmd.exe /k \"\"%s\" -m pip install --upgrade pip && \"%s\" -m pip install --upgrade -r \"%s\"\"", - python, python, req); - } else { + L"cmd.exe /k \"\"%s\" -m pip install --upgrade pip && \"%s\" -m pip install --upgrade -r \"%s\"\"", + python, python, req); + } + else + { /* /c closes the console when done; outer double-quotes are required so cmd correctly parses the && chain when the python path contains spaces */ _snwprintf_s(cmd, MAX_APPPATH * 4 - 1, _TRUNCATE, - L"cmd.exe /c \"\"%s\" -m pip install --upgrade pip && \"%s\" -m pip install --upgrade -r \"%s\"\"", - python, python, req); + L"cmd.exe /c \"\"%s\" -m pip install --upgrade pip && \"%s\" -m pip install --upgrade -r \"%s\"\"", + python, python, req); } - STARTUPINFOW si; PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); + STARTUPINFOW si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); if (CreateProcessW(NULL, cmd, NULL, NULL, FALSE, - CREATE_NEW_CONSOLE, NULL, work_dir, &si, &pi)) { - if (!keep_open) { + CREATE_NEW_CONSOLE, NULL, work_dir, &si, &pi)) + { + if (!keep_open) + { WaitForSingleObject(pi.hProcess, INFINITE); /* wait for pip to finish before returning */ } CloseHandle(pi.hProcess); @@ -428,11 +484,27 @@ static void RunPipInstall(const WCHAR *python, const WCHAR *req, static void EscapeForCmd(const WCHAR *src, WCHAR *dst, int dst_max) { int i = 0; - for (; *src && i < dst_max - 3; src++) { - if (*src == L'^') { dst[i++] = L'^'; dst[i++] = L'^'; } - else if (*src == L'"') { dst[i++] = L'^'; dst[i++] = L'"'; } - else if (*src == L'%') { dst[i++] = L'%'; dst[i++] = L'%'; } - else { dst[i++] = *src; } + for (; *src && i < dst_max - 3; src++) + { + if (*src == L'^') + { + dst[i++] = L'^'; + dst[i++] = L'^'; + } + else if (*src == L'"') + { + dst[i++] = L'^'; + dst[i++] = L'"'; + } + else if (*src == L'%') + { + dst[i++] = L'%'; + dst[i++] = L'%'; + } + else + { + dst[i++] = *src; + } } dst[i] = L'\0'; } @@ -448,23 +520,28 @@ bool Runner_RunWithArgs(int fi, int si, const WCHAR *args) WCHAR cmd[MAX_APPPATH * 2]; DWORD flags; - if (g.cfg.show_console && g.cfg.console_keep_open) { + if (g.cfg.show_console && g.cfg.console_keep_open) + { /* Routed through cmd.exe /k — must escape args to prevent shell metacharacter injection */ WCHAR esc[MAX_APPPATH] = {0}; EscapeForCmd(args ? args : L"", esc, MAX_APPPATH); - _snwprintf_s(cmd, MAX_APPPATH*2-1, _TRUNCATE, + _snwprintf_s(cmd, MAX_APPPATH * 2 - 1, _TRUNCATE, L"cmd.exe /k \"\\\"%s\\\" \\\"%s\\\" %s\"", python, s->local, esc); flags = CREATE_NEW_CONSOLE; - } else { + } + else + { /* Python is invoked directly — no shell interpretation, no injection risk */ - _snwprintf_s(cmd, MAX_APPPATH*2-1, _TRUNCATE, L"\"%s\" \"%s\" %s", + _snwprintf_s(cmd, MAX_APPPATH * 2 - 1, _TRUNCATE, L"\"%s\" \"%s\" %s", python, s->local, args ? args : L""); flags = g.cfg.show_console ? CREATE_NEW_CONSOLE : 0; /* 0 = no special console flag = hidden window */ } - STARTUPINFOW si2; PROCESS_INFORMATION pi; - ZeroMemory(&si2, sizeof(si2)); si2.cb = sizeof(si2); + STARTUPINFOW si2; + PROCESS_INFORMATION pi; + ZeroMemory(&si2, sizeof(si2)); + si2.cb = sizeof(si2); if (!CreateProcessW(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si2, &pi)) @@ -491,7 +568,8 @@ bool Runner_RunWithArgs(int fi, int si, const WCHAR *args) void Runner_UpdateDeps(void) { WCHAR python[MAX_APPPATH] = {0}; - if (!Runner_FindPython(python, MAX_APPPATH)) { + if (!Runner_FindPython(python, MAX_APPPATH)) + { MessageBox(g.hwnd, L"Python executable not found.\n\n" L"Please set the path in File \u2192 Settings.", @@ -506,23 +584,26 @@ void Runner_UpdateDeps(void) bool found_any = false; /* set to true once any requirements.txt is located and run */ /* 1. Main repo: try update.bat first, then requirements.txt */ - if (g.cfg.main_repo_enabled) { + if (g.cfg.main_repo_enabled) + { WCHAR req[MAX_APPPATH]; _snwprintf_s(req, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\requirements.txt", - g.cfg.cache_dir); + g.cfg.cache_dir); WCHAR work_dir[MAX_APPPATH]; _snwprintf_s(work_dir, MAX_APPPATH, _TRUNCATE, L"%s\\setup", g.cfg.cache_dir); SHCreateDirectoryEx(NULL, work_dir, NULL); /* Download if missing */ - if (GetFileAttributes(req) == INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(req) == INVALID_FILE_ATTRIBUTES) + { SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)L"Downloading main repo requirements.txt..."); const WCHAR *tok = g.cfg.github_token[0] ? g.cfg.github_token : NULL; GitHub_DownloadRaw(L"setup/requirements.txt", req, tok); } - if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) + { SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)L"Installing main repo requirements..."); RunPipInstall(python, req, work_dir, g.cfg.deps_keep_open); @@ -531,7 +612,8 @@ void Runner_UpdateDeps(void) } /* 2. Extra GitHub repos: each has its own cached requirements.txt */ - for (int i = 0; i < g.cfg.extra_repo_count; i++) { + for (int i = 0; i < g.cfg.extra_repo_count; i++) + { ExtraRepo *repo = &g.cfg.extra_repos[i]; if (!repo->enabled || !repo->url[0]) continue; /* skip disabled or empty entries */ @@ -542,27 +624,29 @@ void Runner_UpdateDeps(void) equirements.txt */ WCHAR sub_dir[MAX_APPPATH]; _snwprintf_s(sub_dir, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\%s_%s", - g.cfg.cache_dir, owner, reponame); + g.cfg.cache_dir, owner, reponame); SHCreateDirectoryEx(NULL, sub_dir, NULL); WCHAR req[MAX_APPPATH]; _snwprintf_s(req, MAX_APPPATH, _TRUNCATE, L"%s\\requirements.txt", sub_dir); /* Download if missing */ - if (GetFileAttributes(req) == INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(req) == INVALID_FILE_ATTRIBUTES) + { const WCHAR *tok = repo->token[0] ? repo->token - : (g.cfg.github_token[0] ? g.cfg.github_token : NULL); + : (g.cfg.github_token[0] ? g.cfg.github_token : NULL); const WCHAR *branch = repo->branch[0] ? repo->branch : L"main"; WCHAR raw_path[MAX_APPPATH]; _snwprintf_s(raw_path, MAX_APPPATH, _TRUNCATE, L"/%s/%s/%s/setup/requirements.txt", - owner, reponame, branch); + owner, reponame, branch); GitHub_DownloadRawFull(GITHUB_RAW_HOST, raw_path, req, tok); } - if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) + { WCHAR status[256]; _snwprintf_s(status, 255, _TRUNCATE, L"Installing %s/%s requirements...", - owner, reponame); + owner, reponame); SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)status); RunPipInstall(python, req, sub_dir, g.cfg.deps_keep_open); found_any = true; @@ -570,27 +654,30 @@ equirements.txt */ } /* 3. Local dirs: run directly from source - no caching needed */ - for (int i = 0; i < g.cfg.local_dir_count; i++) { + for (int i = 0; i < g.cfg.local_dir_count; i++) + { LocalDir *dir = &g.cfg.local_dirs[i]; if (!dir->enabled || !dir->path[0]) continue; /* skip disabled or empty local dir entries */ WCHAR req[MAX_APPPATH]; _snwprintf_s(req, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\requirements.txt", - dir->path); + dir->path); - if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) + { WCHAR work_dir[MAX_APPPATH]; _snwprintf_s(work_dir, MAX_APPPATH, _TRUNCATE, L"%s\\setup", dir->path); WCHAR status[256]; _snwprintf_s(status, 255, _TRUNCATE, L"Installing local folder requirements (%d)...", - i + 1); + i + 1); SendMessage(g.hwnd_status, SB_SETTEXT, 0, (LPARAM)status); RunPipInstall(python, req, work_dir, g.cfg.deps_keep_open); found_any = true; } } - if (!found_any) { + if (!found_any) + { MessageBox(g.hwnd, L"No requirements.txt or update.bat found in any source.\n\n" L"Make sure at least one source has a setup folder containing\n" diff --git a/src/settings.c b/src/settings.c index e11d27e..971ac28 100644 --- a/src/settings.c +++ b/src/settings.c @@ -16,7 +16,8 @@ /* max — capacity of out in WCHARs */ /* Out: (void — out is populated) */ /* ================================================================== */ -static void IniPath(WCHAR *out, int max) { +static void IniPath(WCHAR *out, int max) +{ _snwprintf_s(out, max, _TRUNCATE, L"%s\\%s", g.appdata_dir, SETTINGS_FILE); } @@ -33,68 +34,77 @@ static void IniPath(WCHAR *out, int max) { void Settings_Load(Settings *s) { ZeroMemory(s, sizeof(*s)); - WCHAR ini[MAX_APPPATH]; IniPath(ini, MAX_APPPATH); + WCHAR ini[MAX_APPPATH]; + IniPath(ini, MAX_APPPATH); - GetPrivateProfileString(L"Python", L"Executable", L"", s->python_exe, MAX_APPPATH, ini); - GetPrivateProfileString(L"Scripts", L"CacheDir", L"", s->cache_dir, MAX_APPPATH, ini); - GetPrivateProfileString(L"GitHub", L"Token", L"", s->github_token, 256, ini); + GetPrivateProfileString(L"Python", L"Executable", L"", s->python_exe, MAX_APPPATH, ini); + GetPrivateProfileString(L"Scripts", L"CacheDir", L"", s->cache_dir, MAX_APPPATH, ini); + GetPrivateProfileString(L"GitHub", L"Token", L"", s->github_token, 256, ini); - s->auto_sync = GetPrivateProfileInt(L"Options", L"AutoSync", 1, ini) != 0; /* default 1 = on */ + s->auto_sync = GetPrivateProfileInt(L"Options", L"AutoSync", 1, ini) != 0; /* default 1 = on */ s->download_before_run = GetPrivateProfileInt(L"Options", L"DownloadBeforeRun", 0, ini) != 0; /* default 0 = off */ - s->show_console = GetPrivateProfileInt(L"Options", L"ShowConsole", 0, ini) != 0; /* default 0 = hidden */ - s->console_keep_open = GetPrivateProfileInt(L"Options", L"ConsoleKeepOpen", 1, ini) != 0; /* default 1 = on */ - s->check_updates = GetPrivateProfileInt(L"Options", L"CheckUpdates", 1, ini) != 0; /* default 1 = on */ - s->deps_keep_open = GetPrivateProfileInt(L"Options", L"DepsKeepOpen", 0, ini) != 0; /* default 0 = off */ - s->auto_update = GetPrivateProfileInt(L"Options", L"AutoUpdate", 1, ini) != 0; /* default 1 = on */ - s->offline_use_cache = GetPrivateProfileInt(L"Options", L"OfflineUseCache", 0, ini) != 0; /* default 0 = off */ - s->refresh_interval = GetPrivateProfileInt(L"Options", L"RefreshInterval", 6, ini); /* default 6 hours */ - s->sort_mode = (SortMode)GetPrivateProfileInt(L"Options", L"SortMode", 0, ini); /* default 0 = SORT_ORDER */ - s->main_repo_enabled = GetPrivateProfileInt(L"Sources", L"MainRepoEnabled", 1, ini) != 0; /* default 1 = enabled */ + s->show_console = GetPrivateProfileInt(L"Options", L"ShowConsole", 0, ini) != 0; /* default 0 = hidden */ + s->console_keep_open = GetPrivateProfileInt(L"Options", L"ConsoleKeepOpen", 1, ini) != 0; /* default 1 = on */ + s->check_updates = GetPrivateProfileInt(L"Options", L"CheckUpdates", 1, ini) != 0; /* default 1 = on */ + s->deps_keep_open = GetPrivateProfileInt(L"Options", L"DepsKeepOpen", 0, ini) != 0; /* default 0 = off */ + s->auto_update = GetPrivateProfileInt(L"Options", L"AutoUpdate", 1, ini) != 0; /* default 1 = on */ + s->offline_use_cache = GetPrivateProfileInt(L"Options", L"OfflineUseCache", 0, ini) != 0; /* default 0 = off */ + s->refresh_interval = GetPrivateProfileInt(L"Options", L"RefreshInterval", 6, ini); /* default 6 hours */ + s->sort_mode = (SortMode)GetPrivateProfileInt(L"Options", L"SortMode", 0, ini); /* default 0 = SORT_ORDER */ + s->main_repo_enabled = GetPrivateProfileInt(L"Sources", L"MainRepoEnabled", 1, ini) != 0; /* default 1 = enabled */ /* Extra repos */ s->extra_repo_count = GetPrivateProfileInt(L"Sources", L"ExtraRepoCount", 0, ini); if (s->extra_repo_count > MAX_EXTRA_REPOS) s->extra_repo_count = MAX_EXTRA_REPOS; - for (int i = 0; i < s->extra_repo_count; i++) { + for (int i = 0; i < s->extra_repo_count; i++) + { WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", i); GetPrivateProfileString(L"Sources", key, L"", s->extra_repos[i].url, 511, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", i); GetPrivateProfileString(L"Sources", key, L"main", s->extra_repos[i].branch, 63, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", i); GetPrivateProfileString(L"Sources", key, L"", s->extra_repos[i].token, 255, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dEnabled", i); s->extra_repos[i].enabled = GetPrivateProfileInt(L"Sources", key, 1, ini) != 0; + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", i); + GetPrivateProfileString(L"Sources", key, L"", s->extra_repos[i].url, 511, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", i); + GetPrivateProfileString(L"Sources", key, L"main", s->extra_repos[i].branch, 63, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", i); + GetPrivateProfileString(L"Sources", key, L"", s->extra_repos[i].token, 255, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dEnabled", i); + s->extra_repos[i].enabled = GetPrivateProfileInt(L"Sources", key, 1, ini) != 0; if (!s->extra_repos[i].branch[0]) wcsncpy_s(s->extra_repos[i].branch, 64, L"main", _TRUNCATE); /* fall back to 'main' if key was missing */ } /* Local dirs */ s->local_dir_count = GetPrivateProfileInt(L"Sources", L"LocalDirCount", 0, ini); if (s->local_dir_count > MAX_LOCAL_DIRS) s->local_dir_count = MAX_LOCAL_DIRS; - for (int i = 0; i < s->local_dir_count; i++) { + for (int i = 0; i < s->local_dir_count; i++) + { WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", i); GetPrivateProfileString(L"Sources", key, L"", s->local_dirs[i].path, MAX_APPPATH-1, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Local%dEnabled", i); s->local_dirs[i].enabled = GetPrivateProfileInt(L"Sources", key, 1, ini) != 0; + _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", i); + GetPrivateProfileString(L"Sources", key, L"", s->local_dirs[i].path, MAX_APPPATH - 1, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Local%dEnabled", i); + s->local_dirs[i].enabled = GetPrivateProfileInt(L"Sources", key, 1, ini) != 0; } - s->always_on_top = GetPrivateProfileInt(L"Window", L"AlwaysOnTop", 1, ini) != 0; - s->minimize_to_tray = GetPrivateProfileInt(L"Window", L"MinimizeToTray", 1, ini) != 0; - s->start_with_windows = GetPrivateProfileInt(L"Window", L"StartWithWindows", 1, ini) != 0; - s->start_minimized = GetPrivateProfileInt(L"Window", L"StartMinimized", 1, ini) != 0; - s->theme = (ThemeMode)GetPrivateProfileInt(L"Window", L"Theme", 0, ini); + s->always_on_top = GetPrivateProfileInt(L"Window", L"AlwaysOnTop", 1, ini) != 0; + s->minimize_to_tray = GetPrivateProfileInt(L"Window", L"MinimizeToTray", 1, ini) != 0; + s->start_with_windows = GetPrivateProfileInt(L"Window", L"StartWithWindows", 1, ini) != 0; + s->start_minimized = GetPrivateProfileInt(L"Window", L"StartMinimized", 1, ini) != 0; + s->theme = (ThemeMode)GetPrivateProfileInt(L"Window", L"Theme", 0, ini); if (s->theme < 0 || s->theme > 2) s->theme = THEME_SYSTEM; /* clamp: a corrupt INI could store an out-of-range value */ /* Quick Launch Bar */ - s->qbar_enabled = GetPrivateProfileInt(L"QuickBar", L"Enabled", 1, ini) != 0; /* default 1 = shown */ - s->qbar_horizontal = GetPrivateProfileInt(L"QuickBar", L"Horizontal", 0, ini) != 0; /* default 0 = vertical */ + s->qbar_enabled = GetPrivateProfileInt(L"QuickBar", L"Enabled", 1, ini) != 0; /* default 1 = shown */ + s->qbar_horizontal = GetPrivateProfileInt(L"QuickBar", L"Horizontal", 0, ini) != 0; /* default 0 = vertical */ s->qbar_topmost_with_catia = GetPrivateProfileInt(L"QuickBar", L"TopmostWithCatia", 1, ini) != 0; /* default 1 = topmost when CATIA is foreground */ - s->qbar_x = GetPrivateProfileInt(L"QuickBar", L"X", 0, ini); - s->qbar_y = GetPrivateProfileInt(L"QuickBar", L"Y", 0, ini); + s->qbar_x = GetPrivateProfileInt(L"QuickBar", L"X", 0, ini); + s->qbar_y = GetPrivateProfileInt(L"QuickBar", L"Y", 0, ini); GetPrivateProfileString(L"QuickBar", L"TargetApp", L"CATIA V5", s->qbar_target_app, MAX_NAME, ini); GetPrivateProfileString(L"QuickBar", L"TargetExe", L"CNEXT.exe", s->qbar_target_exe, MAX_NAME, ini); /* Double-click repeat */ - s->repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"RepeatOnDblClick", 1, ini) != 0; - s->qbar_repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"QBarRepeatOnDblClick",1, ini) != 0; + s->repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"RepeatOnDblClick", 1, ini) != 0; + s->qbar_repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"QBarRepeatOnDblClick", 1, ini) != 0; /* Script display */ - s->tint_script_sources = GetPrivateProfileInt(L"Options", L"TintScriptSources", 1, ini) != 0; /* default 1 = on */ + s->tint_script_sources = GetPrivateProfileInt(L"Options", L"TintScriptSources", 1, ini) != 0; /* default 1 = on */ if (!s->cache_dir[0]) /* no cache dir stored — default to %APPDATA%\CatiaMenuWin32\scripts */ _snwprintf_s(s->cache_dir, MAX_APPPATH, _TRUNCATE, L"%s\\scripts", g.appdata_dir); @@ -113,22 +123,23 @@ void Settings_Load(Settings *s) /* ================================================================== */ void Settings_Save(const Settings *s) { - WCHAR ini[MAX_APPPATH]; IniPath(ini, MAX_APPPATH); + WCHAR ini[MAX_APPPATH]; + IniPath(ini, MAX_APPPATH); WCHAR tmp[8]; - WritePrivateProfileString(L"Python", L"Executable", s->python_exe, ini); - WritePrivateProfileString(L"Scripts", L"CacheDir", s->cache_dir, ini); - WritePrivateProfileString(L"GitHub", L"Token", s->github_token, ini); + WritePrivateProfileString(L"Python", L"Executable", s->python_exe, ini); + WritePrivateProfileString(L"Scripts", L"CacheDir", s->cache_dir, ini); + WritePrivateProfileString(L"GitHub", L"Token", s->github_token, ini); -#define WB(sec,key,val) WritePrivateProfileString(sec, key, (val)?L"1":L"0", ini) /* writes "1" or "0" for each boolean flag */ - WB(L"Options", L"AutoSync", s->auto_sync); +#define WB(sec, key, val) WritePrivateProfileString(sec, key, (val) ? L"1" : L"0", ini) /* writes "1" or "0" for each boolean flag */ + WB(L"Options", L"AutoSync", s->auto_sync); WB(L"Options", L"DownloadBeforeRun", s->download_before_run); - WB(L"Options", L"ShowConsole", s->show_console); - WB(L"Options", L"ConsoleKeepOpen", s->console_keep_open); - WB(L"Options", L"CheckUpdates", s->check_updates); - WB(L"Options", L"DepsKeepOpen", s->deps_keep_open); - WB(L"Options", L"AutoUpdate", s->auto_update); - WB(L"Options", L"OfflineUseCache", s->offline_use_cache); + WB(L"Options", L"ShowConsole", s->show_console); + WB(L"Options", L"ConsoleKeepOpen", s->console_keep_open); + WB(L"Options", L"CheckUpdates", s->check_updates); + WB(L"Options", L"DepsKeepOpen", s->deps_keep_open); + WB(L"Options", L"AutoUpdate", s->auto_update); + WB(L"Options", L"OfflineUseCache", s->offline_use_cache); _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", s->refresh_interval); WritePrivateProfileString(L"Options", L"RefreshInterval", tmp, ini); _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", (int)s->sort_mode); @@ -138,28 +149,36 @@ void Settings_Save(const Settings *s) WB(L"Sources", L"MainRepoEnabled", s->main_repo_enabled); _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", s->extra_repo_count); WritePrivateProfileString(L"Sources", L"ExtraRepoCount", tmp, ini); - for (int i = 0; i < s->extra_repo_count; i++) { + for (int i = 0; i < s->extra_repo_count; i++) + { WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", i); WritePrivateProfileString(L"Sources", key, s->extra_repos[i].url, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", i); WritePrivateProfileString(L"Sources", key, s->extra_repos[i].branch, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", i); WritePrivateProfileString(L"Sources", key, s->extra_repos[i].token, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dEnabled", i); WritePrivateProfileString(L"Sources", key, s->extra_repos[i].enabled ? L"1" : L"0", ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", i); + WritePrivateProfileString(L"Sources", key, s->extra_repos[i].url, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", i); + WritePrivateProfileString(L"Sources", key, s->extra_repos[i].branch, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", i); + WritePrivateProfileString(L"Sources", key, s->extra_repos[i].token, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dEnabled", i); + WritePrivateProfileString(L"Sources", key, s->extra_repos[i].enabled ? L"1" : L"0", ini); } _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", s->local_dir_count); WritePrivateProfileString(L"Sources", L"LocalDirCount", tmp, ini); - for (int i = 0; i < s->local_dir_count; i++) { + for (int i = 0; i < s->local_dir_count; i++) + { WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", i); WritePrivateProfileString(L"Sources", key, s->local_dirs[i].path, ini); - _snwprintf_s(key, 31, _TRUNCATE, L"Local%dEnabled", i); WritePrivateProfileString(L"Sources", key, s->local_dirs[i].enabled ? L"1" : L"0", ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", i); + WritePrivateProfileString(L"Sources", key, s->local_dirs[i].path, ini); + _snwprintf_s(key, 31, _TRUNCATE, L"Local%dEnabled", i); + WritePrivateProfileString(L"Sources", key, s->local_dirs[i].enabled ? L"1" : L"0", ini); } - WB(L"Window", L"AlwaysOnTop", s->always_on_top); - WB(L"Window", L"MinimizeToTray", s->minimize_to_tray); - WB(L"Window", L"StartWithWindows", s->start_with_windows); - WB(L"Window", L"StartMinimized", s->start_minimized); + WB(L"Window", L"AlwaysOnTop", s->always_on_top); + WB(L"Window", L"MinimizeToTray", s->minimize_to_tray); + WB(L"Window", L"StartWithWindows", s->start_with_windows); + WB(L"Window", L"StartMinimized", s->start_minimized); /* Quick Launch Bar */ - WB(L"QuickBar", L"Enabled", s->qbar_enabled); - WB(L"QuickBar", L"Horizontal", s->qbar_horizontal); + WB(L"QuickBar", L"Enabled", s->qbar_enabled); + WB(L"QuickBar", L"Horizontal", s->qbar_horizontal); WB(L"QuickBar", L"TopmostWithCatia", s->qbar_topmost_with_catia); #undef WB _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", s->qbar_x); @@ -171,7 +190,7 @@ void Settings_Save(const Settings *s) /* Double-click repeat */ WritePrivateProfileString(L"Options", L"RepeatOnDblClick", - s->repeat_on_dblclick ? L"1" : L"0", ini); + s->repeat_on_dblclick ? L"1" : L"0", ini); WritePrivateProfileString(L"Options", L"QBarRepeatOnDblClick", s->qbar_repeat_on_dblclick ? L"1" : L"0", ini); @@ -199,7 +218,8 @@ void Settings_ApplyAutorun(bool enable, bool minimized) if (RegOpenKeyEx(HKEY_CURRENT_USER, AUTORUN_KEY, 0, KEY_SET_VALUE, &hk) != ERROR_SUCCESS) return; - if (enable) { + if (enable) + { WCHAR exe[MAX_APPPATH]; GetModuleFileName(NULL, exe, MAX_APPPATH); WCHAR cmd[MAX_APPPATH + 16]; /* +16 = room for quoted path, " /minimized" flag, and null */ @@ -210,7 +230,9 @@ void Settings_ApplyAutorun(bool enable, bool minimized) RegSetValueEx(hk, AUTORUN_NAME, 0, REG_SZ, (BYTE *)cmd, (DWORD)((wcslen(cmd) + 1) * sizeof(WCHAR))); /* byte count must include null terminator */ - } else { + } + else + { RegDeleteValue(hk, AUTORUN_NAME); } RegCloseKey(hk); @@ -222,51 +244,45 @@ void Settings_ApplyAutorun(bool enable, bool minimized) /* tab page. Used to show/hide entire pages when the tab */ /* selection changes. Terminated by -1. */ /* ================================================================== */ -static const int s_stab0[] = { /* General */ - IDC_GRP_PYTHON, IDC_LBL_PYTHON_PATH, IDC_EDIT_PYTHON, IDC_BTN_BROWSE_PY, - IDC_BTN_CREATE_VENV, IDC_BTN_BROWSE_VENV, IDC_BTN_USE_GLOBAL_PY, IDC_BTN_DELETE_VENV, - IDC_GRP_CACHE, IDC_LBL_CACHE_PATH, IDC_EDIT_CACHE, IDC_BTN_BROWSE_DIR, - IDC_GRP_TOKEN, IDC_CHK_TOKEN, IDC_EDIT_TOKEN, - -1 -}; -static const int s_stab1[] = { /* Sync */ - IDC_GRP_SYNC, - IDC_CHK_AUTOSYNC, IDC_CHK_DOWNLOAD, IDC_CHK_CHECK_UPDATES, IDC_CHK_AUTO_UPDATE, - IDC_LBL_REFRESH1, IDC_EDIT_REFRESH_INTERVAL, IDC_LBL_REFRESH2, - IDC_CHK_OFFLINE_CACHE, - -1 -}; -static const int s_stab2[] = { /* Console */ - IDC_GRP_CONSOLE, - IDC_CHK_CONSOLE, IDC_CHK_KEEP_OPEN, IDC_CHK_DEPS_KEEP_OPEN, - IDC_CHK_REPEAT_MAIN, - -1 -}; -static const int s_stab3[] = { /* Window */ - IDC_GRP_WINDOW, - IDC_CHK_ALWAYS_ON_TOP, IDC_CHK_MINIMIZE_TRAY, - IDC_CHK_START_WINDOWS, IDC_CHK_START_MIN, - IDC_GRP_THEME, - IDC_RAD_THEME_SYSTEM, IDC_RAD_THEME_DARK, IDC_RAD_THEME_LIGHT, - IDC_GRP_SORT, - IDC_RAD_SORT_DEFAULT, IDC_RAD_SORT_ALPHA, IDC_RAD_SORT_DATE, IDC_RAD_SORT_USED, - IDC_GRP_DISPLAY, - IDC_CHK_TINT_SOURCES, - -1 -}; -static const int s_stab4[] = { /* Quick Bar */ - IDC_CHK_QBAR_ENABLE, - IDC_GRP_QBAR_ORI, IDC_RAD_QBAR_VERT, IDC_RAD_QBAR_HORIZ, - IDC_CHK_QBAR_TOPMOST, - IDC_LBL_QBAR_TARGET, IDC_EDIT_QBAR_TARGET_S, IDC_LBL_QBAR_TIP, - IDC_LBL_QBAR_TARGET_EXE, IDC_EDIT_QBAR_TARGET_EXE_S, IDC_BTN_BROWSE_QBAR_EXE_S, - IDC_LBL_QBAR_EXE_TIP, - IDC_CHK_REPEAT_QBAR, - -1 -}; +static const int s_stab0[] = {/* General */ + IDC_GRP_PYTHON, IDC_LBL_PYTHON_PATH, IDC_EDIT_PYTHON, IDC_BTN_BROWSE_PY, + IDC_BTN_CREATE_VENV, IDC_BTN_BROWSE_VENV, IDC_BTN_USE_GLOBAL_PY, IDC_BTN_DELETE_VENV, + IDC_GRP_CACHE, IDC_LBL_CACHE_PATH, IDC_EDIT_CACHE, IDC_BTN_BROWSE_DIR, + IDC_GRP_TOKEN, IDC_CHK_TOKEN, IDC_EDIT_TOKEN, + -1}; +static const int s_stab1[] = {/* Sync */ + IDC_GRP_SYNC, + IDC_CHK_AUTOSYNC, IDC_CHK_DOWNLOAD, IDC_CHK_CHECK_UPDATES, IDC_CHK_AUTO_UPDATE, + IDC_LBL_REFRESH1, IDC_EDIT_REFRESH_INTERVAL, IDC_LBL_REFRESH2, + IDC_CHK_OFFLINE_CACHE, + -1}; +static const int s_stab2[] = {/* Console */ + IDC_GRP_CONSOLE, + IDC_CHK_CONSOLE, IDC_CHK_KEEP_OPEN, IDC_CHK_DEPS_KEEP_OPEN, + IDC_CHK_REPEAT_MAIN, + -1}; +static const int s_stab3[] = {/* Window */ + IDC_GRP_WINDOW, + IDC_CHK_ALWAYS_ON_TOP, IDC_CHK_MINIMIZE_TRAY, + IDC_CHK_START_WINDOWS, IDC_CHK_START_MIN, + IDC_GRP_THEME, + IDC_RAD_THEME_SYSTEM, IDC_RAD_THEME_DARK, IDC_RAD_THEME_LIGHT, + IDC_GRP_SORT, + IDC_RAD_SORT_DEFAULT, IDC_RAD_SORT_ALPHA, IDC_RAD_SORT_DATE, IDC_RAD_SORT_USED, + IDC_GRP_DISPLAY, + IDC_CHK_TINT_SOURCES, + -1}; +static const int s_stab4[] = {/* Quick Bar */ + IDC_CHK_QBAR_ENABLE, + IDC_GRP_QBAR_ORI, IDC_RAD_QBAR_VERT, IDC_RAD_QBAR_HORIZ, + IDC_CHK_QBAR_TOPMOST, + IDC_LBL_QBAR_TARGET, IDC_EDIT_QBAR_TARGET_S, IDC_LBL_QBAR_TIP, + IDC_LBL_QBAR_TARGET_EXE, IDC_EDIT_QBAR_TARGET_EXE_S, IDC_BTN_BROWSE_QBAR_EXE_S, + IDC_LBL_QBAR_EXE_TIP, + IDC_CHK_REPEAT_QBAR, + -1}; static const int *s_stabs[5] = { - s_stab0, s_stab1, s_stab2, s_stab3, s_stab4 -}; + s_stab0, s_stab1, s_stab2, s_stab3, s_stab4}; /* ================================================================== */ /* Settings_ShowTab (static) */ @@ -278,7 +294,8 @@ static const int *s_stabs[5] = { /* ================================================================== */ static void Settings_ShowTab(HWND hwnd, int tab) { - for (int t = 0; t < 5; t++) { + for (int t = 0; t < 5; t++) + { int sw = (t == tab) ? SW_SHOW : SW_HIDE; for (const int *p = s_stabs[t]; *p != -1; p++) ShowWindow(GetDlgItem(hwnd, *p), sw); @@ -298,12 +315,11 @@ static void Settings_QBarEnableControls(HWND hwnd, bool enabled) static const int ids[] = { IDC_GRP_QBAR_ORI, IDC_RAD_QBAR_VERT, IDC_RAD_QBAR_HORIZ, IDC_CHK_QBAR_TOPMOST, - IDC_LBL_QBAR_TARGET, IDC_EDIT_QBAR_TARGET_S, IDC_LBL_QBAR_TIP, + IDC_LBL_QBAR_TARGET, IDC_EDIT_QBAR_TARGET_S, IDC_LBL_QBAR_TIP, IDC_LBL_QBAR_TARGET_EXE, IDC_EDIT_QBAR_TARGET_EXE_S, IDC_BTN_BROWSE_QBAR_EXE_S, IDC_LBL_QBAR_EXE_TIP, IDC_CHK_REPEAT_QBAR, - -1 - }; + -1}; for (const int *p = ids; *p != -1; p++) EnableWindow(GetDlgItem(hwnd, *p), enabled); } @@ -316,26 +332,27 @@ static void Settings_QBarEnableControls(HWND hwnd, bool enabled) /* SettingsTransferDlgProc fills repos[], dirs[], and the */ /* incl_* / *_selected[] fields on IDOK. */ /* ================================================================== */ -typedef struct { +typedef struct +{ /* ── Input fields (set by caller before DialogBoxParam) ───────── */ - bool is_export; /* true = Export; false = Import */ - WCHAR src_path[MAX_APPPATH]; /* import only: source INI path */ + bool is_export; /* true = Export; false = Import */ + WCHAR src_path[MAX_APPPATH]; /* import only: source INI path */ /* ── Items shown in the dialog (filled in WM_INITDIALOG) ──────── */ /* Export: mirrored from g.cfg. Import: read from src_path. */ ExtraRepo repos[MAX_EXTRA_REPOS]; - int repo_count; - LocalDir dirs[MAX_LOCAL_DIRS]; - int dir_count; + int repo_count; + LocalDir dirs[MAX_LOCAL_DIRS]; + int dir_count; /* ── Output fields (populated on IDOK) ────────────────────────── */ - bool incl_python_path; /* Python path checkbox */ - bool incl_cache_dir; /* Cache folder checkbox */ - bool incl_options; /* Options/theme/window/QBar */ - bool main_token_selected; /* GitHub account token */ - bool repo_token_selected[MAX_EXTRA_REPOS]; /* per-repo token */ - bool repo_selected[MAX_EXTRA_REPOS]; /* true = user ticked this repo */ - bool dir_selected[MAX_LOCAL_DIRS]; /* true = user ticked this dir */ + bool incl_python_path; /* Python path checkbox */ + bool incl_cache_dir; /* Cache folder checkbox */ + bool incl_options; /* Options/theme/window/QBar */ + bool main_token_selected; /* GitHub account token */ + bool repo_token_selected[MAX_EXTRA_REPOS]; /* per-repo token */ + bool repo_selected[MAX_EXTRA_REPOS]; /* true = user ticked this repo */ + bool dir_selected[MAX_LOCAL_DIRS]; /* true = user ticked this dir */ } SettingsTransferParams; /* ================================================================== */ @@ -358,38 +375,38 @@ static void Settings_WriteGeneralTo(const Settings *s, const WCHAR *ini, WCHAR tmp[8]; if (incl_python_path) - WritePrivateProfileString(L"Python", L"Executable", s->python_exe, ini); + WritePrivateProfileString(L"Python", L"Executable", s->python_exe, ini); if (incl_cache_dir) - WritePrivateProfileString(L"Scripts", L"CacheDir", s->cache_dir, ini); + WritePrivateProfileString(L"Scripts", L"CacheDir", s->cache_dir, ini); if (!incl_options) return; #define WB(sec, key, val) WritePrivateProfileString(sec, key, (val) ? L"1" : L"0", ini) - WB(L"Options", L"AutoSync", s->auto_sync); - WB(L"Options", L"DownloadBeforeRun", s->download_before_run); - WB(L"Options", L"ShowConsole", s->show_console); - WB(L"Options", L"ConsoleKeepOpen", s->console_keep_open); - WB(L"Options", L"CheckUpdates", s->check_updates); - WB(L"Options", L"DepsKeepOpen", s->deps_keep_open); - WB(L"Options", L"AutoUpdate", s->auto_update); - WB(L"Options", L"OfflineUseCache", s->offline_use_cache); + WB(L"Options", L"AutoSync", s->auto_sync); + WB(L"Options", L"DownloadBeforeRun", s->download_before_run); + WB(L"Options", L"ShowConsole", s->show_console); + WB(L"Options", L"ConsoleKeepOpen", s->console_keep_open); + WB(L"Options", L"CheckUpdates", s->check_updates); + WB(L"Options", L"DepsKeepOpen", s->deps_keep_open); + WB(L"Options", L"AutoUpdate", s->auto_update); + WB(L"Options", L"OfflineUseCache", s->offline_use_cache); _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", s->refresh_interval); WritePrivateProfileString(L"Options", L"RefreshInterval", tmp, ini); _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", (int)s->sort_mode); WritePrivateProfileString(L"Options", L"SortMode", tmp, ini); - WB(L"Options", L"RepeatOnDblClick", s->repeat_on_dblclick); + WB(L"Options", L"RepeatOnDblClick", s->repeat_on_dblclick); WB(L"Options", L"QBarRepeatOnDblClick", s->qbar_repeat_on_dblclick); - WB(L"Options", L"TintScriptSources", s->tint_script_sources); + WB(L"Options", L"TintScriptSources", s->tint_script_sources); - WB(L"Window", L"AlwaysOnTop", s->always_on_top); - WB(L"Window", L"MinimizeToTray", s->minimize_to_tray); + WB(L"Window", L"AlwaysOnTop", s->always_on_top); + WB(L"Window", L"MinimizeToTray", s->minimize_to_tray); WB(L"Window", L"StartWithWindows", s->start_with_windows); - WB(L"Window", L"StartMinimized", s->start_minimized); + WB(L"Window", L"StartMinimized", s->start_minimized); _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", (int)s->theme); WritePrivateProfileString(L"Window", L"Theme", tmp, ini); - WB(L"QuickBar", L"Enabled", s->qbar_enabled); - WB(L"QuickBar", L"Horizontal", s->qbar_horizontal); + WB(L"QuickBar", L"Enabled", s->qbar_enabled); + WB(L"QuickBar", L"Horizontal", s->qbar_horizontal); WB(L"QuickBar", L"TopmostWithCatia", s->qbar_topmost_with_catia); #undef WB _snwprintf_s(tmp, 7, _TRUNCATE, L"%d", s->qbar_x); @@ -400,7 +417,6 @@ static void Settings_WriteGeneralTo(const Settings *s, const WCHAR *ini, WritePrivateProfileString(L"QuickBar", L"TargetExe", s->qbar_target_exe, ini); } - /* ================================================================== */ /* Settings_ReadGeneralFrom (static) */ /* Purpose: Selectively reads Python path, cache dir, and/or all */ @@ -419,37 +435,38 @@ static void Settings_ReadGeneralFrom(Settings *s, const WCHAR *ini, bool incl_options) { if (incl_python_path) - GetPrivateProfileString(L"Python", L"Executable", L"", s->python_exe, MAX_APPPATH, ini); + GetPrivateProfileString(L"Python", L"Executable", L"", s->python_exe, MAX_APPPATH, ini); if (incl_cache_dir) - GetPrivateProfileString(L"Scripts", L"CacheDir", L"", s->cache_dir, MAX_APPPATH, ini); - - if (incl_options) { - s->auto_sync = GetPrivateProfileInt(L"Options", L"AutoSync", 1, ini) != 0; - s->download_before_run = GetPrivateProfileInt(L"Options", L"DownloadBeforeRun", 0, ini) != 0; - s->show_console = GetPrivateProfileInt(L"Options", L"ShowConsole", 0, ini) != 0; - s->console_keep_open = GetPrivateProfileInt(L"Options", L"ConsoleKeepOpen", 1, ini) != 0; - s->check_updates = GetPrivateProfileInt(L"Options", L"CheckUpdates", 1, ini) != 0; - s->deps_keep_open = GetPrivateProfileInt(L"Options", L"DepsKeepOpen", 0, ini) != 0; - s->auto_update = GetPrivateProfileInt(L"Options", L"AutoUpdate", 1, ini) != 0; - s->offline_use_cache = GetPrivateProfileInt(L"Options", L"OfflineUseCache", 0, ini) != 0; - s->refresh_interval = GetPrivateProfileInt(L"Options", L"RefreshInterval", 6, ini); - s->sort_mode = (SortMode)GetPrivateProfileInt(L"Options", L"SortMode", 0, ini); - s->repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"RepeatOnDblClick", 1, ini) != 0; + GetPrivateProfileString(L"Scripts", L"CacheDir", L"", s->cache_dir, MAX_APPPATH, ini); + + if (incl_options) + { + s->auto_sync = GetPrivateProfileInt(L"Options", L"AutoSync", 1, ini) != 0; + s->download_before_run = GetPrivateProfileInt(L"Options", L"DownloadBeforeRun", 0, ini) != 0; + s->show_console = GetPrivateProfileInt(L"Options", L"ShowConsole", 0, ini) != 0; + s->console_keep_open = GetPrivateProfileInt(L"Options", L"ConsoleKeepOpen", 1, ini) != 0; + s->check_updates = GetPrivateProfileInt(L"Options", L"CheckUpdates", 1, ini) != 0; + s->deps_keep_open = GetPrivateProfileInt(L"Options", L"DepsKeepOpen", 0, ini) != 0; + s->auto_update = GetPrivateProfileInt(L"Options", L"AutoUpdate", 1, ini) != 0; + s->offline_use_cache = GetPrivateProfileInt(L"Options", L"OfflineUseCache", 0, ini) != 0; + s->refresh_interval = GetPrivateProfileInt(L"Options", L"RefreshInterval", 6, ini); + s->sort_mode = (SortMode)GetPrivateProfileInt(L"Options", L"SortMode", 0, ini); + s->repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"RepeatOnDblClick", 1, ini) != 0; s->qbar_repeat_on_dblclick = GetPrivateProfileInt(L"Options", L"QBarRepeatOnDblClick", 1, ini) != 0; - s->tint_script_sources = GetPrivateProfileInt(L"Options", L"TintScriptSources", 1, ini) != 0; + s->tint_script_sources = GetPrivateProfileInt(L"Options", L"TintScriptSources", 1, ini) != 0; - s->always_on_top = GetPrivateProfileInt(L"Window", L"AlwaysOnTop", 1, ini) != 0; - s->minimize_to_tray = GetPrivateProfileInt(L"Window", L"MinimizeToTray", 1, ini) != 0; + s->always_on_top = GetPrivateProfileInt(L"Window", L"AlwaysOnTop", 1, ini) != 0; + s->minimize_to_tray = GetPrivateProfileInt(L"Window", L"MinimizeToTray", 1, ini) != 0; s->start_with_windows = GetPrivateProfileInt(L"Window", L"StartWithWindows", 1, ini) != 0; - s->start_minimized = GetPrivateProfileInt(L"Window", L"StartMinimized", 1, ini) != 0; + s->start_minimized = GetPrivateProfileInt(L"Window", L"StartMinimized", 1, ini) != 0; s->theme = (ThemeMode)GetPrivateProfileInt(L"Window", L"Theme", 0, ini); if (s->theme < 0 || s->theme > 2) s->theme = THEME_SYSTEM; - s->qbar_enabled = GetPrivateProfileInt(L"QuickBar", L"Enabled", 1, ini) != 0; - s->qbar_horizontal = GetPrivateProfileInt(L"QuickBar", L"Horizontal", 0, ini) != 0; + s->qbar_enabled = GetPrivateProfileInt(L"QuickBar", L"Enabled", 1, ini) != 0; + s->qbar_horizontal = GetPrivateProfileInt(L"QuickBar", L"Horizontal", 0, ini) != 0; s->qbar_topmost_with_catia = GetPrivateProfileInt(L"QuickBar", L"TopmostWithCatia", 1, ini) != 0; - s->qbar_x = GetPrivateProfileInt(L"QuickBar", L"X", 0, ini); - s->qbar_y = GetPrivateProfileInt(L"QuickBar", L"Y", 0, ini); + s->qbar_x = GetPrivateProfileInt(L"QuickBar", L"X", 0, ini); + s->qbar_y = GetPrivateProfileInt(L"QuickBar", L"Y", 0, ini); GetPrivateProfileString(L"QuickBar", L"TargetApp", L"CATIA V5", s->qbar_target_app, MAX_NAME, ini); GetPrivateProfileString(L"QuickBar", L"TargetExe", L"CNEXT.exe", @@ -462,7 +479,6 @@ static void Settings_ReadGeneralFrom(Settings *s, const WCHAR *ini, Runner_FindPython(s->python_exe, MAX_APPPATH); } - /* ================================================================== */ /* SettingsTransferDlgProc (static) */ /* Purpose: Dialog procedure for IDD_SETTINGS_TRANSFER. Used for */ @@ -482,7 +498,8 @@ static void Settings_ReadGeneralFrom(Settings *s, const WCHAR *ini, static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { - switch (msg) { + switch (msg) + { case WM_INITDIALOG: { SetWindowLongPtr(hwnd, GWLP_USERDATA, lp); @@ -493,25 +510,29 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, SetDlgItemText(hwnd, IDOK, L"Import"); /* ── Populate repos and dirs arrays ─────────────────────── */ - if (p->is_export) { + if (p->is_export) + { p->repo_count = g.cfg.extra_repo_count; for (int i = 0; i < p->repo_count; i++) p->repos[i] = g.cfg.extra_repos[i]; p->dir_count = g.cfg.local_dir_count; for (int i = 0; i < p->dir_count; i++) p->dirs[i] = g.cfg.local_dirs[i]; - } else { + } + else + { /* Read sources available in the import file */ p->repo_count = GetPrivateProfileInt(L"Sources", L"ExtraRepoCount", 0, p->src_path); if (p->repo_count > MAX_EXTRA_REPOS) p->repo_count = MAX_EXTRA_REPOS; - for (int i = 0; i < p->repo_count; i++) { + for (int i = 0; i < p->repo_count; i++) + { WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", i); - GetPrivateProfileString(L"Sources", key, L"", p->repos[i].url, 511, p->src_path); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", i); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", i); + GetPrivateProfileString(L"Sources", key, L"", p->repos[i].url, 511, p->src_path); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", i); GetPrivateProfileString(L"Sources", key, L"main", p->repos[i].branch, 63, p->src_path); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", i); - GetPrivateProfileString(L"Sources", key, L"", p->repos[i].token, 255, p->src_path); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", i); + GetPrivateProfileString(L"Sources", key, L"", p->repos[i].token, 255, p->src_path); _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dEnabled", i); p->repos[i].enabled = GetPrivateProfileInt(L"Sources", key, 1, p->src_path) != 0; if (!p->repos[i].branch[0]) @@ -519,9 +540,10 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, } p->dir_count = GetPrivateProfileInt(L"Sources", L"LocalDirCount", 0, p->src_path); if (p->dir_count > MAX_LOCAL_DIRS) p->dir_count = MAX_LOCAL_DIRS; - for (int i = 0; i < p->dir_count; i++) { + for (int i = 0; i < p->dir_count; i++) + { WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", i); + _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", i); GetPrivateProfileString(L"Sources", key, L"", p->dirs[i].path, MAX_APPPATH - 1, p->src_path); _snwprintf_s(key, 31, _TRUNCATE, L"Local%dEnabled", i); p->dirs[i].enabled = GetPrivateProfileInt(L"Sources", key, 1, p->src_path) != 0; @@ -532,24 +554,29 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, HWND hRepos = GetDlgItem(hwnd, IDC_LST_TRANS_REPOS); ListView_SetExtendedListViewStyle(hRepos, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT); { - RECT rc; GetClientRect(hRepos, &rc); - LVCOLUMN lvc; ZeroMemory(&lvc, sizeof(lvc)); + RECT rc; + GetClientRect(hRepos, &rc); + LVCOLUMN lvc; + ZeroMemory(&lvc, sizeof(lvc)); lvc.mask = LVCF_WIDTH; - lvc.cx = rc.right; + lvc.cx = rc.right; ListView_InsertColumn(hRepos, 0, &lvc); } - for (int i = 0; i < p->repo_count; i++) { + for (int i = 0; i < p->repo_count; i++) + { WCHAR label[600]; _snwprintf_s(label, 599, _TRUNCATE, L"%s [%s]", p->repos[i].url, p->repos[i].branch); - LVITEM lvi; ZeroMemory(&lvi, sizeof(lvi)); - lvi.mask = LVIF_TEXT; - lvi.iItem = i; + LVITEM lvi; + ZeroMemory(&lvi, sizeof(lvi)); + lvi.mask = LVIF_TEXT; + lvi.iItem = i; lvi.pszText = label; ListView_InsertItem(hRepos, &lvi); ListView_SetCheckState(hRepos, i, TRUE); } - if (p->repo_count == 0) { + if (p->repo_count == 0) + { ShowWindow(GetDlgItem(hwnd, IDC_LBL_TRANS_REPOS), SW_HIDE); ShowWindow(hRepos, SW_HIDE); } @@ -558,21 +585,26 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, HWND hDirs = GetDlgItem(hwnd, IDC_LST_TRANS_DIRS); ListView_SetExtendedListViewStyle(hDirs, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT); { - RECT rc; GetClientRect(hDirs, &rc); - LVCOLUMN lvc; ZeroMemory(&lvc, sizeof(lvc)); + RECT rc; + GetClientRect(hDirs, &rc); + LVCOLUMN lvc; + ZeroMemory(&lvc, sizeof(lvc)); lvc.mask = LVCF_WIDTH; - lvc.cx = rc.right; + lvc.cx = rc.right; ListView_InsertColumn(hDirs, 0, &lvc); } - for (int i = 0; i < p->dir_count; i++) { - LVITEM lvi; ZeroMemory(&lvi, sizeof(lvi)); - lvi.mask = LVIF_TEXT; - lvi.iItem = i; + for (int i = 0; i < p->dir_count; i++) + { + LVITEM lvi; + ZeroMemory(&lvi, sizeof(lvi)); + lvi.mask = LVIF_TEXT; + lvi.iItem = i; lvi.pszText = p->dirs[i].path; ListView_InsertItem(hDirs, &lvi); ListView_SetCheckState(hDirs, i, TRUE); } - if (p->dir_count == 0) { + if (p->dir_count == 0) + { ShowWindow(GetDlgItem(hwnd, IDC_LBL_TRANS_DIRS), SW_HIDE); ShowWindow(hDirs, SW_HIDE); } @@ -581,68 +613,79 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, HWND hTokens = GetDlgItem(hwnd, IDC_LST_TRANS_TOKENS); ListView_SetExtendedListViewStyle(hTokens, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT); { - RECT rc; GetClientRect(hTokens, &rc); - LVCOLUMN lvc; ZeroMemory(&lvc, sizeof(lvc)); + RECT rc; + GetClientRect(hTokens, &rc); + LVCOLUMN lvc; + ZeroMemory(&lvc, sizeof(lvc)); lvc.mask = LVCF_WIDTH; - lvc.cx = rc.right; + lvc.cx = rc.right; ListView_InsertColumn(hTokens, 0, &lvc); } int tok_row = 0; /* Main GitHub account token (lParam = -1) */ bool has_main_token; - if (p->is_export) { + if (p->is_export) + { has_main_token = g.cfg.github_token[0] != L'\0'; - } else { + } + else + { WCHAR tmp_tok[256] = {0}; GetPrivateProfileString(L"GitHub", L"Token", L"", tmp_tok, 255, p->src_path); has_main_token = tmp_tok[0] != L'\0'; } - if (has_main_token) { - LVITEM lvi; ZeroMemory(&lvi, sizeof(lvi)); - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = tok_row; + if (has_main_token) + { + LVITEM lvi; + ZeroMemory(&lvi, sizeof(lvi)); + lvi.mask = LVIF_TEXT | LVIF_PARAM; + lvi.iItem = tok_row; lvi.pszText = L"GitHub account token"; - lvi.lParam = (LPARAM)(-1); + lvi.lParam = (LPARAM)(-1); ListView_InsertItem(hTokens, &lvi); ListView_SetCheckState(hTokens, tok_row, TRUE); tok_row++; } /* Per-repo tokens (lParam = repo index in p->repos[]) */ - for (int i = 0; i < p->repo_count; i++) { + for (int i = 0; i < p->repo_count; i++) + { if (!p->repos[i].token[0]) continue; WCHAR label[600]; _snwprintf_s(label, 599, _TRUNCATE, L"Token for %s", p->repos[i].url); - LVITEM lvi; ZeroMemory(&lvi, sizeof(lvi)); - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = tok_row; + LVITEM lvi; + ZeroMemory(&lvi, sizeof(lvi)); + lvi.mask = LVIF_TEXT | LVIF_PARAM; + lvi.iItem = tok_row; lvi.pszText = label; - lvi.lParam = (LPARAM)(LONG_PTR)i; + lvi.lParam = (LPARAM)(LONG_PTR)i; ListView_InsertItem(hTokens, &lvi); ListView_SetCheckState(hTokens, tok_row, TRUE); tok_row++; } - if (tok_row == 0) { + if (tok_row == 0) + { ShowWindow(GetDlgItem(hwnd, IDC_LBL_TRANS_TOKENS), SW_HIDE); ShowWindow(hTokens, SW_HIDE); } - CheckDlgButton(hwnd, IDC_CHK_TRANS_PYTHON, BST_CHECKED); - CheckDlgButton(hwnd, IDC_CHK_TRANS_CACHE, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_TRANS_PYTHON, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_TRANS_CACHE, BST_CHECKED); CheckDlgButton(hwnd, IDC_CHK_TRANS_OPTIONS, BST_CHECKED); return TRUE; } case WM_COMMAND: - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDOK: { SettingsTransferParams *p = (SettingsTransferParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - p->incl_python_path = IsDlgButtonChecked(hwnd, IDC_CHK_TRANS_PYTHON) == BST_CHECKED; - p->incl_cache_dir = IsDlgButtonChecked(hwnd, IDC_CHK_TRANS_CACHE) == BST_CHECKED; - p->incl_options = IsDlgButtonChecked(hwnd, IDC_CHK_TRANS_OPTIONS) == BST_CHECKED; + p->incl_python_path = IsDlgButtonChecked(hwnd, IDC_CHK_TRANS_PYTHON) == BST_CHECKED; + p->incl_cache_dir = IsDlgButtonChecked(hwnd, IDC_CHK_TRANS_CACHE) == BST_CHECKED; + p->incl_options = IsDlgButtonChecked(hwnd, IDC_CHK_TRANS_OPTIONS) == BST_CHECKED; HWND hRepos = GetDlgItem(hwnd, IDC_LST_TRANS_REPOS); for (int i = 0; i < p->repo_count; i++) @@ -655,9 +698,11 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, /* Read token ListView — lParam identifies which token each row maps to */ HWND hTokens = GetDlgItem(hwnd, IDC_LST_TRANS_TOKENS); int n_tok = ListView_GetItemCount(hTokens); - for (int row = 0; row < n_tok; row++) { - LVITEM lvi2; ZeroMemory(&lvi2, sizeof(lvi2)); - lvi2.mask = LVIF_PARAM; + for (int row = 0; row < n_tok; row++) + { + LVITEM lvi2; + ZeroMemory(&lvi2, sizeof(lvi2)); + lvi2.mask = LVIF_PARAM; lvi2.iItem = row; ListView_GetItem(hTokens, &lvi2); bool checked = ListView_GetCheckState(hTokens, row) != 0; @@ -668,13 +713,13 @@ static INT_PTR CALLBACK SettingsTransferDlgProc(HWND hwnd, UINT msg, } /* Require at least one item selected */ - bool any = p->incl_python_path || p->incl_cache_dir || p->incl_options - || p->main_token_selected; + bool any = p->incl_python_path || p->incl_cache_dir || p->incl_options || p->main_token_selected; for (int i = 0; i < p->repo_count && !any; i++) if (p->repo_selected[i] || p->repo_token_selected[i]) any = true; for (int i = 0; i < p->dir_count && !any; i++) if (p->dir_selected[i]) any = true; - if (!any) { + if (!any) + { MessageBox(hwnd, L"Please select at least one item.", p->is_export ? L"Export Settings" : L"Import Settings", MB_ICONWARNING | MB_OK); @@ -716,11 +761,11 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* ── Build the tab control ──────────────────────────────── */ HWND hTab = GetDlgItem(hwnd, IDC_TAB_SETTINGS); { - TCITEM ti = { TCIF_TEXT, 0, 0, NULL, 0, -1, 0 }; /* only TCIF_TEXT is set; other members zeroed */ + TCITEM ti = {TCIF_TEXT, 0, 0, NULL, 0, -1, 0}; /* only TCIF_TEXT is set; other members zeroed */ static const WCHAR *labels[] = { - L"General", L"Sync", L"Console", L"Window", L"Quick Bar" - }; - for (int i = 0; i < 5; i++) { + L"General", L"Sync", L"Console", L"Window", L"Quick Bar"}; + for (int i = 0; i < 5; i++) + { ti.pszText = (LPWSTR)labels[i]; TabCtrl_InsertItem(hTab, i, &ti); } @@ -728,7 +773,7 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* ── Tab 0: General ─────────────────────────────────────── */ SetDlgItemText(hwnd, IDC_EDIT_PYTHON, s->python_exe); - SetDlgItemText(hwnd, IDC_EDIT_CACHE, s->cache_dir); + SetDlgItemText(hwnd, IDC_EDIT_CACHE, s->cache_dir); { bool has_tok = (s->github_token[0] != L'\0'); /* token present if string is non-empty */ CheckDlgButton(hwnd, IDC_CHK_TOKEN, has_tok ? BST_CHECKED : BST_UNCHECKED); @@ -737,11 +782,11 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) } /* ── Tab 1: Sync ─────────────────────────────────────────── */ - CheckDlgButton(hwnd, IDC_CHK_AUTOSYNC, s->auto_sync ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_DOWNLOAD, s->download_before_run ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_CHECK_UPDATES, s->check_updates ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_AUTO_UPDATE, s->auto_update ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_OFFLINE_CACHE, s->offline_use_cache ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_AUTOSYNC, s->auto_sync ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_DOWNLOAD, s->download_before_run ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_CHECK_UPDATES, s->check_updates ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_AUTO_UPDATE, s->auto_update ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_OFFLINE_CACHE, s->offline_use_cache ? BST_CHECKED : BST_UNCHECKED); { WCHAR ri[8]; _snwprintf_s(ri, 7, _TRUNCATE, L"%d", s->refresh_interval); @@ -749,32 +794,32 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) } /* ── Tab 2: Console ─────────────────────────────────────── */ - CheckDlgButton(hwnd, IDC_CHK_CONSOLE, s->show_console ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_KEEP_OPEN, s->console_keep_open ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_DEPS_KEEP_OPEN, s->deps_keep_open ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_REPEAT_MAIN, s->repeat_on_dblclick ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_CONSOLE, s->show_console ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_KEEP_OPEN, s->console_keep_open ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_DEPS_KEEP_OPEN, s->deps_keep_open ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_REPEAT_MAIN, s->repeat_on_dblclick ? BST_CHECKED : BST_UNCHECKED); EnableWindow(GetDlgItem(hwnd, IDC_CHK_KEEP_OPEN), s->show_console); /* keep-open only meaningful when a console window is visible */ /* ── Tab 3: Window ──────────────────────────────────────── */ - CheckDlgButton(hwnd, IDC_CHK_ALWAYS_ON_TOP, s->always_on_top ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_MINIMIZE_TRAY, s->minimize_to_tray ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_START_WINDOWS, s->start_with_windows? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_START_MIN, s->start_minimized ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_THEME_DARK, s->theme == THEME_DARK ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_THEME_LIGHT, s->theme == THEME_LIGHT ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_ALWAYS_ON_TOP, s->always_on_top ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_MINIMIZE_TRAY, s->minimize_to_tray ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_START_WINDOWS, s->start_with_windows ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_START_MIN, s->start_minimized ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_THEME_DARK, s->theme == THEME_DARK ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_THEME_LIGHT, s->theme == THEME_LIGHT ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwnd, IDC_RAD_THEME_SYSTEM, s->theme == THEME_SYSTEM ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_DEFAULT, s->sort_mode == SORT_ORDER ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_ALPHA, s->sort_mode == SORT_ALPHA ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_DATE, s->sort_mode == SORT_DATE ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_USED, s->sort_mode == SORT_MOST_USED? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_TINT_SOURCES, s->tint_script_sources ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_DEFAULT, s->sort_mode == SORT_ORDER ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_ALPHA, s->sort_mode == SORT_ALPHA ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_DATE, s->sort_mode == SORT_DATE ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_USED, s->sort_mode == SORT_MOST_USED ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_TINT_SOURCES, s->tint_script_sources ? BST_CHECKED : BST_UNCHECKED); /* ── Tab 4: Quick Bar ───────────────────────────────────── */ - CheckDlgButton(hwnd, IDC_CHK_QBAR_ENABLE, s->qbar_enabled ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_QBAR_HORIZ, s->qbar_horizontal ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_QBAR_VERT, !s->qbar_horizontal ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_QBAR_TOPMOST, s->qbar_topmost_with_catia? BST_CHECKED : BST_UNCHECKED); - SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_S, s->qbar_target_app); + CheckDlgButton(hwnd, IDC_CHK_QBAR_ENABLE, s->qbar_enabled ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_QBAR_HORIZ, s->qbar_horizontal ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_QBAR_VERT, !s->qbar_horizontal ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_QBAR_TOPMOST, s->qbar_topmost_with_catia ? BST_CHECKED : BST_UNCHECKED); + SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_S, s->qbar_target_app); SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE_S, s->qbar_target_exe); CheckDlgButton(hwnd, IDC_CHK_REPEAT_QBAR, s->qbar_repeat_on_dblclick ? BST_CHECKED : BST_UNCHECKED); Settings_QBarEnableControls(hwnd, s->qbar_enabled); @@ -787,7 +832,8 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case WM_NOTIFY: { NMHDR *nm = (NMHDR *)lp; - if (nm->idFrom == IDC_TAB_SETTINGS && nm->code == TCN_SELCHANGE) { /* TCN_SELCHANGE fires when the user clicks a tab */ + if (nm->idFrom == IDC_TAB_SETTINGS && nm->code == TCN_SELCHANGE) + { /* TCN_SELCHANGE fires when the user clicks a tab */ int sel = TabCtrl_GetCurSel(GetDlgItem(hwnd, IDC_TAB_SETTINGS)); Settings_ShowTab(hwnd, sel); } @@ -795,19 +841,15 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) } case WM_COMMAND: - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDC_BTN_BROWSE_PY: { WCHAR path[MAX_APPPATH] = {0}; GetDlgItemText(hwnd, IDC_EDIT_PYTHON, path, MAX_APPPATH); OPENFILENAME ofn = { - .lStructSize=sizeof(ofn), .hwndOwner=hwnd, - .lpstrFilter=L"Python\0python.exe;python3.exe\0All Files\0*.*\0", - .lpstrFile=path, .nMaxFile=MAX_APPPATH, - .Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST, - .lpstrTitle=L"Select Python Executable" - }; + .lStructSize = sizeof(ofn), .hwndOwner = hwnd, .lpstrFilter = L"Python\0python.exe;python3.exe\0All Files\0*.*\0", .lpstrFile = path, .nMaxFile = MAX_APPPATH, .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, .lpstrTitle = L"Select Python Executable"}; if (GetOpenFileName(&ofn)) SetDlgItemText(hwnd, IDC_EDIT_PYTHON, path); break; } @@ -816,11 +858,12 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { WCHAR path[MAX_APPPATH] = {0}; GetDlgItemText(hwnd, IDC_EDIT_CACHE, path, MAX_APPPATH); - BROWSEINFO bi = { .hwndOwner=hwnd, - .lpszTitle=L"Select Script Cache Folder", - .ulFlags=BIF_USENEWUI|BIF_RETURNONLYFSDIRS }; + BROWSEINFO bi = {.hwndOwner = hwnd, + .lpszTitle = L"Select Script Cache Folder", + .ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS}; PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi); - if (pidl) { + if (pidl) + { SHGetPathFromIDList(pidl, path); SetDlgItemText(hwnd, IDC_EDIT_CACHE, path); CoTaskMemFree(pidl); @@ -843,19 +886,20 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) GetDlgItemText(hwnd, IDC_EDIT_PYTHON, python_path, MAX_APPPATH); if (!python_path[0]) Runner_FindPython(python_path, MAX_APPPATH); - if (!python_path[0]) { + if (!python_path[0]) + { MessageBox(hwnd, - L"No Python executable found.\n\n" - L"Set the Python interpreter path before creating a virtual environment.", - L"Create Virtual Environment", MB_ICONWARNING | MB_OK); + L"No Python executable found.\n\n" + L"Set the Python interpreter path before creating a virtual environment.", + L"Create Virtual Environment", MB_ICONWARNING | MB_OK); break; } /* ── 2. Build venv and venv python.exe paths ────────────────── */ WCHAR venv_dir[MAX_APPPATH]; WCHAR venv_python[MAX_APPPATH]; - _snwprintf_s(venv_dir, MAX_APPPATH, _TRUNCATE, - L"%s\\venv", g.appdata_dir); + _snwprintf_s(venv_dir, MAX_APPPATH, _TRUNCATE, + L"%s\\venv", g.appdata_dir); _snwprintf_s(venv_python, MAX_APPPATH, _TRUNCATE, L"%s\\venv\\Scripts\\python.exe", g.appdata_dir); @@ -863,13 +907,13 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) bool already = GetFileAttributes(venv_python) != INVALID_FILE_ATTRIBUTES; WCHAR confirm[MAX_APPPATH * 2 + 64]; _snwprintf_s(confirm, MAX_APPPATH * 2 + 63, _TRUNCATE, - already - ? L"A virtual environment already exists at:\n%s\n\n" - L"Recreate it? The existing environment will be replaced.\n\n" - L"Using Python:\n%s" - : L"Create a virtual environment at:\n%s\n\n" - L"Using Python:\n%s", - venv_dir, python_path); + already + ? L"A virtual environment already exists at:\n%s\n\n" + L"Recreate it? The existing environment will be replaced.\n\n" + L"Using Python:\n%s" + : L"Create a virtual environment at:\n%s\n\n" + L"Using Python:\n%s", + venv_dir, python_path); if (MessageBox(hwnd, confirm, L"Create Virtual Environment", MB_ICONQUESTION | MB_YESNO) != IDYES) break; @@ -884,21 +928,23 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(sei)); - sei.cbSize = sizeof(sei); - sei.fMask = SEE_MASK_NOCLOSEPROCESS; - sei.hwnd = hwnd; - sei.lpFile = L"cmd.exe"; + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.hwnd = hwnd; + sei.lpFile = L"cmd.exe"; sei.lpParameters = params; - sei.nShow = SW_SHOW; + sei.nShow = SW_SHOW; bool launched = ShellExecuteEx(&sei) && sei.hProcess; - if (launched) { + if (launched) + { /* Pump messages while waiting so the dialog stays responsive */ HANDLE h = sei.hProcess; - while (MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLINPUT) - == (WAIT_OBJECT_0 + 1)) { + while (MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLINPUT) == (WAIT_OBJECT_0 + 1)) + { MSG m; - while (PeekMessage(&m, NULL, 0, 0, PM_REMOVE)) { + while (PeekMessage(&m, NULL, 0, 0, PM_REMOVE)) + { TranslateMessage(&m); DispatchMessage(&m); } @@ -910,19 +956,22 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) SetDlgItemText(hwnd, IDC_BTN_CREATE_VENV, L"Create venv"); /* ── 5. Verify outcome and update the path field ────────────── */ - if (launched && GetFileAttributes(venv_python) != INVALID_FILE_ATTRIBUTES) { + if (launched && GetFileAttributes(venv_python) != INVALID_FILE_ATTRIBUTES) + { SetDlgItemText(hwnd, IDC_EDIT_PYTHON, venv_python); MessageBox(hwnd, - L"Virtual environment created successfully.\n\n" - L"The Python path has been updated to point to the new environment.\n\n" - L"Click OK to save settings, then use ↓ Deps to install packages.", - L"Create Virtual Environment", MB_ICONINFORMATION | MB_OK); - } else { + L"Virtual environment created successfully.\n\n" + L"The Python path has been updated to point to the new environment.\n\n" + L"Click OK to save settings, then use ↓ Deps to install packages.", + L"Create Virtual Environment", MB_ICONINFORMATION | MB_OK); + } + else + { WCHAR err_msg[MAX_APPPATH + 128]; _snwprintf_s(err_msg, MAX_APPPATH + 127, _TRUNCATE, - L"Virtual environment creation failed or was cancelled.\n\n" - L"Expected Python executable not found at:\n%s", - venv_python); + L"Virtual environment creation failed or was cancelled.\n\n" + L"Expected Python executable not found at:\n%s", + venv_python); MessageBox(hwnd, err_msg, L"Create Virtual Environment", MB_ICONWARNING | MB_OK); } @@ -949,14 +998,17 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* Restore g.cfg — the dialog has not saved yet */ wcsncpy_s(g.cfg.python_exe, MAX_APPPATH, saved, _TRUNCATE); - if (ok) { + if (ok) + { SetDlgItemText(hwnd, IDC_EDIT_PYTHON, found); - } else { + } + else + { SetDlgItemText(hwnd, IDC_EDIT_PYTHON, L""); MessageBox(hwnd, - L"No Python installation found automatically.\n\n" - L"Use Browse... to locate python.exe manually.", - L"Auto-detect Python", MB_ICONWARNING | MB_OK); + L"No Python installation found automatically.\n\n" + L"Use Browse... to locate python.exe manually.", + L"Auto-detect Python", MB_ICONWARNING | MB_OK); } break; } @@ -975,7 +1027,7 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) ZeroMemory(&bi, sizeof(bi)); bi.hwndOwner = hwnd; bi.lpszTitle = L"Select the root folder of a Python virtual environment"; - bi.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + bi.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi); if (!pidl) break; SHGetPathFromIDList(pidl, dir); @@ -985,15 +1037,18 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) _snwprintf_s(venv_python, MAX_APPPATH, _TRUNCATE, L"%s\\Scripts\\python.exe", dir); - if (GetFileAttributes(venv_python) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(venv_python) != INVALID_FILE_ATTRIBUTES) + { SetDlgItemText(hwnd, IDC_EDIT_PYTHON, venv_python); - } else { + } + else + { WCHAR warn[MAX_APPPATH + 128]; _snwprintf_s(warn, MAX_APPPATH + 127, _TRUNCATE, - L"No Python executable found at:\n%s\n\n" - L"Make sure you selected the root folder of a valid " - L"virtual environment (it must contain Scripts\\python.exe).", - venv_python); + L"No Python executable found at:\n%s\n\n" + L"Make sure you selected the root folder of a valid " + L"virtual environment (it must contain Scripts\\python.exe).", + venv_python); MessageBox(hwnd, warn, L"Browse Virtual Environment", MB_ICONWARNING | MB_OK); } @@ -1013,18 +1068,19 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) _snwprintf_s(venv_dir, MAX_APPPATH, _TRUNCATE, L"%s\\venv", g.appdata_dir); - if (GetFileAttributes(venv_dir) == INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(venv_dir) == INVALID_FILE_ATTRIBUTES) + { MessageBox(hwnd, - L"No virtual environment found at the default location.", - L"Delete Virtual Environment", MB_ICONINFORMATION | MB_OK); + L"No virtual environment found at the default location.", + L"Delete Virtual Environment", MB_ICONINFORMATION | MB_OK); break; } WCHAR confirm[MAX_APPPATH + 128]; _snwprintf_s(confirm, MAX_APPPATH + 127, _TRUNCATE, - L"Delete the virtual environment at:\n%s\n\n" - L"All installed packages will be removed. This cannot be undone.", - venv_dir); + L"Delete the virtual environment at:\n%s\n\n" + L"All installed packages will be removed. This cannot be undone.", + venv_dir); if (MessageBox(hwnd, confirm, L"Delete Virtual Environment", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2) != IDYES) break; @@ -1036,14 +1092,15 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) SHFILEOPSTRUCTW fop; ZeroMemory(&fop, sizeof(fop)); - fop.hwnd = hwnd; - fop.wFunc = FO_DELETE; - fop.pFrom = del_path; + fop.hwnd = hwnd; + fop.wFunc = FO_DELETE; + fop.pFrom = del_path; fop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; int res = SHFileOperationW(&fop); - if (res == 0 && !fop.fAnyOperationsAborted) { + if (res == 0 && !fop.fAnyOperationsAborted) + { /* If the path field was pointing into the deleted venv, clear it */ WCHAR cur[MAX_APPPATH] = {0}; GetDlgItemText(hwnd, IDC_EDIT_PYTHON, cur, MAX_APPPATH); @@ -1051,14 +1108,16 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) SetDlgItemText(hwnd, IDC_EDIT_PYTHON, L""); MessageBox(hwnd, - L"Virtual environment deleted successfully.", - L"Delete Virtual Environment", MB_ICONINFORMATION | MB_OK); - } else { + L"Virtual environment deleted successfully.", + L"Delete Virtual Environment", MB_ICONINFORMATION | MB_OK); + } + else + { MessageBox(hwnd, - L"Failed to delete the virtual environment.\n\n" - L"Some files may be in use — close any terminals or " - L"scripts using this environment and try again.", - L"Delete Virtual Environment", MB_ICONERROR | MB_OK); + L"Failed to delete the virtual environment.\n\n" + L"Some files may be in use — close any terminals or " + L"scripts using this environment and try again.", + L"Delete Virtual Environment", MB_ICONERROR | MB_OK); } break; } @@ -1067,13 +1126,9 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { WCHAR path[MAX_APPPATH] = {0}; OPENFILENAME ofn = { - .lStructSize = sizeof(ofn), .hwndOwner = hwnd, - .lpstrFilter = L"Executables\0*.exe\0All Files\0*.*\0", - .lpstrFile = path, .nMaxFile = MAX_APPPATH, - .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, - .lpstrTitle = L"Select Target Application" - }; - if (GetOpenFileName(&ofn)) { + .lStructSize = sizeof(ofn), .hwndOwner = hwnd, .lpstrFilter = L"Executables\0*.exe\0All Files\0*.*\0", .lpstrFile = path, .nMaxFile = MAX_APPPATH, .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, .lpstrTitle = L"Select Target Application"}; + if (GetOpenFileName(&ofn)) + { WCHAR *slash = wcsrchr(path, L'\\'); SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE_S, slash ? slash + 1 : path); /* strip path — show filename only */ } @@ -1082,17 +1137,17 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case IDC_CHK_CONSOLE: EnableWindow(GetDlgItem(hwnd, IDC_CHK_KEEP_OPEN), - IsDlgButtonChecked(hwnd, IDC_CHK_CONSOLE) == BST_CHECKED); /* grey out Keep Open when Show Console is unchecked */ + IsDlgButtonChecked(hwnd, IDC_CHK_CONSOLE) == BST_CHECKED); /* grey out Keep Open when Show Console is unchecked */ break; case IDC_CHK_TOKEN: EnableWindow(GetDlgItem(hwnd, IDC_EDIT_TOKEN), - IsDlgButtonChecked(hwnd, IDC_CHK_TOKEN) == BST_CHECKED); + IsDlgButtonChecked(hwnd, IDC_CHK_TOKEN) == BST_CHECKED); break; case IDC_CHK_QBAR_ENABLE: Settings_QBarEnableControls(hwnd, - IsDlgButtonChecked(hwnd, IDC_CHK_QBAR_ENABLE) == BST_CHECKED); + IsDlgButtonChecked(hwnd, IDC_CHK_QBAR_ENABLE) == BST_CHECKED); break; /* ================================================================== */ @@ -1120,13 +1175,13 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = hwnd; + ofn.hwndOwner = hwnd; ofn.lpstrFilter = L"INI Files\0*.ini\0All Files\0*.*\0"; - ofn.lpstrFile = path; - ofn.nMaxFile = MAX_APPPATH; + ofn.lpstrFile = path; + ofn.nMaxFile = MAX_APPPATH; ofn.lpstrDefExt = L"ini"; - ofn.lpstrTitle = L"Export Settings"; - ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; + ofn.lpstrTitle = L"Export Settings"; + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (!GetSaveFileName(&ofn)) break; /* Step 3: Clear any existing file so stale keys don't persist */ @@ -1142,14 +1197,15 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* Step 5: Write only the individually selected repos */ WCHAR tmp[8]; int n_repos = 0; - for (int i = 0; i < tp.repo_count; i++) { + for (int i = 0; i < tp.repo_count; i++) + { if (!tp.repo_selected[i]) continue; WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", n_repos); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dUrl", n_repos); WritePrivateProfileString(L"Sources", key, tp.repos[i].url, path); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", n_repos); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dBranch", n_repos); WritePrivateProfileString(L"Sources", key, tp.repos[i].branch, path); - _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", n_repos); + _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dToken", n_repos); WritePrivateProfileString(L"Sources", key, tp.repo_token_selected[i] ? tp.repos[i].token : L"", path); _snwprintf_s(key, 31, _TRUNCATE, L"Repo%dEnabled", n_repos); @@ -1162,10 +1218,11 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* Step 6: Write only the individually selected local dirs */ int n_dirs = 0; - for (int i = 0; i < tp.dir_count; i++) { + for (int i = 0; i < tp.dir_count; i++) + { if (!tp.dir_selected[i]) continue; WCHAR key[32]; - _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", n_dirs); + _snwprintf_s(key, 31, _TRUNCATE, L"Local%dPath", n_dirs); WritePrivateProfileString(L"Sources", key, tp.dirs[i].path, path); _snwprintf_s(key, 31, _TRUNCATE, L"Local%dEnabled", n_dirs); WritePrivateProfileString(L"Sources", key, @@ -1198,12 +1255,12 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = hwnd; + ofn.hwndOwner = hwnd; ofn.lpstrFilter = L"INI Files\0*.ini\0All Files\0*.*\0"; - ofn.lpstrFile = path; - ofn.nMaxFile = MAX_APPPATH; - ofn.lpstrTitle = L"Import Settings"; - ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + ofn.lpstrFile = path; + ofn.nMaxFile = MAX_APPPATH; + ofn.lpstrTitle = L"Import Settings"; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; if (!GetOpenFileName(&ofn)) break; /* Step 2: Show selection dialog (reads repos/dirs from file) */ @@ -1217,8 +1274,8 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) break; /* Step 3: Capture state for side-effect delta */ - bool old_dark = g.dark_mode; - bool old_qbar_en = g.cfg.qbar_enabled; + bool old_dark = g.dark_mode; + bool old_qbar_en = g.cfg.qbar_enabled; bool old_qbar_horiz = g.cfg.qbar_horizontal; /* Step 4: Apply selectively — Python path, cache dir, options, GitHub token */ @@ -1230,35 +1287,42 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) g.cfg.github_token, 256, path); /* Step 5: Append selected repos; prompt on token conflict for duplicates */ - for (int i = 0; i < tp.repo_count; i++) { + for (int i = 0; i < tp.repo_count; i++) + { if (!tp.repo_selected[i]) continue; /* Search for an existing entry with the same URL */ int dup = -1; - for (int j = 0; j < g.cfg.extra_repo_count; j++) { - if (_wcsicmp(g.cfg.extra_repos[j].url, tp.repos[i].url) == 0) { + for (int j = 0; j < g.cfg.extra_repo_count; j++) + { + if (_wcsicmp(g.cfg.extra_repos[j].url, tp.repos[i].url) == 0) + { dup = j; break; } } - if (dup >= 0) { + if (dup >= 0) + { /* Duplicate: only prompt when the user selected this repo's token */ - if (tp.repo_token_selected[i] && tp.repos[i].token[0]) { + if (tp.repo_token_selected[i] && tp.repos[i].token[0]) + { WCHAR dup_msg[720]; _snwprintf_s(dup_msg, 719, _TRUNCATE, - L"This repository is already in your sources:\n%s\n\n" - L"Which token do you want to keep?\n\n" - L"Yes → Keep existing token\n" - L"No → Use imported token", - tp.repos[i].url); + L"This repository is already in your sources:\n%s\n\n" + L"Which token do you want to keep?\n\n" + L"Yes → Keep existing token\n" + L"No → Use imported token", + tp.repos[i].url); if (MessageBox(hwnd, dup_msg, L"Duplicate Repository", MB_ICONQUESTION | MB_YESNO) == IDNO) wcsncpy_s(g.cfg.extra_repos[dup].token, 256, tp.repos[i].token, _TRUNCATE); } /* else: no token conflict — skip silently */ - } else if (g.cfg.extra_repo_count < MAX_EXTRA_REPOS) { + } + else if (g.cfg.extra_repo_count < MAX_EXTRA_REPOS) + { /* New repo — append */ g.cfg.extra_repos[g.cfg.extra_repo_count] = tp.repos[i]; if (!tp.repo_token_selected[i]) @@ -1268,17 +1332,21 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) } /* Step 6: Append selected local dirs; duplicates skipped silently */ - for (int i = 0; i < tp.dir_count; i++) { + for (int i = 0; i < tp.dir_count; i++) + { if (!tp.dir_selected[i]) continue; bool dup = false; - for (int j = 0; j < g.cfg.local_dir_count; j++) { - if (_wcsicmp(g.cfg.local_dirs[j].path, tp.dirs[i].path) == 0) { + for (int j = 0; j < g.cfg.local_dir_count; j++) + { + if (_wcsicmp(g.cfg.local_dirs[j].path, tp.dirs[i].path) == 0) + { dup = true; break; } } - if (!dup && g.cfg.local_dir_count < MAX_LOCAL_DIRS) { + if (!dup && g.cfg.local_dir_count < MAX_LOCAL_DIRS) + { g.cfg.local_dirs[g.cfg.local_dir_count] = tp.dirs[i]; g.cfg.local_dir_count++; } @@ -1289,7 +1357,8 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) SHCreateDirectoryEx(NULL, g.cfg.cache_dir, NULL); App_ResolveTheme(); - if (g.dark_mode != old_dark) { + if (g.dark_mode != old_dark) + { App_RebuildGDI(); Window_ApplyDarkMode(g.hwnd); Window_ApplyThemeToChildren(g.hwnd); @@ -1303,23 +1372,29 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) Tabs_RebuildButtons(); if (g.hwnd_qbar) InvalidateRect(g.hwnd_qbar, NULL, FALSE); - if (g.cfg.qbar_enabled) { - if (g.cfg.qbar_horizontal != old_qbar_horiz) { + if (g.cfg.qbar_enabled) + { + if (g.cfg.qbar_horizontal != old_qbar_horiz) + { QuickBar_Destroy(); QuickBar_Register(GetModuleHandle(NULL)); QuickBar_Create(); QuickBar_Rebuild(); - } else if (!old_qbar_en) { + } + else if (!old_qbar_en) + { QuickBar_Show(true); } - } else if (old_qbar_en) { + } + else if (old_qbar_en) + { QuickBar_Show(false); } MessageBox(hwnd, - L"Settings imported successfully.\n\n" - L"The dialog will now close.", - L"Import Settings", MB_ICONINFORMATION | MB_OK); + L"Settings imported successfully.\n\n" + L"The dialog will now close.", + L"Import Settings", MB_ICONINFORMATION | MB_OK); /* Close via IDCANCEL so the open dialog controls do not write back over the settings we just imported. */ @@ -1330,83 +1405,83 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case IDC_BTN_RESET: { int res = MessageBox(hwnd, - L"Reset all settings to defaults?\n\n" - L"This will clear your Python path, cache folder,\n" - L"GitHub token, and all options.\n\n" - L"Sources (extra repos and local folders) are not affected.", - L"Reset to Defaults", - MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); + L"Reset all settings to defaults?\n\n" + L"This will clear your Python path, cache folder,\n" + L"GitHub token, and all options.\n\n" + L"Sources (extra repos and local folders) are not affected.", + L"Reset to Defaults", + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); if (res != IDYES) break; Settings *s = &g.cfg; - s->python_exe[0] = L'\0'; - s->github_token[0] = L'\0'; - s->auto_sync = true; - s->download_before_run = false; - s->show_console = false; - s->console_keep_open = true; - s->deps_keep_open = false; - s->check_updates = true; - s->auto_update = true; - s->offline_use_cache = false; - s->refresh_interval = 6; - s->always_on_top = true; - s->minimize_to_tray = true; - s->start_with_windows = true; - s->start_minimized = true; - s->theme = THEME_SYSTEM; - s->sort_mode = SORT_ORDER; - s->qbar_enabled = true; - s->qbar_horizontal = false; + s->python_exe[0] = L'\0'; + s->github_token[0] = L'\0'; + s->auto_sync = true; + s->download_before_run = false; + s->show_console = false; + s->console_keep_open = true; + s->deps_keep_open = false; + s->check_updates = true; + s->auto_update = true; + s->offline_use_cache = false; + s->refresh_interval = 6; + s->always_on_top = true; + s->minimize_to_tray = true; + s->start_with_windows = true; + s->start_minimized = true; + s->theme = THEME_SYSTEM; + s->sort_mode = SORT_ORDER; + s->qbar_enabled = true; + s->qbar_horizontal = false; s->qbar_topmost_with_catia = true; - wcsncpy_s(s->qbar_target_app, MAX_NAME, L"CATIA V5", _TRUNCATE); + wcsncpy_s(s->qbar_target_app, MAX_NAME, L"CATIA V5", _TRUNCATE); wcsncpy_s(s->qbar_target_exe, MAX_NAME, L"CNEXT.exe", _TRUNCATE); - s->repeat_on_dblclick = true; + s->repeat_on_dblclick = true; s->qbar_repeat_on_dblclick = true; _snwprintf_s(s->cache_dir, MAX_APPPATH, _TRUNCATE, L"%s\\scripts", g.appdata_dir); Runner_FindPython(s->python_exe, MAX_APPPATH); /* Reload all tab controls */ SetDlgItemText(hwnd, IDC_EDIT_PYTHON, s->python_exe); - SetDlgItemText(hwnd, IDC_EDIT_CACHE, s->cache_dir); - SetDlgItemText(hwnd, IDC_EDIT_TOKEN, L""); - CheckDlgButton(hwnd, IDC_CHK_TOKEN, BST_UNCHECKED); + SetDlgItemText(hwnd, IDC_EDIT_CACHE, s->cache_dir); + SetDlgItemText(hwnd, IDC_EDIT_TOKEN, L""); + CheckDlgButton(hwnd, IDC_CHK_TOKEN, BST_UNCHECKED); EnableWindow(GetDlgItem(hwnd, IDC_EDIT_TOKEN), FALSE); - CheckDlgButton(hwnd, IDC_CHK_AUTOSYNC, BST_CHECKED); - CheckDlgButton(hwnd, IDC_CHK_DOWNLOAD, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_CHECK_UPDATES, BST_CHECKED); - CheckDlgButton(hwnd, IDC_CHK_AUTO_UPDATE, BST_CHECKED); - CheckDlgButton(hwnd, IDC_CHK_OFFLINE_CACHE, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_AUTOSYNC, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_DOWNLOAD, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_CHECK_UPDATES, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_AUTO_UPDATE, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_OFFLINE_CACHE, BST_UNCHECKED); SetDlgItemText(hwnd, IDC_EDIT_REFRESH_INTERVAL, L"6"); - CheckDlgButton(hwnd, IDC_CHK_CONSOLE, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_KEEP_OPEN, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_CONSOLE, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_KEEP_OPEN, BST_CHECKED); CheckDlgButton(hwnd, IDC_CHK_DEPS_KEEP_OPEN, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_CHK_REPEAT_MAIN, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_REPEAT_MAIN, BST_CHECKED); EnableWindow(GetDlgItem(hwnd, IDC_CHK_KEEP_OPEN), FALSE); CheckDlgButton(hwnd, IDC_CHK_ALWAYS_ON_TOP, BST_CHECKED); CheckDlgButton(hwnd, IDC_CHK_MINIMIZE_TRAY, BST_CHECKED); CheckDlgButton(hwnd, IDC_CHK_START_WINDOWS, BST_CHECKED); - CheckDlgButton(hwnd, IDC_CHK_START_MIN, BST_CHECKED); - CheckDlgButton(hwnd, IDC_RAD_THEME_SYSTEM, BST_CHECKED); - CheckDlgButton(hwnd, IDC_RAD_THEME_DARK, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_THEME_LIGHT, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_DEFAULT, BST_CHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_ALPHA, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_DATE, BST_UNCHECKED); - CheckDlgButton(hwnd, IDC_RAD_SORT_USED, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_START_MIN, BST_CHECKED); + CheckDlgButton(hwnd, IDC_RAD_THEME_SYSTEM, BST_CHECKED); + CheckDlgButton(hwnd, IDC_RAD_THEME_DARK, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_THEME_LIGHT, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_DEFAULT, BST_CHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_ALPHA, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_DATE, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_RAD_SORT_USED, BST_UNCHECKED); s->tint_script_sources = true; - CheckDlgButton(hwnd, IDC_CHK_TINT_SOURCES, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_TINT_SOURCES, BST_CHECKED); - CheckDlgButton(hwnd, IDC_CHK_QBAR_ENABLE, BST_CHECKED); - CheckDlgButton(hwnd, IDC_RAD_QBAR_VERT, BST_CHECKED); - CheckDlgButton(hwnd, IDC_RAD_QBAR_HORIZ, BST_UNCHECKED); + CheckDlgButton(hwnd, IDC_CHK_QBAR_ENABLE, BST_CHECKED); + CheckDlgButton(hwnd, IDC_RAD_QBAR_VERT, BST_CHECKED); + CheckDlgButton(hwnd, IDC_RAD_QBAR_HORIZ, BST_UNCHECKED); CheckDlgButton(hwnd, IDC_CHK_QBAR_TOPMOST, BST_CHECKED); - SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_S, L"CATIA V5"); + SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_S, L"CATIA V5"); SetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE_S, L"CNEXT.exe"); - CheckDlgButton(hwnd, IDC_CHK_REPEAT_QBAR, BST_CHECKED); + CheckDlgButton(hwnd, IDC_CHK_REPEAT_QBAR, BST_CHECKED); Settings_QBarEnableControls(hwnd, true); break; } @@ -1414,58 +1489,65 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case IDOK: { Settings *s = &g.cfg; - bool old_dark = g.dark_mode; + bool old_dark = g.dark_mode; bool old_qbar_horiz = s->qbar_horizontal; - bool old_qbar_en = s->qbar_enabled; + bool old_qbar_en = s->qbar_enabled; /* General */ GetDlgItemText(hwnd, IDC_EDIT_PYTHON, s->python_exe, MAX_APPPATH); - GetDlgItemText(hwnd, IDC_EDIT_CACHE, s->cache_dir, MAX_APPPATH); + GetDlgItemText(hwnd, IDC_EDIT_CACHE, s->cache_dir, MAX_APPPATH); if (IsDlgButtonChecked(hwnd, IDC_CHK_TOKEN) == BST_CHECKED) GetDlgItemText(hwnd, IDC_EDIT_TOKEN, s->github_token, 256); else s->github_token[0] = L'\0'; /* Sync */ - s->auto_sync = IsDlgButtonChecked(hwnd, IDC_CHK_AUTOSYNC) == BST_CHECKED; - s->download_before_run = IsDlgButtonChecked(hwnd, IDC_CHK_DOWNLOAD) == BST_CHECKED; - s->check_updates = IsDlgButtonChecked(hwnd, IDC_CHK_CHECK_UPDATES) == BST_CHECKED; - s->auto_update = IsDlgButtonChecked(hwnd, IDC_CHK_AUTO_UPDATE) == BST_CHECKED; - s->offline_use_cache = IsDlgButtonChecked(hwnd, IDC_CHK_OFFLINE_CACHE) == BST_CHECKED; + s->auto_sync = IsDlgButtonChecked(hwnd, IDC_CHK_AUTOSYNC) == BST_CHECKED; + s->download_before_run = IsDlgButtonChecked(hwnd, IDC_CHK_DOWNLOAD) == BST_CHECKED; + s->check_updates = IsDlgButtonChecked(hwnd, IDC_CHK_CHECK_UPDATES) == BST_CHECKED; + s->auto_update = IsDlgButtonChecked(hwnd, IDC_CHK_AUTO_UPDATE) == BST_CHECKED; + s->offline_use_cache = IsDlgButtonChecked(hwnd, IDC_CHK_OFFLINE_CACHE) == BST_CHECKED; { WCHAR ri[8] = {0}; GetDlgItemText(hwnd, IDC_EDIT_REFRESH_INTERVAL, ri, 7); s->refresh_interval = _wtoi(ri); - if (s->refresh_interval < 0) s->refresh_interval = 0; /* 0 = no auto-sync */ + if (s->refresh_interval < 0) s->refresh_interval = 0; /* 0 = no auto-sync */ if (s->refresh_interval > 168) s->refresh_interval = 168; /* cap at 168 hours (1 week) */ } /* Console */ - s->show_console = IsDlgButtonChecked(hwnd, IDC_CHK_CONSOLE) == BST_CHECKED; - s->console_keep_open = IsDlgButtonChecked(hwnd, IDC_CHK_KEEP_OPEN) == BST_CHECKED; - s->deps_keep_open = IsDlgButtonChecked(hwnd, IDC_CHK_DEPS_KEEP_OPEN) == BST_CHECKED; - s->repeat_on_dblclick = IsDlgButtonChecked(hwnd, IDC_CHK_REPEAT_MAIN) == BST_CHECKED; + s->show_console = IsDlgButtonChecked(hwnd, IDC_CHK_CONSOLE) == BST_CHECKED; + s->console_keep_open = IsDlgButtonChecked(hwnd, IDC_CHK_KEEP_OPEN) == BST_CHECKED; + s->deps_keep_open = IsDlgButtonChecked(hwnd, IDC_CHK_DEPS_KEEP_OPEN) == BST_CHECKED; + s->repeat_on_dblclick = IsDlgButtonChecked(hwnd, IDC_CHK_REPEAT_MAIN) == BST_CHECKED; /* Window */ - s->always_on_top = IsDlgButtonChecked(hwnd, IDC_CHK_ALWAYS_ON_TOP)== BST_CHECKED; - s->minimize_to_tray = IsDlgButtonChecked(hwnd, IDC_CHK_MINIMIZE_TRAY)== BST_CHECKED; - s->start_with_windows = IsDlgButtonChecked(hwnd, IDC_CHK_START_WINDOWS)== BST_CHECKED; - s->start_minimized = IsDlgButtonChecked(hwnd, IDC_CHK_START_MIN) == BST_CHECKED; - if (IsDlgButtonChecked(hwnd, IDC_RAD_THEME_DARK) == BST_CHECKED) s->theme = THEME_DARK; - else if (IsDlgButtonChecked(hwnd, IDC_RAD_THEME_LIGHT) == BST_CHECKED) s->theme = THEME_LIGHT; - else s->theme = THEME_SYSTEM; /* checked radio wins; fall through to THEME_SYSTEM */ - if (IsDlgButtonChecked(hwnd, IDC_RAD_SORT_ALPHA) == BST_CHECKED) s->sort_mode = SORT_ALPHA; - else if (IsDlgButtonChecked(hwnd, IDC_RAD_SORT_DATE) == BST_CHECKED) s->sort_mode = SORT_DATE; - else if (IsDlgButtonChecked(hwnd, IDC_RAD_SORT_USED) == BST_CHECKED) s->sort_mode = SORT_MOST_USED; - else s->sort_mode = SORT_ORDER; /* fall through to SORT_ORDER (GitHub/disk order) */ + s->always_on_top = IsDlgButtonChecked(hwnd, IDC_CHK_ALWAYS_ON_TOP) == BST_CHECKED; + s->minimize_to_tray = IsDlgButtonChecked(hwnd, IDC_CHK_MINIMIZE_TRAY) == BST_CHECKED; + s->start_with_windows = IsDlgButtonChecked(hwnd, IDC_CHK_START_WINDOWS) == BST_CHECKED; + s->start_minimized = IsDlgButtonChecked(hwnd, IDC_CHK_START_MIN) == BST_CHECKED; + if (IsDlgButtonChecked(hwnd, IDC_RAD_THEME_DARK) == BST_CHECKED) + s->theme = THEME_DARK; + else if (IsDlgButtonChecked(hwnd, IDC_RAD_THEME_LIGHT) == BST_CHECKED) + s->theme = THEME_LIGHT; + else + s->theme = THEME_SYSTEM; /* checked radio wins; fall through to THEME_SYSTEM */ + if (IsDlgButtonChecked(hwnd, IDC_RAD_SORT_ALPHA) == BST_CHECKED) + s->sort_mode = SORT_ALPHA; + else if (IsDlgButtonChecked(hwnd, IDC_RAD_SORT_DATE) == BST_CHECKED) + s->sort_mode = SORT_DATE; + else if (IsDlgButtonChecked(hwnd, IDC_RAD_SORT_USED) == BST_CHECKED) + s->sort_mode = SORT_MOST_USED; + else + s->sort_mode = SORT_ORDER; /* fall through to SORT_ORDER (GitHub/disk order) */ s->tint_script_sources = IsDlgButtonChecked(hwnd, IDC_CHK_TINT_SOURCES) == BST_CHECKED; /* Quick Bar */ - s->qbar_enabled = IsDlgButtonChecked(hwnd, IDC_CHK_QBAR_ENABLE) == BST_CHECKED; - s->qbar_horizontal = IsDlgButtonChecked(hwnd, IDC_RAD_QBAR_HORIZ) == BST_CHECKED; - s->qbar_topmost_with_catia = IsDlgButtonChecked(hwnd, IDC_CHK_QBAR_TOPMOST)== BST_CHECKED; - s->qbar_repeat_on_dblclick = IsDlgButtonChecked(hwnd, IDC_CHK_REPEAT_QBAR) == BST_CHECKED; - GetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_S, s->qbar_target_app, MAX_NAME); + s->qbar_enabled = IsDlgButtonChecked(hwnd, IDC_CHK_QBAR_ENABLE) == BST_CHECKED; + s->qbar_horizontal = IsDlgButtonChecked(hwnd, IDC_RAD_QBAR_HORIZ) == BST_CHECKED; + s->qbar_topmost_with_catia = IsDlgButtonChecked(hwnd, IDC_CHK_QBAR_TOPMOST) == BST_CHECKED; + s->qbar_repeat_on_dblclick = IsDlgButtonChecked(hwnd, IDC_CHK_REPEAT_QBAR) == BST_CHECKED; + GetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_S, s->qbar_target_app, MAX_NAME); GetDlgItemText(hwnd, IDC_EDIT_QBAR_TARGET_EXE_S, s->qbar_target_exe, MAX_NAME); Settings_Save(s); @@ -1473,7 +1555,8 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /* ── Apply side effects ─────────────────────────────── */ App_ResolveTheme(); - if (g.dark_mode != old_dark) { + if (g.dark_mode != old_dark) + { App_RebuildGDI(); Window_ApplyDarkMode(g.hwnd); Window_ApplyThemeToChildren(g.hwnd); @@ -1487,17 +1570,23 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) Tabs_RebuildButtons(); if (g.hwnd_qbar) InvalidateRect(g.hwnd_qbar, NULL, FALSE); /* repaint quick bar in case tint setting changed */ - if (s->qbar_enabled) { - if (s->qbar_horizontal != old_qbar_horiz) { + if (s->qbar_enabled) + { + if (s->qbar_horizontal != old_qbar_horiz) + { /* orientation changed — must destroy and recreate the window */ QuickBar_Destroy(); QuickBar_Register(GetModuleHandle(NULL)); QuickBar_Create(); QuickBar_Rebuild(); - } else if (!old_qbar_en) { + } + else if (!old_qbar_en) + { QuickBar_Show(true); /* was disabled, now enabled — just show it */ } - } else if (old_qbar_en) { + } + else if (old_qbar_en) + { QuickBar_Show(false); /* was enabled, now disabled — hide it */ } @@ -1528,10 +1617,11 @@ INT_PTR CALLBACK SettingsDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { static HFONT s_hFontTitle = NULL; - static HICON s_hIconApp = NULL; + static HICON s_hIconApp = NULL; (void)lp; - switch (msg) { + switch (msg) + { case WM_INITDIALOG: { /* App icon at 48×48 — standard large icon size */ @@ -1557,7 +1647,8 @@ INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) return TRUE; } case WM_COMMAND: - switch (LOWORD(wp)) { + switch (LOWORD(wp)) + { case IDC_BTN_ABOUT_GITHUB: ShellExecuteW(hwnd, L"open", L"https://kaiur.github.io/CatiaMenuWin32/", @@ -1565,8 +1656,16 @@ INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) break; case IDOK: case IDCANCEL: - if (s_hFontTitle) { DeleteObject(s_hFontTitle); s_hFontTitle = NULL; } /* free GDI font to avoid leak */ - if (s_hIconApp) { DestroyIcon(s_hIconApp); s_hIconApp = NULL; } /* free icon handle to avoid leak */ + if (s_hFontTitle) + { + DeleteObject(s_hFontTitle); + s_hFontTitle = NULL; + } /* free GDI font to avoid leak */ + if (s_hIconApp) + { + DestroyIcon(s_hIconApp); + s_hIconApp = NULL; + } /* free icon handle to avoid leak */ EndDialog(hwnd, IDOK); break; } diff --git a/src/sources.c b/src/sources.c index d8ff9bf..ca31af6 100644 --- a/src/sources.c +++ b/src/sources.c @@ -25,7 +25,8 @@ static void DeleteFolderRecursive(const WCHAR *path) HANDLE h = FindFirstFileW(pattern, &fd); if (h == INVALID_HANDLE_VALUE) return; - do { + do + { /* "." and ".." are the current and parent directory entries — never recurse into them */ if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0) continue; @@ -36,7 +37,7 @@ static void DeleteFolderRecursive(const WCHAR *path) if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) DeleteFolderRecursive(full); /* recurse into subdirectory */ else - DeleteFileW(full); /* delete regular file */ + DeleteFileW(full); /* delete regular file */ } while (FindNextFileW(h, &fd)); FindClose(h); @@ -53,17 +54,17 @@ static void DeleteFolderRecursive(const WCHAR *path) static void Repos_Populate(HWND hList) { ListView_DeleteAllItems(hList); - for (int i = 0; i < g.cfg.extra_repo_count; i++) { + for (int i = 0; i < g.cfg.extra_repo_count; i++) + { ExtraRepo *r = &g.cfg.extra_repos[i]; LVITEM lvi = {0}; - lvi.mask = LVIF_TEXT; - lvi.iItem = i; + lvi.mask = LVIF_TEXT; + lvi.iItem = i; lvi.iSubItem = 0; lvi.pszText = r->url; ListView_InsertItem(hList, &lvi); ListView_SetItemText(hList, i, 1, r->branch[0] ? r->branch : L"main"); /* display "main" if branch was left blank */ ListView_SetItemText(hList, i, 2, r->enabled ? L"Yes" : L"No"); - } } @@ -77,12 +78,13 @@ static void Repos_Populate(HWND hList) static void Locals_Populate(HWND hList) { ListView_DeleteAllItems(hList); - for (int i = 0; i < g.cfg.local_dir_count; i++) { + for (int i = 0; i < g.cfg.local_dir_count; i++) + { LocalDir *d = &g.cfg.local_dirs[i]; LVITEM lvi = {0}; - lvi.mask = LVIF_TEXT; - lvi.iItem = i; - lvi.pszText = d->path; + lvi.mask = LVIF_TEXT; + lvi.iItem = i; + lvi.pszText = d->path; ListView_InsertItem(hList, &lvi); ListView_SetItemText(hList, i, 1, d->enabled ? L"Yes" : L"No"); } @@ -96,7 +98,11 @@ static void Locals_Populate(HWND hList) /* In: (set by SourcesDlgProc before DialogBoxParam call) */ /* Out: (repo is modified in-place when IDOK is returned) */ /* ================================================================== */ -typedef struct { ExtraRepo *repo; bool is_new; } RepoEditArg; +typedef struct +{ + ExtraRepo *repo; + bool is_new; +} RepoEditArg; /* ================================================================== */ /* RepoEditDlgProc (static) */ @@ -110,52 +116,62 @@ typedef struct { ExtraRepo *repo; bool is_new; } RepoEditArg; /* Out: INT_PTR — TRUE for handled; FALSE otherwise */ /* ================================================================== */ static INT_PTR CALLBACK RepoEditDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { /* Retrieve the arg pointer stored during WM_INITDIALOG; NULL before first message */ RepoEditArg *arg = (RepoEditArg *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (msg) { case WM_INITDIALOG: - arg = (RepoEditArg *)lp; /* lp carries the RepoEditArg passed to DialogBoxParam */ - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)arg);/* stash it so WM_COMMAND can retrieve it */ - if (!arg || !arg->repo) { EndDialog(hwnd, IDCANCEL); return FALSE; } /* safety: malformed caller */ - SetDlgItemText(hwnd, IDC_EDIT_REPO_URL, arg->repo->url); - SetDlgItemText(hwnd, IDC_EDIT_REPO_BRANCH, arg->repo->branch[0] - ? arg->repo->branch : L"main"); /* show "main" as default */ + arg = (RepoEditArg *)lp; /* lp carries the RepoEditArg passed to DialogBoxParam */ + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)arg); /* stash it so WM_COMMAND can retrieve it */ + if (!arg || !arg->repo) + { + EndDialog(hwnd, IDCANCEL); + return FALSE; + } /* safety: malformed caller */ + SetDlgItemText(hwnd, IDC_EDIT_REPO_URL, arg->repo->url); + SetDlgItemText(hwnd, IDC_EDIT_REPO_BRANCH, arg->repo->branch[0] ? arg->repo->branch : L"main"); /* show "main" as default */ CheckDlgButton(hwnd, IDC_CHK_REPO_TOKEN, - arg->repo->token[0] ? BST_CHECKED : BST_UNCHECKED); + arg->repo->token[0] ? BST_CHECKED : BST_UNCHECKED); SetDlgItemText(hwnd, IDC_EDIT_REPO_TOKEN, arg->repo->token); EnableWindow(GetDlgItem(hwnd, IDC_EDIT_REPO_TOKEN), arg->repo->token[0] != 0); /* grey out token field if no token is set */ CheckDlgButton(hwnd, IDC_CHK_REPO_ENABLED, - arg->repo->enabled ? BST_CHECKED : BST_UNCHECKED); + arg->repo->enabled ? BST_CHECKED : BST_UNCHECKED); return TRUE; case WM_COMMAND: - if (!arg) { EndDialog(hwnd, IDCANCEL); return TRUE; } /* arg not yet set — ignore */ - switch (LOWORD(wp)) { + if (!arg) + { + EndDialog(hwnd, IDCANCEL); + return TRUE; + } /* arg not yet set — ignore */ + switch (LOWORD(wp)) + { case IDC_CHK_REPO_TOKEN: /* Enable or disable the token text field to match the checkbox state */ EnableWindow(GetDlgItem(hwnd, IDC_EDIT_REPO_TOKEN), - IsDlgButtonChecked(hwnd, IDC_CHK_REPO_TOKEN) == BST_CHECKED); + IsDlgButtonChecked(hwnd, IDC_CHK_REPO_TOKEN) == BST_CHECKED); break; case IDOK: { WCHAR url[512] = {0}; GetDlgItemText(hwnd, IDC_EDIT_REPO_URL, url, 511); - if (!url[0]) { + if (!url[0]) + { /* Reject empty URL — repo cannot be identified */ MessageBox(hwnd, L"Please enter a GitHub URL.", L"Sources", MB_ICONWARNING | MB_OK); return TRUE; /* stay in dialog */ } /* Basic domain check — full URL validation is left to the sync engine */ - if (!wcsstr(url, L"github.com")) { + if (!wcsstr(url, L"github.com")) + { MessageBox(hwnd, - L"URL must be a GitHub repository URL.\n" - L"Example: https://github.com/owner/repo", - L"Sources", MB_ICONWARNING | MB_OK); + L"URL must be a GitHub repository URL.\n" + L"Example: https://github.com/owner/repo", + L"Sources", MB_ICONWARNING | MB_OK); return TRUE; /* stay in dialog */ } wcsncpy_s(arg->repo->url, 512, url, _TRUNCATE); @@ -197,7 +213,7 @@ static INT_PTR CALLBACK RepoEditDlgProc(HWND hwnd, UINT msg, /* via EndDialog */ /* ================================================================== */ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { (void)lp; switch (msg) @@ -205,27 +221,31 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, case WM_INITDIALOG: { /* Set up repo list columns */ - HWND hRepos = GetDlgItem(hwnd, IDC_LST_REPOS); + HWND hRepos = GetDlgItem(hwnd, IDC_LST_REPOS); HWND hLocals = GetDlgItem(hwnd, IDC_LST_LOCAL); LVCOLUMN lvc = {0}; lvc.mask = LVCF_TEXT | LVCF_WIDTH; - lvc.pszText = L"Repository URL"; lvc.cx = 180; + lvc.pszText = L"Repository URL"; + lvc.cx = 180; ListView_InsertColumn(hRepos, 0, &lvc); - lvc.pszText = L"Branch"; lvc.cx = 60; + lvc.pszText = L"Branch"; + lvc.cx = 60; ListView_InsertColumn(hRepos, 1, &lvc); - lvc.pszText = L"Enabled"; lvc.cx = 46; + lvc.pszText = L"Enabled"; + lvc.cx = 46; ListView_InsertColumn(hRepos, 2, &lvc); - lvc.pszText = L"Local Folder Path"; lvc.cx = 220; + lvc.pszText = L"Local Folder Path"; + lvc.cx = 220; ListView_InsertColumn(hLocals, 0, &lvc); - lvc.pszText = L"Enabled"; lvc.cx = 46; + lvc.pszText = L"Enabled"; + lvc.cx = 46; ListView_InsertColumn(hLocals, 1, &lvc); CheckDlgButton(hwnd, IDC_CHK_MAIN_REPO, - g.cfg.main_repo_enabled ? BST_CHECKED : BST_UNCHECKED); - + g.cfg.main_repo_enabled ? BST_CHECKED : BST_UNCHECKED); Repos_Populate(hRepos); Locals_Populate(hLocals); @@ -243,7 +263,8 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, /* ── Extra repos ── */ case IDC_BTN_REPO_ADD: - if (g.cfg.extra_repo_count >= MAX_EXTRA_REPOS) { + if (g.cfg.extra_repo_count >= MAX_EXTRA_REPOS) + { /* Array is fixed-size — cannot grow further */ MessageBox(hwnd, L"Maximum number of repositories reached.", L"Sources", MB_ICONWARNING | MB_OK); @@ -253,10 +274,11 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, ExtraRepo tmp = {0}; tmp.enabled = true; wcsncpy_s(tmp.branch, 64, L"main", _TRUNCATE); - RepoEditArg arg = { &tmp, true }; + RepoEditArg arg = {&tmp, true}; if (DialogBoxParam(GetModuleHandle(NULL), - MAKEINTRESOURCE(IDD_REPO_EDIT), - hwnd, RepoEditDlgProc, (LPARAM)&arg) == IDOK) { + MAKEINTRESOURCE(IDD_REPO_EDIT), + hwnd, RepoEditDlgProc, (LPARAM)&arg) == IDOK) + { g.cfg.extra_repos[g.cfg.extra_repo_count++] = tmp; Repos_Populate(GetDlgItem(hwnd, IDC_LST_REPOS)); } @@ -266,12 +288,12 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, case IDC_BTN_REPO_EDIT: { HWND hList = GetDlgItem(hwnd, IDC_LST_REPOS); - int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); /* -1 = start from beginning; returns -1 if nothing selected */ + int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); /* -1 = start from beginning; returns -1 if nothing selected */ if (sel < 0) break; /* no selection — ignore button click */ - RepoEditArg arg = { &g.cfg.extra_repos[sel], false }; + RepoEditArg arg = {&g.cfg.extra_repos[sel], false}; if (DialogBoxParam(GetModuleHandle(NULL), - MAKEINTRESOURCE(IDD_REPO_EDIT), - hwnd, RepoEditDlgProc, (LPARAM)&arg) == IDOK) + MAKEINTRESOURCE(IDD_REPO_EDIT), + hwnd, RepoEditDlgProc, (LPARAM)&arg) == IDOK) Repos_Populate(hList); break; } @@ -279,7 +301,7 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, case IDC_BTN_REPO_TOGGLE: { HWND hList = GetDlgItem(hwnd, IDC_LST_REPOS); - int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); + int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); if (sel < 0) break; /* nothing selected */ g.cfg.extra_repos[sel].enabled = !g.cfg.extra_repos[sel].enabled; Repos_Populate(hList); @@ -289,7 +311,7 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, case IDC_BTN_REPO_REMOVE: { HWND hList = GetDlgItem(hwnd, IDC_LST_REPOS); - int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); + int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); if (sel < 0) break; ExtraRepo *repo = &g.cfg.extra_repos[sel]; @@ -300,16 +322,19 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, /* Build confirmation message */ WCHAR confirm[512]; - if (has_cache) { + if (has_cache) + { _snwprintf_s(confirm, 511, _TRUNCATE, L"Remove repository:\n%s\n\n" - L"Delete cached scripts and requirements from:\n" - L"%s\\%s_%s\n\n" - L"This cannot be undone.", - repo->url, g.cfg.cache_dir, owner, reponame); - } else { + L"Delete cached scripts and requirements from:\n" + L"%s\\%s_%s\n\n" + L"This cannot be undone.", + repo->url, g.cfg.cache_dir, owner, reponame); + } + else + { _snwprintf_s(confirm, 511, _TRUNCATE, L"Remove repository:\n%s\n\n" - L"No cached files found to delete.", - repo->url); + L"No cached files found to delete.", + repo->url); } int res = MessageBox(hwnd, confirm, L"Remove Repository", @@ -317,20 +342,21 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, if (res != IDYES) break; /* user cancelled — leave the repo list unchanged */ /* Delete cached scripts folder: cache_dir\owner_reponame\ */ - if (has_cache) { + if (has_cache) + { WCHAR scripts_dir[MAX_APPPATH]; _snwprintf_s(scripts_dir, MAX_APPPATH, _TRUNCATE, L"%s\\%s_%s", - g.cfg.cache_dir, owner, reponame); + g.cfg.cache_dir, owner, reponame); DeleteFolderRecursive(scripts_dir); /* Delete cached requirements.txt */ WCHAR req[MAX_APPPATH]; _snwprintf_s(req, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\%s_%s\\requirements.txt", - g.cfg.cache_dir, owner, reponame); + g.cfg.cache_dir, owner, reponame); DeleteFileW(req); WCHAR req_dir[MAX_APPPATH]; _snwprintf_s(req_dir, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\%s_%s", - g.cfg.cache_dir, owner, reponame); + g.cfg.cache_dir, owner, reponame); RemoveDirectoryW(req_dir); } @@ -344,7 +370,8 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, /* ── Local dirs ── */ case IDC_BTN_LOCAL_ADD: - if (g.cfg.local_dir_count >= MAX_LOCAL_DIRS) { + if (g.cfg.local_dir_count >= MAX_LOCAL_DIRS) + { /* Fixed-size array — cannot add more entries */ MessageBox(hwnd, L"Maximum number of local folders reached.", L"Sources", MB_ICONWARNING | MB_OK); @@ -355,13 +382,14 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, BROWSEINFO bi = { .hwndOwner = hwnd, .lpszTitle = L"Select Script Folder", - .ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS - }; + .ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS}; PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi); - if (pidl) { + if (pidl) + { SHGetPathFromIDList(pidl, path); CoTaskMemFree(pidl); - if (path[0]) { + if (path[0]) + { LocalDir d = {0}; wcsncpy_s(d.path, MAX_APPPATH, path, _TRUNCATE); d.enabled = true; @@ -375,7 +403,7 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, case IDC_BTN_LOCAL_TOGGLE: { HWND hList = GetDlgItem(hwnd, IDC_LST_LOCAL); - int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); + int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); if (sel < 0) break; /* nothing selected */ g.cfg.local_dirs[sel].enabled = !g.cfg.local_dirs[sel].enabled; Locals_Populate(hList); @@ -385,7 +413,7 @@ INT_PTR CALLBACK SourcesDlgProc(HWND hwnd, UINT msg, case IDC_BTN_LOCAL_REMOVE: { HWND hList = GetDlgItem(hwnd, IDC_LST_LOCAL); - int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); + int sel = ListView_GetNextItem(hList, -1, LVNI_SELECTED); if (sel < 0) break; /* nothing selected */ /* Remove by shifting entries after sel one position left */ for (int i = sel; i < g.cfg.local_dir_count - 1; i++) diff --git a/src/sync.c b/src/sync.c index 3e5b829..910f853 100644 --- a/src/sync.c +++ b/src/sync.c @@ -90,9 +90,11 @@ void Sync_SaveManifest(void) /* Delete old manifest so stale keys disappear */ DeleteFile(manifest); - for (int fi = 0; fi < g.folder_count; fi++) { + for (int fi = 0; fi < g.folder_count; fi++) + { ScriptFolder *f = &g.folders[fi]; - for (int si = 0; si < f->count; si++) { + for (int si = 0; si < f->count; si++) + { Script *s = &f->scripts[si]; WritePrivateProfileString(f->name, s->gh_path, s->sha, manifest); } @@ -116,90 +118,97 @@ void Sync_LoadManifest(void) immediately even before the sync completes or if there is no internet. */ if (!g.cfg.cache_dir[0]) return; /* no cache path configured — nothing to scan */ - for (int _fi = 0; _fi < g.folder_count; _fi++) Folder_Free(&g.folders[_fi]); + for (int _fi = 0; _fi < g.folder_count; _fi++) + Folder_Free(&g.folders[_fi]); g.folder_count = 0; - if (g.cfg.main_repo_enabled) { - WCHAR pattern[MAX_APPPATH]; - _snwprintf_s(pattern, MAX_APPPATH, _TRUNCATE, L"%s\\*", g.cfg.cache_dir); - - WIN32_FIND_DATAW fd; - HANDLE hFind = FindFirstFileW(pattern, &fd); - if (hFind == INVALID_HANDLE_VALUE) return; - - do { - if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; /* skip files — only process subdirs */ - if (wcscmp(fd.cFileName, L".") == 0) continue; /* skip current-dir entry */ - if (wcscmp(fd.cFileName, L"..") == 0) continue; /* skip parent-dir entry */ - if (wcscmp(fd.cFileName, L"setup") == 0) continue; /* setup/ holds requirements, not scripts */ - if (g.folder_count >= MAX_FOLDERS) break; /* hard cap — stop adding tabs */ - - ScriptFolder *f = &g.folders[g.folder_count++]; - ZeroMemory(f, sizeof(*f)); - wcsncpy_s(f->name, MAX_NAME, fd.cFileName, _TRUNCATE); - wcsncpy_s(f->display, MAX_NAME, f->name, _TRUNCATE); - Util_SnakeToTitle(f->display); - Folder_Alloc(f, 64); - - /* Scan .py files inside this folder */ - WCHAR sub[MAX_APPPATH]; - _snwprintf_s(sub, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\*.py", - g.cfg.cache_dir, f->name); - - WIN32_FIND_DATAW sf; - HANDLE hSub = FindFirstFileW(sub, &sf); - if (hSub == INVALID_HANDLE_VALUE) continue; - - do { - if (sf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; /* skip subdirs inside the script folder */ - - Script *s = Folder_Push(f); - if (!s) break; /* OOM — stop adding scripts for this folder */ - - /* gh_path: FolderName/filename.py */ - _snwprintf_s(s->gh_path, MAX_APPPATH, _TRUNCATE, L"%s/%s", f->name, sf.cFileName); - - /* local path */ - _snwprintf_s(s->local, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\%s", - g.cfg.cache_dir, f->name, sf.cFileName); - - /* display name: strip .py and format */ - wcsncpy_s(s->name, MAX_NAME, sf.cFileName, _TRUNCATE); - Util_StripExt(s->name); - Util_SnakeToTitle(s->name); - - s->source = SCRIPT_SRC_MAIN; /* loaded from built-in main repo cache */ - - /* SHA from manifest */ - Sync_GetLocalSHA(s->gh_path, s->sha); - - /* Verify local file actually matches the manifest SHA. - If not (e.g. previous failed download wrote wrong SHA to manifest), - clear the SHA so sync will re-download on next refresh. */ - if (s->sha[0] && GetFileAttributes(s->local) != INVALID_FILE_ATTRIBUTES) { - WCHAR computed[MAX_SHA] = {0}; - if (GitHub_ComputeFileSHA1(s->local, computed, MAX_SHA) && - wcscmp(computed, s->sha) != 0) { - /* SHA mismatch — clear so sync re-downloads */ - s->sha[0] = L'\0'; - /* Also clear manifest entry */ - WCHAR manifest[MAX_APPPATH]; - ManifestPath(manifest, MAX_APPPATH); - WritePrivateProfileString(f->name, s->gh_path, L"", manifest); + if (g.cfg.main_repo_enabled) + { + WCHAR pattern[MAX_APPPATH]; + _snwprintf_s(pattern, MAX_APPPATH, _TRUNCATE, L"%s\\*", g.cfg.cache_dir); + + WIN32_FIND_DATAW fd; + HANDLE hFind = FindFirstFileW(pattern, &fd); + if (hFind == INVALID_HANDLE_VALUE) return; + + do + { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; /* skip files — only process subdirs */ + if (wcscmp(fd.cFileName, L".") == 0) continue; /* skip current-dir entry */ + if (wcscmp(fd.cFileName, L"..") == 0) continue; /* skip parent-dir entry */ + if (wcscmp(fd.cFileName, L"setup") == 0) continue; /* setup/ holds requirements, not scripts */ + if (g.folder_count >= MAX_FOLDERS) break; /* hard cap — stop adding tabs */ + + ScriptFolder *f = &g.folders[g.folder_count++]; + ZeroMemory(f, sizeof(*f)); + wcsncpy_s(f->name, MAX_NAME, fd.cFileName, _TRUNCATE); + wcsncpy_s(f->display, MAX_NAME, f->name, _TRUNCATE); + Util_SnakeToTitle(f->display); + Folder_Alloc(f, 64); + + /* Scan .py files inside this folder */ + WCHAR sub[MAX_APPPATH]; + _snwprintf_s(sub, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\*.py", + g.cfg.cache_dir, f->name); + + WIN32_FIND_DATAW sf; + HANDLE hSub = FindFirstFileW(sub, &sf); + if (hSub == INVALID_HANDLE_VALUE) continue; + + do + { + if (sf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; /* skip subdirs inside the script folder */ + + Script *s = Folder_Push(f); + if (!s) break; /* OOM — stop adding scripts for this folder */ + + /* gh_path: FolderName/filename.py */ + _snwprintf_s(s->gh_path, MAX_APPPATH, _TRUNCATE, L"%s/%s", f->name, sf.cFileName); + + /* local path */ + _snwprintf_s(s->local, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\%s", + g.cfg.cache_dir, f->name, sf.cFileName); + + /* display name: strip .py and format */ + wcsncpy_s(s->name, MAX_NAME, sf.cFileName, _TRUNCATE); + Util_StripExt(s->name); + Util_SnakeToTitle(s->name); + + s->source = SCRIPT_SRC_MAIN; /* loaded from built-in main repo cache */ + + /* SHA from manifest */ + Sync_GetLocalSHA(s->gh_path, s->sha); + + /* Verify local file actually matches the manifest SHA. + If not (e.g. previous failed download wrote wrong SHA to manifest), + clear the SHA so sync will re-download on next refresh. */ + if (s->sha[0] && GetFileAttributes(s->local) != INVALID_FILE_ATTRIBUTES) + { + WCHAR computed[MAX_SHA] = {0}; + if (GitHub_ComputeFileSHA1(s->local, computed, MAX_SHA) && + wcscmp(computed, s->sha) != 0) + { + /* SHA mismatch — clear so sync re-downloads */ + s->sha[0] = L'\0'; + /* Also clear manifest entry */ + WCHAR manifest[MAX_APPPATH]; + ManifestPath(manifest, MAX_APPPATH); + WritePrivateProfileString(f->name, s->gh_path, L"", manifest); + } } - } - } while (FindNextFileW(hSub, &sf)); - FindClose(hSub); + } while (FindNextFileW(hSub, &sf)); + FindClose(hSub); - f->loaded = (f->count > 0); /* mark loaded only if at least one script was found on disk */ + f->loaded = (f->count > 0); /* mark loaded only if at least one script was found on disk */ - } while (FindNextFileW(hFind, &fd)); - FindClose(hFind); + } while (FindNextFileW(hFind, &fd)); + FindClose(hFind); } /* end if (main_repo_enabled) */ /* Also scan local dirs so they appear at startup without internet */ - for (int i = 0; i < g.cfg.local_dir_count; i++) { + for (int i = 0; i < g.cfg.local_dir_count; i++) + { if (g.cfg.local_dirs[i].enabled && g.cfg.local_dirs[i].path[0]) Sync_LocalDir(&g.cfg.local_dirs[i]); } @@ -221,7 +230,7 @@ static void DeleteLocalScript(const WCHAR *local_path) WCHAR dir[MAX_APPPATH]; wcsncpy_s(dir, MAX_APPPATH, local_path, _TRUNCATE); PathRemoveFileSpec(dir); - RemoveDirectory(dir); /* silently fails if not empty - that's fine */ + RemoveDirectory(dir); /* silently fails if not empty - that's fine */ } /* ================================================================== */ @@ -241,21 +250,28 @@ void Sync_MergeFolder(const WCHAR *folder_name, Script *scripts, int count) EnterCriticalSection(&g.cs_folders); /* Find existing folder with same name */ - for (int fi = 0; fi < g.folder_count; fi++) { - if (_wcsicmp(g.folders[fi].name, folder_name) == 0) { + for (int fi = 0; fi < g.folder_count; fi++) + { + if (_wcsicmp(g.folders[fi].name, folder_name) == 0) + { /* Merge: append scripts not already present */ - for (int si = 0; si < count; si++) { + for (int si = 0; si < count; si++) + { /* Only skip if exact same gh_path (same file from same repo). Scripts with the same display name from different sources are both added - the user can distinguish them by running them. */ bool found = false; - for (int ei = 0; ei < g.folders[fi].count; ei++) { + for (int ei = 0; ei < g.folders[fi].count; ei++) + { if (wcscmp(g.folders[fi].scripts[ei].gh_path, - scripts[si].gh_path) == 0) { - found = true; break; + scripts[si].gh_path) == 0) + { + found = true; + break; } } - if (!found) { + if (!found) + { Script *dst = Folder_Push(&g.folders[fi]); if (dst) *dst = scripts[si]; } @@ -268,11 +284,16 @@ void Sync_MergeFolder(const WCHAR *folder_name, Script *scripts, int count) { ScriptFolder *f = &g.folders[g.folder_count++]; ZeroMemory(f, sizeof(*f)); - wcsncpy_s(f->name, MAX_NAME, folder_name, _TRUNCATE); - wcsncpy_s(f->display, MAX_NAME, f->name, _TRUNCATE); + wcsncpy_s(f->name, MAX_NAME, folder_name, _TRUNCATE); + wcsncpy_s(f->display, MAX_NAME, f->name, _TRUNCATE); Util_SnakeToTitle(f->display); - if (!Folder_Alloc(f, count > 0 ? count : 64)) { g.folder_count--; goto done; } - for (int i = 0; i < count; i++) { + if (!Folder_Alloc(f, count > 0 ? count : 64)) + { + g.folder_count--; + goto done; + } + for (int i = 0; i < count; i++) + { Script *dst = Folder_Push(f); if (dst) *dst = scripts[i]; } @@ -294,13 +315,14 @@ void Sync_MergeFolder(const WCHAR *folder_name, Script *scripts, int count) static void Sync_ExtraRepo(const ExtraRepo *repo, char *buf) { WCHAR owner[MAX_NAME] = {0}, reponame[MAX_NAME] = {0}; - if (!GitHub_ParseOwnerRepo(repo->url, owner, reponame)) { + if (!GitHub_ParseOwnerRepo(repo->url, owner, reponame)) + { PostStatus(L"Sources: invalid URL %s", repo->url); return; } const WCHAR *tok = repo->token[0] ? repo->token - : (g.cfg.github_token[0] ? g.cfg.github_token : NULL); + : (g.cfg.github_token[0] ? g.cfg.github_token : NULL); const WCHAR *branch = repo->branch[0] ? repo->branch : L"main"; PostStatus(L"Syncing %s/%s...", owner, reponame); @@ -308,10 +330,11 @@ static void Sync_ExtraRepo(const ExtraRepo *repo, char *buf) /* Get root contents */ WCHAR api_path[MAX_APPPATH]; _snwprintf_s(api_path, MAX_APPPATH, _TRUNCATE, L"/repos/%s/%s/contents/", - owner, reponame); + owner, reponame); DWORD len = 0; - if (!GitHub_HttpGet(GITHUB_API_HOST, api_path, tok, buf, &len)) { + if (!GitHub_HttpGet(GITHUB_API_HOST, api_path, tok, buf, &len)) + { PostStatus(L"Sources: failed to reach %s/%s", owner, reponame); return; @@ -323,38 +346,62 @@ static void Sync_ExtraRepo(const ExtraRepo *repo, char *buf) if (!fbuf) return; const char *p = buf; - while ((p = strstr(p, "\"type\"")) != NULL) { + while ((p = strstr(p, "\"type\"")) != NULL) + { char type_val[32] = {0}; char name_a[MAX_NAME] = {0}; const char *obj = p; - while (obj > buf && *obj != '{') obj--; + while (obj > buf && *obj != '{') + obj--; /* Get type */ const char *tp = strstr(obj, "\"type\""); - if (!tp) { p++; continue; } + if (!tp) + { + p++; + continue; + } tp += 6; - while (*tp == ' ' || *tp == ':') tp++; - if (*tp != '"') { p++; continue; } + while (*tp == ' ' || *tp == ':') + tp++; + if (*tp != '"') + { + p++; + continue; + } tp++; int ti = 0; - while (*tp && *tp != '"' && ti < 31) type_val[ti++] = *tp++; + while (*tp && *tp != '"' && ti < 31) + type_val[ti++] = *tp++; type_val[ti] = '\0'; - if (strcmp(type_val, "dir") != 0) { p = tp; continue; } + if (strcmp(type_val, "dir") != 0) + { + p = tp; + continue; + } /* Get name */ const char *np = strstr(obj, "\"name\""); - if (np) { + if (np) + { np += 6; - while (*np == ' ' || *np == ':') np++; - if (*np == '"') { + while (*np == ' ' || *np == ':') + np++; + if (*np == '"') + { np++; int ni = 0; - while (*np && *np != '"' && ni < MAX_NAME-1) name_a[ni++] = *np++; + while (*np && *np != '"' && ni < MAX_NAME - 1) + name_a[ni++] = *np++; name_a[ni] = '\0'; } } - if (!name_a[0] || strcmp(name_a, "setup") == 0 || name_a[0] == '.') { p = tp; continue; } + if (!name_a[0] || strcmp(name_a, "setup") == 0 || name_a[0] == '.') + { + p = tp; + continue; + } /* Get folder contents into fbuf so buf (root listing) stays intact */ WCHAR folder_w[MAX_NAME] = {0}; @@ -365,53 +412,118 @@ static void Sync_ExtraRepo(const ExtraRepo *repo, char *buf) DWORD flen = 0; if (!GitHub_HttpGet(GITHUB_API_HOST, folder_api, tok, fbuf, &flen)) - { p = tp; continue; } + { + p = tp; + continue; + } /* Parse scripts */ Script *scripts = (Script *)calloc(MAX_SCRIPTS, sizeof(Script)); - if (!scripts) { p = tp; continue; } - int script_count = 0; - WCHAR cache_sub[MAX_APPPATH]; + if (!scripts) + { + p = tp; + continue; + } + int script_count = 0; + WCHAR cache_sub[MAX_APPPATH]; _snwprintf_s(cache_sub, MAX_APPPATH, _TRUNCATE, L"%s\\%s_%s\\%s", - g.cfg.cache_dir, owner, reponame, folder_w); + g.cfg.cache_dir, owner, reponame, folder_w); SHCreateDirectoryEx(NULL, cache_sub, NULL); const char *fp = fbuf; - while ((fp = strstr(fp, "\"type\"")) != NULL && script_count < MAX_SCRIPTS) { + while ((fp = strstr(fp, "\"type\"")) != NULL && script_count < MAX_SCRIPTS) + { const char *fo = fp; - while (fo > fbuf && *fo != '{') fo--; + while (fo > fbuf && *fo != '{') + fo--; - char ftype[16]={0}, fname[MAX_NAME]={0}, - fpath[MAX_APPPATH]={0}, fsha[MAX_SHA]={0}; + char ftype[16] = {0}, fname[MAX_NAME] = {0}, + fpath[MAX_APPPATH] = {0}, fsha[MAX_SHA] = {0}; /* type */ const char *ftp = strstr(fo, "\"type\""); - if (ftp) { ftp+=6; while(*ftp==' '||*ftp==':') ftp++; - if(*ftp=='"'){ftp++; int i=0; while(*ftp&&*ftp!='"'&&i<15) ftype[i++]=*ftp++; ftype[i]=0;}} - if (strcmp(ftype,"file")!=0) { fp=ftp?ftp+1:fp+1; continue; } + if (ftp) + { + ftp += 6; + while (*ftp == ' ' || *ftp == ':') + ftp++; + if (*ftp == '"') + { + ftp++; + int i = 0; + while (*ftp && *ftp != '"' && i < 15) + ftype[i++] = *ftp++; + ftype[i] = 0; + } + } + if (strcmp(ftype, "file") != 0) + { + fp = ftp ? ftp + 1 : fp + 1; + continue; + } /* name */ const char *fnp = strstr(fo, "\"name\""); - if (fnp) { fnp+=6; while(*fnp==' '||*fnp==':') fnp++; - if(*fnp=='"'){fnp++; int i=0; while(*fnp&&*fnp!='"'&&iname, MAX_NAME); + MultiByteToWideChar(CP_UTF8, 0, fname, -1, s->name, MAX_NAME); MultiByteToWideChar(CP_UTF8, 0, fpath, -1, s->gh_path, MAX_APPPATH); - MultiByteToWideChar(CP_UTF8, 0, fsha, -1, s->sha, MAX_SHA); + MultiByteToWideChar(CP_UTF8, 0, fsha, -1, s->sha, MAX_SHA); /* Build local path from original filename (includes .py), then strip .py from display name */ WCHAR fname_w[MAX_NAME] = {0}; @@ -424,15 +536,16 @@ static void Sync_ExtraRepo(const ExtraRepo *repo, char *buf) /* Download if changed */ WCHAR existing_sha[MAX_SHA] = {0}; Sync_GetLocalSHA(s->gh_path, existing_sha); - if (wcscmp(existing_sha, s->sha) != 0 || - GetFileAttributes(s->local) == INVALID_FILE_ATTRIBUTES) { + if (wcscmp(existing_sha, s->sha) != 0 || + GetFileAttributes(s->local) == INVALID_FILE_ATTRIBUTES) + { WCHAR raw_path[MAX_APPPATH]; _snwprintf_s(raw_path, MAX_APPPATH, _TRUNCATE, L"/%s/%s/%s/%s", owner, reponame, branch, s->gh_path); GitHub_DownloadRawFull(GITHUB_RAW_HOST, raw_path, s->local, tok); } - fp = ftp ? ftp+1 : fp+1; + fp = ftp ? ftp + 1 : fp + 1; } if (script_count > 0) @@ -463,37 +576,43 @@ static void Sync_LocalDir(const LocalDir *dir) HANDLE h = FindFirstFileW(pattern, &fd); if (h == INVALID_HANDLE_VALUE) return; - do { + do + { if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; - if (wcscmp(fd.cFileName, L".") == 0) continue; - if (wcscmp(fd.cFileName, L"..") == 0) continue; + if (wcscmp(fd.cFileName, L".") == 0) continue; + if (wcscmp(fd.cFileName, L"..") == 0) continue; if (_wcsicmp(fd.cFileName, L"setup") == 0) continue; /* skip setup */ /* Scan .py files in subfolder */ WCHAR sub[MAX_APPPATH]; _snwprintf_s(sub, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\*.py", - dir->path, fd.cFileName); + dir->path, fd.cFileName); Script *scripts = (Script *)calloc(MAX_SCRIPTS, sizeof(Script)); if (!scripts) continue; - int count = 0; + int count = 0; WIN32_FIND_DATAW sf; HANDLE hs = FindFirstFileW(sub, &sf); - if (hs == INVALID_HANDLE_VALUE) { free(scripts); continue; } - do { + if (hs == INVALID_HANDLE_VALUE) + { + free(scripts); + continue; + } + do + { if (sf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; if (count >= MAX_SCRIPTS) break; Script *s = &scripts[count++]; ZeroMemory(s, sizeof(*s)); /* Local scripts have no gh_path or sha */ _snwprintf_s(s->local, MAX_APPPATH, _TRUNCATE, L"%s\\%s\\%s", - dir->path, fd.cFileName, sf.cFileName); - wcsncpy_s(s->name, MAX_NAME, sf.cFileName, _TRUNCATE); + dir->path, fd.cFileName, sf.cFileName); + wcsncpy_s(s->name, MAX_NAME, sf.cFileName, _TRUNCATE); Util_StripExt(s->name); Util_SnakeToTitle(s->name); /* Use local path as gh_path for uniqueness */ - wcsncpy_s(s->gh_path, MAX_APPPATH, s->local, _TRUNCATE); /* local scripts have no GitHub path; use local path as a unique key for prefs */ + wcsncpy_s(s->gh_path, MAX_APPPATH, s->local, _TRUNCATE); /* local scripts have no GitHub path; use local path as a unique key for prefs */ s->source = SCRIPT_SRC_LOCAL; } while (FindNextFileW(hs, &sf)); FindClose(hs); @@ -508,7 +627,8 @@ static void Sync_LocalDir(const LocalDir *dir) /* Check for a setup/requirements.txt in this local dir */ WCHAR req[MAX_APPPATH]; _snwprintf_s(req, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\requirements.txt", dir->path); - if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributes(req) != INVALID_FILE_ATTRIBUTES) + { /* Store path so Runner_UpdateDeps can find it. We reuse the cache_dir setup folder concept - just copy the path into the setup subfolder of cache so runner finds it automatically. */ @@ -538,247 +658,290 @@ DWORD WINAPI Sync_Thread(LPVOID unused) (void)unused; const WCHAR *token = g.cfg.github_token[0] - ? g.cfg.github_token : NULL; + ? g.cfg.github_token + : NULL; SyncResult *sr = (SyncResult *)calloc(1, sizeof(SyncResult)); - if (!sr) { + if (!sr) + { /* OOM: post NULL so the main window clears the syncing flag even without a result */ PostMessage(g.hwnd, WM_SYNC_DONE, (WPARAM)NULL, 0); return 1; } char *buf = (char *)malloc(HTTP_BUF_SIZE); - if (!buf) { + if (!buf) + { sr->status = SR_API_ERROR; wcsncpy_s(sr->message, 256, L"Out of memory.", _TRUNCATE); PostMessage(g.hwnd, WM_SYNC_DONE, (WPARAM)sr, 0); return 1; } - if (g.cfg.main_repo_enabled) { - /* ── Step 1: Fetch root to get current folder list ───────────── */ - PostStatus(L"Connecting to GitHub\u2026"); + if (g.cfg.main_repo_enabled) + { + /* ── Step 1: Fetch root to get current folder list ───────────── */ + PostStatus(L"Connecting to GitHub\u2026"); + + WCHAR api_root[MAX_APPPATH]; + _snwprintf_s(api_root, MAX_APPPATH, _TRUNCATE, L"/repos/%s/%s/contents/", + GITHUB_OWNER, GITHUB_REPO); + + DWORD len = 0; + bool main_repo_ok = true; + if (!GitHub_HttpGet(GITHUB_API_HOST, api_root, token, buf, &len)) + { + main_repo_ok = false; + sr->status = SR_NO_INTERNET; + if (!g.cfg.offline_use_cache) + { + /* User opted out of offline mode \u2014 clear folders so the UI shows nothing */ + EnterCriticalSection(&g.cs_folders); + for (int _fi = 0; _fi < g.folder_count; _fi++) + Folder_Free(&g.folders[_fi]); + g.folder_count = 0; + LeaveCriticalSection(&g.cs_folders); + wcsncpy_s(sr->message, 256, + L"\u26a0 No internet connection.", _TRUNCATE); + } + else if (g.folder_count > 0) + { + /* Offline but cache exists \u2014 keep showing cached scripts with a warning */ + _snwprintf_s(sr->message, 255, _TRUNCATE, + L"\u26a0 Offline \u2013 showing %d cached folder(s). Scripts may be out of date.", + g.folder_count); + } + else + { + wcsncpy_s(sr->message, 256, + L"\u26a0 No internet connection. No cached scripts found.", _TRUNCATE); + } + /* Do NOT return here \u2014 fall through to sync extra repos and local dirs, + which may be reachable even when the main repo is not. */ + } - WCHAR api_root[MAX_APPPATH]; - _snwprintf_s(api_root, MAX_APPPATH, _TRUNCATE, L"/repos/%s/%s/contents/", - GITHUB_OWNER, GITHUB_REPO); + if (main_repo_ok) + { - DWORD len = 0; - bool main_repo_ok = true; - if (!GitHub_HttpGet(GITHUB_API_HOST, api_root, token, buf, &len)) { - main_repo_ok = false; - sr->status = SR_NO_INTERNET; - if (!g.cfg.offline_use_cache) { - /* User opted out of offline mode \u2014 clear folders so the UI shows nothing */ + /* Connected ─ clear folders so disabled sources don't linger before rebuild. + Hold cs_folders to guard against a concurrent UI-thread paint reading freed pointers. */ EnterCriticalSection(&g.cs_folders); - for (int _fi = 0; _fi < g.folder_count; _fi++) Folder_Free(&g.folders[_fi]); + for (int _fi = 0; _fi < g.folder_count; _fi++) + Folder_Free(&g.folders[_fi]); g.folder_count = 0; LeaveCriticalSection(&g.cs_folders); - wcsncpy_s(sr->message, 256, - L"\u26a0 No internet connection.", _TRUNCATE); - } else if (g.folder_count > 0) { - /* Offline but cache exists \u2014 keep showing cached scripts with a warning */ - _snwprintf_s(sr->message, 255, _TRUNCATE, - L"\u26a0 Offline \u2013 showing %d cached folder(s). Scripts may be out of date.", - g.folder_count); - } else { - wcsncpy_s(sr->message, 256, - L"\u26a0 No internet connection. No cached scripts found.", _TRUNCATE); - } - /* Do NOT return here \u2014 fall through to sync extra repos and local dirs, - which may be reachable even when the main repo is not. */ - } - if (main_repo_ok) { - - /* Connected ─ clear folders so disabled sources don't linger before rebuild. - Hold cs_folders to guard against a concurrent UI-thread paint reading freed pointers. */ - EnterCriticalSection(&g.cs_folders); - for (int _fi = 0; _fi < g.folder_count; _fi++) Folder_Free(&g.folders[_fi]); - g.folder_count = 0; - LeaveCriticalSection(&g.cs_folders); + /* ── Step 2: Parse root and detect folder changes ───────────── */ - /* ── Step 2: Parse root and detect folder changes ───────────── */ + /* Save the old folder names to detect removals */ + WCHAR old_names[MAX_FOLDERS][MAX_NAME]; + int old_count = g.folder_count; + for (int i = 0; i < old_count; i++) + wcsncpy_s(old_names[i], MAX_NAME, g.folders[i].name, _TRUNCATE); - /* Save the old folder names to detect removals */ - WCHAR old_names[MAX_FOLDERS][MAX_NAME]; - int old_count = g.folder_count; - for (int i = 0; i < old_count; i++) - wcsncpy_s(old_names[i], MAX_NAME, g.folders[i].name, _TRUNCATE); + /* Parse new folder list from API */ + GitHub_ParseRoot(buf); - /* Parse new folder list from API */ - GitHub_ParseRoot(buf); - - /* Count additions */ - for (int ni = 0; ni < g.folder_count; ni++) { - bool found = false; - for (int oi = 0; oi < old_count; oi++) { - if (wcscmp(g.folders[ni].name, old_names[oi]) == 0) { - found = true; break; + /* Count additions */ + for (int ni = 0; ni < g.folder_count; ni++) + { + bool found = false; + for (int oi = 0; oi < old_count; oi++) + { + if (wcscmp(g.folders[ni].name, old_names[oi]) == 0) + { + found = true; + break; + } + } + if (!found) sr->folders_added++; } - } - if (!found) sr->folders_added++; - } - /* Count removals and delete their cached files */ - for (int oi = 0; oi < old_count; oi++) { - bool found = false; - for (int ni = 0; ni < g.folder_count; ni++) { - if (wcscmp(old_names[oi], g.folders[ni].name) == 0) { - found = true; break; + /* Count removals and delete their cached files */ + for (int oi = 0; oi < old_count; oi++) + { + bool found = false; + for (int ni = 0; ni < g.folder_count; ni++) + { + if (wcscmp(old_names[oi], g.folders[ni].name) == 0) + { + found = true; + break; + } + } + if (!found) + { + sr->folders_removed++; + /* Delete the folder's cache directory */ + WCHAR cache_dir[MAX_APPPATH]; + _snwprintf_s(cache_dir, MAX_APPPATH, _TRUNCATE, L"%s\\%s", + g.cfg.cache_dir, old_names[oi]); + /* Simple recursive delete via SHFileOperation */ + WCHAR del_from[MAX_APPPATH + 2]; + _snwprintf_s(del_from, MAX_APPPATH + 1, _TRUNCATE, L"%s\\", cache_dir); + del_from[wcslen(del_from) + 1] = L'\0'; /* SHFileOperation requires double-null terminator after the path */ + SHFILEOPSTRUCT fo = { + .wFunc = FO_DELETE, + .pFrom = del_from, + .fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT}; + SHFileOperation(&fo); + } } - } - if (!found) { - sr->folders_removed++; - /* Delete the folder's cache directory */ - WCHAR cache_dir[MAX_APPPATH]; - _snwprintf_s(cache_dir, MAX_APPPATH, _TRUNCATE, L"%s\\%s", - g.cfg.cache_dir, old_names[oi]); - /* Simple recursive delete via SHFileOperation */ - WCHAR del_from[MAX_APPPATH + 2]; - _snwprintf_s(del_from, MAX_APPPATH + 1, _TRUNCATE, L"%s\\", cache_dir); - del_from[wcslen(del_from) + 1] = L'\0'; /* SHFileOperation requires double-null terminator after the path */ - SHFILEOPSTRUCT fo = { - .wFunc = FO_DELETE, - .pFrom = del_from, - .fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT - }; - SHFileOperation(&fo); - } - } - /* ── Step 3: For each folder, sync scripts ───────────────────── */ - for (int fi = 0; fi < g.folder_count; fi++) { - ScriptFolder *f = &g.folders[fi]; + /* ── Step 3: For each folder, sync scripts ───────────────────── */ + for (int fi = 0; fi < g.folder_count; fi++) + { + ScriptFolder *f = &g.folders[fi]; - WCHAR status_msg[128]; - _snwprintf_s(status_msg, 127, _TRUNCATE, L"Checking %s\u2026", f->display); - PostStatus(L"%s", status_msg); + WCHAR status_msg[128]; + _snwprintf_s(status_msg, 127, _TRUNCATE, L"Checking %s\u2026", f->display); + PostStatus(L"%s", status_msg); - /* Fetch folder contents from API */ - WCHAR api_folder[MAX_APPPATH]; - _snwprintf_s(api_folder, MAX_APPPATH, _TRUNCATE, L"/repos/%s/%s/contents/%s", - GITHUB_OWNER, GITHUB_REPO, f->name); + /* Fetch folder contents from API */ + WCHAR api_folder[MAX_APPPATH]; + _snwprintf_s(api_folder, MAX_APPPATH, _TRUNCATE, L"/repos/%s/%s/contents/%s", + GITHUB_OWNER, GITHUB_REPO, f->name); - len = 0; - if (!GitHub_HttpGet(GITHUB_API_HOST, api_folder, - token, buf, &len)) { - sr->status = SR_PARTIAL; - continue; - } - - /* Save old script list for removal detection */ - Script *old_scripts = (Script *)calloc(MAX_SCRIPTS, sizeof(Script)); - if (!old_scripts) continue; - int old_scount = f->count; - if (old_scount > 0) - memcpy_s(old_scripts, MAX_SCRIPTS * sizeof(Script), - f->scripts, old_scount * sizeof(Script)); - - /* Parse new script list (fills f->scripts[], f->count) */ - GitHub_ParseFolder(buf, fi); + len = 0; + if (!GitHub_HttpGet(GITHUB_API_HOST, api_folder, + token, buf, &len)) + { + sr->status = SR_PARTIAL; + continue; + } - /* Tag all scripts in this folder as main-repo origin */ - for (int si = 0; si < f->count; si++) - f->scripts[si].source = SCRIPT_SRC_MAIN; - - /* Ensure cache folder exists */ - WCHAR folder_cache[MAX_APPPATH]; - _snwprintf_s(folder_cache, MAX_APPPATH, _TRUNCATE, L"%s\\%s", - g.cfg.cache_dir, f->name); - SHCreateDirectoryEx(NULL, folder_cache, NULL); - - /* Detect newly added scripts */ - for (int si = 0; si < f->count; si++) { - bool existed = false; - for (int oi = 0; oi < old_scount; oi++) { - if (wcscmp(f->scripts[si].gh_path, - old_scripts[oi].gh_path) == 0) { - existed = true; break; + /* Save old script list for removal detection */ + Script *old_scripts = (Script *)calloc(MAX_SCRIPTS, sizeof(Script)); + if (!old_scripts) continue; + int old_scount = f->count; + if (old_scount > 0) + memcpy_s(old_scripts, MAX_SCRIPTS * sizeof(Script), + f->scripts, old_scount * sizeof(Script)); + + /* Parse new script list (fills f->scripts[], f->count) */ + GitHub_ParseFolder(buf, fi); + + /* Tag all scripts in this folder as main-repo origin */ + for (int si = 0; si < f->count; si++) + f->scripts[si].source = SCRIPT_SRC_MAIN; + + /* Ensure cache folder exists */ + WCHAR folder_cache[MAX_APPPATH]; + _snwprintf_s(folder_cache, MAX_APPPATH, _TRUNCATE, L"%s\\%s", + g.cfg.cache_dir, f->name); + SHCreateDirectoryEx(NULL, folder_cache, NULL); + + /* Detect newly added scripts */ + for (int si = 0; si < f->count; si++) + { + bool existed = false; + for (int oi = 0; oi < old_scount; oi++) + { + if (wcscmp(f->scripts[si].gh_path, + old_scripts[oi].gh_path) == 0) + { + existed = true; + break; + } + } + if (!existed) sr->scripts_added++; } - } - if (!existed) sr->scripts_added++; - } - /* Detect removed scripts and delete from cache */ - for (int oi = 0; oi < old_scount; oi++) { - bool still_present = false; - for (int si = 0; si < f->count; si++) { - if (wcscmp(old_scripts[oi].gh_path, - f->scripts[si].gh_path) == 0) { - still_present = true; break; + /* Detect removed scripts and delete from cache */ + for (int oi = 0; oi < old_scount; oi++) + { + bool still_present = false; + for (int si = 0; si < f->count; si++) + { + if (wcscmp(old_scripts[oi].gh_path, + f->scripts[si].gh_path) == 0) + { + still_present = true; + break; + } + } + if (!still_present) + { + sr->scripts_removed++; + DeleteLocalScript(old_scripts[oi].local); + } + } + free(old_scripts); + + /* ── Step 4: Download changed or missing scripts ─────────── */ + for (int si = 0; si < f->count; si++) + { + Script *s = &f->scripts[si]; + + /* Check stored SHA vs GitHub SHA */ + WCHAR stored_sha[MAX_SHA] = {0}; + bool have_local = Sync_GetLocalSHA(s->gh_path, stored_sha); + + bool sha_changed = (wcscmp(stored_sha, s->sha) != 0); + bool file_missing = (GetFileAttributes(s->local) == INVALID_FILE_ATTRIBUTES); + + /* Download when: no manifest entry, SHA changed on GitHub, or cached file is gone */ + if (!have_local || sha_changed || file_missing) + { + WCHAR dl_msg[128]; + const WCHAR *fname = wcsrchr(s->gh_path, L'/'); + _snwprintf_s(dl_msg, 127, _TRUNCATE, L"Downloading %s\u2026", + fname ? fname + 1 : s->gh_path); + PostStatus(L"%s", dl_msg); + + if (GitHub_DownloadRaw(s->gh_path, s->local, token)) + { + sr->scripts_updated++; + } + else + { + /* Download failed — revert the in-memory SHA to the old manifest value. + Saving the old SHA prevents a false tamper warning on the next run; + the script will simply retry on the next sync cycle. */ + wcsncpy_s(s->sha, MAX_SHA, stored_sha, _TRUNCATE); + sr->status = SR_PARTIAL; + } + } } } - if (!still_present) { - sr->scripts_removed++; - DeleteLocalScript(old_scripts[oi].local); - } - } - free(old_scripts); - /* ── Step 4: Download changed or missing scripts ─────────── */ - for (int si = 0; si < f->count; si++) { - Script *s = &f->scripts[si]; - - /* Check stored SHA vs GitHub SHA */ - WCHAR stored_sha[MAX_SHA] = {0}; - bool have_local = Sync_GetLocalSHA(s->gh_path, stored_sha); - - bool sha_changed = (wcscmp(stored_sha, s->sha) != 0); - bool file_missing = (GetFileAttributes(s->local) - == INVALID_FILE_ATTRIBUTES); - - /* Download when: no manifest entry, SHA changed on GitHub, or cached file is gone */ - if (!have_local || sha_changed || file_missing) { - WCHAR dl_msg[128]; - const WCHAR *fname = wcsrchr(s->gh_path, L'/'); - _snwprintf_s(dl_msg, 127, _TRUNCATE, L"Downloading %s\u2026", - fname ? fname + 1 : s->gh_path); - PostStatus(L"%s", dl_msg); - - if (GitHub_DownloadRaw(s->gh_path, s->local, token)) { - sr->scripts_updated++; - } else { - /* Download failed — revert the in-memory SHA to the old manifest value. - Saving the old SHA prevents a false tamper warning on the next run; - the script will simply retry on the next sync cycle. */ - wcsncpy_s(s->sha, MAX_SHA, stored_sha, _TRUNCATE); - sr->status = SR_PARTIAL; + /* Step 5: Download setup files */ + { + WCHAR setup_dir[MAX_APPPATH]; + _snwprintf_s(setup_dir, MAX_APPPATH, _TRUNCATE, L"%s" + L"\\" + L"setup", + g.cfg.cache_dir); + setup_dir[MAX_APPPATH - 1] = L'\0'; + SHCreateDirectoryEx(NULL, setup_dir, NULL); + + const WCHAR *gh_files[] = { + L"setup/requirements.txt", + NULL}; + const WCHAR *local_names[] = { + L"requirements.txt", + NULL}; + for (int i = 0; gh_files[i]; i++) + { + WCHAR lpath[MAX_APPPATH]; + _snwprintf_s(lpath, MAX_APPPATH, _TRUNCATE, L"%s" + L"\\" + L"%s", + setup_dir, local_names[i]); + lpath[MAX_APPPATH - 1] = L'\0'; + GitHub_DownloadRaw(gh_files[i], lpath, token); } } - } - } - /* Step 5: Download setup files */ - { - WCHAR setup_dir[MAX_APPPATH]; - _snwprintf_s(setup_dir, MAX_APPPATH, _TRUNCATE, L"%s" L"\\" L"setup", g.cfg.cache_dir); - setup_dir[MAX_APPPATH - 1] = L'\0'; - SHCreateDirectoryEx(NULL, setup_dir, NULL); - - const WCHAR *gh_files[] = { - L"setup/requirements.txt", - NULL - }; - const WCHAR *local_names[] = { - L"requirements.txt", - NULL - }; - for (int i = 0; gh_files[i]; i++) { - WCHAR lpath[MAX_APPPATH]; - _snwprintf_s(lpath, MAX_APPPATH, _TRUNCATE, L"%s" L"\\" L"%s", setup_dir, local_names[i]); - lpath[MAX_APPPATH - 1] = L'\0'; - GitHub_DownloadRaw(gh_files[i], lpath, token); - } + } /* end if (main_repo_ok) */ } - - } /* end if (main_repo_ok) */ - - } else { + else + { /* Main repo disabled — clear now so extra repos and local dirs rebuild from scratch rather than merging onto stale manifest data. */ EnterCriticalSection(&g.cs_folders); - for (int _fi = 0; _fi < g.folder_count; _fi++) Folder_Free(&g.folders[_fi]); + for (int _fi = 0; _fi < g.folder_count; _fi++) + Folder_Free(&g.folders[_fi]); g.folder_count = 0; LeaveCriticalSection(&g.cs_folders); } /* end if/else (main_repo_enabled) */ @@ -786,28 +949,32 @@ DWORD WINAPI Sync_Thread(LPVOID unused) /* ── Step 6: Sync extra GitHub repos ───────────────────────── */ { char *xbuf = (char *)malloc(HTTP_BUF_SIZE); - if (xbuf) { - for (int i = 0; i < g.cfg.extra_repo_count; i++) { + if (xbuf) + { + for (int i = 0; i < g.cfg.extra_repo_count; i++) + { ExtraRepo *xr = &g.cfg.extra_repos[i]; if (!xr->enabled || !xr->url[0]) continue; /* skip disabled or empty extra repo entries */ Sync_ExtraRepo(xr, xbuf); /* Cache requirements.txt for this repo in unique subfolder */ - WCHAR owner[MAX_NAME]={0}, reponame[MAX_NAME]={0}; - if (GitHub_ParseOwnerRepo(xr->url, owner, reponame)) { + WCHAR owner[MAX_NAME] = {0}, reponame[MAX_NAME] = {0}; + if (GitHub_ParseOwnerRepo(xr->url, owner, reponame)) + { WCHAR sub[MAX_APPPATH]; _snwprintf_s(sub, MAX_APPPATH, _TRUNCATE, L"%s\\setup\\%s_%s", - g.cfg.cache_dir, owner, reponame); + g.cfg.cache_dir, owner, reponame); SHCreateDirectoryEx(NULL, sub, NULL); WCHAR req[MAX_APPPATH]; _snwprintf_s(req, MAX_APPPATH, _TRUNCATE, L"%s\\requirements.txt", sub); - if (GetFileAttributes(req) == INVALID_FILE_ATTRIBUTES) { /* only download if not already cached */ + if (GetFileAttributes(req) == INVALID_FILE_ATTRIBUTES) + { /* only download if not already cached */ const WCHAR *tok = xr->token[0] ? xr->token - : (g.cfg.github_token[0] ? g.cfg.github_token : NULL); + : (g.cfg.github_token[0] ? g.cfg.github_token : NULL); const WCHAR *branch = xr->branch[0] ? xr->branch : L"main"; WCHAR raw_path[MAX_APPPATH]; _snwprintf_s(raw_path, MAX_APPPATH, _TRUNCATE, L"/%s/%s/%s/setup/requirements.txt", - owner, reponame, branch); + owner, reponame, branch); GitHub_DownloadRawFull(GITHUB_RAW_HOST, raw_path, req, tok); } } @@ -817,7 +984,8 @@ DWORD WINAPI Sync_Thread(LPVOID unused) } /* ── Step 7: Scan local dirs ─────────────────────────────────── */ - for (int i = 0; i < g.cfg.local_dir_count; i++) { + for (int i = 0; i < g.cfg.local_dir_count; i++) + { if (g.cfg.local_dirs[i].enabled && g.cfg.local_dirs[i].path[0]) Sync_LocalDir(&g.cfg.local_dirs[i]); } @@ -825,27 +993,31 @@ DWORD WINAPI Sync_Thread(LPVOID unused) /* ── Step 8: Persist updated manifest ───────────────────────── */ Sync_SaveManifest(); - /* ── Step 9: Build human-readable result message ────────────── */ - if (sr->status == SR_OK) { - if (sr->scripts_updated == 0 && sr->folders_added == 0 - && sr->folders_removed == 0 && sr->scripts_added == 0 - && sr->scripts_removed == 0) { + if (sr->status == SR_OK) + { + if (sr->scripts_updated == 0 && sr->folders_added == 0 && sr->folders_removed == 0 && sr->scripts_added == 0 && sr->scripts_removed == 0) + { /* Nothing changed — brief "up to date" message */ wcsncpy_s(sr->message, 256, L"All scripts are up to date.", _TRUNCATE); - } else { + } + else + { /* Something changed — show a change summary */ _snwprintf_s(sr->message, 255, _TRUNCATE, L"Sync complete. " - L"+%d/-%d folders, " - L"+%d/-%d scripts, " - L"%d updated.", - sr->folders_added, sr->folders_removed, - sr->scripts_added, sr->scripts_removed, - sr->scripts_updated); + L"+%d/-%d folders, " + L"+%d/-%d scripts, " + L"%d updated.", + sr->folders_added, sr->folders_removed, + sr->scripts_added, sr->scripts_removed, + sr->scripts_updated); } - } else if (sr->status == SR_PARTIAL) { /* at least one folder or file failed to download */ + } + else if (sr->status == SR_PARTIAL) + { /* at least one folder or file failed to download */ _snwprintf_s(sr->message, 255, _TRUNCATE, L"Sync partial \u2013 some downloads failed. " - L"%d updated.", sr->scripts_updated); + L"%d updated.", + sr->scripts_updated); } free(buf); diff --git a/src/tabs.c b/src/tabs.c index 0c8812d..b4bbbdc 100644 --- a/src/tabs.c +++ b/src/tabs.c @@ -10,7 +10,7 @@ /* live button handles for the current tab */ static HWND s_btns[MAX_SCRIPTS] = {0}; -static int s_btn_count = 0; +static int s_btn_count = 0; /* ================================================================== */ /* Tabs_ApplyFilter */ @@ -39,17 +39,20 @@ bool Tabs_ScriptMatchesFilter(const Script *s) { if (g.filter_text[0] == L'\0') return true; /* empty filter — show everything */ - WCHAR name_lower[MAX_NAME] = {0}; - WCHAR purp_lower[128] = {0}; - WCHAR filt_lower[MAX_NAME] = {0}; + WCHAR name_lower[MAX_NAME] = {0}; + WCHAR purp_lower[128] = {0}; + WCHAR filt_lower[MAX_NAME] = {0}; - wcsncpy_s(name_lower, MAX_NAME, s->name, _TRUNCATE); - wcsncpy_s(purp_lower, 128, s->meta.purpose, _TRUNCATE); - wcsncpy_s(filt_lower, MAX_NAME, g.filter_text, _TRUNCATE); + wcsncpy_s(name_lower, MAX_NAME, s->name, _TRUNCATE); + wcsncpy_s(purp_lower, 128, s->meta.purpose, _TRUNCATE); + wcsncpy_s(filt_lower, MAX_NAME, g.filter_text, _TRUNCATE); - for (WCHAR *p = name_lower; *p; p++) *p = towlower(*p); - for (WCHAR *p = purp_lower; *p; p++) *p = towlower(*p); - for (WCHAR *p = filt_lower; *p; p++) *p = towlower(*p); + for (WCHAR *p = name_lower; *p; p++) + *p = towlower(*p); + for (WCHAR *p = purp_lower; *p; p++) + *p = towlower(*p); + for (WCHAR *p = filt_lower; *p; p++) + *p = towlower(*p); return wcsstr(name_lower, filt_lower) != NULL || wcsstr(purp_lower, filt_lower) != NULL; @@ -61,15 +64,19 @@ bool Tabs_ScriptMatchesFilter(const Script *s) /* In: a, b — pointers to Script elements being compared */ /* Out: negative / zero / positive per qsort convention */ /* ================================================================== */ -static int cmp_alpha(const void *a, const void *b) { - return _wcsicmp(((Script*)a)->name, ((Script*)b)->name); +static int cmp_alpha(const void *a, const void *b) +{ + return _wcsicmp(((Script *)a)->name, ((Script *)b)->name); } -static int cmp_date(const void *a, const void *b) { - return wcscmp(((Script*)b)->meta.date, ((Script*)a)->meta.date); /* b before a = newest first */ +static int cmp_date(const void *a, const void *b) +{ + return wcscmp(((Script *)b)->meta.date, ((Script *)a)->meta.date); /* b before a = newest first */ } -static int cmp_runs(const void *a, const void *b) { - int br = ((Script*)b)->run_count, ar = ((Script*)a)->run_count; - return (br > ar) ? 1 : (br < ar) ? -1 : 0; /* descending: highest count first */ +static int cmp_runs(const void *a, const void *b) +{ + int br = ((Script *)b)->run_count, ar = ((Script *)a)->run_count; + return (br > ar) ? 1 : (br < ar) ? -1 + : 0; /* descending: highest count first */ } /* ================================================================== */ @@ -101,11 +108,19 @@ void Tabs_ApplySort(int fi) { if (fi < 0 || fi >= g.folder_count) return; ScriptFolder *f = &g.folders[fi]; - switch (g.cfg.sort_mode) { - case SORT_ALPHA: qsort(f->scripts, f->count, sizeof(Script), cmp_alpha); break; - case SORT_DATE: qsort(f->scripts, f->count, sizeof(Script), cmp_date); break; - case SORT_MOST_USED: qsort(f->scripts, f->count, sizeof(Script), cmp_runs); break; - default: break; /* SORT_ORDER = preserve the order received from GitHub/disk */ + switch (g.cfg.sort_mode) + { + case SORT_ALPHA: + qsort(f->scripts, f->count, sizeof(Script), cmp_alpha); + break; + case SORT_DATE: + qsort(f->scripts, f->count, sizeof(Script), cmp_date); + break; + case SORT_MOST_USED: + qsort(f->scripts, f->count, sizeof(Script), cmp_runs); + break; + default: + break; /* SORT_ORDER = preserve the order received from GitHub/disk */ } } @@ -132,15 +147,17 @@ void Tabs_Build(void) /* ================================================================== */ void Tabs_DestroyButtons(void) { - for (int i = 0; i < s_btn_count; i++) { + for (int i = 0; i < s_btn_count; i++) + { if (IsWindow(s_btns[i])) DestroyWindow(s_btns[i]); s_btns[i] = NULL; } - s_btn_count = 0; - g.scroll_y = 0; - g.scroll_max = 0; + s_btn_count = 0; + g.scroll_y = 0; + g.scroll_max = 0; /* Hide the tooltip and reset its state so it re-appears correctly after rebuild */ - if (g.tip_btn != -1) { + if (g.tip_btn != -1) + { g.tip_btn = -1; ShowWindow(g.hwnd_tip, SW_HIDE); } @@ -186,7 +203,7 @@ void Tabs_RebuildButtons(void) RECT rc; GetClientRect(g.hwnd_scroll, &rc); int panel_w = rc.right; - int btn_w = panel_w - BTN_MX * 2; + int btn_w = panel_w - BTN_MX * 2; if (btn_w < 80) btn_w = 80; /* 80 px = minimum usable button width */ HINSTANCE hInst = @@ -194,14 +211,16 @@ void Tabs_RebuildButtons(void) int y = BTN_MY; - if (g.syncing && f->count == 0) { + if (g.syncing && f->count == 0) + { /* Placeholder while syncing */ s_btn_count = 0; goto update_scroll; } - for (int i = 0; i < f->count; i++) { - if (f->scripts[i].is_hidden) continue; /* skip hidden */ + for (int i = 0; i < f->count; i++) + { + if (f->scripts[i].is_hidden) continue; /* skip hidden */ if (!Tabs_ScriptMatchesFilter(&f->scripts[i])) continue; /* skip filtered */ int id = IDC_SCRIPT_BTN_BASE + i; @@ -222,17 +241,16 @@ void Tabs_RebuildButtons(void) update_scroll:; int total_h = y + BTN_MY; /* include bottom margin in total content height */ GetClientRect(g.hwnd_scroll, &rc); - int visible = rc.bottom - rc.top; - g.scroll_max = (total_h > visible) ? (total_h - visible) : 0; /* 0 when all buttons fit without scrolling */ + int visible = rc.bottom - rc.top; + g.scroll_max = (total_h > visible) ? (total_h - visible) : 0; /* 0 when all buttons fit without scrolling */ SCROLLINFO si = { .cbSize = sizeof(si), - .fMask = SIF_RANGE | SIF_PAGE | SIF_POS, - .nMin = 0, - .nMax = total_h, - .nPage = (UINT)visible, - .nPos = 0 - }; + .fMask = SIF_RANGE | SIF_PAGE | SIF_POS, + .nMin = 0, + .nMax = total_h, + .nPage = (UINT)visible, + .nPos = 0}; SetScrollInfo(g.hwnd_scroll, SB_VERT, &si, TRUE); done: @@ -242,9 +260,12 @@ update_scroll:; /* Auto-switch: if the current tab is empty and no filter is active, move to the first tab that has visible scripts so the UI is never blank. */ - if (!g.filter_text[0] && !Tabs_FolderHasVisible(g.active_tab)) { - for (int _fi = 0; _fi < g.folder_count; _fi++) { - if (Tabs_FolderHasVisible(_fi)) { + if (!g.filter_text[0] && !Tabs_FolderHasVisible(g.active_tab)) + { + for (int _fi = 0; _fi < g.folder_count; _fi++) + { + if (Tabs_FolderHasVisible(_fi)) + { g.active_tab = _fi; wcsncpy_s(g.active_folder_name, MAX_NAME, g.folders[_fi].name, _TRUNCATE); InvalidateRect(g.hwnd_tab, NULL, FALSE); @@ -269,7 +290,7 @@ update_scroll:; /* Out: LRESULT — TRUE for WM_DRAWITEM; 0 for handled; DefWindowProc */ /* ================================================================== */ LRESULT CALLBACK ScrollPanelProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { switch (msg) { @@ -282,17 +303,17 @@ LRESULT CALLBACK ScrollPanelProc(HWND hwnd, UINT msg, /* Track when mouse leaves the scroll panel so all highlights clear */ { TRACKMOUSEEVENT tme = { - .cbSize = sizeof(tme), - .dwFlags = TME_LEAVE, - .hwndTrack = hwnd - }; + .cbSize = sizeof(tme), + .dwFlags = TME_LEAVE, + .hwndTrack = hwnd}; TrackMouseEvent(&tme); } break; case WM_MOUSELEAVE: /* Mouse left the scroll panel entirely - clear any stuck highlight */ - if (g.hot_btn >= IDC_SCRIPT_BTN_BASE) { + if (g.hot_btn >= IDC_SCRIPT_BTN_BASE) + { HWND hPrev = GetDlgItem(hwnd, g.hot_btn); g.hot_btn = -1; if (hPrev) InvalidateRect(hPrev, NULL, FALSE); @@ -306,11 +327,15 @@ LRESULT CALLBACK ScrollPanelProc(HWND hwnd, UINT msg, { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); - RECT rc; GetClientRect(hwnd, &rc); - HBRUSH _bg = CreateSolidBrush(COL_BG()); FillRect(hdc, &rc, _bg); DeleteObject(_bg); + RECT rc; + GetClientRect(hwnd, &rc); + HBRUSH _bg = CreateSolidBrush(COL_BG()); + FillRect(hdc, &rc, _bg); + DeleteObject(_bg); /* "Syncing…" placeholder text — only when no buttons are present */ - if (g.syncing && !GetWindow(hwnd, GW_CHILD)) { + if (g.syncing && !GetWindow(hwnd, GW_CHILD)) + { SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, COL_SUBTEXT()); HFONT of = SelectObject(hdc, g.font_ui); @@ -328,26 +353,27 @@ LRESULT CALLBACK ScrollPanelProc(HWND hwnd, UINT msg, DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lp; if (dis->CtlType != ODT_BUTTON) break; /* only handle button owner-draw messages */ - int fi = g.active_tab; + int fi = g.active_tab; int idx = (int)GetWindowLongPtr(dis->hwndItem, GWLP_USERDATA); /* script index stored in WM_CREATE */ /* Copy the script under cs_folders to prevent a concurrent Folder_Push realloc (in the sync thread) from freeing the pointer while we read it. */ - Script s_copy = {0}; - const Script *s = NULL; + Script s_copy = {0}; + const Script *s = NULL; EnterCriticalSection(&g.cs_folders); if (fi >= 0 && fi < g.folder_count && - idx >= 0 && idx < g.folders[fi].count) { + idx >= 0 && idx < g.folders[fi].count) + { s_copy = g.folders[fi].scripts[idx]; s = &s_copy; } LeaveCriticalSection(&g.cs_folders); - bool hot = (g.hot_btn == (int)(UINT_PTR)dis->CtlID); /* mouse is hovering over this button */ - bool pressed = (dis->itemState & ODS_SELECTED) != 0; /* ODS_SELECTED = button is being clicked */ - bool info_hot = (g.tip_btn == (int)(UINT_PTR)dis->CtlID); /* info (ℹ) badge is hovered */ - bool repeat = g.repeat_mode && g.repeat_fi == fi && g.repeat_si == idx; /* this button is in amber repeat mode */ - bool running = g.script_running && !g.repeat_mode && g.run_fi == fi && g.run_si == idx; /* green "running" state; repeat takes priority */ + bool hot = (g.hot_btn == (int)(UINT_PTR)dis->CtlID); /* mouse is hovering over this button */ + bool pressed = (dis->itemState & ODS_SELECTED) != 0; /* ODS_SELECTED = button is being clicked */ + bool info_hot = (g.tip_btn == (int)(UINT_PTR)dis->CtlID); /* info (ℹ) badge is hovered */ + bool repeat = g.repeat_mode && g.repeat_fi == fi && g.repeat_si == idx; /* this button is in amber repeat mode */ + bool running = g.script_running && !g.repeat_mode && g.run_fi == fi && g.run_si == idx; /* green "running" state; repeat takes priority */ Paint_ScriptButton(dis->hwndItem, dis->hDC, hot, pressed, info_hot, repeat, running, s); return TRUE; } @@ -355,28 +381,44 @@ LRESULT CALLBACK ScrollPanelProc(HWND hwnd, UINT msg, /* Scroll bar */ case WM_VSCROLL: { - SCROLLINFO si = { .cbSize = sizeof(si), .fMask = SIF_ALL }; + SCROLLINFO si = {.cbSize = sizeof(si), .fMask = SIF_ALL}; GetScrollInfo(hwnd, SB_VERT, &si); int pos = si.nPos; - switch (LOWORD(wp)) { - case SB_LINEUP: pos -= SCROLL_STEP; break; /* arrow up: one SCROLL_STEP */ - case SB_LINEDOWN: pos += SCROLL_STEP; break; /* arrow down */ - case SB_PAGEUP: pos -= (int)si.nPage; break; /* click track above thumb */ - case SB_PAGEDOWN: pos += (int)si.nPage; break; /* click track below thumb */ + switch (LOWORD(wp)) + { + case SB_LINEUP: + pos -= SCROLL_STEP; + break; /* arrow up: one SCROLL_STEP */ + case SB_LINEDOWN: + pos += SCROLL_STEP; + break; /* arrow down */ + case SB_PAGEUP: + pos -= (int)si.nPage; + break; /* click track above thumb */ + case SB_PAGEDOWN: + pos += (int)si.nPage; + break; /* click track below thumb */ case SB_THUMBTRACK: - case SB_THUMBPOSITION: pos = HIWORD(wp); break; /* dragging or releasing thumb */ - case SB_TOP: pos = si.nMin; break; /* Home key */ - case SB_BOTTOM: pos = si.nMax; break; /* End key */ + case SB_THUMBPOSITION: + pos = HIWORD(wp); + break; /* dragging or releasing thumb */ + case SB_TOP: + pos = si.nMin; + break; /* Home key */ + case SB_BOTTOM: + pos = si.nMax; + break; /* End key */ } int max_pos = si.nMax - (int)si.nPage + 1; /* last position at which the thumb rests at the bottom */ if (pos < si.nMin) pos = si.nMin; if (pos > max_pos) pos = max_pos; - if (pos != si.nPos) { + if (pos != si.nPos) + { int delta = si.nPos - pos; - si.nPos = pos; + si.nPos = pos; SetScrollInfo(hwnd, SB_VERT, &si, TRUE); ScrollWindowEx(hwnd, 0, delta, NULL, NULL, NULL, NULL, SW_SCROLLCHILDREN | SW_INVALIDATE); diff --git a/src/updater.c b/src/updater.c index 3953e6f..f168b97 100644 --- a/src/updater.c +++ b/src/updater.c @@ -25,10 +25,14 @@ static void ParseVersion(const WCHAR *s, int v[4]) { v[0] = v[1] = v[2] = v[3] = 0; int part = 0; - for (const WCHAR *p = s; *p && part < 4; p++) { - if (*p >= L'0' && *p <= L'9') { + for (const WCHAR *p = s; *p && part < 4; p++) + { + if (*p >= L'0' && *p <= L'9') + { v[part] = v[part] * 10 + (*p - L'0'); /* accumulate digit into current component */ - } else if (*p == L'.') { + } + else if (*p == L'.') + { part++; /* move to next version component (major → minor → patch → build) */ } } @@ -56,8 +60,9 @@ static bool IsNewer(const WCHAR *remote_tag) /* Only compare major.minor.patch (first 3 parts). Build number (4th part) is ignored - a local build with a higher build number than the latest release should not prompt for update. */ - for (int i = 0; i < 3; i++) { - if (rv[i] > lv[i]) return true; /* remote is newer at this position — done */ + for (int i = 0; i < 3; i++) + { + if (rv[i] > lv[i]) return true; /* remote is newer at this position — done */ if (rv[i] < lv[i]) return false; /* local is newer at this position — done */ /* equal at this position — check the next component */ } @@ -78,18 +83,19 @@ static bool ParseLatestTag(const char *json, WCHAR *tag_out, int max) const char *p = strstr(json, "\"tag_name\""); if (!p) return false; /* key absent — not a releases response */ p += strlen("\"tag_name\""); - while (*p == ' ' || *p == ':' || *p == '\t') p++; /* skip whitespace and the colon separator */ + while (*p == ' ' || *p == ':' || *p == '\t') + p++; /* skip whitespace and the colon separator */ if (*p != '"') return false; /* value is not a JSON string */ p++; /* step past the opening quote */ int i = 0; char tmp[64] = {0}; - while (*p && *p != '"' && i < 62) tmp[i++] = *p++; /* i < 62 leaves room for null terminator in 64-byte buffer */ + while (*p && *p != '"' && i < 62) + tmp[i++] = *p++; /* i < 62 leaves room for null terminator in 64-byte buffer */ tmp[i] = '\0'; MultiByteToWideChar(CP_UTF8, 0, tmp, -1, tag_out, max); /* convert UTF-8 tag to wide string */ return (tag_out[0] != L'\0'); /* false if conversion produced an empty string */ } - /* ================================================================== */ /* Updater_CheckThread */ /* Purpose: Background thread that queries the GitHub releases API, */ @@ -117,27 +123,41 @@ DWORD WINAPI Updater_CheckThread(LPVOID unused) DWORD len = 0; bool ok = GitHub_HttpGet(GITHUB_API_HOST, api_path, - g.cfg.github_token[0] ? g.cfg.github_token : NULL, - buf, &len); + g.cfg.github_token[0] ? g.cfg.github_token : NULL, + buf, &len); - if (!ok || len == 0) { free(buf); return 1; } /* HTTP failure or empty response */ + if (!ok || len == 0) + { + free(buf); + return 1; + } /* HTTP failure or empty response */ WCHAR tag[32] = {0}; - if (!ParseLatestTag(buf, tag, 32)) { free(buf); return 1; } /* response did not contain a tag_name */ + if (!ParseLatestTag(buf, tag, 32)) + { + free(buf); + return 1; + } /* response did not contain a tag_name */ - if (IsNewer(tag)) { + if (IsNewer(tag)) + { /* Store display version without the leading 'v' prefix */ const WCHAR *display_tag = tag; if (display_tag[0] == L'v' || display_tag[0] == L'V') display_tag++; wcsncpy_s(g.latest_version, 32, display_tag, _TRUNCATE); - if (g.cfg.auto_update) { + if (g.cfg.auto_update) + { PostMessage(g.hwnd, WM_UPDATE_AVAIL, 1, 0); /* wParam=1 = auto-update mode; triggers silent download */ - } else { + } + else + { PostMessage(g.hwnd, WM_UPDATE_AVAIL, 0, 0); /* wParam=0 = prompt mode; shows badge + message box */ } - } else if (manual) { + } + else if (manual) + { /* Only report "up to date" for a manual check — auto-check is silent when current */ PostStatus(L"App is up to date (v%s).", VERSION_STRING_W); } @@ -151,7 +171,10 @@ DWORD WINAPI Updater_CheckThread(LPVOID unused) /* Thread parameter passed from Updater_AutoUpdate to */ /* Updater_DownloadThread. Heap-allocated; the thread frees it. */ /* ================================================================== */ -typedef struct { WCHAR tag[32]; } UpdateDlParam; +typedef struct +{ + WCHAR tag[32]; +} UpdateDlParam; /* ================================================================== */ /* Updater_DownloadThread (static) */ @@ -182,17 +205,22 @@ static DWORD WINAPI Updater_DownloadThread(LPVOID lp) /* Download */ WCHAR raw_path[512] = {0}; _snwprintf_s(raw_path, 511, _TRUNCATE, - L"/KaiUR/CatiaMenuWin32/releases/download/v%s/CatiaMenuWin32.exe", - latest_tag); + L"/KaiUR/CatiaMenuWin32/releases/download/v%s/CatiaMenuWin32.exe", + latest_tag); char *buf = (char *)malloc(8 * 1024 * 1024); /* 8 MB buffer sized for the release exe */ - if (!buf) { Updater_PromptAndInstall(latest_tag); return 1; } + if (!buf) + { + Updater_PromptAndInstall(latest_tag); + return 1; + } DWORD len = 8 * 1024 * 1024; bool ok = GitHub_HttpGet(L"github.com", raw_path, g.cfg.github_token[0] ? g.cfg.github_token : NULL, buf, &len); - if (!ok || len == 0) { + if (!ok || len == 0) + { free(buf); MessageBox(g.hwnd, L"Download failed. Opening releases page instead.", L"Update", MB_ICONWARNING | MB_OK); @@ -203,7 +231,8 @@ static DWORD WINAPI Updater_DownloadThread(LPVOID lp) /* Write downloaded exe to temp file */ HANDLE hf = CreateFileW(temp_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (hf == INVALID_HANDLE_VALUE) { + if (hf == INVALID_HANDLE_VALUE) + { free(buf); Updater_PromptAndInstall(latest_tag); return 1; @@ -213,7 +242,8 @@ static DWORD WINAPI Updater_DownloadThread(LPVOID lp) CloseHandle(hf); free(buf); - if (written != len || GetFileAttributes(temp_path) == INVALID_FILE_ATTRIBUTES) { + if (written != len || GetFileAttributes(temp_path) == INVALID_FILE_ATTRIBUTES) + { DeleteFile(temp_path); MessageBox(g.hwnd, L"Download incomplete. Opening releases page instead.", L"Update", MB_ICONWARNING | MB_OK); @@ -227,8 +257,8 @@ static DWORD WINAPI Updater_DownloadThread(LPVOID lp) cmd.exe reads .bat files as ANSI; non-Latin user profiles can have Unicode characters in %TEMP% or the exe path that ANSI cannot represent. */ WCHAR s_exe[MAX_APPPATH] = {0}, s_tmp[MAX_APPPATH] = {0}; - if (!GetShortPathNameW(exe_path, s_exe, MAX_APPPATH) || !s_exe[0]) - wcsncpy_s(s_exe, MAX_APPPATH, exe_path, _TRUNCATE); + if (!GetShortPathNameW(exe_path, s_exe, MAX_APPPATH) || !s_exe[0]) + wcsncpy_s(s_exe, MAX_APPPATH, exe_path, _TRUNCATE); if (!GetShortPathNameW(temp_path, s_tmp, MAX_APPPATH) || !s_tmp[0]) wcsncpy_s(s_tmp, MAX_APPPATH, temp_path, _TRUNCATE); @@ -241,16 +271,17 @@ static DWORD WINAPI Updater_DownloadThread(LPVOID lp) _snwprintf_s(bat_path, MAX_APPPATH - 1, _TRUNCATE, L"%sCatiaMenuWin32_update.bat", short_tmp_dir); - char bat_a[MAX_APPPATH] = {0}; + char bat_a[MAX_APPPATH] = {0}; char s_exe_a[MAX_APPPATH] = {0}; char s_tmp_a[MAX_APPPATH] = {0}; - WideCharToMultiByte(CP_ACP, 0, bat_path, -1, bat_a, MAX_APPPATH, NULL, NULL); - WideCharToMultiByte(CP_ACP, 0, s_exe, -1, s_exe_a, MAX_APPPATH, NULL, NULL); - WideCharToMultiByte(CP_ACP, 0, s_tmp, -1, s_tmp_a, MAX_APPPATH, NULL, NULL); + WideCharToMultiByte(CP_ACP, 0, bat_path, -1, bat_a, MAX_APPPATH, NULL, NULL); + WideCharToMultiByte(CP_ACP, 0, s_exe, -1, s_exe_a, MAX_APPPATH, NULL, NULL); + WideCharToMultiByte(CP_ACP, 0, s_tmp, -1, s_tmp_a, MAX_APPPATH, NULL, NULL); /* Write a plain ANSI batch file — cmd.exe reads ANSI, not UTF-16 */ FILE *f = NULL; - if (fopen_s(&f, bat_a, "w") != 0 || !f) { + if (fopen_s(&f, bat_a, "w") != 0 || !f) + { Updater_PromptAndInstall(latest_tag); return 1; } @@ -287,25 +318,33 @@ static DWORD WINAPI Updater_DownloadThread(LPVOID lp) void Updater_AutoUpdate(const WCHAR *latest_tag) { int res = MessageBox(g.hwnd, - L"A new version is available. Download and install automatically?\n\n" - L"The application will restart after the update.", - L"Auto Update", MB_ICONINFORMATION | MB_YESNO | MB_DEFBUTTON1); + L"A new version is available. Download and install automatically?\n\n" + L"The application will restart after the update.", + L"Auto Update", MB_ICONINFORMATION | MB_YESNO | MB_DEFBUTTON1); - if (res != IDYES) { + if (res != IDYES) + { Updater_PromptAndInstall(latest_tag); return; } /* Spawn download thread so the UI remains responsive during the download */ UpdateDlParam *p = (UpdateDlParam *)calloc(1, sizeof(UpdateDlParam)); - if (!p) { Updater_PromptAndInstall(latest_tag); return; } + if (!p) + { + Updater_PromptAndInstall(latest_tag); + return; + } wcsncpy_s(p->tag, 32, latest_tag, _TRUNCATE); HANDLE hT = CreateThread(NULL, 0, Updater_DownloadThread, p, 0, NULL); - if (hT) { + if (hT) + { CloseHandle(hT); PostStatus(L"Downloading update v%s…", latest_tag); - } else { + } + else + { free(p); Updater_PromptAndInstall(latest_tag); } @@ -323,19 +362,20 @@ void Updater_PromptAndInstall(const WCHAR *latest_tag) { WCHAR msg[512]; _snwprintf_s(msg, 511, _TRUNCATE, L"A new version of CatiaMenuWin32 is available!\n\n" - L" Current version: v%s\n" - L" Latest version: v%s\n\n" - L"Would you like to open the releases page to download the update?", - VERSION_STRING_W, latest_tag); + L" Current version: v%s\n" + L" Latest version: v%s\n\n" + L"Would you like to open the releases page to download the update?", + VERSION_STRING_W, latest_tag); int res = MessageBox(g.hwnd, msg, L"Update Available", MB_ICONINFORMATION | MB_YESNO | MB_DEFBUTTON1); - if (res == IDYES) { + if (res == IDYES) + { /* User confirmed — open GitHub releases in the default browser */ WCHAR url[256]; _snwprintf_s(url, 255, _TRUNCATE, L"https://github.com/%s/CatiaMenuWin32/releases/latest", - GITHUB_OWNER); + GITHUB_OWNER); ShellExecute(NULL, L"open", url, NULL, NULL, SW_SHOW); } } diff --git a/src/window.c b/src/window.c index 8b4bda7..1804bac 100644 --- a/src/window.c +++ b/src/window.c @@ -50,7 +50,7 @@ void Window_OpenExeFolder(void) void Window_ApplyThemeToChildren(HWND hwnd) { SetWindowTheme(g.hwnd_status, L"", L""); - InvalidateRect(g.hwnd_tab, NULL, TRUE); + InvalidateRect(g.hwnd_tab, NULL, TRUE); InvalidateRect(g.hwnd_status, NULL, TRUE); RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); Log_OnThemeChange(); /* update log window title bar and RichEdit colours */ @@ -64,7 +64,10 @@ void Window_ApplyThemeToChildren(HWND hwnd) /* In: hwnd — (unused) */ /* Out: (void) */ /* ================================================================== */ -void Window_ApplyDarkMenu(HWND hwnd) { (void)hwnd; } +void Window_ApplyDarkMenu(HWND hwnd) +{ + (void)hwnd; +} /* ================================================================== */ /* Window_ApplyAlwaysOnTop */ @@ -91,114 +94,115 @@ void Window_ApplyAlwaysOnTop(void) /* ================================================================== */ void Window_ShowMenu(void) { - HMENU hm = CreatePopupMenu(); - HMENU hFile = CreatePopupMenu(); - HMENU hRun = CreatePopupMenu(); - HMENU hView = CreatePopupMenu(); + HMENU hm = CreatePopupMenu(); + HMENU hFile = CreatePopupMenu(); + HMENU hRun = CreatePopupMenu(); + HMENU hView = CreatePopupMenu(); HMENU hTheme = CreatePopupMenu(); - HMENU hWin = CreatePopupMenu(); - HMENU hHelp = CreatePopupMenu(); + HMENU hWin = CreatePopupMenu(); + HMENU hHelp = CreatePopupMenu(); - AppendMenu(hFile, MF_STRING, IDM_REFRESH, L"Refresh + Sync\tF5"); + AppendMenu(hFile, MF_STRING, IDM_REFRESH, L"Refresh + Sync\tF5"); AppendMenu(hFile, MF_SEPARATOR, 0, NULL); - AppendMenu(hFile, MF_STRING, IDM_SETTINGS, L"Settings..."); - AppendMenu(hFile, MF_STRING, IDM_SOURCES, L"Sources..."); - AppendMenu(hFile, MF_STRING, IDM_HIDDEN_SCRIPTS, L"Manage Hidden Scripts..."); + AppendMenu(hFile, MF_STRING, IDM_SETTINGS, L"Settings..."); + AppendMenu(hFile, MF_STRING, IDM_SOURCES, L"Sources..."); + AppendMenu(hFile, MF_STRING, IDM_HIDDEN_SCRIPTS, L"Manage Hidden Scripts..."); AppendMenu(hFile, MF_SEPARATOR, 0, NULL); - AppendMenu(hFile, MF_STRING, IDM_OPEN_EXE_FOLDER, L"Open Executable Folder"); + AppendMenu(hFile, MF_STRING, IDM_OPEN_EXE_FOLDER, L"Open Executable Folder"); AppendMenu(hFile, MF_SEPARATOR, 0, NULL); - AppendMenu(hFile, MF_STRING, IDM_EXIT, L"Exit"); + AppendMenu(hFile, MF_STRING, IDM_EXIT, L"Exit"); - AppendMenu(hRun, MF_STRING, IDM_RUN_LAST, L"Run Last Script\tF9"); + AppendMenu(hRun, MF_STRING, IDM_RUN_LAST, L"Run Last Script\tF9"); AppendMenu(hRun, MF_SEPARATOR, 0, NULL); - AppendMenu(hRun, MF_STRING, IDM_OPEN_CACHE, L"Open Cache Folder..."); - AppendMenu(hRun, MF_STRING, IDM_UPDATE_DEPS, L"Update Dependencies"); + AppendMenu(hRun, MF_STRING, IDM_OPEN_CACHE, L"Open Cache Folder..."); + AppendMenu(hRun, MF_STRING, IDM_UPDATE_DEPS, L"Update Dependencies"); AppendMenu(hRun, MF_SEPARATOR, 0, NULL); - AppendMenu(hRun, MF_STRING, IDM_REPEAT_MAINAPP, L"Repeat Script on Double-Click"); + AppendMenu(hRun, MF_STRING, IDM_REPEAT_MAINAPP, L"Repeat Script on Double-Click"); - AppendMenu(hTheme, MF_STRING, IDM_THEME_DARK, L"Dark"); - AppendMenu(hTheme, MF_STRING, IDM_THEME_LIGHT, L"Light"); + AppendMenu(hTheme, MF_STRING, IDM_THEME_DARK, L"Dark"); + AppendMenu(hTheme, MF_STRING, IDM_THEME_LIGHT, L"Light"); AppendMenu(hTheme, MF_STRING, IDM_THEME_SYSTEM, L"System (default)"); - AppendMenu(hView, MF_STRING, IDM_ALWAYS_ON_TOP, L"Always on Top"); + AppendMenu(hView, MF_STRING, IDM_ALWAYS_ON_TOP, L"Always on Top"); AppendMenu(hView, MF_SEPARATOR, 0, NULL); AppendMenu(hView, MF_POPUP, (UINT_PTR)hTheme, L"Theme"); AppendMenu(hView, MF_SEPARATOR, 0, NULL); HMENU hSort = CreatePopupMenu(); - AppendMenu(hSort, MF_STRING, IDM_SORT_DEFAULT, L"Default Order"); - AppendMenu(hSort, MF_STRING, IDM_SORT_ALPHA, L"Alphabetical"); - AppendMenu(hSort, MF_STRING, IDM_SORT_DATE, L"By Date"); + AppendMenu(hSort, MF_STRING, IDM_SORT_DEFAULT, L"Default Order"); + AppendMenu(hSort, MF_STRING, IDM_SORT_ALPHA, L"Alphabetical"); + AppendMenu(hSort, MF_STRING, IDM_SORT_DATE, L"By Date"); AppendMenu(hSort, MF_STRING, IDM_SORT_MOST_USED, L"Most Used"); - AppendMenu(hView, MF_POPUP, (UINT_PTR)hSort, L"Sort Scripts"); + AppendMenu(hView, MF_POPUP, (UINT_PTR)hSort, L"Sort Scripts"); AppendMenu(hView, MF_SEPARATOR, 0, NULL); bool qbar_has_target = g.cfg.qbar_target_app[0] != L'\0'; HMENU hQBar = CreatePopupMenu(); - AppendMenu(hQBar, MF_STRING, IDM_QBAR_TOGGLE, L"Enable Quick Bar"); + AppendMenu(hQBar, MF_STRING, IDM_QBAR_TOGGLE, L"Enable Quick Bar"); AppendMenu(hQBar, MF_SEPARATOR, 0, NULL); - AppendMenu(hQBar, MF_STRING, IDM_QBAR_HORIZONTAL, L"Horizontal"); - AppendMenu(hQBar, MF_STRING, IDM_QBAR_VERTICAL, L"Vertical"); + AppendMenu(hQBar, MF_STRING, IDM_QBAR_HORIZONTAL, L"Horizontal"); + AppendMenu(hQBar, MF_STRING, IDM_QBAR_VERTICAL, L"Vertical"); AppendMenu(hQBar, MF_SEPARATOR, 0, NULL); AppendMenu(hQBar, qbar_has_target ? MF_STRING : (MF_STRING | MF_GRAYED), IDM_QBAR_TOPMOST, L"On Top with Target App"); - AppendMenu(hQBar, MF_STRING, IDM_QBAR_SET_TARGET, L"Set Target App..."); + AppendMenu(hQBar, MF_STRING, IDM_QBAR_SET_TARGET, L"Set Target App..."); AppendMenu(hQBar, MF_SEPARATOR, 0, NULL); - AppendMenu(hQBar, MF_STRING, IDM_QBAR_RESET_POS, L"Reset Position"); + AppendMenu(hQBar, MF_STRING, IDM_QBAR_RESET_POS, L"Reset Position"); AppendMenu(hQBar, MF_SEPARATOR, 0, NULL); - AppendMenu(hQBar, MF_STRING, IDM_REPEAT_QBAR, L"Repeat on Double-Click"); - AppendMenu(hView, MF_POPUP, (UINT_PTR)hQBar, L"Quick Bar"); + AppendMenu(hQBar, MF_STRING, IDM_REPEAT_QBAR, L"Repeat on Double-Click"); + AppendMenu(hView, MF_POPUP, (UINT_PTR)hQBar, L"Quick Bar"); - AppendMenu(hWin, MF_STRING, IDM_MINIMIZE_TO_TRAY, L"Minimize to Tray"); + AppendMenu(hWin, MF_STRING, IDM_MINIMIZE_TO_TRAY, L"Minimize to Tray"); AppendMenu(hWin, MF_STRING, IDM_START_WITH_WINDOWS, L"Start with Windows"); - AppendMenu(hWin, MF_STRING, IDM_START_MINIMIZED, L"Start Minimized"); + AppendMenu(hWin, MF_STRING, IDM_START_MINIMIZED, L"Start Minimized"); - AppendMenu(hHelp, MF_STRING, IDM_HELP_CONTENTS, L"Help Contents\tF1"); - AppendMenu(hHelp, MF_STRING, IDM_CHECK_UPDATES, L"Check for Updates..."); + AppendMenu(hHelp, MF_STRING, IDM_HELP_CONTENTS, L"Help Contents\tF1"); + AppendMenu(hHelp, MF_STRING, IDM_CHECK_UPDATES, L"Check for Updates..."); AppendMenu(hHelp, MF_SEPARATOR, 0, NULL); - AppendMenu(hHelp, MF_STRING, IDM_REPORT_BUG, L"Report a Bug..."); + AppendMenu(hHelp, MF_STRING, IDM_REPORT_BUG, L"Report a Bug..."); AppendMenu(hHelp, MF_SEPARATOR, 0, NULL); - AppendMenu(hHelp, MF_STRING, IDM_ABOUT, L"About"); - AppendMenu(hHelp, MF_STRING, IDM_GITHUB, L"View App on GitHub"); - AppendMenu(hHelp, MF_STRING, IDM_GITHUB_PAGES, L"GitHub Pages"); - AppendMenu(hHelp, MF_STRING, IDM_WIKI, L"App Wiki"); + AppendMenu(hHelp, MF_STRING, IDM_ABOUT, L"About"); + AppendMenu(hHelp, MF_STRING, IDM_GITHUB, L"View App on GitHub"); + AppendMenu(hHelp, MF_STRING, IDM_GITHUB_PAGES, L"GitHub Pages"); + AppendMenu(hHelp, MF_STRING, IDM_WIKI, L"App Wiki"); AppendMenu(hHelp, MF_STRING, IDM_GITHUB_SCRIPTS, L"View Scripts on GitHub"); - AppendMenu(hHelp, MF_STRING, IDM_WIKI_SCRIPTS, L"Scripts Wiki"); + AppendMenu(hHelp, MF_STRING, IDM_WIKI_SCRIPTS, L"Scripts Wiki"); AppendMenu(hm, MF_POPUP, (UINT_PTR)hFile, L"File"); - AppendMenu(hm, MF_POPUP, (UINT_PTR)hRun, L"Run"); + AppendMenu(hm, MF_POPUP, (UINT_PTR)hRun, L"Run"); AppendMenu(hm, MF_POPUP, (UINT_PTR)hView, L"View"); - AppendMenu(hm, MF_POPUP, (UINT_PTR)hWin, L"Window"); + AppendMenu(hm, MF_POPUP, (UINT_PTR)hWin, L"Window"); AppendMenu(hm, MF_POPUP, (UINT_PTR)hHelp, L"Help"); CheckMenuItem(hView, IDM_ALWAYS_ON_TOP, - g.cfg.always_on_top ? MF_CHECKED : MF_UNCHECKED); + g.cfg.always_on_top ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hWin, IDM_MINIMIZE_TO_TRAY, - g.cfg.minimize_to_tray ? MF_CHECKED : MF_UNCHECKED); + g.cfg.minimize_to_tray ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hWin, IDM_START_WITH_WINDOWS, - g.cfg.start_with_windows ? MF_CHECKED : MF_UNCHECKED); + g.cfg.start_with_windows ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hWin, IDM_START_MINIMIZED, - g.cfg.start_minimized ? MF_CHECKED : MF_UNCHECKED); + g.cfg.start_minimized ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hTheme, IDM_THEME_DARK, - g.cfg.theme == THEME_DARK ? MF_CHECKED : MF_UNCHECKED); + g.cfg.theme == THEME_DARK ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hTheme, IDM_THEME_LIGHT, - g.cfg.theme == THEME_LIGHT ? MF_CHECKED : MF_UNCHECKED); + g.cfg.theme == THEME_LIGHT ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hTheme, IDM_THEME_SYSTEM, - g.cfg.theme == THEME_SYSTEM ? MF_CHECKED : MF_UNCHECKED); + g.cfg.theme == THEME_SYSTEM ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hQBar, IDM_QBAR_TOGGLE, - g.cfg.qbar_enabled ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_enabled ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hQBar, IDM_QBAR_HORIZONTAL, - g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hQBar, IDM_QBAR_VERTICAL, - !g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); + !g.cfg.qbar_horizontal ? MF_CHECKED : MF_UNCHECKED); if (qbar_has_target) CheckMenuItem(hQBar, IDM_QBAR_TOPMOST, - g.cfg.qbar_topmost_with_catia ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_topmost_with_catia ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hQBar, IDM_REPEAT_QBAR, - g.cfg.qbar_repeat_on_dblclick ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(hRun, IDM_REPEAT_MAINAPP, - g.cfg.repeat_on_dblclick ? MF_CHECKED : MF_UNCHECKED); + g.cfg.qbar_repeat_on_dblclick ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hRun, IDM_REPEAT_MAINAPP, + g.cfg.repeat_on_dblclick ? MF_CHECKED : MF_UNCHECKED); HWND hBtn = GetDlgItem(g.hwnd, IDC_BTN_MENU); - RECT rc; GetWindowRect(hBtn, &rc); + RECT rc; + GetWindowRect(hBtn, &rc); TrackPopupMenu(hm, TPM_LEFTALIGN | TPM_TOPALIGN, rc.left, rc.bottom, 0, g.hwnd, NULL); @@ -217,13 +221,13 @@ void Window_AddTrayIcon(void) { if (g.tray_icon_added) return; NOTIFYICONDATA nid = {0}; - nid.cbSize = sizeof(nid); - nid.hWnd = g.hwnd; - nid.uID = TRAY_ID; - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.cbSize = sizeof(nid); + nid.hWnd = g.hwnd; + nid.uID = TRAY_ID; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_TRAYICON; - nid.hIcon = LoadIcon(GetModuleHandle(NULL), - MAKEINTRESOURCE(IDI_APP_ICON)); + nid.hIcon = LoadIcon(GetModuleHandle(NULL), + MAKEINTRESOURCE(IDI_APP_ICON)); wcsncpy_s(nid.szTip, _countof(nid.szTip), APP_TITLE, _TRUNCATE); Shell_NotifyIcon(NIM_ADD, &nid); g.tray_icon_added = true; @@ -240,7 +244,9 @@ void Window_RemoveTrayIcon(void) { if (!g.tray_icon_added) return; NOTIFYICONDATA nid = {0}; - nid.cbSize = sizeof(nid); nid.hWnd = g.hwnd; nid.uID = TRAY_ID; + nid.cbSize = sizeof(nid); + nid.hWnd = g.hwnd; + nid.uID = TRAY_ID; Shell_NotifyIcon(NIM_DELETE, &nid); g.tray_icon_added = false; } @@ -255,11 +261,12 @@ void Window_RemoveTrayIcon(void) void Window_ShowTrayMenu(void) { HMENU hm = CreatePopupMenu(); - AppendMenu(hm, MF_STRING, IDM_REFRESH, L"Refresh Scripts"); + AppendMenu(hm, MF_STRING, IDM_REFRESH, L"Refresh Scripts"); AppendMenu(hm, MF_SEPARATOR, 0, NULL); - AppendMenu(hm, MF_STRING, IDM_EXIT, L"Exit"); + AppendMenu(hm, MF_STRING, IDM_EXIT, L"Exit"); - POINT pt; GetCursorPos(&pt); + POINT pt; + GetCursorPos(&pt); SetForegroundWindow(g.hwnd); TrackPopupMenu(hm, TPM_RIGHTBUTTON, pt.x, pt.y, 0, g.hwnd, NULL); DestroyMenu(hm); @@ -297,12 +304,13 @@ static int TabBar_NaturalWidth(const WCHAR *label) /* ================================================================== */ /* offset is an index into vtabs[], not into g.folders[]. */ static int TabBar_CountFit(int bar_w, const int *vtabs, int vn, - int offset, bool with_arrows) + int offset, bool with_arrows) { int avail = with_arrows ? bar_w - 2 * TAB_ARROW_W : bar_w; - int used = 0; + int used = 0; int count = 0; - for (int v = offset; v < vn; v++) { + for (int v = offset; v < vn; v++) + { int tw = TabBar_NaturalWidth(g.folders[vtabs[v]].display); if (used + tw > avail) break; used += tw; @@ -340,7 +348,7 @@ static bool TabBar_NeedsArrows(int bar_w, const int *vtabs, int vn) /* Out: LRESULT — 0 for handled messages; DefWindowProc result */ /* ================================================================== */ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { switch (msg) { @@ -351,7 +359,8 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int w = rc.right, h = rc.bottom; HBRUSH bg = CreateSolidBrush(COL_TOOLBAR()); @@ -366,13 +375,18 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, if (Tabs_FolderHasVisible(i)) vtabs[vn++] = i; LeaveCriticalSection(&g.cs_folders); - if (vn == 0) { EndPaint(hwnd, &ps); return 0; } + if (vn == 0) + { + EndPaint(hwnd, &ps); + return 0; + } bool need_arrows = TabBar_NeedsArrows(w, vtabs, vn); int left_x = 0, right_x = w; - if (need_arrows) { - left_x = TAB_ARROW_W; + if (need_arrows) + { + left_x = TAB_ARROW_W; right_x = w - TAB_ARROW_W; /* Find the last offset from which at least one page of tabs is reachable */ @@ -381,12 +395,13 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, max_off + TabBar_CountFit(w, vtabs, vn, max_off, true) < vn) max_off++; if (g.tab_offset > max_off) g.tab_offset = max_off; /* clamp to valid range */ - if (g.tab_offset < 0) g.tab_offset = 0; + if (g.tab_offset < 0) g.tab_offset = 0; bool la_hot = (g.tab_offset > 0); /* left arrow active only when not at the start */ HBRUSH ab = CreateSolidBrush(la_hot ? COL_BTN_HOT() : COL_TOOLBAR()); - RECT ar = { 0, 0, TAB_ARROW_W, h }; - FillRect(hdc, &ar, ab); DeleteObject(ab); + RECT ar = {0, 0, TAB_ARROW_W, h}; + FillRect(hdc, &ar, ab); + DeleteObject(ab); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, la_hot ? COL_ACCENT : COL_SUBTEXT()); HFONT of2 = SelectObject(hdc, g.font_ui); @@ -396,31 +411,36 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, int visible_count = TabBar_CountFit(w, vtabs, vn, g.tab_offset, true); bool ra_hot = (g.tab_offset + visible_count < vn); /* right arrow active when more tabs are hidden to the right */ ab = CreateSolidBrush(ra_hot ? COL_BTN_HOT() : COL_TOOLBAR()); - ar = (RECT){ right_x, 0, w, h }; - FillRect(hdc, &ar, ab); DeleteObject(ab); + ar = (RECT){right_x, 0, w, h}; + FillRect(hdc, &ar, ab); + DeleteObject(ab); SetTextColor(hdc, ra_hot ? COL_ACCENT : COL_SUBTEXT()); HFONT of3 = SelectObject(hdc, g.font_ui); DrawText(hdc, L"►", -1, &ar, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, of3); - } else { + } + else + { g.tab_offset = 0; /* all tabs fit — no offset needed */ } SetBkMode(hdc, TRANSPARENT); int x = left_x; - for (int v = g.tab_offset; v < vn; v++) { + for (int v = g.tab_offset; v < vn; v++) + { int fi = vtabs[v]; int tw = TabBar_NaturalWidth(g.folders[fi].display); if (x + tw > right_x) break; bool sel = (fi == g.active_tab); HBRUSH tbr = CreateSolidBrush(sel ? COL_BTN_NORM() : COL_TOOLBAR()); - RECT tr = { x, 0, x + tw, h }; + RECT tr = {x, 0, x + tw, h}; FillRect(hdc, &tr, tbr); DeleteObject(tbr); - if (sel) { + if (sel) + { /* 2-px accent line at the top of the selected tab */ HPEN ap = CreatePen(PS_SOLID, 2, COL_ACCENT); HPEN op = SelectObject(hdc, ap); @@ -430,7 +450,8 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, DeleteObject(ap); } - if (v + 1 < vn) { /* draw vertical divider between tabs, but not after the last visible one */ + if (v + 1 < vn) + { /* draw vertical divider between tabs, but not after the last visible one */ HPEN dp = CreatePen(PS_SOLID, 1, COL_DIVIDER()); HPEN op = SelectObject(hdc, dp); MoveToEx(hdc, x + tw - 1, 3, NULL); @@ -441,7 +462,7 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, SetTextColor(hdc, sel ? COL_ACCENT : COL_TEXT()); HFONT of = SelectObject(hdc, sel ? g.font_bold : g.font_ui); - RECT lr = { x + 14, 0, x + tw - 14, h }; /* 14 px text margin matches the 28-total-padding from NaturalWidth */ + RECT lr = {x + 14, 0, x + tw - 14, h}; /* 14 px text margin matches the 28-total-padding from NaturalWidth */ DrawText(hdc, g.folders[fi].display, -1, &lr, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, of); @@ -461,7 +482,8 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, case WM_LBUTTONDOWN: { - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int w = rc.right; if (g.folder_count == 0) break; @@ -474,14 +496,25 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, int mx = GET_X_LPARAM(lp); bool need_arrows = TabBar_NeedsArrows(w, vtabs, vn); - if (need_arrows) { - if (mx < TAB_ARROW_W) { /* click on left arrow — scroll left */ - if (g.tab_offset > 0) { g.tab_offset--; InvalidateRect(hwnd, NULL, FALSE); } + if (need_arrows) + { + if (mx < TAB_ARROW_W) + { /* click on left arrow — scroll left */ + if (g.tab_offset > 0) + { + g.tab_offset--; + InvalidateRect(hwnd, NULL, FALSE); + } return 0; } - if (mx >= w - TAB_ARROW_W) { /* click on right arrow — scroll right */ + if (mx >= w - TAB_ARROW_W) + { /* click on right arrow — scroll right */ int vis = TabBar_CountFit(w, vtabs, vn, g.tab_offset, true); - if (g.tab_offset + vis < vn) { g.tab_offset++; InvalidateRect(hwnd, NULL, FALSE); } + if (g.tab_offset + vis < vn) + { + g.tab_offset++; + InvalidateRect(hwnd, NULL, FALSE); + } return 0; } } @@ -489,11 +522,13 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, int left_x = need_arrows ? TAB_ARROW_W : 0; int right_x = need_arrows ? w - TAB_ARROW_W : w; int x = left_x; - for (int v = g.tab_offset; v < vn; v++) { + for (int v = g.tab_offset; v < vn; v++) + { int fi = vtabs[v]; int tw = TabBar_NaturalWidth(g.folders[fi].display); if (x + tw > right_x) tw = right_x - x; /* clip last visible tab at the arrow zone boundary */ - if (mx >= x && mx < x + tw) { + if (mx >= x && mx < x + tw) + { if (fi != g.active_tab) Tabs_Switch(fi); /* only switch if a different tab was clicked */ return 0; } @@ -506,7 +541,8 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, case WM_MOUSEWHEEL: { if (g.folder_count == 0) break; - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); int vtabs[MAX_FOLDERS]; int vn = 0; @@ -514,12 +550,17 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, if (Tabs_FolderHasVisible(i)) vtabs[vn++] = i; if (!TabBar_NeedsArrows(rc.right, vtabs, vn)) break; - int vis = TabBar_CountFit(rc.right, vtabs, vn, g.tab_offset, true); + int vis = TabBar_CountFit(rc.right, vtabs, vn, g.tab_offset, true); int delta = GET_WHEEL_DELTA_WPARAM(wp); /* positive = wheel up = scroll left */ - if (delta < 0 && g.tab_offset + vis < vn) { /* wheel down = scroll right */ - g.tab_offset++; InvalidateRect(hwnd, NULL, FALSE); - } else if (delta > 0 && g.tab_offset > 0) { /* wheel up = scroll left */ - g.tab_offset--; InvalidateRect(hwnd, NULL, FALSE); + if (delta < 0 && g.tab_offset + vis < vn) + { /* wheel down = scroll right */ + g.tab_offset++; + InvalidateRect(hwnd, NULL, FALSE); + } + else if (delta > 0 && g.tab_offset > 0) + { /* wheel up = scroll left */ + g.tab_offset--; + InvalidateRect(hwnd, NULL, FALSE); } return 0; } @@ -545,7 +586,7 @@ static LRESULT CALLBACK TabBarProc(HWND hwnd, UINT msg, /* 0 for paint; DefWindowProc for unhandled messages */ /* ================================================================== */ static LRESULT CALLBACK StatusBarProc(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { switch (msg) { @@ -556,24 +597,26 @@ static LRESULT CALLBACK StatusBarProc(HWND hwnd, UINT msg, { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); - RECT rc; GetClientRect(hwnd, &rc); + RECT rc; + GetClientRect(hwnd, &rc); HBRUSH bg = CreateSolidBrush(COL_TOOLBAR()); FillRect(hdc, &rc, bg); DeleteObject(bg); HPEN pen = CreatePen(PS_SOLID, 1, COL_DIVIDER()); - HPEN op = SelectObject(hdc, pen); + HPEN op = SelectObject(hdc, pen); MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, rc.right, 0); - SelectObject(hdc, op); DeleteObject(pen); + SelectObject(hdc, op); + DeleteObject(pen); WCHAR text[256] = {0}; GetWindowText(hwnd, text, 255); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, g.status_offline ? COL_WARN : COL_SUBTEXT()); /* amber text when offline */ HFONT of = SelectObject(hdc, g.font_small); - RECT tr = { 6, 0, rc.right - 6, rc.bottom }; + RECT tr = {6, 0, rc.right - 6, rc.bottom}; DrawText(hdc, text, -1, &tr, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); SelectObject(hdc, of); @@ -610,11 +653,15 @@ static LRESULT CALLBACK StatusBarProc(HWND hwnd, UINT msg, /* for all other messages */ /* ================================================================== */ static LRESULT CALLBACK TipWndProcInternal(HWND hwnd, UINT msg, - WPARAM wp, LPARAM lp) + WPARAM wp, LPARAM lp) { - switch (msg) { - case WM_PAINT: Paint_Tooltip(hwnd); return 0; - case WM_ERASEBKGND: return 1; /* suppress erase to prevent flicker */ + switch (msg) + { + case WM_PAINT: + Paint_Tooltip(hwnd); + return 0; + case WM_ERASEBKGND: + return 1; /* suppress erase to prevent flicker */ } return DefWindowProc(hwnd, msg, wp, lp); } @@ -633,42 +680,19 @@ void Window_Create(HINSTANCE hInst) { App_InitGDI(); - WNDCLASSEX wcsb = { .cbSize=sizeof(wcsb), .style=CS_HREDRAW|CS_VREDRAW, - .lpfnWndProc=StatusBarProc, .hInstance=hInst, - .hCursor=LoadCursor(NULL,IDC_ARROW), - .hbrBackground=(HBRUSH)GetStockObject(NULL_BRUSH), - .lpszClassName=L"CMW32StatusBar" }; + WNDCLASSEX wcsb = {.cbSize = sizeof(wcsb), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = StatusBarProc, .hInstance = hInst, .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), .lpszClassName = L"CMW32StatusBar"}; RegisterClassEx(&wcsb); - WNDCLASSEX wcs = { .cbSize=sizeof(wcs), .style=CS_HREDRAW|CS_VREDRAW, - .lpfnWndProc=ScrollPanelProc, .hInstance=hInst, - .hCursor=LoadCursor(NULL,IDC_ARROW), - .hbrBackground=(HBRUSH)GetStockObject(NULL_BRUSH), - .lpszClassName=L"CMW32ScrollPanel" }; + WNDCLASSEX wcs = {.cbSize = sizeof(wcs), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = ScrollPanelProc, .hInstance = hInst, .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), .lpszClassName = L"CMW32ScrollPanel"}; RegisterClassEx(&wcs); - WNDCLASSEX wct = { .cbSize=sizeof(wct), .style=CS_HREDRAW|CS_VREDRAW, - .lpfnWndProc=TipWndProcInternal, .hInstance=hInst, - .hCursor=LoadCursor(NULL,IDC_ARROW), - .hbrBackground=(HBRUSH)GetStockObject(NULL_BRUSH), - .lpszClassName=L"CMW32Tip" }; + WNDCLASSEX wct = {.cbSize = sizeof(wct), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = TipWndProcInternal, .hInstance = hInst, .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), .lpszClassName = L"CMW32Tip"}; RegisterClassEx(&wct); - WNDCLASSEX wctb = { .cbSize=sizeof(wctb), .style=CS_HREDRAW|CS_VREDRAW, - .lpfnWndProc=TabBarProc, .hInstance=hInst, - .hCursor=LoadCursor(NULL,IDC_ARROW), - .hbrBackground=(HBRUSH)GetStockObject(NULL_BRUSH), - .lpszClassName=L"CMW32TabBar" }; + WNDCLASSEX wctb = {.cbSize = sizeof(wctb), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = TabBarProc, .hInstance = hInst, .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), .lpszClassName = L"CMW32TabBar"}; RegisterClassEx(&wctb); - WNDCLASSEX wc = { .cbSize=sizeof(wc), .style=CS_HREDRAW|CS_VREDRAW, - .lpfnWndProc=MainWndProc, .hInstance=hInst, - .hCursor=LoadCursor(NULL,IDC_ARROW), - .hbrBackground=(HBRUSH)GetStockObject(NULL_BRUSH), - .lpszMenuName=NULL, - .lpszClassName=APP_CLASS, - .hIcon=LoadIcon(hInst,MAKEINTRESOURCE(IDI_APP_ICON)), - .hIconSm=LoadIcon(hInst,MAKEINTRESOURCE(IDI_APP_ICON)) }; + WNDCLASSEX wc = {.cbSize = sizeof(wc), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = MainWndProc, .hInstance = hInst, .hCursor = LoadCursor(NULL, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH), .lpszMenuName = NULL, .lpszClassName = APP_CLASS, .hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APP_ICON)), .hIconSm = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APP_ICON))}; RegisterClassEx(&wc); QuickBar_Register(hInst); @@ -678,58 +702,59 @@ void Window_Create(HINSTANCE hInst) int ww = 820, wh = 540; /* initial window dimensions */ g.hwnd = CreateWindowEx(0, APP_CLASS, APP_TITLE, WS_OVERLAPPEDWINDOW, - (sw-ww)/2, (sh-wh)/2, ww, wh, NULL, NULL, hInst, NULL); /* (sw-ww)/2 centres horizontally */ + (sw - ww) / 2, (sh - wh) / 2, ww, wh, NULL, NULL, hInst, NULL); /* (sw-ww)/2 centres horizontally */ Window_ApplyDarkMode(g.hwnd); CreateWindow(L"BUTTON", L"\u2630 Menu", - WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, - 6, 5, 82, 28, g.hwnd, - (HMENU)(UINT_PTR)IDC_BTN_MENU, hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, + 6, 5, 82, 28, g.hwnd, + (HMENU)(UINT_PTR)IDC_BTN_MENU, hInst, NULL); CreateWindow(L"BUTTON", L"\u27F3 Refresh", - WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, - 94, 5, 88, 28, g.hwnd, - (HMENU)(UINT_PTR)IDC_BTN_REFRESH, hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, + 94, 5, 88, 28, g.hwnd, + (HMENU)(UINT_PTR)IDC_BTN_REFRESH, hInst, NULL); CreateWindow(L"BUTTON", L"\u2699 Settings", - WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, - 188, 5, 88, 28, g.hwnd, - (HMENU)(UINT_PTR)IDC_BTN_SETTINGS, hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, + 188, 5, 88, 28, g.hwnd, + (HMENU)(UINT_PTR)IDC_BTN_SETTINGS, hInst, NULL); CreateWindow(L"BUTTON", L"\u2B07 Deps", - WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, - 282, 5, 76, 28, g.hwnd, - (HMENU)(UINT_PTR)IDC_BTN_UPDATE_DEPS, hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, + 282, 5, 76, 28, g.hwnd, + (HMENU)(UINT_PTR)IDC_BTN_UPDATE_DEPS, hInst, NULL); CreateWindow(L"BUTTON", L"\u25A0 Stop", - WS_CHILD|WS_VISIBLE|BS_OWNERDRAW|WS_DISABLED, - 364, 5, 70, 28, g.hwnd, - (HMENU)(UINT_PTR)IDC_BTN_STOP, hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW | WS_DISABLED, + 364, 5, 70, 28, g.hwnd, + (HMENU)(UINT_PTR)IDC_BTN_STOP, hInst, NULL); CreateWindow(L"BUTTON", L"\u2261 Log", - WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, - 440, 5, 62, 28, g.hwnd, - (HMENU)(UINT_PTR)IDC_BTN_LOG, hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, + 440, 5, 62, 28, g.hwnd, + (HMENU)(UINT_PTR)IDC_BTN_LOG, hInst, NULL); - int ids[] = { IDC_BTN_MENU, IDC_BTN_REFRESH, - IDC_BTN_SETTINGS, IDC_BTN_UPDATE_DEPS, IDC_BTN_STOP, IDC_BTN_LOG }; + int ids[] = {IDC_BTN_MENU, IDC_BTN_REFRESH, + IDC_BTN_SETTINGS, IDC_BTN_UPDATE_DEPS, IDC_BTN_STOP, IDC_BTN_LOG}; for (int i = 0; i < 6; i++) /* apply UI font to all 6 toolbar buttons */ SendDlgItemMessage(g.hwnd, ids[i], WM_SETFONT, (WPARAM)g.font_ui, TRUE); /* Search/filter box - full client width, positioned below toolbar */ { - RECT cr; GetClientRect(g.hwnd, &cr); + RECT cr; + GetClientRect(g.hwnd, &cr); int cw = cr.right; g.hwnd_search = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, - 4, TOOLBAR_H + 2, cw - 8, SEARCH_H - 4, g.hwnd, - (HMENU)(UINT_PTR)IDC_SEARCH, hInst, NULL); + WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, + 4, TOOLBAR_H + 2, cw - 8, SEARCH_H - 4, g.hwnd, + (HMENU)(UINT_PTR)IDC_SEARCH, hInst, NULL); } SendMessage(g.hwnd_search, EM_SETCUEBANNER, 0, (LPARAM)L" Filter scripts..."); SendMessage(g.hwnd_search, WM_SETFONT, (WPARAM)g.font_ui, TRUE); g.hwnd_tab = CreateWindow(L"CMW32TabBar", NULL, - WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS, - 0, TOOLBAR_H + SEARCH_H, ww, TAB_H, - g.hwnd, (HMENU)(UINT_PTR)IDC_TAB_CTRL, hInst, NULL); + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, + 0, TOOLBAR_H + SEARCH_H, ww, TAB_H, + g.hwnd, (HMENU)(UINT_PTR)IDC_TAB_CTRL, hInst, NULL); int ct = TOOLBAR_H + SEARCH_H + TAB_H; /* top of scroll panel = toolbar + search + tab bar */ int ch = wh - ct - STATUS_H; /* scroll panel height = remaining space above status bar */ @@ -739,20 +764,20 @@ void Window_Create(HINSTANCE hInst) SetWindowPos(g.hwnd_search, NULL, 4, TOOLBAR_H + 2, ww - 8, SEARCH_H - 4, SWP_NOZORDER | SWP_NOACTIVATE); g.hwnd_scroll = CreateWindowEx(0, L"CMW32ScrollPanel", NULL, - WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_CLIPCHILDREN, - 0, ct, ww, ch, - g.hwnd, (HMENU)(UINT_PTR)IDC_SCROLL_PANEL, hInst, NULL); + WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_CLIPCHILDREN, + 0, ct, ww, ch, + g.hwnd, (HMENU)(UINT_PTR)IDC_SCROLL_PANEL, hInst, NULL); g.hwnd_status = CreateWindow(L"CMW32StatusBar", - L"Checking for updates\u2026", - WS_CHILD|WS_VISIBLE, - 0, wh-STATUS_H, ww, STATUS_H, - g.hwnd, (HMENU)(UINT_PTR)IDC_STATUS_BAR, hInst, NULL); + L"Checking for updates\u2026", + WS_CHILD | WS_VISIBLE, + 0, wh - STATUS_H, ww, STATUS_H, + g.hwnd, (HMENU)(UINT_PTR)IDC_STATUS_BAR, hInst, NULL); SendMessage(g.hwnd_status, WM_SETFONT, (WPARAM)g.font_small, TRUE); - g.hwnd_tip = CreateWindowEx(WS_EX_TOPMOST|WS_EX_NOACTIVATE, /* WS_EX_NOACTIVATE: tooltip never steals focus */ - L"CMW32Tip", NULL, WS_POPUP, - 0, 0, 300, 160, g.hwnd, NULL, hInst, NULL); + g.hwnd_tip = CreateWindowEx(WS_EX_TOPMOST | WS_EX_NOACTIVATE, /* WS_EX_NOACTIVATE: tooltip never steals focus */ + L"CMW32Tip", NULL, WS_POPUP, + 0, 0, 300, 160, g.hwnd, NULL, hInst, NULL); } /* ================================================================== */ @@ -766,9 +791,9 @@ void Window_Create(HINSTANCE hInst) /* ================================================================== */ void Window_OnSize(int w, int h) { - int tab_y = TOOLBAR_H + SEARCH_H; - int ct = tab_y + TAB_H; - int ch = h - ct - STATUS_H; + int tab_y = TOOLBAR_H + SEARCH_H; + int ct = tab_y + TAB_H; + int ch = h - ct - STATUS_H; if (ch < 0) ch = 0; /* prevent negative height if window is smaller than the fixed chrome */ /* Search box - full client width with small margin */ @@ -776,11 +801,11 @@ void Window_OnSize(int w, int h) SetWindowPos(g.hwnd_search, NULL, 4, TOOLBAR_H + 2, w - 8, SEARCH_H - 4, SWP_NOZORDER | SWP_NOACTIVATE); - SetWindowPos(g.hwnd_tab, NULL, 0, tab_y, w, TAB_H, + SetWindowPos(g.hwnd_tab, NULL, 0, tab_y, w, TAB_H, SWP_NOZORDER | SWP_NOACTIVATE); - SetWindowPos(g.hwnd_scroll, NULL, 0, ct, w, ch, + SetWindowPos(g.hwnd_scroll, NULL, 0, ct, w, ch, SWP_NOZORDER | SWP_NOACTIVATE); - SetWindowPos(g.hwnd_status, NULL, 0, h-STATUS_H, w, STATUS_H, + SetWindowPos(g.hwnd_status, NULL, 0, h - STATUS_H, w, STATUS_H, SWP_NOZORDER | SWP_NOACTIVATE); Tabs_RebuildButtons();