4 #include <WinSock2.h>
\r
8 #define LAY_IMPLEMENTATION
\r
16 #include <stdbool.h>
\r
19 #include <windows.h>
\r
20 #include <shlwapi.h>
\r
22 bool recording = false;
\r
23 HANDLE process = NULL;
\r
25 HINSTANCE hInstance;
\r
26 HICON icon_white, icon_green, icon_red;
\r
28 std::vector<std::string> gameExes;
\r
31 NOTIFYICONDATAA niData = { 0 };
\r
32 const UINT ICON_MSG = WM_APP+1;
\r
34 ShowNotificationIcon()
\r
36 Shell_NotifyIconA(NIM_ADD, &niData);
\r
37 Shell_NotifyIconA(NIM_SETVERSION, &niData);
\r
41 HideNotificationIcon()
\r
43 Shell_NotifyIconA(NIM_DELETE, &niData);
\r
46 void changeIcon(HWND hwnd, HICON icon)
\r
48 niData.hIcon = icon;
\r
49 if (! IsWindowVisible(hwnd))
\r
50 Shell_NotifyIconA(NIM_MODIFY, &niData);
\r
51 SendMessage(hwnd, WM_SETICON, 0, (LPARAM)icon);
\r
52 SendMessage(hwnd, WM_SETICON, 1, (LPARAM)icon);
\r
60 ws::sendRequest("StartRecord");
\r
61 changeIcon(hwnd, icon_green);
\r
67 ws::sendRequest("StopRecord");
\r
68 changeIcon(hwnd, icon_red);
\r
72 checkProcessRunning(HANDLE handle)
\r
76 if (handle != NULL && GetExitCodeProcess(handle, &exit_code)) {
\r
77 return exit_code == STILL_ACTIVE;
\r
84 getHwndProcess(HWND hwnd)
\r
86 DWORD processId, threadId = GetWindowThreadProcessId(hwnd, &processId);
\r
87 return OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
\r
90 // HWND getProcessHwnd(HANDLE handle) {}
\r
93 checkFullscreenWindow()
\r
95 HWND desktopHwnd = GetDesktopWindow();
\r
96 HWND fgHwnd = GetForegroundWindow();
\r
98 if (fgHwnd != desktopHwnd && fgHwnd != GetShellWindow()) {
\r
99 RECT windowRect, desktopRect;
\r
100 // Get Window and Desktop size
\r
101 GetWindowRect(fgHwnd, &windowRect);
\r
102 GetWindowRect(desktopHwnd, &desktopRect);
\r
104 bool fullscreen = windowRect.bottom == desktopRect.bottom &&
\r
105 windowRect.top == desktopRect.top &&
\r
106 windowRect.left == desktopRect.left &&
\r
107 windowRect.right == desktopRect.right;
\r
116 checkForegroundProcess(std::string exeName)
\r
118 HWND fgHwnd = GetForegroundWindow();
\r
119 HANDLE fgHandle = getHwndProcess(fgHwnd);
\r
121 char filename[MAX_PATH];
\r
122 int len = GetModuleFileNameExA(fgHandle, NULL, filename, MAX_PATH);
\r
124 if (strcmp(filename, exeName.c_str()) == 0)
\r
127 PathStripPathA(filename);
\r
129 return strcmp(filename, exeName.c_str()) == 0;
\r
132 void ReadGameExes()
\r
134 std::ifstream ifs("games.txt");
\r
140 std::getline(ifs, str);
\r
142 gameExes.push_back(str);
\r
148 void WriteGameExes()
\r
150 std::ofstream ofs("games.txt", std::ios::trunc);
\r
152 for (const auto &exe : gameExes)
\r
154 ofs.write(exe.c_str(), exe.size());
\r
155 ofs.write("\n", 1);
\r
161 PROCESS_INFORMATION StartOBS()
\r
163 // delete .sentinel folder to stop obs from displaying
\r
164 // error message on startup
\r
165 system("del %appdata%\\obs-studio\\.sentinel /Q");
\r
167 PROCESS_INFORMATION pi;
\r
169 GetStartupInfoA(&sui);
\r
170 CreateProcessA(nullptr, "C:\\Program Files\\obs-studio\\bin\\64bit\\obs64.exe",
\r
171 nullptr, nullptr, false, NORMAL_PRIORITY_CLASS, nullptr, "C:\\Program Files\\obs-studio\\bin\\64bit", &sui, &pi);
\r
175 void StopOBS(PROCESS_INFORMATION &pi)
\r
177 EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
\r
178 PROCESS_INFORMATION *pi = (PROCESS_INFORMATION*)lParam;
\r
181 tid = GetWindowThreadProcessId(hwnd, &pid);
\r
182 if (tid == pi->dwThreadId && pid == pi->dwProcessId) {
\r
184 char title[1024]; GetWindowTextA(hwnd, title, 1024);
\r
186 if (strncmp(title, "OBS", 3) == 0) {
\r
187 PostMessage(hwnd, WM_CLOSE, 0, 0);
\r
197 - Disconnect while recording
\r
203 WinMain(HINSTANCE hInstance,
\r
204 HINSTANCE hPrevInstance,
\r
207 //int main(int argc, char **argv)
\r
209 hInstance = GetModuleHandle(0);
\r
211 icon_white = LoadIconA(hInstance, MAKEINTRESOURCEA(IDI_ICON_WHITE));
\r
212 icon_green = LoadIconA(hInstance, MAKEINTRESOURCEA(IDI_ICON_GREEN));
\r
213 icon_red = LoadIconA(hInstance, MAKEINTRESOURCEA(IDI_ICON_RED));
\r
215 win::Window window("Title", "MyWindowClass", hInstance);
\r
216 hwnd = window.hwnd;
\r
218 niData.cbSize = sizeof(niData);
\r
219 niData.uID = 12345;
\r
220 niData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
\r
221 niData.hIcon = icon_white;
\r
222 niData.hWnd = window.hwnd;
\r
223 niData.uCallbackMessage = ICON_MSG;
\r
224 niData.uVersion = NOTIFYICON_VERSION_4;
\r
226 PROCESS_INFORMATION pi_obs = StartOBS();
\r
228 window.handlers[WM_SIZE].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
229 if (wParam == SIZE_MINIMIZED) {
\r
230 ShowNotificationIcon();
\r
231 ShowWindow(hwnd, false);
\r
234 window.handlers[WM_DESTROY].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
235 HideNotificationIcon();
\r
237 window.handlers[ICON_MSG].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
238 if (LOWORD(lParam) == NIN_SELECT) {
\r
239 HideNotificationIcon();
\r
240 ShowWindow(hwnd, true);
\r
241 SetForegroundWindow(hwnd);
\r
242 SetActiveWindow(hwnd);
\r
245 window.handlers[WM_GETMINMAXINFO].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
246 MINMAXINFO *mmInfo = (MINMAXINFO*)lParam;
\r
247 mmInfo->ptMinTrackSize.x = 400;
\r
248 mmInfo->ptMinTrackSize.y = 200;
\r
250 window.handlers[WM_QUERYENDSESSION].push_back([&pi_obs](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
252 }); window.handlers[WM_ENDSESSION].push_back([&pi_obs](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
255 window.handlers[WM_CLOSE].push_back([&pi_obs](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
\r
259 lay_context *ctx = &window.ctx;
\r
260 lay_id root = window.lId;
\r
262 lay_id row1 = win::createLayId(&window.ctx, window.lId, 0, 25, LAY_ROW, LAY_LEFT);
\r
263 lay_set_margins_ltrb(ctx, row1, 5, 5, 5, 5);
\r
264 lay_id row2 = win::createLayId(&window.ctx, window.lId, 0, 0, LAY_ROW, LAY_FILL);
\r
265 lay_set_margins_ltrb(ctx, row2, 5, 5, 5, 5);
\r
267 win::CheckBox cbWindowTitle(&window, "Window Title", row1, 100, 25, 0, 0);
\r
268 win::CheckBox cbFullscreenWindow(&window, "Any Fullscreen Application", row1, 200, 25, 0, 0);
\r
270 win::Button btnConnect(&window, "Connect", row1, 100, 25, 0, 0);
\r
271 btnConnect.onClick([&]() {
\r
272 if (! ws::isConnected)
\r
273 ws::connect("ws://127.0.0.1:4455");
\r
276 win::ListBox lstActiveProcesses(&window, row2, 0, 0, 0, LAY_FILL);
\r
278 lay_id col1 = win::createLayId(&window.ctx, row2, 80, 0, LAY_COLUMN, LAY_VCENTER);
\r
279 lstActiveProcesses.addStyle(WS_VSCROLL);
\r
281 lay_set_margins_ltrb(ctx, col1, 5, 0, 5, 0);
\r
283 win::ListBox lstMonitoredProcesses(&window, row2, 0, 0, 0, LAY_FILL);
\r
284 lstMonitoredProcesses.addStyle(WS_VSCROLL);
\r
287 for (const auto &exe : gameExes)
\r
288 lstMonitoredProcesses.addString(exe);
\r
290 win::Button btnUpdateWindows(&window, "Update", col1, 85, 25, 0, 0);
\r
291 win::Button btnStartMonitoringName(&window, "Exe name >>", col1, 85, 25, 0, 0);
\r
292 win::Button btnStartMonitoringPath(&window, "Full path >>", col1, 85, 25, 0, 0);
\r
293 win::Button btnStopMonitoring(&window, "Remove", col1, 85, 25, 0, 0);
\r
294 btnUpdateWindows.onClick([&]() {
\r
295 lstActiveProcesses.clear();
\r
296 for (HWND hwnd = GetTopWindow(NULL); hwnd != nullptr;
\r
297 hwnd = GetNextWindow(hwnd, GW_HWNDNEXT)) {
\r
298 if (!IsWindowVisible(hwnd))
\r
302 GetWindowRect(hwnd, &rect);
\r
305 if (GetModuleFileNameExA(getHwndProcess(hwnd), 0, str, 1024) != 0 &&
\r
306 lstActiveProcesses.findString(str) == LB_ERR) {
\r
307 lstActiveProcesses.addString(str);
\r
311 btnStartMonitoringName.onClick([&]() {
\r
312 int sel = lstActiveProcesses.getSelectedIndex();
\r
313 if (sel < 0) return;
\r
315 std::string selStr = lstActiveProcesses.getText(sel);
\r
317 char *filename = new char[selStr.size()+1];
\r
318 std::memcpy(filename, selStr.c_str(), selStr.size());
\r
319 filename[selStr.size()] = '\0';
\r
320 PathStripPathA(filename);
\r
322 std::string filenameStr(filename);
\r
324 if (lstMonitoredProcesses.findString(filenameStr) == LB_ERR)
\r
326 lstMonitoredProcesses.addString(filenameStr);
\r
327 gameExes.push_back(filenameStr);
\r
333 btnStartMonitoringPath.onClick([&]() {
\r
334 int sel = lstActiveProcesses.getSelectedIndex();
\r
335 if (sel < 0) return;
\r
336 std::string selStr = lstActiveProcesses.getText(sel);
\r
337 if (lstMonitoredProcesses.findString(selStr) == LB_ERR)
\r
339 lstMonitoredProcesses.addString(selStr);
\r
340 gameExes.push_back(selStr);
\r
344 btnStopMonitoring.onClick([&]() {
\r
345 int sel = lstMonitoredProcesses.getSelectedIndex();
\r
346 if (sel < 0) return;
\r
347 lstMonitoredProcesses.remove(sel);
\r
348 gameExes.erase(gameExes.begin() + sel);
\r
353 window.setDefaultFont();
\r
355 ws::onConnect = [&]() {
\r
356 changeIcon(window.hwnd, icon_red);
\r
357 btnConnect.setActive(false);
\r
359 ws::onClose = [&]() {
\r
360 changeIcon(window.hwnd, icon_white);
\r
361 btnConnect.setActive(true);
\r
363 ws::onError = ws::onClose;
\r
366 window.setTimer(1000, [&btnConnect]() {
\r
367 if (! ws::isConnected)
\r
368 ws::connect("ws://127.0.0.1:4455");
\r
371 window.setTimer(100, []() {
\r
373 for (auto exe : gameExes) {
\r
374 if (checkForegroundProcess(exe)) {
\r
376 process = getHwndProcess(GetForegroundWindow());
\r
381 if (!checkProcessRunning(process)) {
\r
389 while (window.update()) {
\r