Add event-driven Telegram Web K chat profiles for Chrome
This commit is contained in:
1 parent
15197da548
commit
c18148a574
8 files changed
+197
-6
No files matched your search
@@ -317,6 +317,14 @@ Telegram ігнорується. Teams підтримує зазначений
|
|||||||
Профілі локальні, входять до ручного експорту/імпорту, але не до
|
Профілі локальні, входять до ручного експорту/імпорту, але не до
|
||||||
хмарної синхронізації. Доступно з v0.6.48.
|
хмарної синхронізації. Доступно з v0.6.48.
|
||||||
|
|
||||||
|
|
||||||
|
### Telegram Web in Chrome — development build
|
||||||
|
|
||||||
|
Open a chat in Telegram Web K (`https://web.telegram.org/k/`) in Chrome, then use **Settings → Languages → Languages for a chat...** and select **Telegram Web: name**. Choose languages, then **Use languages → Save**. The same subset applies to Alt+Shift, Win+Space, Win+Shift+Space and Next layout. Web profiles are separate from desktop Telegram profiles. Same-name chats across browser profiles/accounts share a web profile.
|
||||||
|
|
||||||
|
Recognition uses Chrome accessibility: the address bar and active document must both identify a Telegram chat, then the visible header supplies the name. The app watches foreground/focus and address/header-change events; it does not use OCR, a polling timer, an extension, or message-history extraction. The address bar, home page and unsupported URLs do not match. Web A and other browsers have not been validated and are not advertised as supported. Page or accessibility changes can make a chat unavailable instead of guessing.
|
||||||
|
|
||||||
|
**Українською.** У дев-версії підтримано Telegram Web K у Chrome. Відкрий чат, потім **Settings → Languages → Languages for a chat...**, вибери **Telegram Web: ім’я**, мови та **Use languages → Save**. Набір діє для Alt+Shift, Win+Space, Win+Shift+Space і Next layout. Вебпрофілі окремі від Telegram Desktop; однакові назви в різних акаунтах/профілях браузера мають спільний вебпрофіль. Адреса й назва беруться через accessibility, без OCR, таймера, розширення та читання історії повідомлень. В адресному рядку, на головній сторінці й для непідтримуваних адрес профіль не діє. Web A та інші браузери не перевірені й не заявлені як підтримувані.
|
||||||
### Existing features
|
### Existing features
|
||||||
|
|
||||||
- Installed Windows keyboard layouts, configurable cycle/order and direct shortcuts; IMEs are switch-only.
|
- Installed Windows keyboard layouts, configurable cycle/order and direct shortcuts; IMEs are switch-only.
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot, HWND ser
|
|||||||
}
|
}
|
||||||
App::~App()
|
App::~App()
|
||||||
{
|
{
|
||||||
|
stopBrowserChatMonitor();
|
||||||
if (auto host = historyController(historyProcess)) PostMessageW(host, WM_CLOSE, 0, 0);
|
if (auto host = historyController(historyProcess)) PostMessageW(host, WM_CLOSE, 0, 0);
|
||||||
if (historyProcess) CloseHandle(historyProcess);
|
if (historyProcess) CloseHandle(historyProcess);
|
||||||
if (notificationRegistration) CloseHandle(notificationRegistration);
|
if (notificationRegistration) CloseHandle(notificationRegistration);
|
||||||
@@ -978,6 +979,7 @@ void App::switchChatLayout(bool reverse)
|
|||||||
{
|
{
|
||||||
struct Reset { std::atomic<bool> &busy; ~Reset() { busy = false; } } reset{busy};
|
struct Reset { std::atomic<bool> &busy; ~Reset() { busy = false; } } reset{busy};
|
||||||
auto valid = [&] { return GetForegroundWindow() == target && windowChatKey(target) == key; };
|
auto valid = [&] { return GetForegroundWindow() == target && windowChatKey(target) == key; };
|
||||||
|
if (key.starts_with(L"telegram-web\n") && readChromeChat(target) != key) return;
|
||||||
if (!valid()) return;
|
if (!valid()) return;
|
||||||
auto next = nextChatLayout(cycle, available, windowLayout(target), reverse);
|
auto next = nextChatLayout(cycle, available, windowLayout(target), reverse);
|
||||||
if (next) switchWindowLayout(target, next, valid);
|
if (next) switchWindowLayout(target, next, valid);
|
||||||
@@ -1275,6 +1277,7 @@ void App::transform(int command, int snippet)
|
|||||||
}
|
}
|
||||||
} reset{busy, window, preDeferred};
|
} reset{busy, window, preDeferred};
|
||||||
auto effectiveChat = requestedChat;
|
auto effectiveChat = requestedChat;
|
||||||
|
if (lower(processName(target)) == L"chrome") effectiveChat = readChromeChat(target);
|
||||||
config.cycle = chatCycle(config, effectiveChat, available);
|
config.cycle = chatCycle(config, effectiveChat, available);
|
||||||
auto start = GetTickCount64();
|
auto start = GetTickCount64();
|
||||||
std::wstring reason = L"cancelled-before-selection";
|
std::wstring reason = L"cancelled-before-selection";
|
||||||
@@ -2318,6 +2321,7 @@ int App::execute()
|
|||||||
// Resume pending OCR, including older images without a saved OCR state.
|
// Resume pending OCR, including older images without a saved OCR state.
|
||||||
scheduleHistoryOcr(settings);
|
scheduleHistoryOcr(settings);
|
||||||
addTray();
|
addTray();
|
||||||
|
if (!smoke) startBrowserChatMonitor();
|
||||||
SetTimer(window, 1, 60000, nullptr);
|
SetTimer(window, 1, 60000, nullptr);
|
||||||
if (!smoke) SetTimer(window, 6, 1000, nullptr);
|
if (!smoke) SetTimer(window, 6, 1000, nullptr);
|
||||||
SetThreadExecutionState(ES_CONTINUOUS |
|
SetThreadExecutionState(ES_CONTINUOUS |
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Chrome Telegram Web integration: native accessibility, no OCR or polling timer.
|
||||||
|
#include "chat_context.h"
|
||||||
|
#include <uiautomation.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
namespace ks {
|
||||||
|
namespace {
|
||||||
|
template<class T> struct BrowserCom {
|
||||||
|
T *p{}; ~BrowserCom(){if(p)p->Release();}
|
||||||
|
T** out(){return &p;} T* operator->(){return p;}
|
||||||
|
};
|
||||||
|
std::wstring browserString(IUIAutomationElement* e, PROPERTYID id) {
|
||||||
|
VARIANT v;VariantInit(&v);std::wstring s;
|
||||||
|
if(SUCCEEDED(e->GetCurrentPropertyValue(id,&v)) && v.vt==VT_BSTR && v.bstrVal) s.assign(v.bstrVal,SysStringLen(v.bstrVal));
|
||||||
|
VariantClear(&v);return s;
|
||||||
|
}
|
||||||
|
struct ChromeElements { BrowserCom<IUIAutomationElement> address,document,header; int addresses{},documents{}; };
|
||||||
|
void findChromeElements(IUIAutomationTreeWalker* walker,IUIAutomationElement* e,ChromeElements& found,int depth,int& budget) {
|
||||||
|
if(depth>12 || --budget<0)return;
|
||||||
|
CONTROLTYPEID type{};BOOL offscreen=TRUE;e->get_CurrentControlType(&type);e->get_CurrentIsOffscreen(&offscreen);
|
||||||
|
if(type==UIA_DocumentControlTypeId){
|
||||||
|
if(!offscreen && browserString(e,UIA_AutomationIdPropertyId)==L"RootWebArea"){
|
||||||
|
if(++found.documents==1){e->AddRef();found.document.p=e;}}
|
||||||
|
return; // Never traverse message content.
|
||||||
|
}
|
||||||
|
if(type==UIA_EditControlTypeId && !offscreen && browserString(e,UIA_ClassNamePropertyId)==L"OmniboxViewViews"){
|
||||||
|
if(++found.addresses==1){e->AddRef();found.address.p=e;}}
|
||||||
|
BrowserCom<IUIAutomationElement> child;walker->GetFirstChildElement(e,child.out());
|
||||||
|
while(child.p && budget>0){findChromeElements(walker,child.p,found,depth+1,budget);BrowserCom<IUIAutomationElement> next;walker->GetNextSiblingElement(child.p,next.out());child.p->Release();child.p=next.p;next.p=nullptr;}
|
||||||
|
}
|
||||||
|
IUIAutomationElement* chromeHeader(IUIAutomationTreeWalker* walker,IUIAutomationElement* document) {
|
||||||
|
// Web K's center pane is separate from its chat list and message history.
|
||||||
|
std::function<IUIAutomationElement*(IUIAutomationElement*,int)> center;
|
||||||
|
center=[&](IUIAutomationElement* e,int depth)->IUIAutomationElement* {
|
||||||
|
if(depth>3)return nullptr;
|
||||||
|
if(browserString(e,UIA_AutomationIdPropertyId)==L"column-center"){e->AddRef();return e;}
|
||||||
|
auto id=browserString(e,UIA_AutomationIdPropertyId);if(id==L"column-left"||id==L"column-right")return nullptr;
|
||||||
|
BrowserCom<IUIAutomationElement> c;walker->GetFirstChildElement(e,c.out());int limit=12;
|
||||||
|
while(c.p && --limit>0){if(auto result=center(c.p,depth+1))return result;BrowserCom<IUIAutomationElement> next;walker->GetNextSiblingElement(c.p,next.out());c.p->Release();c.p=next.p;next.p=nullptr;}return nullptr;
|
||||||
|
};
|
||||||
|
BrowserCom<IUIAutomationElement> pane;pane.p=center(document,0);if(!pane.p)return nullptr;
|
||||||
|
BrowserCom<IUIAutomationElement> header;walker->GetFirstChildElement(pane.p,header.out());if(!header.p)return nullptr;
|
||||||
|
BrowserCom<IUIAutomationElement> c;walker->GetFirstChildElement(header.p,c.out());int limit=16,buttons=0;IUIAutomationElement* title{};
|
||||||
|
while(c.p && --limit>0){CONTROLTYPEID type{};c->get_CurrentControlType(&type);if(type==UIA_ButtonControlTypeId)++buttons;
|
||||||
|
if(type==UIA_TextControlTypeId && !title){title=c.p;title->AddRef();}
|
||||||
|
BrowserCom<IUIAutomationElement> next;walker->GetNextSiblingElement(c.p,next.out());c.p->Release();c.p=next.p;next.p=nullptr;}
|
||||||
|
if(buttons<2 && title){title->Release();title=nullptr;}return title;
|
||||||
|
}
|
||||||
|
std::wstring chromeTitleKey(IUIAutomationElement* header){
|
||||||
|
if(!header)return {};
|
||||||
|
auto name=browserString(header,UIA_NamePropertyId);
|
||||||
|
if(name.empty()||name.size()>512||name.find_first_of(L"\r\n")!=name.npos)return {};
|
||||||
|
return L"telegram-web\n"+name;
|
||||||
|
}
|
||||||
|
std::wstring chromeSnapshot(IUIAutomation* uia,HWND window,ChromeElements& found) {
|
||||||
|
if(!IsWindow(window)||IsIconic(window)||lower(processName(window))!=L"chrome")return {};
|
||||||
|
BrowserCom<IUIAutomationElement> root;BrowserCom<IUIAutomationTreeWalker> walker;
|
||||||
|
if(FAILED(uia->ElementFromHandle(window,root.out()))||!root.p||FAILED(uia->get_ControlViewWalker(walker.out())))return {};
|
||||||
|
int budget=250;findChromeElements(walker.p,root.p,found,0,budget);
|
||||||
|
if(found.addresses!=1||found.documents!=1)return {};
|
||||||
|
auto address=telegramWebKey(browserString(found.address.p,UIA_ValueValuePropertyId));
|
||||||
|
auto document=telegramWebKey(browserString(found.document.p,UIA_ValueValuePropertyId));
|
||||||
|
BOOL editing=FALSE;found.address->get_CurrentHasKeyboardFocus(&editing);
|
||||||
|
if(editing || address.empty() || address!=document)return {};
|
||||||
|
found.header.p=chromeHeader(walker.p,found.document.p);
|
||||||
|
return chromeTitleKey(found.header.p);
|
||||||
|
}
|
||||||
|
std::mutex browserMutex;
|
||||||
|
HWND browserWindow{};std::wstring browserKey;
|
||||||
|
std::atomic<DWORD> browserThreadId{};
|
||||||
|
std::atomic<bool> browserPending{}, browserStopping{};
|
||||||
|
std::thread browserThread;
|
||||||
|
void invalidateBrowser(){std::lock_guard lock(browserMutex);browserKey.clear();}
|
||||||
|
void requestBrowser(){invalidateBrowser();auto id=browserThreadId.load();if(id && !browserPending.exchange(true))PostThreadMessageW(id,WM_APP+61,0,0);}
|
||||||
|
void CALLBACK browserForeground(HWINEVENTHOOK,DWORD,HWND,LONG,LONG,DWORD,DWORD){requestBrowser();}
|
||||||
|
class BrowserChanged final: public IUIAutomationPropertyChangedEventHandler {
|
||||||
|
std::atomic<ULONG> refs{1};
|
||||||
|
public:
|
||||||
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,void** out) override {if(!out)return E_POINTER;*out=nullptr;if(iid==IID_IUnknown||iid==IID_IUIAutomationPropertyChangedEventHandler){*out=static_cast<IUIAutomationPropertyChangedEventHandler*>(this);AddRef();return S_OK;}return E_NOINTERFACE;}
|
||||||
|
ULONG STDMETHODCALLTYPE AddRef() override{return ++refs;}
|
||||||
|
ULONG STDMETHODCALLTYPE Release() override{auto n=--refs;if(!n)delete this;return n;}
|
||||||
|
HRESULT STDMETHODCALLTYPE HandlePropertyChangedEvent(IUIAutomationElement*,PROPERTYID,VARIANT) override {requestBrowser();return S_OK;}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
std::wstring readChromeChat(HWND window) {
|
||||||
|
auto hr=CoInitializeEx(nullptr,COINIT_MULTITHREADED);
|
||||||
|
struct Apartment{bool own;~Apartment(){if(own)CoUninitialize();}} apartment{SUCCEEDED(hr)};
|
||||||
|
BrowserCom<IUIAutomation2> uia;
|
||||||
|
if(FAILED(CoCreateInstance(CLSID_CUIAutomation8,nullptr,CLSCTX_INPROC_SERVER,IID_IUIAutomation2,(void**)uia.out())))return {};
|
||||||
|
uia->put_ConnectionTimeout(150);uia->put_TransactionTimeout(150);
|
||||||
|
ChromeElements elements;return chromeSnapshot(uia.p,window,elements);
|
||||||
|
}
|
||||||
|
std::wstring cachedChromeChat(HWND window){std::lock_guard lock(browserMutex);return window==browserWindow?browserKey:L"";}
|
||||||
|
void startBrowserChatMonitor(){
|
||||||
|
if(browserThread.joinable())return;
|
||||||
|
browserStopping=false;
|
||||||
|
browserThread=std::thread([] {
|
||||||
|
CoInitializeEx(nullptr,COINIT_MULTITHREADED);MSG message{};PeekMessageW(&message,nullptr,0,0,PM_NOREMOVE);
|
||||||
|
browserThreadId=GetCurrentThreadId();
|
||||||
|
auto foreground=SetWinEventHook(EVENT_SYSTEM_FOREGROUND,EVENT_SYSTEM_FOREGROUND,nullptr,browserForeground,0,0,WINEVENT_OUTOFCONTEXT);
|
||||||
|
// Focus changes cover tab selection and entering/leaving the address bar.
|
||||||
|
auto focus=SetWinEventHook(EVENT_OBJECT_FOCUS,EVENT_OBJECT_FOCUS,nullptr,browserForeground,0,0,WINEVENT_OUTOFCONTEXT);
|
||||||
|
BrowserCom<IUIAutomation2> uia;CoCreateInstance(CLSID_CUIAutomation8,nullptr,CLSCTX_INPROC_SERVER,IID_IUIAutomation2,(void**)uia.out());
|
||||||
|
BrowserCom<IUIAutomationElement> address,document,header;BrowserCom<BrowserChanged> handler;handler.p=new BrowserChanged;
|
||||||
|
if(uia.p){uia->put_ConnectionTimeout(150);uia->put_TransactionTimeout(150);}
|
||||||
|
requestBrowser();
|
||||||
|
if(browserStopping)PostThreadMessageW(GetCurrentThreadId(),WM_QUIT,0,0);
|
||||||
|
while(GetMessageW(&message,nullptr,0,0)>0){
|
||||||
|
if(message.message!=WM_APP+61){TranslateMessage(&message);DispatchMessageW(&message);continue;}
|
||||||
|
browserPending=false;
|
||||||
|
if(!uia.p)continue;
|
||||||
|
if(address.p){uia->RemovePropertyChangedEventHandler(address.p,handler.p);address.p->Release();address.p=nullptr;}
|
||||||
|
if(document.p){uia->RemovePropertyChangedEventHandler(document.p,handler.p);document.p->Release();document.p=nullptr;}
|
||||||
|
if(header.p){uia->RemovePropertyChangedEventHandler(header.p,handler.p);header.p->Release();header.p=nullptr;}
|
||||||
|
auto window=GetForegroundWindow();ChromeElements elements;auto key=chromeSnapshot(uia.p,window,elements);
|
||||||
|
PROPERTYID properties[]={UIA_ValueValuePropertyId};
|
||||||
|
bool subscribed=elements.address.p && elements.document.p;
|
||||||
|
if(subscribed){address.p=elements.address.p;elements.address.p=nullptr;document.p=elements.document.p;elements.document.p=nullptr;
|
||||||
|
subscribed=SUCCEEDED(uia->AddPropertyChangedEventHandlerNativeArray(address.p,TreeScope_Element,nullptr,handler.p,properties,1));
|
||||||
|
subscribed=SUCCEEDED(uia->AddPropertyChangedEventHandlerNativeArray(document.p,TreeScope_Element,nullptr,handler.p,properties,1)) && subscribed;
|
||||||
|
if(elements.header.p){header.p=elements.header.p;elements.header.p=nullptr;PROPERTYID nameProperty=UIA_NamePropertyId;
|
||||||
|
subscribed=SUCCEEDED(uia->AddPropertyChangedEventHandlerNativeArray(header.p,TreeScope_Element,nullptr,handler.p,&nameProperty,1)) && subscribed;}
|
||||||
|
// Close the read/subscribe race without scanning the content tree.
|
||||||
|
key=telegramWebKey(browserString(address.p,UIA_ValueValuePropertyId));
|
||||||
|
if(key.empty() || key!=telegramWebKey(browserString(document.p,UIA_ValueValuePropertyId)))key.clear();
|
||||||
|
else key=chromeTitleKey(header.p);
|
||||||
|
BOOL editing=FALSE;address->get_CurrentHasKeyboardFocus(&editing);if(editing)key.clear();
|
||||||
|
}
|
||||||
|
std::lock_guard lock(browserMutex);browserWindow=window;browserKey=subscribed && !browserPending && window==GetForegroundWindow()?key:L"";
|
||||||
|
}
|
||||||
|
browserThreadId=0;
|
||||||
|
if(foreground)UnhookWinEvent(foreground);
|
||||||
|
if(focus)UnhookWinEvent(focus);
|
||||||
|
if(uia.p){if(header.p)uia->RemovePropertyChangedEventHandler(header.p,handler.p);if(address.p)uia->RemovePropertyChangedEventHandler(address.p,handler.p);if(document.p)uia->RemovePropertyChangedEventHandler(document.p,handler.p);}
|
||||||
|
invalidateBrowser();
|
||||||
|
// COM objects release before uninitializing the apartment below.
|
||||||
|
if(header.p){header.p->Release();header.p=nullptr;}
|
||||||
|
if(address.p){address.p->Release();address.p=nullptr;}if(document.p){document.p->Release();document.p=nullptr;}
|
||||||
|
if(uia.p){uia.p->Release();uia.p=nullptr;}CoUninitialize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void stopBrowserChatMonitor(){browserStopping=true;auto id=browserThreadId.load();if(id)PostThreadMessageW(id,WM_QUIT,0,0);if(browserThread.joinable())browserThread.join();browserPending=false;}
|
||||||
|
}
|
||||||
@@ -4,6 +4,31 @@
|
|||||||
|
|
||||||
namespace ks
|
namespace ks
|
||||||
{
|
{
|
||||||
|
inline std::wstring telegramWebKey(std::wstring url)
|
||||||
|
{
|
||||||
|
if (url.starts_with(L"https://")) url.erase(0, 8);
|
||||||
|
const std::wstring host = L"web.telegram.org/";
|
||||||
|
if (!url.starts_with(host)) return {};
|
||||||
|
url.erase(0, host.size());
|
||||||
|
if (!(url.starts_with(L"k/#") || url.starts_with(L"a/#"))) return {};
|
||||||
|
auto peer = url.substr(3);
|
||||||
|
if (peer.empty() || peer.size() > 128) return {};
|
||||||
|
if (peer.front() == L'@')
|
||||||
|
{
|
||||||
|
peer = lower(peer);
|
||||||
|
if (peer.size() < 2 || peer.find_first_not_of(L"abcdefghijklmnopqrstuvwxyz0123456789_", 1) != peer.npos) return {};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto first = peer.front() == L'-' ? 1u : 0u;
|
||||||
|
if (first == peer.size() || peer.find_first_not_of(L"0123456789", first) != peer.npos) return {};
|
||||||
|
}
|
||||||
|
return L"telegram-web\n" + peer;
|
||||||
|
}
|
||||||
|
std::wstring readChromeChat(HWND);
|
||||||
|
std::wstring cachedChromeChat(HWND);
|
||||||
|
void startBrowserChatMonitor();
|
||||||
|
void stopBrowserChatMonitor();
|
||||||
struct ChatSwitchHook
|
struct ChatSwitchHook
|
||||||
{
|
{
|
||||||
bool pressed[256]{};
|
bool pressed[256]{};
|
||||||
@@ -64,6 +89,7 @@ inline std::wstring chatKey(std::wstring process, std::wstring title)
|
|||||||
}
|
}
|
||||||
inline std::wstring windowChatKey(HWND window)
|
inline std::wstring windowChatKey(HWND window)
|
||||||
{
|
{
|
||||||
|
if (window && lower(processName(window)) == L"chrome") return cachedChromeChat(window);
|
||||||
return window ? chatKey(processName(window), chatTitle(window)) : L"";
|
return window ? chatKey(processName(window), chatTitle(window)) : L"";
|
||||||
}
|
}
|
||||||
inline std::vector<std::wstring> chatCycle(const Settings &settings, const std::wstring &key,
|
inline std::vector<std::wstring> chatCycle(const Settings &settings, const std::wstring &key,
|
||||||
|
|||||||
@@ -733,3 +733,5 @@ void log(const std::wstring &message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace ks
|
} // namespace ks
|
||||||
|
|
||||||
|
#include "browser_chat.inc"
|
||||||
+1
-1
@@ -67,7 +67,7 @@ void validateSettings(const Settings &s)
|
|||||||
auto separator = key.find(L'\n');
|
auto separator = key.find(L'\n');
|
||||||
if (separator == key.npos || separator + 1 == key.size() || key.size() > 1024 ||
|
if (separator == key.npos || separator + 1 == key.size() || key.size() > 1024 ||
|
||||||
key.find(L'\n', separator + 1) != key.npos || key.find(L'\r') != key.npos ||
|
key.find(L'\n', separator + 1) != key.npos || key.find(L'\r') != key.npos ||
|
||||||
(key.substr(0, separator) != L"telegram" && key.substr(0, separator) != L"ms-teams" && key.substr(0, separator) != L"viber"))
|
(key.substr(0, separator) != L"telegram" && key.substr(0, separator) != L"ms-teams" && key.substr(0, separator) != L"viber" && key.substr(0, separator) != L"telegram-web"))
|
||||||
throw std::runtime_error("Invalid chat language profile");
|
throw std::runtime_error("Invalid chat language profile");
|
||||||
if (cycle.empty() || cycle.size() > 64) throw std::runtime_error("Select at least one chat language");
|
if (cycle.empty() || cycle.size() > 64) throw std::runtime_error("Select at least one chat language");
|
||||||
std::vector<std::wstring> seen;
|
std::vector<std::wstring> seen;
|
||||||
|
|||||||
+5
-5
@@ -1013,7 +1013,7 @@ LRESULT CALLBACK chatLanguagesProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
{
|
{
|
||||||
int selected = (int)SendDlgItemMessageW(w, 710, LB_GETCURSEL, 0, 0);
|
int selected = (int)SendDlgItemMessageW(w, 710, LB_GETCURSEL, 0, 0);
|
||||||
if (selected < 0 || selected >= (int)v->keys.size())
|
if (selected < 0 || selected >= (int)v->keys.size())
|
||||||
throw std::runtime_error("Open a Telegram or Teams chat, then reopen this dialog. Viber OCR is disabled.");
|
throw std::runtime_error("Open Telegram, Telegram Web K in Chrome, or Teams, then reopen this dialog. Viber OCR is disabled.");
|
||||||
auto next = v->settings->draft;
|
auto next = v->settings->draft;
|
||||||
if (id == 713) next.chatCycles.erase(v->keys[selected]);
|
if (id == 713) next.chatCycles.erase(v->keys[selected]);
|
||||||
else
|
else
|
||||||
@@ -1055,7 +1055,7 @@ void showChatLanguages(HWND parent, SettingsView *settings)
|
|||||||
{
|
{
|
||||||
if (!IsWindowVisible(candidate)) return TRUE;
|
if (!IsWindowVisible(candidate)) return TRUE;
|
||||||
auto &data = *(Context *)context;
|
auto &data = *(Context *)context;
|
||||||
auto key = windowChatKey(candidate);
|
auto key = lower(processName(candidate)) == L"chrome" ? readChromeChat(candidate) : windowChatKey(candidate);
|
||||||
if (!key.empty()) data.keys.insert(std::move(key));
|
if (!key.empty()) data.keys.insert(std::move(key));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}, (LPARAM)&context);
|
}, (LPARAM)&context);
|
||||||
@@ -1063,13 +1063,13 @@ void showChatLanguages(HWND parent, SettingsView *settings)
|
|||||||
auto *v = new ChatLanguagesView{parent, settings, {keys.begin(), keys.end()}};
|
auto *v = new ChatLanguagesView{parent, settings, {keys.begin(), keys.end()}};
|
||||||
SetWindowLongPtrW(w, GWLP_HWNDPARENT, (LONG_PTR)parent);
|
SetWindowLongPtrW(w, GWLP_HWNDPARENT, (LONG_PTR)parent);
|
||||||
SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v);
|
SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v);
|
||||||
label(w, L"Telegram / Teams profiles; saved Viber profiles are inactive", 18, 16, 604);
|
label(w, L"Telegram / Telegram Web (Chrome) / Teams; Viber inactive", 18, 16, 604);
|
||||||
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER,
|
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER,
|
||||||
710, 18, 44, 604, 140);
|
710, 18, 44, 604, 140);
|
||||||
for (const auto &key : v->keys)
|
for (const auto &key : v->keys)
|
||||||
{
|
{
|
||||||
auto split = key.find(L'\n');
|
auto split = key.find(L'\n');
|
||||||
auto name = std::wstring(key.starts_with(L"telegram\n") ? L"Telegram: " : key.starts_with(L"viber\n") ? L"Viber (inactive): " : L"Teams: ") + key.substr(split + 1);
|
auto name = std::wstring(key.starts_with(L"telegram\n") ? L"Telegram: " : key.starts_with(L"telegram-web\n") ? L"Telegram Web: " : key.starts_with(L"viber\n") ? L"Viber (inactive): " : L"Teams: ") + key.substr(split + 1);
|
||||||
SendDlgItemMessageW(w, 710, LB_ADDSTRING, 0, (LPARAM)name.c_str());
|
SendDlgItemMessageW(w, 710, LB_ADDSTRING, 0, (LPARAM)name.c_str());
|
||||||
}
|
}
|
||||||
control(w, L"STATIC", L"Open a chat, then reopen this dialog to add it.", 0, 712, 18, 194, 604, 26);
|
control(w, L"STATIC", L"Open a chat, then reopen this dialog to add it.", 0, 712, 18, 194, 604, 26);
|
||||||
@@ -1988,7 +1988,7 @@ void showSettings(App &app, bool visible)
|
|||||||
label(w, L"Backspace / Delete clears it", 454, 270, 230);
|
label(w, L"Backspace / Delete clears it", 454, 270, 230);
|
||||||
button(w, L"Re&fresh layouts", 253, 454, 322, 230);
|
button(w, L"Re&fresh layouts", 253, 454, 322, 230);
|
||||||
button(w, L"Languages for a &chat...", 259, 24, 378, 230);
|
button(w, L"Languages for a &chat...", 259, 24, 378, 230);
|
||||||
label(w, L"Chat languages: Telegram / Teams. Viber OCR disabled.", 272, 382, 412);
|
label(w, L"Telegram / Telegram Web (Chrome) / Teams", 272, 382, 412);
|
||||||
page();
|
page();
|
||||||
|
|
||||||
label(w, L"Click a shortcut field and press the desired keys", 24, 76, 660);
|
label(w, L"Click a shortcut field and press the desired keys", 24, 76, 660);
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ void require(bool ok, const char *message)
|
|||||||
}
|
}
|
||||||
void contextChecks()
|
void contextChecks()
|
||||||
{
|
{
|
||||||
|
require(ks::telegramWebKey(L"https://web.telegram.org/k/#@Example") == L"telegram-web\n@example", "Telegram Web username normalized");
|
||||||
|
require(ks::telegramWebKey(L"web.telegram.org/a/#-123456") == L"telegram-web\n-123456", "Telegram Web numeric peer");
|
||||||
|
for (const auto *url : {L"https://web.telegram.org/k/", L"https://web.telegram.org.evil/k/#@example",
|
||||||
|
L"http://web.telegram.org/k/#@example", L"https://evil/web.telegram.org/k/#@example",
|
||||||
|
L"https://web.telegram.org/k/#@example?foo", L"https://web.telegram.org/k/#@",
|
||||||
|
L"https://web.telegram.org/k/#-", L"https://web.telegram.org/k/#settings"})
|
||||||
|
require(ks::telegramWebKey(url).empty(), "Reject non-chat or untrusted Telegram Web URL");
|
||||||
const auto key = ks::chatKey(L"Telegram.exe", L"\u200eТестовий чат \u2013 (12)");
|
const auto key = ks::chatKey(L"Telegram.exe", L"\u200eТестовий чат \u2013 (12)");
|
||||||
require(key == L"telegram\nТестовий чат", "Telegram unread suffix");
|
require(key == L"telegram\nТестовий чат", "Telegram unread suffix");
|
||||||
require(ks::chatKey(L"Telegram", L"Тестовий чат") == key, "stable unread profile");
|
require(ks::chatKey(L"Telegram", L"Тестовий чат") == key, "stable unread profile");
|
||||||
@@ -28,6 +35,7 @@ void contextChecks()
|
|||||||
settings.cycle = {L"win:04090409", L"win:04220422", L"win:04190419"};
|
settings.cycle = {L"win:04090409", L"win:04220422", L"win:04190419"};
|
||||||
settings.chatCycles[key] = {settings.cycle[1], settings.cycle[0]};
|
settings.chatCycles[key] = {settings.cycle[1], settings.cycle[0]};
|
||||||
settings.chatCycles[L"viber\nТестовий контакт"] = {settings.cycle[1], settings.cycle[0]};
|
settings.chatCycles[L"viber\nТестовий контакт"] = {settings.cycle[1], settings.cycle[0]};
|
||||||
|
settings.chatCycles[L"telegram-web\n@example"] = {settings.cycle[0], settings.cycle[1]};
|
||||||
std::vector<ks::Layout> installed(3);
|
std::vector<ks::Layout> installed(3);
|
||||||
for (size_t i = 0; i < installed.size(); ++i)
|
for (size_t i = 0; i < installed.size(); ++i)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user