// 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 #include #include #include #include #include #include #include // 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< 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(tsres.tv_nsec) + static_cast(tsres.tv_sec)*1000000000); g_Time = static_cast(ts.tv_nsec) + static_cast(ts.tv_sec)*1000000000; // Setup back-end capabilities flags g_Display = reinterpret_cast(display); g_Window = reinterpret_cast(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(ts.tv_nsec) + static_cast(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; }