mirror of
https://gitlab.com/Mr_Goldberg/goldberg_emulator.git
synced 2024-11-27 14:08:39 +01:00
390 lines
13 KiB
C++
390 lines
13 KiB
C++
|
// dear imgui: Platform Binding for Linux (standard X11 API for 32 and 64 bits applications)
|
||
|
// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..)
|
||
|
|
||
|
// https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html
|
||
|
// https://stackoverflow.com/questions/27378318/c-get-string-from-clipboard-on-linux
|
||
|
|
||
|
// Implemented features:
|
||
|
// [X] Platform: Clipboard support
|
||
|
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
|
||
|
// [X] Platform: Keyboard arrays indexed using
|
||
|
// [ ] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
|
||
|
|
||
|
#include "imgui.h"
|
||
|
#include "imgui_impl_x11.h"
|
||
|
|
||
|
#include <X11/X.h>
|
||
|
#include <X11/Xlib.h>
|
||
|
#include <X11/XKBlib.h>
|
||
|
#include <X11/keysym.h>
|
||
|
|
||
|
#include <cstdlib>
|
||
|
#include <climits>
|
||
|
#include <ctime>
|
||
|
#include <cstdint>
|
||
|
|
||
|
// CHANGELOG
|
||
|
// (minor and older changes stripped away, please see git history for details)
|
||
|
// 2021-15-06: Clipboard support.
|
||
|
// 2019-08-31: Initial X11 implementation.
|
||
|
|
||
|
// X11 Data
|
||
|
static Atom g_BufId;
|
||
|
static Atom g_PropId;
|
||
|
static Atom g_FmtIdUtf8String;
|
||
|
static Atom g_IncrId;
|
||
|
|
||
|
static char* g_ClipboardBuffer = NULL;
|
||
|
static size_t g_ClipboardBufferLength = 0;
|
||
|
static size_t g_ClipboardBufferSize = 0;
|
||
|
static bool g_ClipboardOwned = false;
|
||
|
|
||
|
static Display* g_Display = nullptr;
|
||
|
static Window g_Window = 0;
|
||
|
static uint64_t g_Time = 0;
|
||
|
static uint64_t g_TicksPerSecond = 0;
|
||
|
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
|
||
|
static bool g_HasGamepad = false;
|
||
|
static bool g_WantUpdateHasGamepad = true;
|
||
|
|
||
|
static bool GetKeyState(int keysym, char keys[32])
|
||
|
{
|
||
|
int keycode = XKeysymToKeycode(g_Display, keysym);
|
||
|
return keys[keycode/8] & (1<<keycode%8);
|
||
|
}
|
||
|
|
||
|
static inline int KeyToImGuiKey(int key)
|
||
|
{
|
||
|
if (key > 255)
|
||
|
return key - 0xFF00;
|
||
|
|
||
|
return key;
|
||
|
}
|
||
|
|
||
|
static void ImGui_ImplX11_SendClipboard(XSelectionRequestEvent* sender)
|
||
|
{
|
||
|
XSelectionEvent event;
|
||
|
|
||
|
XChangeProperty(g_Display, sender->requestor, sender->property, g_FmtIdUtf8String, 8, PropModeReplace,
|
||
|
(const unsigned char*)g_ClipboardBuffer, g_ClipboardBufferLength);
|
||
|
|
||
|
event.type = SelectionNotify;
|
||
|
event.requestor = sender->requestor;
|
||
|
event.selection = sender->selection;
|
||
|
event.target = sender->target;
|
||
|
event.property = sender->property;
|
||
|
event.time = sender->time;
|
||
|
|
||
|
XSendEvent(g_Display, sender->requestor, True, NoEventMask, (XEvent *)&event);
|
||
|
}
|
||
|
|
||
|
static void ImGui_ImplX11_SetClipboardText(void* user_data, const char* text)
|
||
|
{
|
||
|
g_ClipboardBufferLength = strlen(text);
|
||
|
if(g_ClipboardBufferLength > 0)
|
||
|
{
|
||
|
if(g_ClipboardBufferLength >= g_ClipboardBufferSize)
|
||
|
{
|
||
|
free(g_ClipboardBuffer);
|
||
|
g_ClipboardBuffer = (char*)malloc(sizeof(char) * g_ClipboardBufferLength);
|
||
|
g_ClipboardBufferSize = g_ClipboardBufferLength;
|
||
|
}
|
||
|
memcpy(g_ClipboardBuffer, text, g_ClipboardBufferLength);
|
||
|
|
||
|
if(!g_ClipboardOwned)
|
||
|
{
|
||
|
g_ClipboardOwned = true;
|
||
|
XSetSelectionOwner(g_Display, g_BufId, g_Window, CurrentTime);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static const char* ImGui_ImplX11_GetClipboardText(void *user_data)
|
||
|
{
|
||
|
XEvent event;
|
||
|
char *result;
|
||
|
unsigned long ressize, restail;
|
||
|
int resbits;
|
||
|
bool timed_out;
|
||
|
time_t now;
|
||
|
|
||
|
now = time(NULL);
|
||
|
timed_out = false;
|
||
|
XConvertSelection(g_Display, g_BufId, g_FmtIdUtf8String, g_PropId, g_Window, CurrentTime);
|
||
|
do
|
||
|
{
|
||
|
XNextEvent(g_Display, &event);
|
||
|
if(event.type == SelectionRequest)
|
||
|
{// This happens when we are requesting our own buffer.
|
||
|
ImGui_ImplX11_SendClipboard(&event.xselectionrequest);
|
||
|
continue;
|
||
|
}
|
||
|
if(time(NULL) - now > 2)
|
||
|
{
|
||
|
timed_out = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
while (event.type != SelectionNotify || event.xselection.selection != g_BufId);
|
||
|
|
||
|
g_ClipboardBuffer[0] = '\0';
|
||
|
if (!timed_out && event.xselection.property)
|
||
|
{
|
||
|
XGetWindowProperty(g_Display, g_Window, g_PropId, 0, LONG_MAX/4, False, AnyPropertyType,
|
||
|
&g_FmtIdUtf8String, &resbits, &ressize, &restail, (unsigned char**)&result);
|
||
|
|
||
|
if (g_FmtIdUtf8String == g_IncrId)
|
||
|
{
|
||
|
IM_ASSERT(0 && "Buffer is too large and INCR reading is not implemented yet.\n");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(ressize > g_ClipboardBufferSize)
|
||
|
{
|
||
|
free(g_ClipboardBuffer);
|
||
|
g_ClipboardBufferSize = ressize + 1;
|
||
|
g_ClipboardBuffer = (char*)malloc(sizeof(char) * g_ClipboardBufferSize);
|
||
|
}
|
||
|
memcpy(g_ClipboardBuffer, result, ressize);
|
||
|
g_ClipboardBuffer[ressize] = '\0';
|
||
|
}
|
||
|
|
||
|
XFree(result);
|
||
|
}
|
||
|
|
||
|
return g_ClipboardBuffer;
|
||
|
}
|
||
|
|
||
|
// Functions
|
||
|
bool ImGui_ImplX11_Init(void *display, void *window)
|
||
|
{
|
||
|
timespec ts, tsres;
|
||
|
clock_getres(CLOCK_MONOTONIC_RAW, &tsres);
|
||
|
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||
|
|
||
|
g_TicksPerSecond = 1000000000.0f / (static_cast<uint64_t>(tsres.tv_nsec) + static_cast<uint64_t>(tsres.tv_sec)*1000000000);
|
||
|
g_Time = static_cast<uint64_t>(ts.tv_nsec) + static_cast<uint64_t>(ts.tv_sec)*1000000000;
|
||
|
|
||
|
// Setup back-end capabilities flags
|
||
|
g_Display = reinterpret_cast<Display*>(display);
|
||
|
g_Window = reinterpret_cast<Window>(window);
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
|
||
|
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
|
||
|
io.BackendPlatformName = "imgui_impl_x11";
|
||
|
io.ImeWindowHandle = nullptr;
|
||
|
|
||
|
io.GetClipboardTextFn = ImGui_ImplX11_GetClipboardText;
|
||
|
io.SetClipboardTextFn = ImGui_ImplX11_SetClipboardText;
|
||
|
|
||
|
g_ClipboardBuffer = (char*)malloc(sizeof(char) * 256);
|
||
|
g_ClipboardBufferSize = 256;
|
||
|
|
||
|
g_BufId = XInternAtom(g_Display, "CLIPBOARD", False);
|
||
|
g_PropId = XInternAtom(g_Display, "XSEL_DATA", False);
|
||
|
g_FmtIdUtf8String = XInternAtom(g_Display, "UTF8_STRING", False);
|
||
|
g_IncrId = XInternAtom(g_Display, "INCR", False);
|
||
|
|
||
|
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime.
|
||
|
io.KeyMap[ImGuiKey_Tab] = KeyToImGuiKey(XK_Tab);
|
||
|
io.KeyMap[ImGuiKey_LeftArrow] = KeyToImGuiKey(XK_Left);
|
||
|
io.KeyMap[ImGuiKey_RightArrow] = KeyToImGuiKey(XK_Right);
|
||
|
io.KeyMap[ImGuiKey_UpArrow] = KeyToImGuiKey(XK_Up);
|
||
|
io.KeyMap[ImGuiKey_DownArrow] = KeyToImGuiKey(XK_Down);
|
||
|
io.KeyMap[ImGuiKey_PageUp] = KeyToImGuiKey(XK_Prior);
|
||
|
io.KeyMap[ImGuiKey_PageDown] = KeyToImGuiKey(XK_Next);
|
||
|
io.KeyMap[ImGuiKey_Home] = KeyToImGuiKey(XK_Home);
|
||
|
io.KeyMap[ImGuiKey_End] = KeyToImGuiKey(XK_End);
|
||
|
io.KeyMap[ImGuiKey_Insert] = KeyToImGuiKey(XK_Insert);
|
||
|
io.KeyMap[ImGuiKey_Delete] = KeyToImGuiKey(XK_Delete);
|
||
|
io.KeyMap[ImGuiKey_Backspace] = KeyToImGuiKey(XK_BackSpace);
|
||
|
io.KeyMap[ImGuiKey_Space] = KeyToImGuiKey(XK_space);
|
||
|
io.KeyMap[ImGuiKey_Enter] = KeyToImGuiKey(XK_Return);
|
||
|
io.KeyMap[ImGuiKey_Escape] = KeyToImGuiKey(XK_Escape);
|
||
|
io.KeyMap[ImGuiKey_KeyPadEnter] = KeyToImGuiKey(XK_KP_Enter);
|
||
|
io.KeyMap[ImGuiKey_A] = KeyToImGuiKey(XK_a);
|
||
|
io.KeyMap[ImGuiKey_C] = KeyToImGuiKey(XK_c);
|
||
|
io.KeyMap[ImGuiKey_V] = KeyToImGuiKey(XK_v);
|
||
|
io.KeyMap[ImGuiKey_X] = KeyToImGuiKey(XK_x);
|
||
|
io.KeyMap[ImGuiKey_Y] = KeyToImGuiKey(XK_y);
|
||
|
io.KeyMap[ImGuiKey_Z] = KeyToImGuiKey(XK_z);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void ImGui_ImplX11_Shutdown()
|
||
|
{
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
io.GetClipboardTextFn = NULL;
|
||
|
io.SetClipboardTextFn = NULL;
|
||
|
|
||
|
free(g_ClipboardBuffer); g_ClipboardBuffer = NULL;
|
||
|
g_ClipboardBufferSize = 0;
|
||
|
g_ClipboardBufferLength = 0;
|
||
|
|
||
|
g_Display = nullptr;
|
||
|
g_Window = 0;
|
||
|
}
|
||
|
|
||
|
static bool ImGui_ImplX11_UpdateMouseCursor()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void ImGui_ImplX11_UpdateMousePos()
|
||
|
{
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
|
||
|
// Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
|
||
|
if (io.WantSetMousePos)
|
||
|
{
|
||
|
// POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y };
|
||
|
// ::ClientToScreen(g_hWnd, &pos);
|
||
|
// ::SetCursorPos(pos.x, pos.y);
|
||
|
}
|
||
|
|
||
|
// Set mouse position
|
||
|
Window unused_window;
|
||
|
int rx, ry, x, y;
|
||
|
unsigned int mask;
|
||
|
|
||
|
XQueryPointer(g_Display, g_Window, &unused_window, &unused_window, &rx, &ry, &x, &y, &mask);
|
||
|
|
||
|
io.MousePos = ImVec2((float)x, (float)y);
|
||
|
}
|
||
|
|
||
|
// Gamepad navigation mapping
|
||
|
static void ImGui_ImplX11_UpdateGamepads()
|
||
|
{
|
||
|
// TODO: support linux gamepad ?
|
||
|
#ifndef IMGUI_IMPL_X11_DISABLE_GAMEPAD
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void ImGui_ImplX11_NewFrame()
|
||
|
{
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().");
|
||
|
|
||
|
unsigned int width, height;
|
||
|
Window unused_window;
|
||
|
int unused_int;
|
||
|
unsigned int unused_unsigned_int;
|
||
|
|
||
|
XGetGeometry(g_Display, (Window)g_Window, &unused_window, &unused_int, &unused_int, &width, &height, &unused_unsigned_int, &unused_unsigned_int);
|
||
|
|
||
|
io.DisplaySize.x = width;
|
||
|
io.DisplaySize.y = height;
|
||
|
|
||
|
timespec ts, tsres;
|
||
|
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||
|
|
||
|
uint64_t current_time = static_cast<uint64_t>(ts.tv_nsec) + static_cast<uint64_t>(ts.tv_sec)*1000000000;
|
||
|
|
||
|
io.DeltaTime = (float)(current_time - g_Time) / g_TicksPerSecond;
|
||
|
g_Time = current_time;
|
||
|
|
||
|
// Read keyboard modifiers inputs
|
||
|
char keys[32];
|
||
|
XQueryKeymap(g_Display, keys);
|
||
|
|
||
|
io.KeyCtrl = GetKeyState(XK_Control_L, keys);
|
||
|
io.KeyShift = GetKeyState(XK_Shift_L, keys);
|
||
|
io.KeyAlt = GetKeyState(XK_Alt_L, keys);
|
||
|
io.KeySuper = GetKeyState(XK_Super_L, keys);
|
||
|
|
||
|
// io.KeysDown[], io.MousePos, io.MouseDown[], io.MouseWheel: filled by the WndProc handler below.
|
||
|
|
||
|
// Update OS mouse position
|
||
|
ImGui_ImplX11_UpdateMousePos();
|
||
|
|
||
|
// Update game controllers (if enabled and available)
|
||
|
ImGui_ImplX11_UpdateGamepads();
|
||
|
}
|
||
|
|
||
|
// Process X11 mouse/keyboard inputs.
|
||
|
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
||
|
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
|
||
|
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
|
||
|
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
||
|
IMGUI_IMPL_API int ImGui_ImplX11_EventHandler(XEvent &event)
|
||
|
{
|
||
|
if (ImGui::GetCurrentContext() == NULL)
|
||
|
return 0;
|
||
|
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
switch (event.type)
|
||
|
{
|
||
|
case ButtonPress:
|
||
|
case ButtonRelease:
|
||
|
switch(event.xbutton.button)
|
||
|
{
|
||
|
case Button1:
|
||
|
io.MouseDown[0] = event.type == ButtonPress;
|
||
|
break;
|
||
|
|
||
|
case Button2:
|
||
|
io.MouseDown[2] = event.type == ButtonPress;
|
||
|
break;
|
||
|
|
||
|
case Button3:
|
||
|
io.MouseDown[1] = event.type == ButtonPress;
|
||
|
break;
|
||
|
|
||
|
case Button4: // Mouse wheel up
|
||
|
if( event.type == ButtonPress )
|
||
|
io.MouseWheel += 1;
|
||
|
return 0;
|
||
|
|
||
|
case Button5: // Mouse wheel down
|
||
|
if( event.type == ButtonPress )
|
||
|
io.MouseWheel -= 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case KeyPress:
|
||
|
{
|
||
|
int key = XkbKeycodeToKeysym(g_Display, event.xkey.keycode, 0, event.xkey.state & ShiftMask ? 1 : 0);
|
||
|
if( key < 255 )
|
||
|
{
|
||
|
io.KeysDown[key] = true;
|
||
|
io.AddInputCharacter(key);
|
||
|
}
|
||
|
else if( key >= 0x1000100 && key <= 0x110ffff )
|
||
|
{
|
||
|
io.AddInputCharacterUTF16(key);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
io.KeysDown[key - 0xFF00] = true;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
case KeyRelease:
|
||
|
{
|
||
|
int key = XkbKeycodeToKeysym(g_Display, event.xkey.keycode, 0, event.xkey.state & ShiftMask ? 1 : 0);
|
||
|
io.KeysDown[KeyToImGuiKey(key)] = false;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
case FocusOut:
|
||
|
memset(io.KeysDown, 0, sizeof(io.KeysDown));
|
||
|
return 0;
|
||
|
|
||
|
|
||
|
case SelectionClear:
|
||
|
g_ClipboardOwned = false;
|
||
|
return 0;
|
||
|
|
||
|
case SelectionRequest:
|
||
|
{
|
||
|
ImGui_ImplX11_SendClipboard(&event.xselectionrequest);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|