// Included inside ui.cpp's anonymous namespace, alongside the settings editors. std::wstring trimApplicationText(const std::wstring &text) { auto first = text.find_first_not_of(L" \t\r\n"); return first == text.npos ? L"" : text.substr(first, text.find_last_not_of(L" \t\r\n") - first + 1); } struct AppLanguagesView { HWND parent{}; SettingsView *settings{}; std::map> profiles; std::vector applications; int selection = -1; bool dirty{}; }; void fillAppLanguages(HWND w, AppLanguagesView *v) { bool selected = v->selection >= 0 && v->selection < (int)v->applications.size(); EnableWindow(GetDlgItem(w, 722), selected); SendDlgItemMessageW(w, 722, LB_SETSEL, FALSE, -1); if (!selected) { EnableWindow(GetDlgItem(w, 725), FALSE); value(w, 721, L"Select an application, or add its executable name below."); return; } auto found = v->profiles.find(v->applications[v->selection]); const auto &cycle = found == v->profiles.end() ? v->settings->draft.cycle : found->second; for (size_t i = 0; i < v->settings->order.size(); ++i) { const auto &id = v->settings->app->layouts[v->settings->order[i]].id; SendDlgItemMessageW(w, 722, LB_SETSEL, std::find(cycle.begin(), cycle.end(), id) != cycle.end(), i); } value(w, 721, found == v->profiles.end() ? L"Using default languages. Click languages to customize this application." : L"Custom languages for this application. Other applications are unchanged."); EnableWindow(GetDlgItem(w, 725), found != v->profiles.end()); } void collectAppLanguages(HWND w, AppLanguagesView *v) { if (v->selection < 0 || !v->dirty) return; std::vector cycle; for (size_t i = 0; i < v->settings->order.size(); ++i) if (SendDlgItemMessageW(w, 722, LB_GETSEL, i, 0) > 0) cycle.push_back(v->settings->app->layouts[v->settings->order[i]].id); if (cycle.empty()) throw std::runtime_error("Select at least one language."); v->profiles[v->applications[v->selection]] = std::move(cycle); v->dirty = false; } LRESULT CALLBACK appLanguagesProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) { auto *v = (AppLanguagesView *)GetWindowLongPtrW(w, GWLP_USERDATA); if (!v) return DefWindowProcW(w, msg, wp, lp); try { if (msg == WM_COMMAND) { int id = LOWORD(wp); if (id == 722 && HIWORD(wp) == LBN_SELCHANGE) { v->dirty = true; EnableWindow(GetDlgItem(w, 725), TRUE); value(w, 721, L"Custom languages for this application. Other applications are unchanged."); return 0; } if (id == 720 && HIWORD(wp) == LBN_SELCHANGE) { int next = (int)SendDlgItemMessageW(w, 720, LB_GETCURSEL, 0, 0); try { collectAppLanguages(w, v); } catch (...) { SendDlgItemMessageW(w, 720, LB_SETCURSEL, v->selection, 0); throw; } v->selection = next; fillAppLanguages(w, v); return 0; } if (id == 725 && v->selection >= 0) { v->profiles.erase(v->applications[v->selection]); v->dirty = false; fillAppLanguages(w, v); return 0; } if (id == 727) { auto app = applicationName(trimApplicationText(value(w, 726))); Settings check; check.appLanguageSets[L"check"] = {{L"win:04090409"}, {app}}; validateSettings(check); collectAppLanguages(w, v); auto found = std::find(v->applications.begin(), v->applications.end(), app); int index = (int)(found - v->applications.begin()); if (found == v->applications.end()) { v->applications.push_back(app); SendDlgItemMessageW(w, 720, LB_ADDSTRING, 0, (LPARAM)app.c_str()); } v->selection = index; SendDlgItemMessageW(w, 720, LB_SETCURSEL, index, 0); fillAppLanguages(w, v); value(w, 726, L""); SetFocus(GetDlgItem(w, 722)); return 0; } if (id == IDOK) { collectAppLanguages(w, v); auto next = v->settings->draft; next.appLanguageSets.clear(); // Keep the existing on-disk format, but save independent application profiles. for (const auto &[app, cycle] : v->profiles) next.appLanguageSets[app] = {cycle, {app}}; validateSettings(next); v->settings->draft.appLanguageSets = std::move(next.appLanguageSets); DestroyWindow(w); return 0; } if (id == IDCANCEL) { DestroyWindow(w); return 0; } } if (msg == WM_CLOSE) { DestroyWindow(w); return 0; } if (msg == WM_DPICHANGED) { resizeForDpi(w, wp, lp); return 0; } if (msg == WM_NCDESTROY) { EnableWindow(v->parent, TRUE); SetForegroundWindow(v->parent); disposeFont(w); delete v; SetWindowLongPtrW(w, GWLP_USERDATA, 0); } } catch (const std::exception &e) { error(w, e); } return DefWindowProcW(w, msg, wp, lp); } void showAppLanguages(HWND parent, SettingsView *settings) { std::set apps; for (const auto &[name, set] : settings->draft.appLanguageSets) for (const auto &app : set.applications) apps.insert(applicationName(app)); EnumWindows([](HWND candidate, LPARAM context) -> BOOL { if (IsWindowVisible(candidate) && !chatTitle(candidate).empty()) { auto name = applicationName(processName(candidate)); if (!name.empty()) ((std::set *)context)->insert(name); } return TRUE; }, (LPARAM)&apps); HWND w = makeWindow(*settings->app, L"KSwitcher.AppLanguages", L"Languages for applications", appLanguagesProc, 720, 500); auto *v = new AppLanguagesView{parent, settings}; for (const auto &[name, set] : settings->draft.appLanguageSets) for (const auto &app : set.applications) v->profiles[applicationName(app)] = set.languages; v->applications.assign(apps.begin(), apps.end()); SetWindowLongPtrW(w, GWLP_HWNDPARENT, (LONG_PTR)parent); SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v); label(w, L"Application", 18, 16, 278); control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER, 720, 18, 44, 278, 280); label(w, L"Allowed languages (click to include)", 316, 16, 386); control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT | WS_BORDER, 722, 316, 44, 386, 280); for (int i : settings->order) SendDlgItemMessageW(w, 722, LB_ADDSTRING, 0, (LPARAM)settings->app->layouts[i].name.c_str()); for (const auto &app : v->applications) SendDlgItemMessageW(w, 720, LB_ADDSTRING, 0, (LPARAM)app.c_str()); control(w, L"STATIC", L"", 0, 721, 18, 338, 684, 22); label(w, L"Add an executable name (e.g. Codex.exe)", 18, 372, 684); edit(w, 726, 18, 400, 526); button(w, L"&Add application", 727, 558, 398, 144); label(w, L"Switch applications freely. Use languages, then Save settings. Chat profiles take priority.", 18, 436, 684); button(w, L"Use &defaults", 725, 18, 466, 170); button(w, L"&Use languages", IDOK, 414, 466, 144); button(w, L"&Cancel", IDCANCEL, 572, 466, 130); if (!v->applications.empty()) { v->selection = 0; SendDlgItemMessageW(w, 720, LB_SETCURSEL, 0, 0); } fillAppLanguages(w, v); EnableWindow(parent, FALSE); showRequestedWindow(w); SetFocus(GetDlgItem(w, v->selection < 0 ? 726 : 720)); }