]> gitweb.ps.run Git - autorec/blob - src/main.cpp
handle starting and stopping obs
[autorec] / src / main.cpp
1 #ifndef WIN32\r
2   #define WIN32\r
3 #endif\r
4 #include <WinSock2.h>\r
5 #include "win.h"\r
6 #include "ws.h"\r
7 \r
8 #define LAY_IMPLEMENTATION\r
9 #include "layout.h"\r
10 \r
11 #include <fstream>\r
12 #include <vector>\r
13 #include <string>\r
14 \r
15 #include <Psapi.h>\r
16 #include <stdbool.h>\r
17 #include <stdio.h>\r
18 #include <string.h>\r
19 #include <windows.h>\r
20 #include <shlwapi.h>\r
21 \r
22 bool recording = false;\r
23 HANDLE process = NULL;\r
24 HWND hwnd;\r
25 HINSTANCE hInstance;\r
26 HICON icon_white, icon_green, icon_red;\r
27 \r
28 std::vector<std::string> gameExes;\r
29 \r
30 \r
31 NOTIFYICONDATAA niData = { 0 };\r
32 const UINT ICON_MSG = WM_APP+1;\r
33 void\r
34 ShowNotificationIcon()\r
35 {\r
36   Shell_NotifyIconA(NIM_ADD, &niData);\r
37   Shell_NotifyIconA(NIM_SETVERSION, &niData);\r
38 }\r
39 \r
40 void\r
41 HideNotificationIcon()\r
42 {\r
43   Shell_NotifyIconA(NIM_DELETE, &niData);\r
44 }\r
45 \r
46 void changeIcon(HWND hwnd, HICON icon)\r
47 {\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
53 }\r
54 \r
55 \r
56 \r
57 void\r
58 startRecording()\r
59 {\r
60   ws::sendRequest("StartRecord");\r
61   changeIcon(hwnd, icon_green);\r
62 }\r
63 \r
64 void\r
65 stopRecording()\r
66 {\r
67   ws::sendRequest("StopRecord");\r
68   changeIcon(hwnd, icon_red);\r
69 }\r
70 \r
71 bool\r
72 checkProcessRunning(HANDLE handle)\r
73 {\r
74   DWORD exit_code;\r
75 \r
76   if (handle != NULL && GetExitCodeProcess(handle, &exit_code)) {\r
77     return exit_code == STILL_ACTIVE;\r
78   }\r
79 \r
80   return false;\r
81 }\r
82 \r
83 HANDLE\r
84 getHwndProcess(HWND hwnd)\r
85 {\r
86   DWORD processId, threadId = GetWindowThreadProcessId(hwnd, &processId);\r
87   return OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);\r
88 }\r
89 \r
90 // HWND getProcessHwnd(HANDLE handle) {}\r
91 \r
92 bool\r
93 checkFullscreenWindow()\r
94 {\r
95   HWND desktopHwnd = GetDesktopWindow();\r
96   HWND fgHwnd = GetForegroundWindow();\r
97 \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
103 \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
108 \r
109     return fullscreen;\r
110   }\r
111 \r
112   return false;\r
113 }\r
114 \r
115 bool\r
116 checkForegroundProcess(std::string exeName)\r
117 {\r
118   HWND fgHwnd = GetForegroundWindow();\r
119   HANDLE fgHandle = getHwndProcess(fgHwnd);\r
120 \r
121   char filename[MAX_PATH];\r
122   int len = GetModuleFileNameExA(fgHandle, NULL, filename, MAX_PATH);\r
123 \r
124   if (strcmp(filename, exeName.c_str()) == 0)\r
125     return true;\r
126 \r
127   PathStripPathA(filename);\r
128 \r
129   return strcmp(filename, exeName.c_str()) == 0;\r
130 }\r
131 \r
132 void ReadGameExes()\r
133 {\r
134   std::ifstream ifs("games.txt");\r
135 \r
136   gameExes.clear();\r
137 \r
138   while (ifs) {\r
139     std::string str;\r
140     std::getline(ifs, str);\r
141     if (! str.empty())\r
142       gameExes.push_back(str);\r
143   }\r
144 \r
145   ifs.close();\r
146 }\r
147 \r
148 void WriteGameExes()\r
149 {\r
150   std::ofstream ofs("games.txt", std::ios::trunc);\r
151 \r
152   for (const auto &exe : gameExes)\r
153   {\r
154     ofs.write(exe.c_str(), exe.size());\r
155     ofs.write("\n", 1);\r
156   }\r
157 \r
158   ofs.close();\r
159 }\r
160 \r
161 PROCESS_INFORMATION StartOBS()\r
162 {\r
163   PROCESS_INFORMATION pi;\r
164   STARTUPINFOA sui;\r
165   GetStartupInfoA(&sui);\r
166   CreateProcessA(nullptr, "C:\\Program Files\\obs-studio\\bin\\64bit\\obs64.exe --disable-shutdown-check",\r
167     nullptr, nullptr, false, NORMAL_PRIORITY_CLASS, nullptr, "C:\\Program Files\\obs-studio\\bin\\64bit", &sui, &pi);\r
168   return pi;\r
169 }\r
170 \r
171 void StopOBS(PROCESS_INFORMATION &pi)\r
172 {\r
173   EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {\r
174     PROCESS_INFORMATION *pi = (PROCESS_INFORMATION*)lParam;\r
175 \r
176     DWORD pid, tid;\r
177     tid = GetWindowThreadProcessId(hwnd, &pid);\r
178     if (tid == pi->dwThreadId && pid == pi->dwProcessId) {\r
179 \r
180       char title[1024]; GetWindowTextA(hwnd, title, 1024);\r
181 \r
182       if (strncmp(title, "OBS", 3) == 0) {\r
183         PostMessage(hwnd, WM_CLOSE, 0, 0);\r
184         return FALSE; \r
185       }\r
186     }\r
187     return TRUE;\r
188   }, (LPARAM)&pi);\r
189 }\r
190 \r
191 /*\r
192 TODO:\r
193   - Disconnect while recording\r
194 */\r
195 \r
196 \r
197 \r
198 int WINAPI\r
199 WinMain(HINSTANCE hInstance,\r
200         HINSTANCE hPrevInstance,\r
201         LPSTR lpCmdLine,\r
202         int nCmdShow)\r
203 //int main(int argc, char **argv)\r
204 {\r
205   hInstance = GetModuleHandle(0);\r
206 \r
207   icon_white = LoadIconA(hInstance, MAKEINTRESOURCEA(IDI_ICON_WHITE));\r
208   icon_green = LoadIconA(hInstance, MAKEINTRESOURCEA(IDI_ICON_GREEN));\r
209   icon_red = LoadIconA(hInstance, MAKEINTRESOURCEA(IDI_ICON_RED));\r
210 \r
211   win::Window window("Title", "MyWindowClass", hInstance);\r
212   hwnd = window.hwnd;\r
213 \r
214   niData.cbSize = sizeof(niData);\r
215   niData.uID = 12345;\r
216   niData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;\r
217   niData.hIcon = icon_white;\r
218   niData.hWnd = window.hwnd;\r
219   niData.uCallbackMessage = ICON_MSG;\r
220   niData.uVersion = NOTIFYICON_VERSION_4;\r
221 \r
222   PROCESS_INFORMATION pi_obs = StartOBS();\r
223 \r
224   window.handlers[WM_SIZE].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\r
225     if (wParam == SIZE_MINIMIZED) {\r
226       ShowNotificationIcon();\r
227       ShowWindow(hwnd, false);\r
228     }\r
229   });\r
230   window.handlers[WM_DESTROY].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\r
231     HideNotificationIcon();\r
232   });  \r
233   window.handlers[ICON_MSG].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\r
234     if (LOWORD(lParam) == NIN_SELECT) {\r
235       HideNotificationIcon();\r
236       ShowWindow(hwnd, true);\r
237       SetForegroundWindow(hwnd);\r
238       SetActiveWindow(hwnd);\r
239     }\r
240   });\r
241   window.handlers[WM_GETMINMAXINFO].push_back([](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\r
242     MINMAXINFO *mmInfo = (MINMAXINFO*)lParam;\r
243     mmInfo->ptMinTrackSize.x = 400;\r
244     mmInfo->ptMinTrackSize.y = 200;\r
245   });\r
246   window.handlers[WM_QUERYENDSESSION].push_back([&pi_obs](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\r
247     StopOBS(pi_obs);\r
248   });\r
249   window.handlers[WM_CLOSE].push_back([&pi_obs](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\r
250     StopOBS(pi_obs);\r
251   });\r
252 \r
253   lay_context *ctx = &window.ctx;\r
254   lay_id root = window.lId;\r
255 \r
256   lay_id row1 = win::createLayId(&window.ctx, window.lId, 0, 25, LAY_ROW, LAY_LEFT);\r
257   lay_set_margins_ltrb(ctx, row1, 5, 5, 5, 5);\r
258   lay_id row2 = win::createLayId(&window.ctx, window.lId, 0, 0, LAY_ROW, LAY_FILL);\r
259   lay_set_margins_ltrb(ctx, row2, 5, 5, 5, 5);\r
260 \r
261   win::CheckBox cbWindowTitle(&window, "Window Title", row1, 100, 25, 0, 0);\r
262   win::CheckBox cbFullscreenWindow(&window, "Any Fullscreen Application", row1, 200, 25, 0, 0);\r
263 \r
264   win::Button btnConnect(&window, "Connect", row1, 100, 25, 0, 0);\r
265   btnConnect.onClick([&]() {\r
266     if (! ws::isConnected)\r
267       ws::connect("ws://127.0.0.1:4455");\r
268   });\r
269 \r
270   win::ListBox lstActiveProcesses(&window, row2, 0, 0, 0, LAY_FILL);\r
271   \r
272   lay_id col1 = win::createLayId(&window.ctx, row2, 80, 0, LAY_COLUMN, LAY_VCENTER);\r
273   lstActiveProcesses.addStyle(WS_VSCROLL);\r
274 \r
275   lay_set_margins_ltrb(ctx, col1, 5, 0, 5, 0);\r
276 \r
277   win::ListBox lstMonitoredProcesses(&window, row2, 0, 0, 0, LAY_FILL);\r
278   lstMonitoredProcesses.addStyle(WS_VSCROLL);\r
279 \r
280   ReadGameExes();\r
281   for (const auto &exe : gameExes)\r
282     lstMonitoredProcesses.addString(exe);\r
283 \r
284   win::Button btnUpdateWindows(&window, "Update", col1, 85, 25, 0, 0);\r
285   win::Button btnStartMonitoringName(&window, "Exe name >>", col1, 85, 25, 0, 0);\r
286   win::Button btnStartMonitoringPath(&window, "Full path >>", col1, 85, 25, 0, 0);\r
287   win::Button btnStopMonitoring(&window, "Remove", col1, 85, 25, 0, 0);\r
288   btnUpdateWindows.onClick([&]() {\r
289     lstActiveProcesses.clear();\r
290     for (HWND hwnd = GetTopWindow(NULL); hwnd != nullptr;\r
291          hwnd = GetNextWindow(hwnd, GW_HWNDNEXT)) {\r
292       if (!IsWindowVisible(hwnd))\r
293         continue;\r
294 \r
295       RECT rect;\r
296       GetWindowRect(hwnd, &rect);\r
297 \r
298       char str[1024];\r
299       if (GetModuleFileNameExA(getHwndProcess(hwnd), 0, str, 1024) != 0 &&\r
300           lstActiveProcesses.findString(str) == LB_ERR) {\r
301         lstActiveProcesses.addString(str);\r
302       }\r
303     }\r
304   });\r
305   btnStartMonitoringName.onClick([&]() {\r
306     int sel = lstActiveProcesses.getSelectedIndex();\r
307     if (sel < 0) return;\r
308 \r
309     std::string selStr = lstActiveProcesses.getText(sel);\r
310     \r
311     char *filename = new char[selStr.size()+1];\r
312     std::memcpy(filename, selStr.c_str(), selStr.size());\r
313     filename[selStr.size()] = '\0';\r
314     PathStripPathA(filename);\r
315 \r
316     std::string filenameStr(filename);\r
317 \r
318     if (lstMonitoredProcesses.findString(filenameStr) == LB_ERR)\r
319     {\r
320       lstMonitoredProcesses.addString(filenameStr);\r
321       gameExes.push_back(filenameStr);\r
322       WriteGameExes();\r
323     }\r
324 \r
325     delete[] filename;\r
326   });\r
327   btnStartMonitoringPath.onClick([&]() {\r
328     int sel = lstActiveProcesses.getSelectedIndex();\r
329     if (sel < 0) return;\r
330     std::string selStr = lstActiveProcesses.getText(sel);\r
331     if (lstMonitoredProcesses.findString(selStr) == LB_ERR)\r
332     {\r
333       lstMonitoredProcesses.addString(selStr);\r
334       gameExes.push_back(selStr);\r
335       WriteGameExes();  \r
336     }\r
337   });\r
338   btnStopMonitoring.onClick([&]() {\r
339     int sel = lstMonitoredProcesses.getSelectedIndex();\r
340     if (sel < 0) return;\r
341     lstMonitoredProcesses.remove(sel);\r
342     gameExes.erase(gameExes.begin() + sel);\r
343     WriteGameExes();\r
344   });\r
345 \r
346   window.show();\r
347   window.setDefaultFont();\r
348 \r
349   ws::onConnect = [&]() {\r
350     changeIcon(window.hwnd, icon_red);\r
351     btnConnect.setActive(false);\r
352   };\r
353   ws::onClose = [&]() {\r
354     changeIcon(window.hwnd, icon_white);\r
355     btnConnect.setActive(true);\r
356   };\r
357   ws::onError = ws::onClose;\r
358   ws::init();\r
359 \r
360   window.setTimer(1000, [&btnConnect]() {\r
361     if (! ws::isConnected)\r
362       ws::connect("ws://127.0.0.1:4455");\r
363   });\r
364 \r
365   window.setTimer(100, []() {\r
366     if (!recording) {\r
367       for (auto exe : gameExes) {\r
368         if (checkForegroundProcess(exe)) {\r
369           recording = true;\r
370           process = getHwndProcess(GetForegroundWindow());\r
371           startRecording();\r
372         } \r
373       }\r
374     } else {\r
375       if (!checkProcessRunning(process)) {\r
376         recording = false;\r
377         process = NULL;\r
378         stopRecording();\r
379       }\r
380     }\r
381   });\r
382 \r
383   while (window.update()) {\r
384     ws::update();\r
385   }\r
386 \r
387   return 0;\r
388 }\r