Files
msime-windows/server/src/main.cpp
T

197 lines
7.2 KiB
C++

#include "ipc/event_listener.h"
#include "log/fanylog.h"
#include "window/ime_windows.h"
#include <commctrl.h>
#include <cstring>
#include <string>
#include <thread>
#include <utility>
#include "ipc/ipc.h"
#include "config/ime_config.h"
#include "clipboard/clipboard_history.h"
#include <windows.h>
#include <fmt/xchar.h>
#include <spdlog/spdlog.h>
#include "cloud/cloud_ime.h"
#include "cloud/cloud_translation.h"
#include "ai/ai_assistant.h"
#include "english/english_ime.h"
#include "emoji/emoji_ime.h"
#include "kaomoji/kaomoji_ime.h"
#include "utils/common_utils.h"
#include "utils/single_instance.h"
#include "session/session_factory.h"
#include "webview2/windows_webview2.h"
#include "voice-input/voice_input_service.h"
namespace
{
void StartWatchdogIfNeeded(const char *command_line)
{
// The Watchdog passes this marker when it owns the Server lifecycle.
// A Server revived directly by TSF has no marker and restores the
// Watchdog, preserving crash recovery after the six-keystroke fallback.
if (command_line && std::strstr(command_line, "--watchdog-managed")) return;
wchar_t server_path[MAX_PATH]{};
if (!GetModuleFileNameW(nullptr, server_path, MAX_PATH)) return;
std::wstring watchdog_path(server_path);
const size_t separator = watchdog_path.find_last_of(L"\\/");
if (separator == std::wstring::npos) return;
watchdog_path.replace(separator + 1, std::wstring::npos, L"MetasequoiaImeWatchdog.exe");
if (GetFileAttributesW(watchdog_path.c_str()) == INVALID_FILE_ATTRIBUTES) return;
std::wstring command = L"\"" + watchdog_path + L"\"";
STARTUPINFOW startup_info{sizeof(startup_info)};
PROCESS_INFORMATION process_info{};
if (CreateProcessW(watchdog_path.c_str(), command.data(), nullptr, nullptr, FALSE, 0, nullptr,
nullptr, &startup_info, &process_info))
{
CloseHandle(process_info.hThread);
CloseHandle(process_info.hProcess);
}
}
const char *SchemeTypeToString(SchemeType scheme_type)
{
switch (scheme_type)
{
case SchemeType::Quanpin:
return "quanpin";
case SchemeType::Shuangpin:
return "shuangpin";
case SchemeType::Wubi:
return "wubi";
case SchemeType::JapaneseRomaji:
return "japanese-romaji";
default:
return "unknown";
}
}
} // namespace
int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
CommonUtils::SingleInstanceGuard single_instance(L"Local\\MetasequoiaImeServer_SingleInstance");
if (!single_instance.is_valid())
{
(void)0;
return 0;
}
if (single_instance.already_running())
{
(void)0;
return 0;
}
StartWatchdogIfNeeded(lpCmdLine);
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
const HRESULT comResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(comResult))
{
(void)0;
return 1;
}
// #ifdef FANY_DEBUG
// Initialize for logging
InitializeSpdLog();
// #endif
// Initialize config
InitImeConfig();
VoiceInput::Initialize();
Global::candidate_ui.page_size = GetConfiguredCandidatePageSize();
::InitIpc();
::InitNamedPipe();
g_inputSession = CreateInputSessionFromConfig();
const std::string configured_backend = DescribeConfiguredInputSessionBackendFromConfig();
const std::string effective_backend = DescribeEffectiveInputSessionBackendFromConfig();
const std::string session_summary = fmt::format(
"Input session backend configured={}, effective={}, scheme={}",
configured_backend.empty() ? "legacy" : configured_backend, effective_backend,
SchemeTypeToString(g_inputSession->current_scheme_type()));
(void)0;
#ifdef FANY_DEBUG
(void)0;
#endif
RegisterCandidateWindowMessage();
WNDCLASSEX wcex;
RegisterIMEWindowsClass(wcex, hInstance);
//
// Pipe
//
/* Named Pipe for IPC between tsf and server */
std::thread pipe_worker(FanyNamedPipe::WorkerThread);
std::thread pipe_listener(FanyNamedPipe::EventListenerLoopThread);
/* Named Pipe for IPC, used to pass data to TSF */
std::thread to_tsf_pipe_listener(FanyNamedPipe::ToTsfPipeEventListenerLoopThread);
::mainPipeThread = pipe_listener.native_handle();
::toTsfPipeThread = to_tsf_pipe_listener.native_handle();
/* To Tsf Worker Thread Named Pipe for IPC, used to pass data to TSF */
std::thread to_tsf_worker_thread_pipe_listener(FanyNamedPipe::ToTsfWorkerThreadPipeEventListenerLoopThread);
::toTsfWorkerThreadPipeThread = to_tsf_worker_thread_pipe_listener.native_handle();
/* Aux Named Pipe: session-less UI notices (e.g. langbar right-click menu) */
std::thread aux_pipe_listener(FanyNamedPipe::AuxPipeEventListenerLoopThread);
CloudIme::Start([](const std::string &candidate, const std::string &pinyin, uint64_t generation) {
FanyNamedPipe::EnqueueCloudCandidate(candidate, pinyin, generation);
});
AiAssistant::Start([](const std::string &candidate, const std::string &identity, uint64_t generation) {
FanyNamedPipe::EnqueueAiCandidate(candidate, identity, generation);
});
EnglishIme::Start(CommonUtils::get_ime_data_path() + "\\english.db",
[](std::vector<WordItem> candidates, const std::string &input, uint64_t generation) {
FanyNamedPipe::EnqueueEnglishCandidates(std::move(candidates), input, generation);
},
[](std::vector<EnglishIme::TranslationResult> results, uint64_t generation) {
FanyNamedPipe::EnqueueCandidateTranslations(std::move(results), generation);
});
CloudTranslation::Start(CommonUtils::get_ime_data_path() + "\\english.db",
[](std::vector<EnglishIme::TranslationResult> results, uint64_t generation) {
FanyNamedPipe::EnqueueCandidateTranslations(std::move(results), generation, true);
});
EmojiIme::Start(CommonUtils::get_ime_data_path() + "\\others.db",
[](std::vector<WordItem> candidates, const std::string &input, uint64_t generation) {
FanyNamedPipe::EnqueueEmojiCandidates(std::move(candidates), input, generation);
});
KaomojiIme::Start(CommonUtils::get_ime_data_path() + "\\others.db",
[](std::vector<WordItem> candidates, const std::string &input, uint64_t generation) {
FanyNamedPipe::EnqueueKaomojiCandidates(std::move(candidates), input, generation);
});
int ret = CreateCandidateWindow(hInstance);
FanyNamedPipe::RegisterStatusSnapshotWindow(nullptr);
ShutdownWebviews();
EnglishIme::Stop();
CloudTranslation::Stop();
EmojiIme::Stop();
KaomojiIme::Stop();
AiAssistant::Stop();
CloudIme::Stop();
ClipboardMonitor::Stop();
VoiceInput::Shutdown();
pipe_running = false;
pipe_queueCv.notify_one();
pipe_worker.join();
pipe_listener.join();
// To Tsf Pipe
to_tsf_pipe_listener.join();
to_tsf_worker_thread_pipe_listener.join();
aux_pipe_listener.join();
::CloseIpc();
CoUninitialize();
return ret;
}