From ca470cfafaf30ee88ec5c1d9744e6594148fbdab Mon Sep 17 00:00:00 2001 From: AIG Date: Thu, 18 Dec 2025 02:55:49 +0300 Subject: [PATCH] clipboard paste --- Engine/system/api/x11api.cpp | 296 ++++++++++++++++++++++------------- Engine/system/api/x11api.h | 6 + Engine/system/systemapi.cpp | 4 + Engine/system/systemapi.h | 14 ++ 4 files changed, 212 insertions(+), 108 deletions(-) diff --git a/Engine/system/api/x11api.cpp b/Engine/system/api/x11api.cpp index 2822db26..dd24a977 100644 --- a/Engine/system/api/x11api.cpp +++ b/Engine/system/api/x11api.cpp @@ -76,6 +76,21 @@ static Atom& _NET_WM_STATE_FULLSCREEN(){ return w; } +static Atom& CLIPBOARD(){ + static Atom w = XInternAtom( dpy, "CLIPBOARD", 0); + return w; + } + +static Atom& UTF8_STRING(){ + static Atom w = XInternAtom( dpy, "UTF8_STRING", 0); + return w; + } + +static Atom& XSEL_DATA(){ + static Atom w = XInternAtom( dpy, "XSEL_DATA", 0); + return w; + } + static bool nativeIsFullscreen(SystemApi::Window *iw) { HWND w(iw); @@ -521,129 +536,167 @@ int X11Api::implExec(SystemApi::AppCallBack &cb) { return 0; } -void X11Api::implProcessEvents(SystemApi::AppCallBack &cb) { - // main message loop - if(XPending(dpy)>0) { - XEvent xev={}; - XNextEvent(dpy, &xev); - - HWND hWnd = xev.xclient.window; - auto it = impl->findWindow(hWnd.ptr()); - if(it==nullptr) - return; - Tempest::Window& cb = *it->owner; - switch( xev.type ) { - case ClientMessage: { - if(xev.xclient.data.l[0] == long(WM_DELETE_WINDOW())){ - SystemApi::exit(); - } - break; - } - case ConfigureNotify: { - cb.setPosition(xev.xconfigure.x, xev.xconfigure.y); - if(xev.xconfigure.width !=cb.w() || xev.xconfigure.height!=cb.h()) { - Tempest::SizeEvent e(xev.xconfigure.width, xev.xconfigure.height); - SystemApi::dispatchResize(cb,e); - } - break; +void X11Api::dispatchEvent(XEvent &xev) { + HWND hWnd = xev.xclient.window; + auto it = impl->findWindow(hWnd.ptr()); + if(it==nullptr) + return; + Tempest::Window& cb = *it->owner; + switch( xev.type ) { + case ClientMessage: { + if(xev.xclient.data.l[0] == long(WM_DELETE_WINDOW())){ + SystemApi::exit(); } - case PropertyNotify:{ - impl->trackFullscreen(hWnd.ptr()); - break; + break; + } + case ConfigureNotify: { + cb.setPosition(xev.xconfigure.x, xev.xconfigure.y); + if(xev.xconfigure.width !=cb.w() || xev.xconfigure.height!=cb.h()) { + Tempest::SizeEvent e(xev.xconfigure.width, xev.xconfigure.height); + SystemApi::dispatchResize(cb,e); } - case MappingNotify: - XRefreshKeyboardMapping(&xev.xmapping); - break; - case ButtonPress: - case ButtonRelease: { - if(xev.xbutton.button==Button4 || xev.xbutton.button==Button5) { - int ticks = 0; - if( xev.xbutton.button == Button4 ) { - ticks = 120; - } - else if ( xev.xbutton.button == Button5 ) { - ticks = -120; - } - if(xev.type==ButtonPress) { - Tempest::MouseEvent e( xev.xbutton.x, - xev.xbutton.y, - Tempest::Event::ButtonNone, - Event::M_NoModifier, - ticks, - 0, - Event::MouseWheel ); - SystemApi::dispatchMouseWheel(cb, e); - } - } else { - MouseEvent e( xev.xbutton.x, - xev.xbutton.y, - toButton( xev.xbutton ), - Event::M_NoModifier, - 0, - 0, - xev.type==ButtonPress ? Event::MouseDown : Event::MouseUp ); - if(xev.type==ButtonPress) - SystemApi::dispatchMouseDown(cb, e); else - SystemApi::dispatchMouseUp(cb, e); + break; + } + case PropertyNotify:{ + impl->trackFullscreen(hWnd.ptr()); + break; + } + case MappingNotify: + XRefreshKeyboardMapping(&xev.xmapping); + break; + case ButtonPress: + case ButtonRelease: { + if(xev.xbutton.button==Button4 || xev.xbutton.button==Button5) { + int ticks = 0; + if( xev.xbutton.button == Button4 ) { + ticks = 120; } - break; - } - case MotionNotify: { - if(activeCursorChange == 1) { - // FIXME: mouse behave crazy in OpenGothic - activeCursorChange = 0; - break; + else if ( xev.xbutton.button == Button5 ) { + ticks = -120; + } + if(xev.type==ButtonPress) { + Tempest::MouseEvent e( xev.xbutton.x, + xev.xbutton.y, + Tempest::Event::ButtonNone, + Event::M_NoModifier, + ticks, + 0, + Event::MouseWheel ); + SystemApi::dispatchMouseWheel(cb, e); } - MouseEvent e( xev.xmotion.x, - xev.xmotion.y, - Event::ButtonNone, + } else { + MouseEvent e( xev.xbutton.x, + xev.xbutton.y, + toButton( xev.xbutton ), Event::M_NoModifier, 0, 0, - Event::MouseMove ); - SystemApi::dispatchMouseMove(cb, e); + xev.type==ButtonPress ? Event::MouseDown : Event::MouseUp ); + if(xev.type==ButtonPress) + SystemApi::dispatchMouseDown(cb, e); else + SystemApi::dispatchMouseUp(cb, e); + } + break; + } + case MotionNotify: { + if(activeCursorChange == 1) { + // FIXME: mouse behave crazy in OpenGothic + activeCursorChange = 0; break; } - case KeyPress: - case KeyRelease: { - KeySym ksym = XLookupKeysym(&xev.xkey,0); - XIC xic = impl->xicOf(hWnd.ptr()); - - char txt[64]={}; - if(xic!=nullptr) { - XEvent xev2 = xev; - xev2.type = KeyPress; // HACK: Their behavior when a client passes a KeyRelease event is undefined. - Status status = {}; - Xutf8LookupString(xic, &xev2.xkey, txt, sizeof(txt)-1, &ksym, &status); - if(status == XBufferOverflow) { - txt[0] = '\0'; - } - // XLookupString(&xev.xkey, txt, sizeof(txt)-1, ksym, nullptr ); + MouseEvent e( xev.xmotion.x, + xev.xmotion.y, + Event::ButtonNone, + Event::M_NoModifier, + 0, + 0, + Event::MouseMove ); + SystemApi::dispatchMouseMove(cb, e); + break; + } + case KeyPress: + case KeyRelease: { + KeySym ksym = XLookupKeysym(&xev.xkey,0); + XIC xic = impl->xicOf(hWnd.ptr()); + + char txt[64]={}; + if(xic!=nullptr) { + XEvent xev2 = xev; + xev2.type = KeyPress; // HACK: Their behavior when a client passes a KeyRelease event is undefined. + Status status = {}; + Xutf8LookupString(xic, &xev2.xkey, txt, sizeof(txt)-1, &ksym, &status); + if(status == XBufferOverflow) { + txt[0] = '\0'; } + // XLookupString(&xev.xkey, txt, sizeof(txt)-1, ksym, nullptr ); + } - auto u16 = TextCodec::toUtf16(txt); // TODO: remove dynamic allocation - auto key = SystemApi::translateKey(ksym); + auto u16 = TextCodec::toUtf16(txt); // TODO: remove dynamic allocation + auto key = SystemApi::translateKey(ksym); - uint32_t scan = xev.xkey.keycode; - // printf("%s\n",txt); + uint32_t scan = xev.xkey.keycode; + // printf("%s\n",txt); - Tempest::KeyEvent e(Event::KeyType(key),uint32_t(u16.size()>0 ? u16[0] : 0),Event::M_NoModifier,(xev.type==KeyPress) ? Event::KeyDown : Event::KeyUp); - if(xev.type==KeyPress) - SystemApi::dispatchKeyDown(cb,e,scan); else - SystemApi::dispatchKeyUp (cb,e,scan); - break; - } - case FocusIn: { - FocusEvent e(true, Event::UnknownReason); - SystemApi::dispatchFocus(cb, e); - break; - } - case FocusOut: { - FocusEvent e(false, Event::UnknownReason); - SystemApi::dispatchFocus(cb, e); - break; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char* data; + + XGetWindowProperty(dpy, xev.xclient.window, + XSEL_DATA(), 0L, ~0L, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, &data); + + Tempest::KeyEvent e(Event::KeyType(key),uint32_t(u16.size()>0 ? u16[0] : 0),Event::M_NoModifier,(xev.type==KeyPress) ? Event::KeyDown : Event::KeyUp); + if(xev.type==KeyPress) + SystemApi::dispatchKeyDown(cb,e,scan); else + SystemApi::dispatchKeyUp (cb,e,scan); + break; + } + case FocusIn: { + FocusEvent e(true, Event::UnknownReason); + SystemApi::dispatchFocus(cb, e); + break; + } + case FocusOut: { + FocusEvent e(false, Event::UnknownReason); + SystemApi::dispatchFocus(cb, e); + break; + } + case SelectionNotify: { + clipboard.type = SystemApi::ClipboardDataType::Unknown; + clipboard.data.clear(); + + if (xev.xselection.property != None) { + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char* data; + + XGetWindowProperty(xev.xselection.display, xev.xselection.requestor, + xev.xselection.property, 0L, ~0L, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, &data); + + if (actual_type == UTF8_STRING() || actual_type == XA_STRING) { + size_t total_bytes = nitems * (actual_format / 8); + clipboard.type = SystemApi::ClipboardDataType::Text; + clipboard.data.assign(data,data+total_bytes); + XFree(data); + } + + XDeleteProperty(dpy, xev.xselection.requestor, xev.xselection.property); } + break; } + } + } + +void X11Api::implProcessEvents(SystemApi::AppCallBack &cb) { + // main message loop + if(XPending(dpy)>0) { + XEvent xev={}; + XNextEvent(dpy, &xev); + + dispatchEvent(xev); std::this_thread::yield(); } else { @@ -657,4 +710,31 @@ void X11Api::implProcessEvents(SystemApi::AppCallBack &cb) { } } +SystemApi::ClipboardData X11Api::implClipboardData(SystemApi::Window *w) { + XConvertSelection(dpy, CLIPBOARD(), UTF8_STRING(), XSEL_DATA(), HWND(w), CurrentTime); + + auto end = std::chrono::high_resolution_clock::now() + std::chrono::seconds(1); + + // waiting for SelectionNotify + while(true){ + if(XPending(dpy)>0) { + XEvent xev={}; + XNextEvent(dpy, &xev); + + dispatchEvent(xev); + + if (xev.type == SelectionNotify) { + break; + } + } + + if(std::chrono::high_resolution_clock::now() > end) { + Log::e("X11 clipboard paste timeout"); + break; + } + } + + return clipboard; +} + #endif diff --git a/Engine/system/api/x11api.h b/Engine/system/api/x11api.h index 0f0fb46b..9823fd99 100644 --- a/Engine/system/api/x11api.h +++ b/Engine/system/api/x11api.h @@ -2,6 +2,8 @@ #include "system/systemapi.h" +typedef union _XEvent XEvent; + namespace Tempest { class X11Api final: SystemApi { @@ -34,9 +36,13 @@ class X11Api final: SystemApi { void implProcessEvents(AppCallBack& cb) override; bool implIsRunning() override; + void dispatchEvent(XEvent &xev); void alignGeometry(Window *w, Tempest::Window& owner); std::unique_ptr impl; + SystemApi::ClipboardData clipboard; + + SystemApi::ClipboardData implClipboardData(SystemApi::Window *w) override; friend class SystemApi; }; diff --git a/Engine/system/systemapi.cpp b/Engine/system/systemapi.cpp index f942ba89..e8452865 100644 --- a/Engine/system/systemapi.cpp +++ b/Engine/system/systemapi.cpp @@ -208,3 +208,7 @@ void SystemApi::showCursor(SystemApi::Window *w, CursorShape show) { float SystemApi::uiScale(Window* w) { return inst().implUiScale(w); } + +SystemApi::ClipboardData SystemApi::clipboardData(SystemApi::Window *w) { + return SystemApi::inst().implClipboardData(w); +} \ No newline at end of file diff --git a/Engine/system/systemapi.h b/Engine/system/systemapi.h index 3e5704e1..5c2354a5 100644 --- a/Engine/system/systemapi.h +++ b/Engine/system/systemapi.h @@ -6,6 +6,7 @@ #include #include +#include namespace Tempest { @@ -39,6 +40,16 @@ class SystemApi { uint16_t result; }; + enum class ClipboardDataType : uint8_t { + Unknown, + Text, + }; + + struct ClipboardData { + ClipboardDataType type = ClipboardDataType::Unknown; + std::vector data; + }; + virtual ~SystemApi()=default; static Window* createWindow(Tempest::Window* owner, uint32_t width, uint32_t height); static Window* createWindow(Tempest::Window* owner, ShowMode sm); @@ -58,6 +69,7 @@ class SystemApi { static void addOverlay (UiOverlay* ui); static void takeOverlay(UiOverlay* ui); + static ClipboardData clipboardData(SystemApi::Window *w); protected: struct AppCallBack { @@ -87,6 +99,8 @@ class SystemApi { virtual void implSetWindowTitle(SystemApi::Window *w, const char* utf8) = 0; + virtual ClipboardData implClipboardData(SystemApi::Window *w) { return ClipboardData(); }; + static void setCursorPosition(SystemApi::Window *w, int x, int y); static void showCursor(SystemApi::Window *w, CursorShape c);