Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clipboard support #906

Merged
merged 7 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ meson install -C build/ --skip-subprojects
* **Super + I** : Increase FSR sharpness by 1
* **Super + O** : Decrease FSR sharpness by 1
* **Super + S** : Take screenshot (currently goes to `/tmp/gamescope_$DATE.png`)
* **Super + G** : Toggle keyboard grab
* **Super + S** : Update clipboard
ForTheReallys marked this conversation as resolved.
Show resolved Hide resolved

## Examples

Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ const char usage[] =
" Super + O decrease FSR sharpness by 1\n"
" Super + S take a screenshot\n"
" Super + G toggle keyboard grab\n"
" Super + C update clipboard\n"
"";

std::atomic< bool > g_bRun{true};
Expand Down
49 changes: 49 additions & 0 deletions src/sdlwindow.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// For the nested case, reads input from the SDL window and send to wayland

#include <X11/Xlib.h>
#include <thread>
#include <mutex>

#include <linux/input-event-codes.h>

#include "SDL_clipboard.h"
#include "SDL_events.h"
#include "main.hpp"
#include "wlserver.hpp"
#include "sdlwindow.hpp"
Expand Down Expand Up @@ -32,6 +35,9 @@ extern bool g_bFirstFrame;

SDL_Window *g_SDLWindow;

std::string clipboard;
std::string primarySelection;

enum UserEvents
{
USER_EVENT_TITLE,
Expand Down Expand Up @@ -59,6 +65,8 @@ static std::mutex g_SDLCursorLock;
static SDLPendingCursor g_SDLPendingCursorData;
static bool g_bUpdateSDLCursor = false;

static void set_gamescope_selections();

//-----------------------------------------------------------------------------
// Purpose: Convert from the remote scancode to a Linux event keycode
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -165,6 +173,9 @@ void inputSDLThreadRun( void )

switch( event.type )
{
case SDL_CLIPBOARDUPDATE:
set_gamescope_selections();
break;
case SDL_MOUSEMOTION:
if ( bRelativeMouse )
{
Expand Down Expand Up @@ -259,6 +270,9 @@ void inputSDLThreadRun( void )
event.type = g_unSDLUserEventID + USER_EVENT_TITLE;
SDL_PushEvent( &event );
break;
case KEY_C:
set_gamescope_selections();
break;
default:
handled = false;
}
Expand Down Expand Up @@ -469,6 +483,41 @@ void sdlwindow_title( std::shared_ptr<std::string> title, std::shared_ptr<std::v
}
}

void sdlwindow_set_selection(std::string contents, int selection)
{
if (selection == CLIPBOARD)
{
clipboard = contents;
SDL_SetClipboardText(contents.c_str());
}
else if (selection == PRIMARYSELECTION)
{
primarySelection = contents;
SDL_SetPrimarySelectionText(contents.c_str());
}
}

static void set_gamescope_selections()
{
char *text;

text = SDL_GetClipboardText();
clipboard = text;
SDL_free(text);

text = SDL_GetPrimarySelectionText();
primarySelection = text;
SDL_free(text);

for (int i = 0; i < g_nXWaylandCount; i++)
{
gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(0);
xwayland_ctx_t *ctx = server->ctx.get();
x11_set_selection(ctx, clipboard, CLIPBOARD);
x11_set_selection(ctx, primarySelection, PRIMARYSELECTION);
}
}

void sdlwindow_visible( bool bVisible )
{
if ( !BIsSDLSession() )
Expand Down
6 changes: 6 additions & 0 deletions src/sdlwindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
#include <SDL.h>
#include <SDL_vulkan.h>

#define CLIPBOARD 0
#define PRIMARYSELECTION 1

bool sdlwindow_init( void );

void sdlwindow_update( void );
void sdlwindow_title( std::shared_ptr<std::string> title, std::shared_ptr<std::vector<uint32_t>> icon );
void sdlwindow_set_selection(std::string, int selection);

// called from other threads with interesting things have happened with clients that might warrant updating the nested window
void sdlwindow_visible( bool bVisible );
void sdlwindow_grab( bool bGrab );
void sdlwindow_cursor(std::shared_ptr<std::vector<uint32_t>> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot);

extern SDL_Window *g_SDLWindow;
extern std::string clipboard;
extern std::string primarySelection;
147 changes: 147 additions & 0 deletions src/steamcompmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
* says above. Not that I can really do anything about it
*/

#include "xwayland_ctx.hpp"
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/xfixeswire.h>
#include <cstdint>
#include <drm_mode.h>
#include <memory>
Expand Down Expand Up @@ -4581,6 +4584,127 @@ handle_client_message(xwayland_ctx_t *ctx, XClientMessageEvent *ev)
}
}

void x11_set_selection(xwayland_ctx_t *ctx, std::string contents, int selectionTarget)
{
if (!BIsNested())
{
return;
}

Atom target;
if (selectionTarget == CLIPBOARD)
{
target = ctx->atoms.clipboard;
}
else if (selectionTarget == PRIMARYSELECTION)
{
target = ctx->atoms.primarySelection;
}
else
{
return;
}

XSetSelectionOwner(ctx->dpy, target, ctx->ourWindow, CurrentTime);
XChangeProperty(ctx->dpy, ctx->ourWindow, target, ctx->atoms.utf8StringAtom, 8, PropModeReplace,
(unsigned char *)contents.c_str(), contents.length());
XFlush(ctx->dpy);
}

static void
handle_selection_request(xwayland_ctx_t *ctx, XSelectionRequestEvent *ev)
{
if (!BIsNested())
{
return;
}

std::string *selection = ev->selection == ctx->atoms.primarySelection ? &primarySelection : &clipboard;

const char *targetString = XGetAtomName(ctx->dpy, ev->target);

XEvent response;
response.xselection.type = SelectionNotify;
response.xselection.selection = ev->selection;
response.xselection.requestor = ev->requestor;
response.xselection.time = ev->time;
response.xselection.property = None;
response.xselection.target = None;

if (ev->requestor == ctx->ourWindow)
{
return;
}

if (ev->target == ctx->atoms.targets)
{
Atom targetList[] = {
ctx->atoms.targets,
XA_STRING,
};

XChangeProperty(ctx->dpy, ev->requestor, ev->property, XA_ATOM, 32, PropModeReplace,
(unsigned char *)&targetList, 2);
response.xselection.property = ev->property;
response.xselection.target = ev->target;
}
else if (!strcmp(targetString, "text/plain;charset=utf-8") ||
!strcmp(targetString, "text/plain") ||
!strcmp(targetString, "TEXT") ||
!strcmp(targetString, "UTF8_STRING") ||
!strcmp(targetString, "STRING"))
{
XChangeProperty(ctx->dpy, ev->requestor, ev->property, ev->target, 8, PropModeReplace,
(unsigned char *)selection->c_str(), selection->length());
response.xselection.property = ev->property;
response.xselection.target = ev->target;
}
else
{
xwm_log.debugf("Unsupported clipboard type: %s. Ignoring", targetString);
}

XSendEvent(ctx->dpy, ev->requestor, False, NoEventMask, &response);
XFlush(ctx->dpy);
}

static void
handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev)
{
if (!BIsNested())
{
return;
}

Atom actual_type;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *data = NULL;

XGetWindowProperty(ctx->dpy, ev->requestor, ev->property, 0, 0, False, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after, &data);
if (data) {
XFree(data);
}

if (actual_type == ctx->atoms.utf8StringAtom && actual_format == 8) {
XGetWindowProperty(ctx->dpy, ev->requestor, ev->property, 0, bytes_after, False, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after, &data);
if (data) {
const char *contents = (const char *) data;
if (ev->selection == ctx->atoms.clipboard)
{
sdlwindow_set_selection(contents, CLIPBOARD);
}
else if (ev->selection == ctx->atoms.primarySelection)
{
sdlwindow_set_selection(contents, PRIMARYSELECTION);
}
XFree(data);
}
}
}

template<typename T, typename J>
T bit_cast(const J& src) {
Expand Down Expand Up @@ -5974,6 +6098,13 @@ spawn_client( char **argv )
waitThread.detach();
}

static void
handle_xfixes_selection_notify( xwayland_ctx_t *ctx, XFixesSelectionNotifyEvent *event )
{
XConvertSelection(ctx->dpy, event->selection, ctx->atoms.utf8StringAtom, event->selection, ctx->ourWindow, CurrentTime);
XFlush(ctx->dpy);
}

static void
dispatch_x11( xwayland_ctx_t *ctx )
{
Expand Down Expand Up @@ -6113,6 +6244,12 @@ dispatch_x11( xwayland_ctx_t *ctx )
bShouldResetCursor = true;
}
break;
case SelectionNotify:
handle_selection_notify(ctx, &ev.xselection);
break;
case SelectionRequest:
handle_selection_request(ctx, &ev.xselectionrequest);
break;
default:
if (ev.type == ctx->damage_event + XDamageNotify)
{
Expand All @@ -6122,6 +6259,10 @@ dispatch_x11( xwayland_ctx_t *ctx )
{
cursor->setDirty();
}
else if (ev.type == ctx->xfixes_event + XFixesSelectionNotify)
{
handle_xfixes_selection_notify(ctx, (XFixesSelectionNotifyEvent *) &ev);
}
break;
}
XFlush(ctx->dpy);
Expand Down Expand Up @@ -6491,6 +6632,10 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
ctx->atoms.wineHwndStyle = XInternAtom( ctx->dpy, "_WINE_HWND_STYLE", false );
ctx->atoms.wineHwndStyleEx = XInternAtom( ctx->dpy, "_WINE_HWND_EXSTYLE", false );

ctx->atoms.clipboard = XInternAtom(ctx->dpy, "CLIPBOARD", false);
ctx->atoms.primarySelection = XInternAtom(ctx->dpy, "PRIMARY", false);
ctx->atoms.targets = XInternAtom(ctx->dpy, "TARGETS", false);

ctx->root_width = DisplayWidth(ctx->dpy, ctx->scr);
ctx->root_height = DisplayHeight(ctx->dpy, ctx->scr);

Expand Down Expand Up @@ -6519,6 +6664,8 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
PropertyChangeMask);
XShapeSelectInput(ctx->dpy, ctx->root, ShapeNotifyMask);
XFixesSelectCursorInput(ctx->dpy, ctx->root, XFixesDisplayCursorNotifyMask);
XFixesSelectSelectionInput(ctx->dpy, ctx->root, ctx->atoms.clipboard, XFixesSetSelectionOwnerNotifyMask);
XFixesSelectSelectionInput(ctx->dpy, ctx->root, ctx->atoms.primarySelection, XFixesSetSelectionOwnerNotifyMask);
XQueryTree(ctx->dpy, ctx->root, &root_return, &parent_return, &children, &nchildren);
for (uint32_t i = 0; i < nchildren; i++)
add_win(ctx, children[i], i ? children[i-1] : None, 0);
Expand Down
1 change: 1 addition & 0 deletions src/steamcompmgr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,6 @@ extern uint64_t g_SteamCompMgrVBlankTime;
extern pid_t focusWindow_pid;

void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server);
void x11_set_selection(xwayland_ctx_t *ctx, std::string contents, int selectionTarget);

extern int g_nAsyncFlipsEnabled;
1 change: 1 addition & 0 deletions src/wlserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ extern "C" {
#include "log.hpp"
#include "ime.hpp"
#include "xwayland_ctx.hpp"
#include "sdlwindow.hpp"

#if HAVE_PIPEWIRE
#include "pipewire.hpp"
Expand Down
4 changes: 4 additions & 0 deletions src/xwayland_ctx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,5 +207,9 @@ struct xwayland_ctx_t

Atom wineHwndStyle;
Atom wineHwndStyleEx;

Atom clipboard;
Atom primarySelection;
Atom targets;
} atoms;
};
Loading