std::wstring selectedMouseProfile(HWND w, const SettingsView &view) { const auto row = ListView_GetNextItem(GetDlgItem(w, 731), -1, LVNI_SELECTED); return row < 0 || static_cast(row) >= view.mouseProfiles.size() ? GlobalMouseProfile : view.mouseProfiles[row]; } constexpr int RenameMouseProfile = 757, DeleteMouseProfile = 758; constexpr int AddMouseEvent = 762, MouseEventCapture = 763, MouseEventNameField = 764, MouseEventStatus = 765; std::wstring mouseActionLabel(const MouseAction &action) { if (action.type == MouseActionType::Shortcut) return action.shortcut.format(); if (action.type == MouseActionType::Launch) return L"Launch " + std::filesystem::path(action.executable).filename().wstring(); return MouseActions[static_cast(action.type)]; } int mouseEventAtRow(HWND list, int row) { if (row < 0) return -1; LVITEMW item{LVIF_PARAM}; item.iItem = row; return ListView_GetItem(list, &item) ? static_cast(item.lParam) : -1; } void selectMouseEvent(HWND w, int event) { HWND list = GetDlgItem(w, 733); LVFINDINFOW search{LVFI_PARAM}; search.lParam = event; const int row = ListView_FindItem(list, -1, &search); if (row < 0) return; ListView_SetItemState(list, row, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); ListView_EnsureVisible(list, row, FALSE); } void fillMouseEvents(HWND w, SettingsView &view, int selection = 0) { const auto profile = selectedMouseProfile(w, view); EnableWindow(GetDlgItem(w, RenameMouseProfile), isNamedMouseProfile(profile)); EnableWindow(GetDlgItem(w, DeleteMouseProfile), isNamedMouseProfile(profile)); HWND list = GetDlgItem(w, 733); ListView_DeleteAllItems(list); for (int event = 0; event < static_cast(std::size(MouseEvents)); ++event) { if (!mouseEventEnabled(view.draft, event)) continue; auto name = mouseEventName(view.draft, event); LVITEMW item{LVIF_TEXT | LVIF_PARAM}; item.iItem = ListView_GetItemCount(list); item.lParam = event; item.pszText = name.data(); const int row = ListView_InsertItem(list, &item); if (row < 0) continue; auto *action = mouseBinding(view.draft, profile, event); std::wstring text; if (profile != GlobalMouseProfile && !action) { text += L"Global: "; action = mouseBinding(view.draft, GlobalMouseProfile, event); } text += action ? mouseActionLabel(*action) : MouseActions[0]; ListView_SetItemText(list, row, 1, text.data()); } selectMouseEvent(w, selection); } std::set profileApplications(const Settings &settings, const std::wstring &profile) { const auto scope = settings.mouseProfileApplications.find(profile); if (scope != settings.mouseProfileApplications.end()) return scope->second; if (profile != GlobalMouseProfile && !isNamedMouseProfile(profile)) return {profile}; std::set names; const auto bindings = settings.mouseBindings.find(profile); if (bindings != settings.mouseBindings.end()) for (const auto &[event, action] : bindings->second) names.insert(action.applications.begin(), action.applications.end()); return names; } void selectMouseProfile(HWND w, int row) { auto list = GetDlgItem(w, 731); ListView_SetItemState(list, row, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); ListView_EnsureVisible(list, row, FALSE); } void appendMouseProfile(HWND w, SettingsView &view, const std::wstring &profile) { auto list = GetDlgItem(w, 731); std::wstring text; for (const auto &app : profile == GlobalMouseProfile ? std::set{} : profileApplications(view.draft, profile)) { if (!text.empty()) text += L", "; text += app + L".exe"; } if (text.empty()) text = profile == GlobalMouseProfile ? L"Fallback" : L"All applications"; LVITEMW item{LVIF_TEXT}; item.iItem = static_cast(view.mouseProfiles.size()); wchar_t activeLabel[] = L""; item.pszText = activeLabel; const int row = ListView_InsertItem(list, &item); if (row < 0) throw std::runtime_error("Could not add the profile to the list."); auto label = mouseProfileName(profile); ListView_SetItemText(list, row, 1, label.data()); ListView_SetItemText(list, row, 2, text.data()); ListView_SetCheckState(list, row, !view.draft.disabledMouseProfiles.contains(profile)); view.mouseProfiles.push_back(profile); } bool blockMouseProfileStateChange(HWND w, SettingsView &view, const NMLISTVIEW &change) { const UINT previous = change.uOldState & LVIS_STATEIMAGEMASK; const UINT next = change.uNewState & LVIS_STATEIMAGEMASK; if (!(change.uChanged & LVIF_STATE) || !previous || !next || previous == next || change.iItem < 0 || static_cast(change.iItem) >= view.mouseProfiles.size()) return false; const auto &profile = view.mouseProfiles[change.iItem]; if (!isNamedMouseProfile(profile)) return true; if (next == INDEXTOSTATEIMAGEMASK(2)) { Settings candidate; candidate.mouseBindings = view.draft.mouseBindings; candidate.customMouseEvents = view.draft.customMouseEvents; candidate.disabledMouseProfiles = view.draft.disabledMouseProfiles; candidate.mouseProfileApplications = view.draft.mouseProfileApplications; candidate.disabledMouseProfiles.erase(profile); try { validateSettings(candidate); } catch (const std::exception &e) { error(w, e); return true; } view.draft.disabledMouseProfiles.erase(profile); } else view.draft.disabledMouseProfiles.insert(profile); updateSettingsState(w, &view); return false; } void fillMouseProfiles(HWND w, SettingsView &view) { const auto previous = selectedMouseProfile(w, view); ListView_DeleteAllItems(GetDlgItem(w, 731)); view.mouseProfiles.clear(); appendMouseProfile(w, view, GlobalMouseProfile); for (const auto &[app, bindings] : view.draft.mouseBindings) if (app != GlobalMouseProfile) appendMouseProfile(w, view, app); auto selected = std::find(view.mouseProfiles.begin(), view.mouseProfiles.end(), previous); selectMouseProfile(w, selected == view.mouseProfiles.end() ? 0 : static_cast(selected - view.mouseProfiles.begin())); fillMouseEvents(w, view); } LRESULT CALLBACK mouseButtonCaptureProc(HWND field, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR id, DWORD_PTR) { const bool doubleClick = msg == WM_LBUTTONDBLCLK || msg == WM_RBUTTONDBLCLK || msg == WM_MBUTTONDBLCLK; switch (msg) { case WM_LBUTTONDBLCLK: msg = WM_LBUTTONDOWN; break; case WM_RBUTTONDBLCLK: msg = WM_RBUTTONDOWN; break; case WM_MBUTTONDBLCLK: msg = WM_MBUTTONDOWN; break; case WM_XBUTTONDBLCLK: msg = WM_XBUTTONDOWN; break; } const int button = mouseButton(msg, HIWORD(wp)); if (button >= 0) { if (mouseButtonDown(msg)) { SetFocus(field); SetCapture(field); HWND settings = GetParent(field); auto *view = reinterpret_cast(GetWindowLongPtrW(settings, GWLP_USERDATA)); if (view) selectMouseEvent(settings, doubleClick && button < 3 ? button + 5 : button); if (view) value(settings, 738, std::wstring(L"Window event: ") + MouseButtonNames[button] + L" | Presses: " + std::to_wstring(++view->mouseEvents)); } else if (GetCapture() == field) ReleaseCapture(); return msg == WM_XBUTTONDOWN || msg == WM_XBUTTONUP ? TRUE : 0; } if (msg == WM_NCDESTROY) RemoveWindowSubclass(field, mouseButtonCaptureProc, id); return DefSubclassProc(field, msg, wp, lp); } bool registerMouseTestInput(HWND w) { RAWINPUTDEVICE mouse{0x01, 0x02, w ? DWORD{0} : RIDEV_REMOVE, w}; return RegisterRawInputDevices(&mouse, 1, sizeof(mouse)) != FALSE; } void readMouseTestInput(HWND w, SettingsView &view, HRAWINPUT handle) { HWND capture = GetDlgItem(w, 737); if (!IsWindowVisible(capture)) return; RAWINPUT input{}; UINT size = sizeof(input); if (GetRawInputData(handle, RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER)) == UINT(-1) || input.header.dwType != RIM_TYPEMOUSE || !input.data.mouse.usButtonFlags) return; POINT point{}; if (!GetCursorPos(&point) || WindowFromPoint(point) != capture) return; constexpr USHORT downFlags[] = {RI_MOUSE_LEFT_BUTTON_DOWN, RI_MOUSE_RIGHT_BUTTON_DOWN, RI_MOUSE_MIDDLE_BUTTON_DOWN, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_5_DOWN}; static_assert(std::size(downFlags) == std::size(MouseButtonNames)); for (size_t i = 0; i < std::size(downFlags); ++i) { if (!(input.data.mouse.usButtonFlags & downFlags[i])) continue; value(w, 739, std::wstring(L"Raw Input: ") + MouseButtonNames[i] + L" | Presses: " + std::to_wstring(++view.rawMouseEvents)); } } constexpr int MouseVolumeStepField = 759; constexpr int MouseVolumeStepSpin = 760, MouseVolumeStepLabel = 761; constexpr int MouseActionEditorHeight = 194, CompactMouseActionEditorHeight = 138, MaxEditedMouseVolumeStep = 50; void layoutMouseActionFields(HWND w, bool volume, bool extraRow) { auto &layout = *windowLayoutState(w); const auto combo = GetDlgItem(w, 734); const auto buttons = GetDlgItem(w, IDOK); const int height = extraRow ? MouseActionEditorHeight : CompactMouseActionEditorHeight; const int offset = height - layout.height; bool footer = false; for (auto &[child, rect] : layout.controls) { if (child == combo) rect.right = volume ? 368 : 462; if (child == buttons) footer = true; if (footer) OffsetRect(&rect, 0, offset); } if (offset) { RECT frame{}; GetWindowRect(w, &frame); layout.height = height; fitFixedClient(w, layout, frame); } arrangeWindow(w); } void updateMouseActionFields(HWND w) { const auto index = SendDlgItemMessageW(w, 734, CB_GETCURSEL, 0, 0); const auto type = SendDlgItemMessageW(w, 734, CB_GETITEMDATA, index, 0); const bool shortcut = type == static_cast(MouseActionType::Shortcut); const bool launch = type == static_cast(MouseActionType::Launch); const bool volume = isMouseVolumeStep(static_cast(type)); ShowWindow(GetDlgItem(w, 747), shortcut ? SW_SHOWNA : SW_HIDE); ShowWindow(GetDlgItem(w, 742), launch ? SW_SHOWNA : SW_HIDE); ShowWindow(GetDlgItem(w, 743), launch ? SW_SHOWNA : SW_HIDE); ShowWindow(GetDlgItem(w, MouseVolumeStepField), volume ? SW_SHOWNA : SW_HIDE); ShowWindow(GetDlgItem(w, MouseVolumeStepSpin), volume ? SW_SHOWNA : SW_HIDE); ShowWindow(GetDlgItem(w, MouseVolumeStepLabel), volume ? SW_SHOWNA : SW_HIDE); ShowWindow(GetDlgItem(w, 744), shortcut || launch ? SW_SHOWNA : SW_HIDE); value(w, 744, shortcut ? L"Click the field and press the key combination" : launch ? L"Application (e.g. calc.exe)" : L""); layoutMouseActionFields(w, volume, shortcut || launch); } struct MouseActionEditor { HWND parent{}; SettingsView *view{}; std::wstring profile; int event{}; }; struct MouseProfileEditor { std::wstring previous; std::set applications; HWND pickerOutline{}; }; constexpr int ActionApplications = 748, ActionApplicationName = 749, AddActionApp = 750, RemoveActionApp = 751; constexpr int BrowseActionApp = 752; constexpr int PickActionApp = 753; constexpr int AddMouseProfile = 754, MouseProfileName = 755; void drawActionApplicationIcon(HWND editor, const DRAWITEMSTRUCT &item) { RECT bounds = item.rcItem; const bool pressed = (item.itemState & ODS_SELECTED) != 0; const bool disabled = (item.itemState & ODS_DISABLED) != 0; DrawFrameControl(item.hDC, &bounds, DFC_BUTTON, DFCS_BUTTONPUSH | (pressed ? DFCS_PUSHED : 0) | (disabled ? DFCS_INACTIVE : 0)); const int offset = pressed ? scale(editor, 1) : 0; const int x = (bounds.left + bounds.right) / 2 + offset; const int y = (bounds.top + bounds.bottom) / 2 + offset; const int half = scale(editor, 6); const int weight = std::max(1, scale(editor, 2)); HBRUSH ink = GetSysColorBrush(disabled ? COLOR_GRAYTEXT : COLOR_BTNTEXT); if (item.CtlID == BrowseActionApp) { RECT folder{x - half - 1, y - half + weight, x + half + 1, y + half}; RECT tab{folder.left, folder.top - weight, x, folder.top}; FrameRect(item.hDC, &folder, ink); FillRect(item.hDC, &tab, ink); } else if (item.CtlID == PickActionApp) { RECT target{x - half, y - half, x + half + 1, y + half + 1}; RECT horizontal{x - half - weight, y, x + half + weight + 1, y + 1}; RECT vertical{x, y - half - weight, x + 1, y + half + weight + 1}; const auto previousBrush = SelectObject(item.hDC, GetStockObject(HOLLOW_BRUSH)); SetDCPenColor(item.hDC, GetSysColor(disabled ? COLOR_GRAYTEXT : COLOR_BTNTEXT)); const auto previousPen = SelectObject(item.hDC, GetStockObject(DC_PEN)); Ellipse(item.hDC, target.left, target.top, target.right, target.bottom); SelectObject(item.hDC, previousPen); SelectObject(item.hDC, previousBrush); FillRect(item.hDC, &horizontal, ink); FillRect(item.hDC, &vertical, ink); } else if (item.CtlID == RenameMouseProfile) { POINT pencil[]{{x - half, y + half}, {x - half + weight, y + 1}, {x + half - weight, y - half}, {x + half, y - half + weight}, {x - 1, y + half - weight}}; const auto brush = SelectObject(item.hDC, ink); const auto pen = SelectObject(item.hDC, GetStockObject(NULL_PEN)); Polygon(item.hDC, pencil, static_cast(std::size(pencil))); SelectObject(item.hDC, pen); SelectObject(item.hDC, brush); } else if (item.CtlID == DeleteMouseProfile) { RECT bin{x - half + 2, y - half + weight, x + half - 1, y + half}; RECT lid{x - half, y - half, x + half + 1, y - half + 1}; FrameRect(item.hDC, &bin, ink); FillRect(item.hDC, &lid, ink); } else { RECT horizontal{x - half, y - weight / 2, x + half, y - weight / 2 + weight}; FillRect(item.hDC, &horizontal, ink); } if (item.CtlID == AddActionApp || item.CtlID == AddMouseProfile || item.CtlID == AddMouseEvent) { RECT vertical{x - weight / 2, y - half, x - weight / 2 + weight, y + half}; FillRect(item.hDC, &vertical, ink); } if ((item.itemState & ODS_FOCUS) && !(item.itemState & ODS_NOFOCUSRECT)) { InflateRect(&bounds, -scale(editor, 4), -scale(editor, 4)); DrawFocusRect(item.hDC, &bounds); } } void addActionApplicationTooltip(HWND tips, HWND editor, int id, const wchar_t *text) { TOOLINFOW tool{sizeof(tool), TTF_IDISHWND | TTF_SUBCLASS, editor, reinterpret_cast(GetDlgItem(editor, id))}; tool.lpszText = const_cast(text); SendMessageW(tips, TTM_ADDTOOLW, 0, reinterpret_cast(&tool)); } void fillActionApplications(HWND w, const MouseProfileEditor &editor) { SendDlgItemMessageW(w, ActionApplications, LB_RESETCONTENT, 0, 0); for (const auto &name : editor.applications) { const auto label = name + L".exe"; SendDlgItemMessageW(w, ActionApplications, LB_ADDSTRING, 0, reinterpret_cast(label.c_str())); } } void addActionApplication(HWND w, MouseProfileEditor &editor) { const auto name = applicationName(trimApplicationText(value(w, ActionApplicationName))); if (!validMouseApplication(name)) throw std::runtime_error("Enter a valid application executable name."); if (editor.applications.size() >= 1000 && !editor.applications.contains(name)) throw std::runtime_error("Too many applications in this profile."); editor.applications.insert(name); fillActionApplications(w, editor); value(w, ActionApplicationName, L""); } std::wstring actionApplicationExecutable(std::filesystem::path path) { if (lower(path.extension().wstring()) == L".lnk") { Microsoft::WRL::ComPtr link; Microsoft::WRL::ComPtr file; wchar_t target[32768]{}; if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&link))) || FAILED(link.As(&file)) || FAILED(file->Load(path.c_str(), STGM_READ)) || link->GetPath(target, static_cast(std::size(target)), nullptr, 0) != S_OK) throw std::runtime_error("Could not read the shortcut target."); path = target; } if (lower(path.extension().wstring()) != L".exe") throw std::runtime_error("Choose an executable or a shortcut that points to one."); return path.filename().wstring(); } void browseActionApplication(HWND w, MouseProfileEditor &editor) { wchar_t path[32768]{}; OPENFILENAMEW picker{sizeof(picker)}; picker.hwndOwner = w; picker.lpstrFilter = L"Applications and shortcuts (*.exe;*.lnk)\0*.exe;*.lnk\0"; picker.lpstrFile = path; picker.nMaxFile = static_cast(std::size(path)); picker.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_NODEREFERENCELINKS; if (!GetOpenFileNameW(&picker)) return; value(w, ActionApplicationName, actionApplicationExecutable(path)); addActionApplication(w, editor); } void saveMouseAction(HWND w, MouseActionEditor &editor) { const auto choice = SendDlgItemMessageW(w, 734, CB_GETCURSEL, 0, 0); if (choice == CB_ERR) throw std::runtime_error("Choose an action."); const auto type = SendDlgItemMessageW(w, 734, CB_GETITEMDATA, choice, 0); const auto &profile = editor.profile; const auto *event = MouseEvents[editor.event]; Settings check; check.mouseBindings = editor.view->draft.mouseBindings; check.customMouseEvents = editor.view->draft.customMouseEvents; check.disabledMouseProfiles = editor.view->draft.disabledMouseProfiles; check.mouseProfileApplications = editor.view->draft.mouseProfileApplications; if (type < 0 || (type == static_cast(MouseActionType::Standard) && profile == GlobalMouseProfile)) { if (auto found = check.mouseBindings.find(profile); found != check.mouseBindings.end()) { found->second.erase(event); if (found->second.empty() && !isNamedMouseProfile(profile)) check.mouseBindings.erase(found); } } else { MouseAction action{static_cast(type)}; if (action.type == MouseActionType::Shortcut) { auto shortcut = Hotkey::parse(value(w, 747)); if (!shortcut || !shortcut->key) throw std::runtime_error("Click the field and press a key combination."); action.shortcut = *shortcut; } if (action.type == MouseActionType::Launch) action.executable = trimApplicationText(value(w, 742)); if (isMouseVolumeStep(action.type)) { const auto text = value(w, MouseVolumeStepField); if (text.empty() || text.size() > 3 || text.find_first_not_of(L"0123456789") != text.npos) throw std::runtime_error("Enter a volume step from 1 to 50."); action.volumeStep = std::stoi(text); const auto *previous = mouseBinding(editor.view->draft, profile, editor.event); const bool unchanged = previous && previous->type == action.type && previous->volumeStep == action.volumeStep; if (action.volumeStep < 1 || (action.volumeStep > MaxEditedMouseVolumeStep && !unchanged)) throw std::runtime_error("Enter a volume step from 1 to 50."); } if (const auto *previous = mouseBinding(editor.view->draft, profile, editor.event)) action.applications = previous->applications; check.mouseBindings[profile][event] = std::move(action); } validateSettings(check); editor.view->draft.mouseBindings = std::move(check.mouseBindings); fillMouseEvents(editor.parent, *editor.view, editor.event); } LRESULT CALLBACK mouseActionEditorProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) { auto *editor = reinterpret_cast(GetWindowLongPtrW(w, GWLP_USERDATA)); if (!editor) return DefWindowProcW(w, msg, wp, lp); try { if (msg == WM_COMMAND) { switch (LOWORD(wp)) { case 734: if (HIWORD(wp) == CBN_SELCHANGE) updateMouseActionFields(w); return 0; case 743: { wchar_t path[32768]{}; OPENFILENAMEW picker{sizeof(picker)}; picker.hwndOwner = w; picker.lpstrFilter = L"Applications (*.exe)\0*.exe\0"; picker.lpstrFile = path; picker.nMaxFile = static_cast(std::size(path)); picker.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR; if (GetOpenFileNameW(&picker)) value(w, 742, path); return 0; } case IDOK: saveMouseAction(w, *editor); DestroyWindow(w); return 0; case IDCANCEL: DestroyWindow(w); return 0; } } if (msg == WM_CLOSE) { DestroyWindow(w); return 0; } if (msg == WM_NCDESTROY) { auto parent = editor->parent; SetWindowLongPtrW(w, GWLP_USERDATA, 0); disposeFont(w); delete editor; if (IsWindow(parent)) { EnableWindow(parent, TRUE); SetForegroundWindow(parent); SetFocus(GetDlgItem(parent, 733)); } } } catch (const std::exception &e) { error(w, e); } return DefWindowProcW(w, msg, wp, lp); } void showMouseActionEditor(HWND parent, SettingsView &view, int event) { if (!mouseEventEnabled(view.draft, event) || !IsWindowEnabled(parent)) return; const auto profile = selectedMouseProfile(parent, view); const bool application = profile != GlobalMouseProfile; const auto *action = mouseBinding(view.draft, profile, event); const auto title = std::wstring(L"Edit mouse action"); HWND w = makeWindow(*view.app, L"KSwitcher.MouseAction", title.c_str(), mouseActionEditorProc, 480, MouseActionEditorHeight, 0, false, parent); auto *editor = new MouseActionEditor{parent, &view, profile, event}; SetWindowLongPtrW(w, GWLP_USERDATA, reinterpret_cast(editor)); label(w, mouseEventName(view.draft, event).append(L" · ").append(mouseProfileName(profile)).c_str(), 18, 14, 444); label(w, L"&Action", 18, 43, 330); windowLayoutState(w)->fixedClient = true; windowLayoutState(w)->rowCombo = control(w, L"COMBOBOX", L"", WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST, 734, 18, 65, 444, 215); if (application) { SendDlgItemMessageW(w, 734, CB_ADDSTRING, 0, reinterpret_cast(L"Inherit from Global")); SendDlgItemMessageW(w, 734, CB_SETITEMDATA, 0, -1); } for (size_t type = 0; type < std::size(MouseActions); ++type) { const auto row = SendDlgItemMessageW(w, 734, CB_ADDSTRING, 0, reinterpret_cast(MouseActions[type])); if (row >= 0) SendDlgItemMessageW(w, 734, CB_SETITEMDATA, row, type); } control(w, L"STATIC", L"", 0, 744, 18, 101, 444, 21); hotkeyEdit(*view.app, w, 747, 18, 125, 444); edit(w, 742, 18, 125, 334); button(w, L"&Browse…", 743, 362, 124, 100); control(w, L"STATIC", L"Step (%)", 0, MouseVolumeStepLabel, 380, 43, 82, 22); auto stepField = edit(w, MouseVolumeStepField, 380, 65, 64, 25, ES_NUMBER); SendMessageW(stepField, EM_SETLIMITTEXT, 3, 0); INITCOMMONCONTROLSEX spinControls{sizeof(spinControls), ICC_UPDOWN_CLASS}; InitCommonControlsEx(&spinControls); HWND spin = control(w, UPDOWN_CLASSW, L"", UDS_SETBUDDYINT | UDS_ARROWKEYS | UDS_NOTHOUSANDS, MouseVolumeStepSpin, 444, 65, 18, 25); SendMessageW(spin, UDM_SETBUDDY, reinterpret_cast(stepField), 0); SendMessageW(spin, UDM_SETRANGE32, 1, MaxEditedMouseVolumeStep); SetDlgItemInt(w, MouseVolumeStepField, action && isMouseVolumeStep(action->type) ? action->volumeStep : DefaultMouseVolumeStep, FALSE); button(w, L"&Apply", IDOK, 248, 154, 100); button(w, L"Cancel", IDCANCEL, 362, 154, 100); SendDlgItemMessageW(w, 734, CB_SETCURSEL, action ? static_cast(action->type) + application : 0, 0); if (action && action->type == MouseActionType::Shortcut) value(w, 747, action->shortcut.format()); if (action && action->type == MouseActionType::Launch) value(w, 742, action->executable); updateMouseActionFields(w); EnableWindow(parent, FALSE); showRequestedWindow(w); SetFocus(GetDlgItem(w, 734)); } constexpr int EditMouseEvent = 735, ResetMouseEvent = 746; constexpr auto MouseEventHint = L"Double-click to edit. Right-click for more actions."; int mouseProfileRowAt(HWND list, POINT point) { LVHITTESTINFO hit{}; hit.pt = point; const int item = ListView_SubItemHitTest(list, &hit); if (item >= 0) return item; RECT client{}; if (!GetClientRect(list, &client) || !PtInRect(&client, point)) return -1; const int top = ListView_GetTopIndex(list); const int end = std::min(ListView_GetItemCount(list), top + ListView_GetCountPerPage(list) + 1); for (int row = top; row < end; ++row) { RECT bounds{}; if (ListView_GetItemRect(list, row, &bounds, LVIR_BOUNDS) && point.y >= bounds.top && point.y < bounds.bottom) return row; } return -1; } void showMouseListMenu(HWND list, POINT point, bool profiles) { HWND w = GetParent(list); int event; if (point.x == -1 && point.y == -1) { event = ListView_GetNextItem(list, -1, LVNI_SELECTED); RECT row{}; if (event < 0 || !ListView_GetItemRect(list, event, &row, LVIR_BOUNDS)) return; point = {row.left, row.bottom}; ClientToScreen(list, &point); } else { LVHITTESTINFO hit{}; hit.pt = point; ScreenToClient(list, &hit.pt); event = ListView_SubItemHitTest(list, &hit); if (event < 0 && profiles) return; } auto &view = *reinterpret_cast(GetWindowLongPtrW(w, GWLP_USERDATA)); HMENU menu = CreatePopupMenu(); if (!menu) return; if (profiles) { selectMouseProfile(w, event); const UINT flags = MF_STRING | (isNamedMouseProfile(selectedMouseProfile(w, view)) ? MF_ENABLED : MF_GRAYED); AppendMenuW(menu, flags, RenameMouseProfile, L"&Edit…"); AppendMenuW(menu, flags, DeleteMouseProfile, L"&Delete…"); } else { event = mouseEventAtRow(list, event); if (event >= 0) selectMouseEvent(w, event); const bool canReset = event >= 0 && mouseBinding(view.draft, selectedMouseProfile(w, view), event) != nullptr; AppendMenuW(menu, MF_STRING, AddMouseEvent, L"+ &Add mouse event…"); AppendMenuW(menu, MF_STRING | (event >= 0 ? MF_ENABLED : MF_GRAYED), EditMouseEvent, L"✎ &Edit…"); AppendMenuW(menu, MF_STRING | (canReset ? MF_ENABLED : MF_GRAYED), ResetMouseEvent, L"&Reset to default"); } const auto command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, 0, w, nullptr); DestroyMenu(menu); if (command) SendMessageW(w, WM_COMMAND, command, 0); } LRESULT CALLBACK mouseEventListProc(HWND list, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR id, DWORD_PTR) { const bool profiles = GetDlgCtrlID(list) == 731; if (profiles && msg == WM_LBUTTONDBLCLK) { const int row = mouseProfileRowAt(list, {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)}); if (row >= 0) { const auto owner = GetParent(list); selectMouseProfile(owner, row); SendMessageW(owner, WM_COMMAND, RenameMouseProfile, 0); } return 0; } if (msg == WM_GETDLGCODE && wp == VK_RETURN) return DLGC_WANTMESSAGE; if (msg == WM_KEYDOWN && (wp == VK_RETURN || wp == VK_F2)) { SendMessageW(GetParent(list), WM_COMMAND, profiles ? RenameMouseProfile : EditMouseEvent, 0); return 0; } if (msg == WM_CONTEXTMENU) { showMouseListMenu(list, {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)}, profiles); return 0; } if (msg == WM_NCDESTROY) RemoveWindowSubclass(list, mouseEventListProc, id); return DefSubclassProc(list, msg, wp, lp); } LRESULT CALLBACK mouseProfileNameProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) { const HWND owner = GetWindow(w, GW_OWNER); auto *editor = reinterpret_cast(GetWindowLongPtrW(w, GWLP_USERDATA)); if (!editor) return DefWindowProcW(w, msg, wp, lp); try { if (msg == AppWindowPicked) { if (lp == 1) { const auto name = mouseApplicationName(reinterpret_cast(wp)); if (name.empty()) throw std::runtime_error("Drop the crosshair onto an application window."); value(w, ActionApplicationName, name); addActionApplication(w, *editor); } return 0; } if (msg == WM_DRAWITEM && (wp == AddActionApp || wp == RemoveActionApp || wp == BrowseActionApp || wp == PickActionApp)) { drawActionApplicationIcon(w, *reinterpret_cast(lp)); return TRUE; } if (msg == WM_COMMAND) { if (LOWORD(wp) == BrowseActionApp) { browseActionApplication(w, *editor); return 0; } if (LOWORD(wp) == AddActionApp) { addActionApplication(w, *editor); return 0; } if (LOWORD(wp) == RemoveActionApp) { const auto row = SendDlgItemMessageW(w, ActionApplications, LB_GETCURSEL, 0, 0); if (row >= 0 && static_cast(row) < editor->applications.size()) { editor->applications.erase(std::next(editor->applications.begin(), row)); fillActionApplications(w, *editor); } return 0; } } } catch (const std::exception &e) { error(w, e); return 0; } if (msg == WM_COMMAND && LOWORD(wp) == IDOK) { try { auto &view = *reinterpret_cast(GetWindowLongPtrW(owner, GWLP_USERDATA)); const auto &previous = editor->previous; const auto name = trimApplicationText(value(w, MouseProfileName)); const auto duplicate = std::any_of(view.mouseProfiles.begin(), view.mouseProfiles.end(), [&](const auto &profile) { return profile != previous && lower(mouseProfileName(profile)) == lower(name); }); if (duplicate) throw std::runtime_error("A profile with that name already exists."); const auto key = std::wstring(NamedMouseProfilePrefix) + name; Settings check; check.mouseBindings = view.draft.mouseBindings; check.customMouseEvents = view.draft.customMouseEvents; check.disabledMouseProfiles = view.draft.disabledMouseProfiles; check.mouseProfileApplications = view.draft.mouseProfileApplications; if (!previous.empty() && key != previous) { auto profile = check.mouseBindings.extract(previous); if (profile.empty()) throw std::runtime_error("The profile no longer exists."); profile.key() = key; check.mouseBindings.insert(std::move(profile)); if (check.disabledMouseProfiles.erase(previous)) check.disabledMouseProfiles.insert(key); } else check.mouseBindings.try_emplace(key); check.mouseProfileApplications.erase(previous); check.mouseProfileApplications[key] = editor->applications; for (auto &[event, action] : check.mouseBindings.at(key)) action.applications.clear(); validateSettings(check); view.draft.mouseBindings = std::move(check.mouseBindings); view.draft.disabledMouseProfiles = std::move(check.disabledMouseProfiles); view.draft.mouseProfileApplications = std::move(check.mouseProfileApplications); fillMouseProfiles(owner, view); const auto selected = std::find(view.mouseProfiles.begin(), view.mouseProfiles.end(), key); selectMouseProfile(owner, static_cast(selected - view.mouseProfiles.begin())); fillMouseEvents(owner, view); updateSettingsState(owner, &view); DestroyWindow(w); } catch (const std::exception &e) { error(w, e); } return 0; } if (msg == WM_CLOSE || (msg == WM_COMMAND && LOWORD(wp) == IDCANCEL)) { DestroyWindow(w); return 0; } if (msg == WM_NCDESTROY) { SetWindowLongPtrW(w, GWLP_USERDATA, 0); delete editor; disposeFont(w); EnableWindow(owner, TRUE); SetForegroundWindow(owner); } return DefWindowProcW(w, msg, wp, lp); } void showMouseProfileName(HWND owner, SettingsView &view, bool renaming) { HWND w = makeWindow(*view.app, L"KSwitcher.MouseProfileName", renaming ? L"Edit profile" : L"Add profile", mouseProfileNameProc, 480, 296, 0, false, owner); windowLayoutState(w)->fixedClient = true; const auto previous = renaming ? selectedMouseProfile(owner, view) : std::wstring{}; auto *editor = new MouseProfileEditor{previous, renaming ? profileApplications(view.draft, previous) : std::set{}}; SetWindowLongPtrW(w, GWLP_USERDATA, reinterpret_cast(editor)); label(w, L"&Name", 18, 12, 444); edit(w, MouseProfileName, 18, 36, 444); if (renaming) value(w, MouseProfileName, mouseProfileName(previous)); label(w, L"Applications (empty list means all applications)", 18, 75, 444); control(w, L"LISTBOX", L"", WS_TABSTOP | WS_BORDER | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, ActionApplications, 18, 100, 404, 96); control(w, L"BUTTON", L"&Remove application", WS_TABSTOP | BS_OWNERDRAW, RemoveActionApp, 430, 100, 32, 28); HWND appName = edit(w, ActionApplicationName, 18, 207, 324); SendMessageW(appName, EM_SETCUEBANNER, FALSE, reinterpret_cast(L"Application executable (e.g. chrome.exe)")); HWND picker = control(w, L"BUTTON", L"Drag to application window", WS_TABSTOP | BS_OWNERDRAW, PickActionApp, 350, 205, 32, 28); SetWindowSubclass(picker, appWindowPickerProc, 1, reinterpret_cast(&editor->pickerOutline)); control(w, L"BUTTON", L"&Browse for application", WS_TABSTOP | BS_OWNERDRAW, BrowseActionApp, 390, 205, 32, 28); control(w, L"BUTTON", L"Add a&pplication", WS_TABSTOP | BS_OWNERDRAW, AddActionApp, 430, 205, 32, 28); HWND tips = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, nullptr, WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, w, nullptr, view.app->instance, nullptr); addActionApplicationTooltip(tips, w, RemoveActionApp, L"Remove application"); addActionApplicationTooltip(tips, w, AddActionApp, L"Add application"); addActionApplicationTooltip(tips, w, BrowseActionApp, L"Browse for an executable or shortcut"); addActionApplicationTooltip(tips, w, PickActionApp, L"Drag to an application window"); fillActionApplications(w, *editor); control(w, L"BUTTON", renaming ? L"&Apply" : L"&Add", WS_TABSTOP | BS_DEFPUSHBUTTON, IDOK, 248, 251, 100, 28); button(w, L"Cancel", IDCANCEL, 362, 251, 100); EnableWindow(owner, FALSE); showRequestedWindow(w); SetFocus(GetDlgItem(w, MouseProfileName)); } struct AddMouseEventView { HWND parent{}; SettingsView *settings{}; int signal{-1}, lastButton{-1}; DWORD lastPress{}; POINT lastPoint{}; bool rawRegistered{}; HHOOK mouseHook{}; int rawWheel{-1}, hookWheel{-1}; ULONGLONG rawWheelAt{}, hookWheelAt{}; }; constexpr UINT CapturedMouseWheel = WM_APP + 86; constexpr UINT_PTR MouseSignalTimeout = 1; HWND mouseEventCaptureWindow{}; bool inMouseEventCapture(HWND w, POINT point) { RECT area{}; return GetWindowRect(GetDlgItem(w, MouseEventCapture), &area) && PtInRect(&area, point); } LRESULT CALLBACK captureMouseWheelProc(int code, WPARAM wp, LPARAM lp) { if (code == HC_ACTION && mouseEventCaptureWindow && GetForegroundWindow() == mouseEventCaptureWindow) { const auto &mouse = *reinterpret_cast(lp); const int signal = mouseWheelEvent(static_cast(wp), static_cast(HIWORD(mouse.mouseData))); if (signal >= 0 && !(mouse.flags & LLMHF_INJECTED) && inMouseEventCapture(mouseEventCaptureWindow, mouse.pt)) PostMessageW(mouseEventCaptureWindow, CapturedMouseWheel, signal, 0); } return CallNextHookEx(nullptr, code, wp, lp); } void showCapturedMouseSignal(HWND w, AddMouseEventView &view, int signal) { view.signal = signal; const auto &draft = view.settings->draft; const bool duplicate = mouseEventEnabled(draft, signal); EnableWindow(GetDlgItem(w, IDOK), !duplicate); if (duplicate) { selectMouseEvent(view.parent, signal); value(w, MouseEventStatus, L"Already in the list: " + mouseEventName(draft, signal)); value(w, IDCANCEL, L"Close"); } else { value(w, MouseEventStatus, std::wstring(L"Detected: ") + MouseEvents[signal]); if (value(w, MouseEventNameField).empty()) value(w, MouseEventNameField, MouseEvents[signal]); value(w, IDCANCEL, L"Cancel"); } } void matchCapturedMouseWheel(HWND w, AddMouseEventView &view, ULONGLONG now) { if (view.rawWheel >= 0 && view.rawWheel == view.hookWheel && now - view.rawWheelAt <= 500 && now - view.hookWheelAt <= 500) { showCapturedMouseSignal(w, view, view.rawWheel); view.rawWheel = view.hookWheel = -1; KillTimer(w, MouseSignalTimeout); } } void readNewMouseSignal(HWND w, AddMouseEventView &view, HRAWINPUT handle) { POINT point{}; if (!GetCursorPos(&point) || !inMouseEventCapture(w, point)) return; RAWINPUT input{}; UINT size = sizeof(input); const auto bytes = GetRawInputData(handle, RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER)); if (bytes == UINT(-1) || bytes < offsetof(RAWINPUT, data) + sizeof(RAWMOUSE) || input.header.dwType != RIM_TYPEMOUSE || !input.header.hDevice) return; int signal = rawMouseSignal(input.data.mouse); if (signal < 0) return; if (signal >= BuiltInMouseEventCount) { view.signal = -1; EnableWindow(GetDlgItem(w, IDOK), FALSE); value(w, MouseEventStatus, view.mouseHook ? L"Checking whether Windows delivers this wheel signal…" : L"Windows mouse hook is unavailable."); view.rawWheel = signal; view.rawWheelAt = GetTickCount64(); if (view.mouseHook) SetTimer(w, MouseSignalTimeout, 550, nullptr); matchCapturedMouseWheel(w, view, view.rawWheelAt); return; } view.rawWheel = view.hookWheel = -1; KillTimer(w, MouseSignalTimeout); const DWORD now = static_cast(GetMessageTime()); const bool second = signal < 3 && view.lastButton == signal && doubleMouseClick(view.lastPoint, point, now - view.lastPress); view.lastButton = second ? -1 : signal; view.lastPress = now; view.lastPoint = point; if (second) signal += 5; showCapturedMouseSignal(w, view, signal); } LRESULT CALLBACK addMouseEventProc(HWND w, UINT msg, WPARAM wp, LPARAM lp) { auto *view = reinterpret_cast(GetWindowLongPtrW(w, GWLP_USERDATA)); if (!view) return DefWindowProcW(w, msg, wp, lp); try { if (msg == WM_INPUT) readNewMouseSignal(w, *view, reinterpret_cast(lp)); else if (msg == CapturedMouseWheel) { view->hookWheel = static_cast(wp); view->hookWheelAt = GetTickCount64(); matchCapturedMouseWheel(w, *view, view->hookWheelAt); return 0; } else if (msg == WM_TIMER && wp == MouseSignalTimeout) { KillTimer(w, MouseSignalTimeout); view->rawWheel = view->hookWheel = -1; value(w, MouseEventStatus, L"Raw Input saw this signal, but the Windows mouse hook did not."); return 0; } else if (msg == WM_COMMAND) { if (LOWORD(wp) == IDOK) { const int signal = view->signal; auto &settings = *view->settings; if (signal < BuiltInMouseEventCount || signal >= static_cast(std::size(MouseEvents)) || mouseEventEnabled(settings.draft, signal)) return 0; auto candidate = settings.draft; candidate.customMouseEvents.emplace(MouseEvents[signal], trimApplicationText(value(w, MouseEventNameField))); validateSettings(candidate); settings.draft.customMouseEvents = std::move(candidate.customMouseEvents); fillMouseEvents(view->parent, settings, signal); DestroyWindow(w); return 0; } if (LOWORD(wp) == IDCANCEL) { DestroyWindow(w); return 0; } } else if (msg == WM_CLOSE) { DestroyWindow(w); return 0; } else if (msg == WM_DPICHANGED) { resizeForDpi(w, wp, lp); return 0; } else if (msg == WM_NCDESTROY) { const HWND parent = view->parent; mouseEventCaptureWindow = nullptr; if (view->mouseHook) UnhookWindowsHookEx(view->mouseHook); if (view->rawRegistered) { if (IsWindow(parent) && view->settings->rawMouseRegistered) registerMouseTestInput(parent); else registerMouseTestInput(nullptr); } SetWindowLongPtrW(w, GWLP_USERDATA, 0); delete view; disposeFont(w); if (IsWindow(parent)) { EnableWindow(parent, TRUE); SetForegroundWindow(parent); SetFocus(GetDlgItem(parent, 733)); } } } catch (const std::exception &e) { error(w, e); } return DefWindowProcW(w, msg, wp, lp); } void showAddMouseEvent(HWND parent, SettingsView &settings) { if (!IsWindowEnabled(parent)) return; HWND w = makeWindow(*settings.app, L"KSwitcher.AddMouseEvent", L"Add mouse event", addMouseEventProc, 500, 275); auto *view = new AddMouseEventView{parent, &settings}; SetWindowLongPtrW(w, GWLP_HWNDPARENT, reinterpret_cast(parent)); SetWindowLongPtrW(w, GWLP_USERDATA, reinterpret_cast(view)); label(w, L"&Name", 18, 16, 464); edit(w, MouseEventNameField, 18, 39, 464); control(w, L"STATIC", L"Press a mouse button or move the wheel here", WS_BORDER | SS_CENTER | SS_CENTERIMAGE, MouseEventCapture, 18, 78, 464, 92); view->rawRegistered = registerMouseTestInput(w); mouseEventCaptureWindow = w; view->mouseHook = SetWindowsHookExW(WH_MOUSE_LL, captureMouseWheelProc, settings.app->instance, 0); control(w, L"STATIC", view->rawRegistered ? L"Waiting for a supported mouse signal…" : L"Windows Raw Input is unavailable.", 0, MouseEventStatus, 18, 181, 464, 22); label(w, L"Buttons that send no Windows mouse event cannot be added.", 18, 205, 464); control(w, L"BUTTON", L"&Add", WS_TABSTOP | BS_DEFPUSHBUTTON, IDOK, 270, 239, 100, 28); button(w, L"Cancel", IDCANCEL, 382, 239, 100); EnableWindow(GetDlgItem(w, IDOK), FALSE); EnableWindow(parent, FALSE); showRequestedWindow(w); SetFocus(GetDlgItem(w, MouseEventNameField)); } bool handleMouseBindings(HWND w, SettingsView &view, WPARAM wp) { const int id = LOWORD(wp); if (id == AddMouseEvent) { showAddMouseEvent(w, view); return true; } if (id == AddMouseProfile) { showMouseProfileName(w, view, false); return true; } if (id == RenameMouseProfile || id == DeleteMouseProfile) { const auto profile = selectedMouseProfile(w, view); if (!isNamedMouseProfile(profile)) return true; if (id == RenameMouseProfile) showMouseProfileName(w, view, true); else if (MessageBoxW(w, (L"Delete " + mouseProfileName(profile) + L" and all its actions?").c_str(), L"Delete profile", MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES) { view.draft.disabledMouseProfiles.erase(profile); view.draft.mouseBindings.erase(profile); view.draft.mouseProfileApplications.erase(profile); fillMouseProfiles(w, view); } return true; } if (id != EditMouseEvent && id != ResetMouseEvent) return false; const HWND list = GetDlgItem(w, 733); const int event = mouseEventAtRow(list, ListView_GetNextItem(list, -1, LVNI_SELECTED)); if (event < 0) return true; if (id == EditMouseEvent) showMouseActionEditor(w, view, event); else if (auto profile = view.draft.mouseBindings.find(selectedMouseProfile(w, view)); profile != view.draft.mouseBindings.end()) { profile->second.erase(MouseEvents[event]); if (profile->second.empty() && !isNamedMouseProfile(profile->first)) view.draft.mouseBindings.erase(profile); fillMouseEvents(w, view, event); } return true; } void scaleMouseTableColumns(HWND w, int previousDpi, int nextDpi) { for (int id : {731, 733}) { const auto list = GetDlgItem(w, id); const int count = Header_GetItemCount(ListView_GetHeader(list)); for (int column = 0; column < count; ++column) ListView_SetColumnWidth(list, column, MulDiv(ListView_GetColumnWidth(list, column), nextDpi, previousDpi)); } } void createMousePage(HWND w, SettingsView &view) { constexpr int profilesLeft = 18, actionsLeft = 369, tableWidth = 333; constexpr int profilesRight = profilesLeft + tableWidth; label(w, L"Profiles", 18, 64, 120); label(w, L"Mouse events", actionsLeft, 64, tableWidth - 30); control(w, L"BUTTON", L"Add mouse event", WS_TABSTOP | BS_OWNERDRAW, AddMouseEvent, actionsLeft + tableWidth - 24, 61, 24, 24); control(w, L"BUTTON", L"Delete profile", WS_TABSTOP | BS_OWNERDRAW, DeleteMouseProfile, profilesRight - 80, 61, 24, 24); control(w, L"BUTTON", L"Edit profile", WS_TABSTOP | BS_OWNERDRAW, RenameMouseProfile, profilesRight - 52, 61, 24, 24); control(w, L"BUTTON", L"Add profile", WS_TABSTOP | BS_OWNERDRAW, AddMouseProfile, profilesRight - 24, 61, 24, 24); HWND tips = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, nullptr, WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, w, nullptr, view.app->instance, nullptr); addActionApplicationTooltip(tips, w, AddMouseProfile, L"Add profile"); addActionApplicationTooltip(tips, w, RenameMouseProfile, L"Edit profile"); addActionApplicationTooltip(tips, w, DeleteMouseProfile, L"Delete profile"); addActionApplicationTooltip(tips, w, AddMouseEvent, L"Add mouse event"); constexpr int listTop = 90, listHeight = 224; INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_LISTVIEW_CLASSES}; InitCommonControlsEx(&controls); constexpr DWORD listStyle = WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER; HWND profiles = control(w, WC_LISTVIEWW, L"", listStyle, 731, profilesLeft, listTop, tableWidth, listHeight); SetWindowSubclass(profiles, mouseEventListProc, 3, 0); ListView_SetExtendedListViewStyle(profiles, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_LABELTIP | LVS_EX_CHECKBOXES); wchar_t nameTitle[] = L"Name", activeTitle[] = L"Active", applicationsTitle[] = L"Applications"; LVCOLUMNW profileColumn{LVCF_TEXT | LVCF_WIDTH}; profileColumn.pszText = activeTitle; profileColumn.cx = scale(w, 56); ListView_InsertColumn(profiles, 0, &profileColumn); profileColumn.pszText = nameTitle; profileColumn.cx = scale(w, 100); ListView_InsertColumn(profiles, 1, &profileColumn); profileColumn.pszText = applicationsTitle; profileColumn.cx = scale(w, tableWidth - 160); ListView_InsertColumn(profiles, 2, &profileColumn); int order[]{1, 0, 2}; ListView_SetColumnOrderArray(profiles, 3, order); HWND list = control(w, WC_LISTVIEWW, L"", WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER, 733, actionsLeft, listTop, tableWidth, listHeight); SetWindowSubclass(list, mouseEventListProc, 3, 0); ListView_SetExtendedListViewStyle(list, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_LABELTIP); wchar_t actionTitle[] = L"Action"; LVCOLUMNW column{LVCF_TEXT | LVCF_WIDTH}; column.pszText = nameTitle; column.cx = scale(w, 116); ListView_InsertColumn(list, 0, &column); column.pszText = actionTitle; column.cx = scale(w, tableWidth - 120); ListView_InsertColumn(list, 1, &column); control(w, L"STATIC", MouseEventHint, 0, 740, actionsLeft, 324, tableWidth, 22); control(w, L"BUTTON", L"Test mouse buttons here", WS_TABSTOP | BS_PUSHBUTTON, 737, profilesLeft, listTop + listHeight + 10, tableWidth, 56); SetWindowSubclass(GetDlgItem(w, 737), mouseButtonCaptureProc, 1, 0); control(w, L"STATIC", L"Window event: waiting for a press", 0, 738, actionsLeft, 370, tableWidth, 20); view.rawMouseRegistered = registerMouseTestInput(w); control(w, L"STATIC", view.rawMouseRegistered ? L"Raw Input: waiting for a press" : L"Raw Input: unavailable", 0, 739, actionsLeft, 394, tableWidth, 20); }