94 lines
3.8 KiB
C++
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];
|
|
}
|
|
}
|