Files
hrynco-kswitcher/src/chat_context.h
T

94 lines
3.8 KiB
C++

#pragma once
#include "platform.h"
#include <algorithm>
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<std::wstring> chatCycle(const Settings &settings, const std::wstring &key,
const std::vector<Layout> &layouts)
{
auto found = settings.chatCycles.find(key);
if (key.empty() || found == settings.chatCycles.end()) return settings.cycle;
std::vector<std::wstring> 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<std::wstring> &cycle, const std::vector<Layout> &layouts,
HKL current, bool reverse)
{
std::vector<HKL> 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];
}
}