📜 ⬆️ ⬇️

Making a simple cursor in a window of Warcraft 3

Greetings, dear reader. I have a hobby - this is the good old Warcraft 3. On Habré, there was already a series of articles devoted to this wonderful game. I want to share with the community one utility that came in handy when I was streaming. All interested please go under cat.

Foreword

It all started with the fact that one weekend against the background of incessant repair I decided to watch stream on Warcraft III. There are enough sites at the moment, but my preferences relate to the site www.goodgame.ru (not advertising). I was disappointed that nothing interesting at that time was broadcast. And then the thought arose - why not make your own stream with blackjack, etc.

Related software

To conduct the broadcast, among other things, you need an application to capture content. At the moment, there are two of them: xsplit and openbroadcaster . Honestly, I did not use it first. The free version is available basic functionality. But to download the basic version you have to go through the obligatory registration (not that this would be a problem, but ...). The second option was inclined by the GPL license and, accordingly, the availability of the source code. At openbroadcaster, I stopped.

Difficulties

There were no problems with installing and configuring OBS. But the running game did not want to be captured in the recommended Game capture mode (this is probably due to the use of the old version of directx when developing the game). Having played with other capture modes, we managed to find two that provided the necessary behavior — Monitor capture and Window capture.
The first rather strongly affects the performance. It is felt during the game. But it was a working version of what is called "out of the box."
The second option led to discomfort during the game - the cursor constantly went beyond the window. In general, it was absolutely unplayable.
')
Decision

The second option was chosen and it was decided to write a utility to eliminate the discomfort described above.
Initially, Warcraft III runs in full screen mode.
To run in windowed mode, you must use the "-window" key in the application launch command, this will allow you to capture in the Windows capture mode.

To keep the cursor within the client area of ​​the window, the first version of the utility was written. The main cycle of its work is given below:

/* polling version */ void Controller::RunPollingLoop() { while (true) { HWND activeWindow = GetForegroundWindow(); HWND requiredWindow = FindRequiredWindow(m_className, m_winTitle, 5); if (requiredWindow == NULL) throw std::runtime_error("Required window not found"); m_fullScreen.Init(requiredWindow); m_clipHelper.Init(requiredWindow); if (activeWindow == requiredWindow) { if (m_clipHelper.IsClipped() || !CursorInClientArea(requiredWindow)) { Sleep(g_SleepTimeOut); continue; } if (m_fullScreen.Enter()) { DEBUG_TRACE("EnterFullscreen success"); m_clipHelper.Clip(); DEBUG_TRACE("Clip"); } else { DEBUG_TRACE("EnterFullscreen failed"); } } else { if (m_clipHelper.IsClipped()) { if (m_fullScreen.Leave()) { DEBUG_TRACE("LeaveFullscreen success"); } else { DEBUG_TRACE("LeaveFullscreen failed"); } m_clipHelper.UnClip(); DEBUG_TRACE("UnClip"); } Sleep(g_SleepTimeOut); } } } 


Here, the ClipHelper helper class is used to control the cursor hold process and the FullScreen class to manage and restore the fullscreen mode. The loop itself implements the polling algorithm of the active window with a 500 ms timeout. I didn’t like this moment right away, but to move on I needed to check the whole concept, and then go into optimization.

In the process of using the utility, the following options appeared immediately:
- Clip to hold only in the case of a click (hold for polling version) on the client area, to be able to drag the window;
- irritated the taskbar view during the game (relevant if it is fixed). The first thought was to hide it programmatically. But in this case it would be necessary to track the moments when the user exits the game and show the taskbar back. The risk of leaving the user without a taskbar increased. Therefore, I decided to implement the fullscreen implementation by resizing the game window to the size of the monitor resolution for which this window is fixed:

 bool FullScreen::Enter() { if (m_fullScreen) return true; assert(m_hwnd); if (m_hwnd == NULL) return false; HMONITOR hmon = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi = { sizeof(mi) }; if (!GetMonitorInfo(hmon, &mi)) return false; if (!GetWindowRect(m_hwnd, &m_origWindowRect)) { SecureZeroMemory(&m_origWindowRect, sizeof(m_origWindowRect)); return false; } if (!SetWindowPos(m_hwnd, HWND_TOPMOST, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_SHOWWINDOW)) return false; m_fullScreen = true; return true; } 


Optimization

In the second version of the utility, the active window polling was replaced by the hook of the WM_ACTIVATE and WM_LBUTTONDOWN messages. For this, I used two types of hooks: WH_CALLWNDPROC and WH_MOUSE. The bottom line is that we monitor the required game window events and notify our utility through the server window. Hook hung only for the process of the game. Thus, the game should be launched before the utility:

 BOOL SetWinHook(HWND hWnd, DWORD threadId) { if (g_hWndSrv != NULL) return FALSE; //already hooked g_hCallWndHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndHookProc, g_hInst, threadId); if (g_hCallWndHook != NULL) { g_hMouseHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseHookProc, g_hInst, threadId); if (g_hMouseHook != NULL) { g_hWndSrv = hWnd; return TRUE; } ClearWinHook(); } return FALSE; } 

And the main work cycle was reduced to the following procedure:

 LRESULT CALLBACK Controller::MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_ACTIVATE) { switch (wParam) { case WA_ACTIVE: DEBUG_TRACE("WA_ACTIVE"); gs_ActivateClip = true; break; case WA_CLICKACTIVE: DEBUG_TRACE("WA_CLICKACTIVE"); gs_ActivateClip = true; break; case WA_INACTIVE: DEBUG_TRACE("WA_INACTIVE"); gs_ActivateClip = false; if (g_ControllerPtr->ClipCursorHelper().IsClipped()) { if (g_ControllerPtr->FullScreenHelper().Leave()) { DEBUG_TRACE("LeaveFullscreen success"); } else { DEBUG_TRACE("LeaveFullscreen failed"); } g_ControllerPtr->ClipCursorHelper().UnClip(); DEBUG_TRACE("UnClip"); } break; } return 0; } else if (uMsg == WM_LBUTTONDOWN) { DEBUG_TRACE("WM_LBUTTONDOWN"); if (!gs_ActivateClip) return 0; if (g_ControllerPtr->ClipCursorHelper().IsClipped()) return 0; if (g_ControllerPtr->FullScreenHelper().Enter()) { DEBUG_TRACE("EnterFullscreen success"); g_ControllerPtr->ClipCursorHelper().Clip(); DEBUG_TRACE("Clip"); } else { DEBUG_TRACE("EnterFullscreen failed"); } return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } 

The auxiliary classes used are the same as in the first version. This function is the window procedure of the server window of the utility. To capture the cursor and go to full screen, you need to activate the window and left-click on the client area. When a window is no longer active, it is restored to its original size and position, and the cursor is no longer held in it.

Afterword

A utility was developed to make the process of streaming your favorite game more comfortable than the proposed out-of-the-box working version. I would be glad if someone got something interesting for themselves. All source code is uploaded to github WinClipCursor .

Source: https://habr.com/ru/post/194782/


All Articles