goldberg_emulator/overlay_experimental/linux/X11_Hook.cpp

334 lines
8.7 KiB
C++

/*
* Copyright (C) 2019-2020 Nemirtingas
* This file is part of the ingame overlay project
*
* The ingame overlay project is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* The ingame overlay project is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the ingame overlay project; if not, see
* <http://www.gnu.org/licenses/>.
*/
#include "X11_Hook.h"
#include <imgui.h>
#include <backends/imgui_impl_x11.h>
#include <System/Library.h>
extern int ImGui_ImplX11_EventHandler(XEvent &event);
constexpr decltype(X11_Hook::DLL_NAME) X11_Hook::DLL_NAME;
X11_Hook* X11_Hook::_inst = nullptr;
uint32_t ToggleKeyToNativeKey(ingame_overlay::ToggleKey k)
{
struct {
ingame_overlay::ToggleKey lib_key;
uint32_t native_key;
} mapping[] = {
{ ingame_overlay::ToggleKey::ALT , XK_Alt_L },
{ ingame_overlay::ToggleKey::CTRL , XK_Control_L },
{ ingame_overlay::ToggleKey::SHIFT, XK_Shift_L },
{ ingame_overlay::ToggleKey::TAB , XK_Tab },
{ ingame_overlay::ToggleKey::F1 , XK_F1 },
{ ingame_overlay::ToggleKey::F2 , XK_F2 },
{ ingame_overlay::ToggleKey::F3 , XK_F3 },
{ ingame_overlay::ToggleKey::F4 , XK_F4 },
{ ingame_overlay::ToggleKey::F5 , XK_F5 },
{ ingame_overlay::ToggleKey::F6 , XK_F6 },
{ ingame_overlay::ToggleKey::F7 , XK_F7 },
{ ingame_overlay::ToggleKey::F8 , XK_F8 },
{ ingame_overlay::ToggleKey::F9 , XK_F9 },
{ ingame_overlay::ToggleKey::F10 , XK_F10 },
{ ingame_overlay::ToggleKey::F11 , XK_F11 },
{ ingame_overlay::ToggleKey::F12 , XK_F12 },
};
for (auto const& item : mapping)
{
if (item.lib_key == k)
return item.native_key;
}
return 0;
}
bool GetKeyState(Display* d, KeySym keySym, char szKey[32])
{
int iKeyCodeToFind = XKeysymToKeycode(d, keySym);
return szKey[iKeyCodeToFind / 8] & (1 << (iKeyCodeToFind % 8));
}
bool X11_Hook::StartHook(std::function<bool(bool)>& _key_combination_callback, std::set<ingame_overlay::ToggleKey> const& toggle_keys)
{
if (!_Hooked)
{
if (!_key_combination_callback)
{
SPDLOG_ERROR("Failed to hook X11: No key combination callback.");
return false;
}
if (toggle_keys.empty())
{
SPDLOG_ERROR("Failed to hook X11: No key combination.");
return false;
}
void* hX11 = System::Library::GetLibraryHandle(DLL_NAME);
if (hX11 == nullptr)
{
SPDLOG_WARN("Failed to hook X11: Cannot find {}", DLL_NAME);
return false;
}
System::Library::Library libX11;
LibraryName = System::Library::GetLibraryPath(hX11);
if (!libX11.OpenLibrary(LibraryName, false))
{
SPDLOG_WARN("Failed to hook X11: Cannot load {}", LibraryName);
return false;
}
struct {
void** func_ptr;
void* hook_ptr;
const char* func_name;
} hook_array[] = {
{ (void**)&XEventsQueued, &X11_Hook::MyXEventsQueued, "XEventsQueued" },
{ (void**)&XPending , &X11_Hook::MyXPending , "XPending" },
};
for (auto& entry : hook_array)
{
*entry.func_ptr = libX11.GetSymbol<void*>(entry.func_name);
if (entry.func_ptr == nullptr)
{
SPDLOG_ERROR("Failed to hook X11: Event function {} missing.", entry.func_name);
return false;
}
}
SPDLOG_INFO("Hooked X11");
_KeyCombinationCallback = std::move(_key_combination_callback);
for (auto& key : toggle_keys)
{
uint32_t k = ToggleKeyToNativeKey(key);
if (k != 0)
{
_NativeKeyCombination.insert(k);
}
}
_Hooked = true;
BeginHook();
for (auto& entry : hook_array)
{
HookFunc(std::make_pair(entry.func_ptr, entry.hook_ptr));
}
EndHook();
}
return true;
}
void X11_Hook::ResetRenderState()
{
if (_Initialized)
{
_GameWnd = 0;
_Initialized = false;
ImGui_ImplX11_Shutdown();
}
}
void X11_Hook::SetInitialWindowSize(Display* display, Window wnd)
{
unsigned int width, height;
Window unused_window;
int unused_int;
unsigned int unused_unsigned_int;
XGetGeometry(display, wnd, &unused_window, &unused_int, &unused_int, &width, &height, &unused_unsigned_int, &unused_unsigned_int);
ImGui::GetIO().DisplaySize = ImVec2((float)width, (float)height);
}
bool X11_Hook::PrepareForOverlay(Display *display, Window wnd)
{
if(!_Hooked)
return false;
if (_GameWnd != wnd)
ResetRenderState();
if (!_Initialized)
{
ImGui_ImplX11_Init(display, (void*)wnd);
_GameWnd = wnd;
_Initialized = true;
}
ImGui_ImplX11_NewFrame();
return true;
}
/////////////////////////////////////////////////////////////////////////////////////
// X11 window hooks
bool IgnoreEvent(XEvent &event)
{
switch(event.type)
{
// Keyboard
case KeyPress: case KeyRelease:
// MouseButton
case ButtonPress: case ButtonRelease:
// Mouse move
case MotionNotify:
// Copy to clipboard request
case SelectionRequest:
return true;
}
return false;
}
int X11_Hook::_CheckForOverlay(Display *d, int num_events)
{
static Time prev_time = {};
X11_Hook* inst = Inst();
char szKey[32];
if( _Initialized )
{
XEvent event;
while(num_events)
{
bool skip_input = _KeyCombinationCallback(false);
XPeekEvent(d, &event);
ImGui_ImplX11_EventHandler(event);
// Is the event is a key press
if (event.type == KeyPress || event.type == KeyRelease)
{
XQueryKeymap(d, szKey);
int key_count = 0;
for (auto const& key : inst->_NativeKeyCombination)
{
if (GetKeyState(d, key, szKey))
++key_count;
}
if (key_count == inst->_NativeKeyCombination.size())
{// All shortcut keys are pressed
if (!inst->_KeyCombinationPushed)
{
if (inst->_KeyCombinationCallback(true))
{
skip_input = true;
// Save the last known cursor pos when opening the overlay
// so we can spoof the GetCursorPos return value.
//inst->GetCursorPos(&inst->_SavedCursorPos);
}
inst->_KeyCombinationPushed = true;
}
}
else
{
inst->_KeyCombinationPushed = false;
}
}
if (!skip_input || !IgnoreEvent(event))
{
if(num_events)
num_events = 1;
break;
}
XNextEvent(d, &event);
--num_events;
}
}
return num_events;
}
int X11_Hook::MyXEventsQueued(Display *display, int mode)
{
X11_Hook* inst = X11_Hook::Inst();
int res = inst->XEventsQueued(display, mode);
if( res )
{
res = inst->_CheckForOverlay(display, res);
}
return res;
}
int X11_Hook::MyXPending(Display* display)
{
int res = Inst()->XPending(display);
if( res )
{
res = Inst()->_CheckForOverlay(display, res);
}
return res;
}
/////////////////////////////////////////////////////////////////////////////////////
X11_Hook::X11_Hook() :
_Initialized(false),
_Hooked(false),
_GameWnd(0),
_KeyCombinationPushed(false),
XEventsQueued(nullptr),
XPending(nullptr)
{
}
X11_Hook::~X11_Hook()
{
SPDLOG_INFO("X11 Hook removed");
ResetRenderState();
_inst = nullptr;
}
X11_Hook* X11_Hook::Inst()
{
if (_inst == nullptr)
_inst = new X11_Hook;
return _inst;
}
std::string X11_Hook::GetLibraryName() const
{
return LibraryName;
}