#pragma once #include "platform.h" #include namespace ks { struct ChatSwitchHook { bool pressed[256]{}; static bool candidate(UINT key, UINT mods) { bool shift = key == VK_SHIFT || key == VK_LSHIFT || key == VK_RSHIFT; bool alt = key == VK_MENU || key == VK_LMENU || key == VK_RMENU; return (key == VK_SPACE && (mods == MOD_WIN || mods == (MOD_WIN | MOD_SHIFT))) || (shift && mods == MOD_ALT) || (alt && mods == MOD_SHIFT); } // 1 forward, 2 backward, -1 consumed repeat/release, 0 ordinary input. int update(UINT key, bool down, UINT mods, bool enabled) { if (key >= 256) return 0; if (pressed[key]) { if (!down) pressed[key] = false; return -1; } if (!down || !enabled) return 0; if (!candidate(key, mods)) return 0; pressed[key] = true; return key == VK_SPACE && (mods & MOD_SHIFT) ? 2 : 1; } }; inline std::wstring chatTitle(HWND window) { wchar_t title[1024]{}; if (!window) return {}; int length = GetWindowTextW(window, title, std::size(title)); if (!length || length >= (int)std::size(title) - 1) return {}; return title; } inline std::wstring chatKey(std::wstring process, std::wstring title) { process = lower(process); if (process.ends_with(L".exe")) process.resize(process.size() - 4); while (!title.empty() && (title.front() == 0x200e || title.front() == 0x200f)) title.erase(0, 1); if (process == L"telegram") { // Telegram's unread badge is not part of the contact name. auto badge = title.rfind(L" \u2013 ("); if (badge != title.npos && title.back() == L')') { auto digits = title.substr(badge + 4, title.size() - badge - 5); if (!digits.empty() && digits.find_first_not_of(L"0123456789") == digits.npos) title.resize(badge); } if (title.empty() || title == L"Telegram" || title.starts_with(L"Telegram (")) return {}; } else if (process == L"ms-teams") { // Only the observed chat title format; meetings/search are not chats. const std::wstring prefix = L"Chat | ", suffix = L" | Microsoft Teams"; if (!title.starts_with(prefix) || !title.ends_with(suffix) || title.size() <= prefix.size() + suffix.size()) return {}; title = title.substr(prefix.size(), title.size() - prefix.size() - suffix.size()); if (title.empty()) return {}; } else return {}; return process + L"\n" + title; } inline std::wstring windowChatKey(HWND window) { return window ? chatKey(processName(window), chatTitle(window)) : L""; } inline std::vector chatCycle(const Settings &settings, const std::wstring &key, const std::vector &layouts) { auto found = settings.chatCycles.find(key); if (key.empty() || found == settings.chatCycles.end()) return settings.cycle; std::vector result; for (const auto &id : found->second) if (std::any_of(layouts.begin(), layouts.end(), [&](const Layout &l) { return l.id == id; })) result.push_back(id); return result.empty() ? settings.cycle : result; } inline HKL nextChatLayout(const std::vector &cycle, const std::vector &layouts, HKL current, bool reverse) { std::vector handles; for (const auto &id : cycle) for (const auto &layout : layouts) if (layout.id == id) handles.push_back(layout.handle); if (handles.empty()) return nullptr; auto found = std::find(handles.begin(), handles.end(), current); if (found == handles.end()) return handles.front(); auto next = (size_t(found - handles.begin()) + (reverse ? handles.size() - 1 : 1)) % handles.size(); return handles[next]; } }