Pick an application by dragging onto its highlighted window

This commit is contained in:
agrynco committed 2026-11-10 00:43:02 +02:00
1 parent 5d96abb83e
commit a0e1de40ef
3 files changed
+181 -16

No files matched your search

+147 -16
View File
@@ -12,7 +12,18 @@ struct AppLanguagesView
std::vector<std::wstring> applications;
int selection = -1;
bool dirty{};
HWND pickerOutline{};
};
constexpr UINT AppWindowPicked = WM_APP + 78;
void showAppLanguageStatus(HWND w, const AppLanguagesView &v)
{
if (v.selection < 0)
value(w, 721, L"Select an application, drag the crosshair, or enter an executable name.");
else if (v.dirty || v.profiles.contains(v.applications[v.selection]))
value(w, 721, L"Custom languages for this application. Other applications are unchanged.");
else
value(w, 721, L"Using default languages. Click languages to customize this application.");
}
void fillAppLanguages(HWND w, AppLanguagesView *v)
{
bool selected = v->selection >= 0 && v->selection < (int)v->applications.size();
@@ -21,7 +32,7 @@ void fillAppLanguages(HWND w, AppLanguagesView *v)
if (!selected)
{
EnableWindow(GetDlgItem(w, 725), FALSE);
value(w, 721, L"Select an application, or add its executable name below.");
showAppLanguageStatus(w, *v);
return;
}
auto found = v->profiles.find(v->applications[v->selection]);
@@ -32,8 +43,7 @@ void fillAppLanguages(HWND w, AppLanguagesView *v)
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.");
showAppLanguageStatus(w, *v);
EnableWindow(GetDlgItem(w, 725), found != v->profiles.end());
}
void collectAppLanguages(HWND w, AppLanguagesView *v)
@@ -47,12 +57,141 @@ void collectAppLanguages(HWND w, AppLanguagesView *v)
v->profiles[v->applications[v->selection]] = std::move(cycle);
v->dirty = false;
}
void selectAppLanguageProfile(HWND w, AppLanguagesView *v, const std::wstring &app)
{
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())
{
auto row = SendDlgItemMessageW(w, 720, LB_ADDSTRING, 0, (LPARAM)app.c_str());
if (row < 0) throw std::runtime_error("Could not add the application to the list.");
v->applications.push_back(app);
}
v->selection = index;
SendDlgItemMessageW(w, 720, LB_SETCURSEL, index, 0);
fillAppLanguages(w, v);
}
HWND appPickerTarget(HWND button, LPARAM position)
{
POINT point{(short)LOWORD(position), (short)HIWORD(position)};
ClientToScreen(button, &point);
auto target = GetAncestor(WindowFromPoint(point), GA_ROOT);
auto dialog = GetParent(button);
return target && target != dialog && target != GetWindow(dialog, GW_OWNER) ? target : nullptr;
}
void updatePickerOutline(HWND dialog, AppLanguagesView &v, HWND target)
{
RECT bounds{};
if (!target || !GetWindowRect(target, &bounds) || IsRectEmpty(&bounds))
{
if (v.pickerOutline) ShowWindow(v.pickerOutline, SW_HIDE);
return;
}
if (!v.pickerOutline)
{
WNDCLASSW windowClass{};
windowClass.lpfnWndProc = DefWindowProcW;
windowClass.hInstance = GetModuleHandleW(nullptr);
windowClass.hbrBackground = GetSysColorBrush(COLOR_HIGHLIGHT);
windowClass.lpszClassName = L"KSwitcher.AppPickerOutline";
RegisterClassW(&windowClass);
v.pickerOutline = CreateWindowExW(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
windowClass.lpszClassName, nullptr, WS_POPUP, 0, 0, 0, 0, dialog, nullptr, windowClass.hInstance, nullptr);
if (!v.pickerOutline) return;
if (!SetLayeredWindowAttributes(v.pickerOutline, 0, 255, LWA_ALPHA))
{
DestroyWindow(v.pickerOutline);
v.pickerOutline = nullptr;
return;
}
}
RECT previous{};
if (IsWindowVisible(v.pickerOutline) && GetWindowRect(v.pickerOutline, &previous) && EqualRect(&bounds, &previous)) return;
int width = bounds.right - bounds.left, height = bounds.bottom - bounds.top;
int stroke = scale(target, 4);
HRGN frame = CreateRectRgn(0, 0, width, height);
if (!frame) return;
if (width > 2 * stroke && height > 2 * stroke)
{
HRGN opening = CreateRectRgn(stroke, stroke, width - stroke, height - stroke);
if (opening)
{
CombineRgn(frame, frame, opening, RGN_DIFF);
DeleteObject(opening);
}
}
if (!SetWindowRgn(v.pickerOutline, frame, TRUE))
{
DeleteObject(frame);
return;
}
SetWindowPos(v.pickerOutline, HWND_TOPMOST, bounds.left, bounds.top, width, height, SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
LRESULT CALLBACK appWindowPickerProc(HWND button, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR id, DWORD_PTR)
{
HWND dialog = GetParent(button);
auto *view = (AppLanguagesView *)GetWindowLongPtrW(dialog, GWLP_USERDATA);
if (msg == WM_LBUTTONDOWN)
{
SetFocus(button);
SetCapture(button);
if (GetCapture() == button)
{
SetCursor(LoadCursorW(nullptr, IDC_CROSS));
SetDlgItemTextW(dialog, 721, L"Drag to the application's window and release. Esc cancels.");
}
return 0;
}
if (msg == WM_CAPTURECHANGED || msg == WM_NCDESTROY)
{
if (view && view->pickerOutline)
{
DestroyWindow(view->pickerOutline);
view->pickerOutline = nullptr;
}
if (msg == WM_CAPTURECHANGED) SendMessageW(dialog, AppWindowPicked, 0, 0);
}
if (GetCapture() == button)
{
if (msg == WM_GETDLGCODE && wp == VK_ESCAPE) return DLGC_WANTMESSAGE;
if (msg == WM_CANCELMODE || msg == WM_RBUTTONDOWN || (msg == WM_KEYDOWN && wp == VK_ESCAPE))
{
ReleaseCapture();
return 0;
}
if (msg == WM_MOUSEMOVE || msg == WM_SETCURSOR)
{
SetCursor(LoadCursorW(nullptr, IDC_CROSS));
if (msg == WM_MOUSEMOVE && view) updatePickerOutline(dialog, *view, appPickerTarget(button, lp));
return TRUE;
}
if (msg == WM_LBUTTONUP)
{
HWND target = appPickerTarget(button, lp);
ReleaseCapture();
SendMessageW(dialog, AppWindowPicked, (WPARAM)target, 1);
return 0;
}
}
if (msg == WM_NCDESTROY) RemoveWindowSubclass(button, appWindowPickerProc, id);
return DefSubclassProc(button, msg, wp, lp);
}
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 == AppWindowPicked)
{
if (!lp) { showAppLanguageStatus(w, *v); return 0; }
HWND target = (HWND)wp;
auto app = target && target != w && target != v->parent ? applicationName(processName(target)) : L"";
if (app.empty()) value(w, 721, L"No application found. Release the crosshair over another window.");
else selectAppLanguageProfile(w, v, app);
return 0;
}
if (msg == WM_COMMAND)
{
int id = LOWORD(wp);
@@ -60,7 +199,7 @@ LRESULT CALLBACK appLanguagesProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
v->dirty = true;
EnableWindow(GetDlgItem(w, 725), TRUE);
value(w, 721, L"Custom languages for this application. Other applications are unchanged.");
showAppLanguageStatus(w, *v);
return 0;
}
if (id == 720 && HIWORD(wp) == LBN_SELCHANGE)
@@ -85,17 +224,7 @@ LRESULT CALLBACK appLanguagesProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
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);
selectAppLanguageProfile(w, v, app);
value(w, 726, L"");
SetFocus(GetDlgItem(w, 722));
return 0;
@@ -148,7 +277,9 @@ void showAppLanguages(HWND parent, SettingsView *settings)
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);
label(w, L"Application", 18, 16, 108);
button(w, L"⊕ Drag to window", 728, 132, 10, 164);
SetWindowSubclass(GetDlgItem(w, 728), appWindowPickerProc, 1, 0);
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);