Files
FocusIME/Redcraft.FocusIME/Source/Private/Main.cpp

381 lines
7.7 KiB
C++

#include "Defines.h"
#include <windows.h>
#include <shellapi.h>
#include <psapi.h>
#include <iostream>
#include <fstream>
#include <chrono>
#include <string>
#include <ctime>
#pragma comment(lib, "psapi.lib")
#pragma warning(disable: 4996)
// ReSharper disable CppDeprecatedEntity
// ReSharper disable CppClangTidyCertErr33C
std::ofstream GLogFile;
bool GShouldExit = false;
void PrintLog(const std::string& Text);
void CALLBACK FocusEventHookProc(HWINEVENTHOOK, DWORD Event, HWND Window, LONG, LONG, DWORD, DWORD);
LRESULT CALLBACK MainWindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam);
int WINAPI WinMain(HINSTANCE Instance, HINSTANCE, LPSTR, int)
{
using namespace std::chrono_literals;
const HANDLE Mutex = CreateMutex(nullptr, TRUE, "FocusIME_SingleInstance_Mutex");
if (Mutex == nullptr)
{
MessageBox(nullptr, "Failed to create mutex.", "FocusIME", MB_OK | MB_ICONERROR);
return 1;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBox(nullptr, "FocusIME is already running.", "FocusIME", MB_OK | MB_ICONINFORMATION);
CloseHandle(Mutex);
return 0;
}
WNDCLASS MainWindowClass = { };
MainWindowClass.lpfnWndProc = MainWindowProc;
MainWindowClass.hInstance = Instance;
MainWindowClass.lpszClassName = "FocusIME_MainWindow";
MainWindowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
MainWindowClass.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); // NOLINT(performance-no-int-to-ptr)
if (!RegisterClass(&MainWindowClass))
{
MessageBox(nullptr, "Failed to register main window class.", "FocusIME", MB_OK | MB_ICONERROR);
CloseHandle(Mutex);
return 1;
}
const HWND MainWindow = CreateWindow(
"FocusIME_MainWindow",
"FocusIME",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, Instance, nullptr);
if (MainWindow == nullptr)
{
MessageBox(nullptr, "Failed to create main window.", "FocusIME", MB_OK | MB_ICONERROR);
CloseHandle(Mutex);
return 1;
}
ShowWindow(MainWindow, SW_HIDE);
const HWINEVENTHOOK FocusEventHook = SetWinEventHook(
EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS,
nullptr,
FocusEventHookProc,
0, 0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
if (FocusEventHook == nullptr)
{
MessageBox(nullptr, "Failed to set focus event hook.", "FocusIME", MB_OK | MB_ICONWARNING);
DestroyWindow(MainWindow);
CloseHandle(Mutex);
return 1;
}
# if BUILD_DEBUG || BUILD_DEVELOPMENT
{
if (!AllocConsole())
{
MessageBox(nullptr, "Failed to allocate console.", "FocusIME", MB_OK | MB_ICONERROR);
DestroyWindow(MainWindow);
CloseHandle(Mutex);
return 1;
}
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
freopen("CONIN$", "r", stdin);
SetConsoleTitle("FocusIME Debug Console");
std::ios::sync_with_stdio(true);
std::wcout.clear();
std::cout.clear();
std::wcerr.clear();
std::cerr.clear();
std::wcin.clear();
std::cin.clear();
}
# endif
GLogFile.open("Log.txt", std::ios::out | std::ios::app);
if (!GLogFile.is_open())
{
MessageBox(nullptr, "Failed to open log file.", "FocusIME", MB_OK | MB_ICONERROR);
CloseHandle(Mutex);
return 1;
}
PrintLog("FocusIME started successfully");
MSG Message = { };
while (!GShouldExit)
{
BOOL bResult = GetMessage(&Message, nullptr, 0, 0);
if (bResult > 0)
{
TranslateMessage(&Message);
DispatchMessage (&Message);
}
else if (bResult == 0) break;
else
{
DWORD ErrorCode = GetLastError();
LPSTR MessageBuffer = nullptr;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&MessageBuffer), 0, nullptr);
std::string ErrorMessage = "Message loop failed with error code " + std::to_string(ErrorCode);
if (MessageBuffer)
{
ErrorMessage += ": " + std::string(MessageBuffer);
LocalFree(MessageBuffer);
}
PrintLog(ErrorMessage);
break;
}
}
PrintLog("FocusIME is shutting down...");
GLogFile.close();
# if BUILD_DEBUG || BUILD_DEVELOPMENT
{
FreeConsole();
}
# endif
UnhookWinEvent(FocusEventHook);
DestroyWindow(MainWindow);
CloseHandle(Mutex);
return 0;
}
LRESULT CALLBACK MainWindowProc(const HWND Window, const UINT Message, const WPARAM WParam, const LPARAM LParam)
{
static NOTIFYICONDATA NotifyIconData;
constexpr UINT TrayIconMessage = WM_USER + 1;
constexpr UINT ExitMessage = 1001;
switch (Message)
{
case WM_CREATE:
memset(&NotifyIconData, 0, sizeof(NOTIFYICONDATA));
NotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
NotifyIconData.hWnd = Window;
NotifyIconData.uID = 1;
NotifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
NotifyIconData.uCallbackMessage = TrayIconMessage;
NotifyIconData.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
strcpy(NotifyIconData.szTip, "FocusIME");
if (!Shell_NotifyIcon(NIM_ADD, &NotifyIconData)) return -1;
break;
case TrayIconMessage:
switch (LParam)
{
case WM_RBUTTONUP:
case WM_CONTEXTMENU:
POINT Point;
GetCursorPos(&Point);
if (const HMENU Menu = CreatePopupMenu())
{
InsertMenu(Menu, -1, MF_BYPOSITION | MF_STRING, ExitMessage, "Exit(&X)");
SetMenuDefaultItem(Menu, ExitMessage, FALSE);
SetForegroundWindow(Window);
TrackPopupMenu(Menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, Point.x, Point.y, 0, Window, nullptr);
PostMessage(Window, WM_NULL, 0, 0);
DestroyMenu(Menu);
}
break;
default: break;
}
break;
case WM_COMMAND:
if (LOWORD(WParam) == ExitMessage)
{
PrintLog("Exit command received from tray menu");
GShouldExit = true;
PostQuitMessage(0);
}
break;
case WM_DESTROY:
Shell_NotifyIcon(NIM_DELETE, &NotifyIconData);
PostQuitMessage(0);
break;
default: return DefWindowProc(Window, Message, WParam, LParam);
}
return 0;
}
void CALLBACK FocusEventHookProc(HWINEVENTHOOK, const DWORD Event, const HWND Window, LONG, LONG, DWORD, DWORD)
{
if (Event == EVENT_OBJECT_FOCUS && Window != nullptr)
{
std::string ProcessName;
do
{
DWORD PID = 0;
GetWindowThreadProcessId(Window, &PID);
const HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PID);
if (ProcessHandle == nullptr)
{
ProcessName = "Unknown";
break;
}
char Buffer[MAX_PATH] = { };
if (GetModuleBaseName(ProcessHandle, nullptr, Buffer, sizeof(Buffer)) == 0)
{
CloseHandle(ProcessHandle);
ProcessName = "Unknown";
break;
}
CloseHandle(ProcessHandle);
ProcessName = Buffer;
}
while (false);
char Title[1024] = { };
GetWindowText(Window, Title, sizeof(Title));
std::string MessageBuffer = "Focus changed to: " + ProcessName;
if (strlen(Title) > 0)
{
MessageBuffer += " (" + std::string(Title) + ")";
}
PrintLog(MessageBuffer);
}
}
void PrintLog(const std::string& Text)
{
const auto Now = std::chrono::system_clock::now();
const auto Time = std::chrono::system_clock::to_time_t(Now);
const long long Milliseconds = (std::chrono::duration_cast<std::chrono::milliseconds>(Now.time_since_epoch()) % 1000).count();
const std::tm* LocalTime = std::localtime(&Time); // NOLINT(concurrency-mt-unsafe)
char Buffer[] = "[2025-06-21 20:05:04.305]: ";
std::strftime(&Buffer[1], 20, "%Y-%m-%d %H:%M:%S", LocalTime);
Buffer[20] = '.';
constexpr char Digits[] = "0123456789";
Buffer[21] = Digits[Milliseconds / 100 % 10];
Buffer[22] = Digits[Milliseconds / 10 % 10];
Buffer[23] = Digits[Milliseconds / 1 % 10];
GLogFile << Buffer << Text << std::endl;
#if BUILD_DEBUG || BUILD_DEVELOPMENT
{
std::clog << Buffer << Text << std::endl;
}
#endif
}
// ReSharper restore CppDeprecatedEntity
// ReSharper restore CppClangTidyCertErr33C