Skip to content

Commit 788a201

Browse files
committed
Merge branch 'feat-utf8-clipboard'
2 parents 52859bc + 70b1275 commit 788a201

3 files changed

Lines changed: 244 additions & 7 deletions

File tree

examples/client/SDLvncviewer.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ static rfbBool handleSDLEvent(rfbClient *cl, SDL_Event *e)
307307
char *text = SDL_GetClipboardText();
308308
if(text) {
309309
rfbClientLog("sending clipboard text '%s'\n", text);
310-
SendClientCutText(cl, text, strlen(text));
310+
if(!SendClientCutTextUTF8(cl, text, strlen(text)))
311+
SendClientCutText(cl, text, strlen(text));
311312
}
312313
}
313314

@@ -420,11 +421,18 @@ static rfbBool handleSDLEvent(rfbClient *cl, SDL_Event *e)
420421
return TRUE;
421422
}
422423

423-
static void got_selection(rfbClient *cl, const char *text, int len)
424+
static void got_selection_latin1(rfbClient *cl, const char *text, int len)
424425
{
425-
rfbClientLog("received clipboard text '%s'\n", text);
426+
rfbClientLog("received latin1 clipboard text '%s'\n", text);
426427
if(SDL_SetClipboardText(text) != 0)
427-
rfbClientErr("could not set received clipboard text: %s\n", SDL_GetError());
428+
rfbClientErr("could not set received latin1 clipboard text: %s\n", SDL_GetError());
429+
}
430+
431+
static void got_selection_utf8(rfbClient *cl, const char *buf, int len)
432+
{
433+
rfbClientLog("received utf8 clipboard text '%s'\n", buf);
434+
if(SDL_SetClipboardText(buf) != 0)
435+
rfbClientErr("could not set received utf8 clipboard text: %s\n", SDL_GetError());
428436
}
429437

430438

@@ -496,7 +504,11 @@ int main(int argc,char** argv) {
496504
cl->GotFrameBufferUpdate=update;
497505
cl->HandleKeyboardLedState=kbd_leds;
498506
cl->HandleTextChat=text_chat;
499-
cl->GotXCutText = got_selection;
507+
/* two different cut text handlers here for demo purposes, you
508+
might as well use the same callback for both if it doesn't
509+
matter for your application */
510+
cl->GotXCutText = got_selection_latin1;
511+
cl->GotXCutTextUTF8 = got_selection_utf8;
500512
cl->GetCredential = get_credential;
501513
cl->listenPort = LISTEN_PORT_OFFSET;
502514
cl->listen6Port = LISTEN_PORT_OFFSET;

libvncclient/rfbproto.c

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,12 @@ SetFormatAndEncodings(rfbClient* client)
14461446
if (se->nEncodings < MAX_ENCODINGS)
14471447
encs[se->nEncodings++] = rfbClientSwap32IfLE(rfbEncodingQemuExtendedKeyEvent);
14481448

1449+
#ifdef LIBVNCSERVER_HAVE_LIBZ
1450+
/* extendedclipboard */
1451+
if (se->nEncodings < MAX_ENCODINGS)
1452+
encs[se->nEncodings++] = rfbClientSwap32IfLE(rfbEncodingExtendedClipboard);
1453+
#endif
1454+
14491455
/* client extensions */
14501456
for(e = rfbClientExtensions; e; e = e->next)
14511457
if(e->encodings) {
@@ -1769,6 +1775,78 @@ SendExtendedKeyEvent(rfbClient* client, uint32_t keysym, uint32_t keycode, rfbBo
17691775
}
17701776

17711777

1778+
#ifdef LIBVNCSERVER_HAVE_LIBZ
1779+
/*
1780+
* sendExtClientCutTextNotify
1781+
* it is needed when client send utf8 clipboard data
1782+
* please refer to
1783+
* https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#extended-clipboard-pseudo-encoding
1784+
* to supprot utf-8 clipboard we need at least support notify and text
1785+
*/
1786+
1787+
static rfbBool
1788+
sendExtClientCutTextNotify(rfbClient *client)
1789+
{
1790+
rfbClientCutTextMsg cct = {0, };
1791+
const uint32_t be_flags = rfbClientSwap32IfLE(rfbExtendedClipboard_Notify
1792+
| rfbExtendedClipboard_Text); /*text and notify*/
1793+
cct.type = rfbClientCutText;
1794+
cct.length = rfbClientSwap32IfLE(-((uint32_t)sizeof(be_flags)));/*flag*/
1795+
rfbBool ret = WriteToRFBServer(client, (char *)&cct, sz_rfbClientCutTextMsg)
1796+
&& WriteToRFBServer(client, (char *)&be_flags, sizeof(be_flags));
1797+
return ret;
1798+
}
1799+
1800+
1801+
/*
1802+
* sendExtClientCutTextProvide
1803+
* it need send notify first to grab clipboard (server will check that)
1804+
*/
1805+
1806+
static rfbBool
1807+
sendExtClientCutTextProvide(rfbClient *client, char* data, int len)
1808+
{
1809+
rfbClientCutTextMsg cct = {0, };
1810+
const uint32_t be_flags = rfbClientSwap32IfLE(rfbExtendedClipboard_Provide
1811+
| rfbExtendedClipboard_Text); /*text and provide*/
1812+
const uint32_t be_size = rfbClientSwap32IfLE(len);
1813+
const size_t sz_to_compressed = sizeof(be_size) + len; /*size, data*/
1814+
size_t csz = compressBound(sz_to_compressed + 1); /*tricky, some server need extar byte to flush data*/
1815+
1816+
unsigned char *buf = malloc(sz_to_compressed + 1); /*tricky, some server need extra byte to flush data*/
1817+
if (!buf) {
1818+
rfbClientLog("sendExtClientCutTextProvide. alloc buf failed\n");
1819+
return FALSE;
1820+
}
1821+
memcpy(buf, &be_size, sizeof(be_size));
1822+
memcpy(buf + sizeof(be_size), data, len);
1823+
buf[sz_to_compressed] = 0;
1824+
1825+
unsigned char *cbuf = malloc(sizeof(be_flags) + csz); /*flag, compressed*/
1826+
if (!cbuf) {
1827+
rfbClientLog("sendExtClientCutTextProvide. alloc cbuf failed\n");
1828+
free(buf);
1829+
return FALSE;
1830+
}
1831+
memcpy(cbuf, &be_flags, sizeof(be_flags));
1832+
if (compress(cbuf + sizeof(be_flags), &csz, buf, sz_to_compressed + 1) != Z_OK) {
1833+
rfbClientLog("sendExtClientCutTextProvide: compress cbuf failed\n");
1834+
free(buf);
1835+
free(cbuf);
1836+
return FALSE;
1837+
}
1838+
1839+
cct.type = rfbClientCutText;
1840+
cct.length = rfbClientSwap32IfLE(-(sizeof(be_flags) + csz));/*flag, compressed*/
1841+
rfbBool ret = sendExtClientCutTextNotify(client)
1842+
&& WriteToRFBServer(client, (char *)&cct, sz_rfbClientCutTextMsg)
1843+
&& WriteToRFBServer(client, (char *)cbuf, sizeof(be_flags) + csz);
1844+
free(buf);
1845+
free(cbuf);
1846+
return ret;
1847+
}
1848+
#endif
1849+
17721850
/*
17731851
* SendClientCutText.
17741852
*/
@@ -1787,6 +1865,111 @@ SendClientCutText(rfbClient* client, char *str, int len)
17871865
WriteToRFBServer(client, str, len));
17881866
}
17891867

1868+
rfbBool
1869+
SendClientCutTextUTF8(rfbClient* client, char *str, int len)
1870+
{
1871+
#ifdef LIBVNCSERVER_HAVE_LIBZ
1872+
return client->extendedClipboardServerCapabilities && sendExtClientCutTextProvide(client, str, len);
1873+
#else
1874+
return FALSE;
1875+
#endif
1876+
}
1877+
1878+
1879+
#ifdef LIBVNCSERVER_HAVE_LIBZ
1880+
/*
1881+
* process server clipboard extend text
1882+
*/
1883+
1884+
static rfbBool
1885+
rfbClientProcessExtServerCutText(rfbClient* client, char *data, int len)
1886+
{
1887+
uint32_t flags;
1888+
if (len < sizeof(flags)) {
1889+
rfbClientLog("rfbClientProcessExtServerCutText. len < 4\n");
1890+
return FALSE;
1891+
}
1892+
memcpy(&flags, data, sizeof(flags));
1893+
data += sizeof(flags);
1894+
len -= sizeof(flags);
1895+
flags = rfbClientSwap32IfLE(flags);
1896+
1897+
/*
1898+
* only process (text | provide). Ignore all others
1899+
* modify here if need more types(rtf,html,dib,files)
1900+
*/
1901+
if (!(flags & rfbExtendedClipboard_Text)) {
1902+
rfbClientLog("rfbClientProcessExtServerCutText. not text type. ignore\n");
1903+
return TRUE;
1904+
}
1905+
if (!(flags & rfbExtendedClipboard_Provide)) {
1906+
rfbClientLog("rfbClientProcessExtServerCutText. not provide type. ignore\n");
1907+
return TRUE;
1908+
}
1909+
if (flags & rfbExtendedClipboard_Caps) {
1910+
rfbClientLog("rfbClientProcessExtServerCutText. default cap.\n");
1911+
client->extendedClipboardServerCapabilities |= rfbExtendedClipboard_Text; /* for now, only text */
1912+
return TRUE;
1913+
}
1914+
1915+
z_stream stream;
1916+
stream.zalloc = NULL;
1917+
stream.zfree = NULL;
1918+
stream.opaque = NULL;
1919+
stream.avail_in = 0;
1920+
stream.next_in = NULL;
1921+
if (inflateInit(&stream) != Z_OK) {
1922+
rfbClientLog("rfbClientProcessExtServerCutText. inflateInit failed\n");
1923+
return FALSE;
1924+
}
1925+
stream.avail_in = len;
1926+
stream.next_in = (unsigned char *)data;
1927+
1928+
uint32_t size;
1929+
stream.avail_out = sizeof(size);
1930+
stream.next_out = (unsigned char *)&size;
1931+
if (inflate(&stream, Z_SYNC_FLUSH) != Z_OK) {
1932+
rfbClientLog("rfbClientProcessExtServerCutText. inflate size failed\n");
1933+
inflateEnd(&stream);
1934+
return FALSE;
1935+
}
1936+
size = rfbClientSwap32IfLE(size);
1937+
if (size > (1 << 20)) {
1938+
rfbClientLog("rfbClientProcessExtServerCutText. size too large\n");
1939+
inflateEnd(&stream);
1940+
return FALSE;
1941+
}
1942+
1943+
unsigned char *buf = malloc(size);
1944+
if (!buf) {
1945+
rfbClientLog("rfbClientProcessExtServerCutText. alloc buf failed\n");
1946+
inflateEnd(&stream);
1947+
return FALSE;
1948+
}
1949+
stream.avail_out = size;
1950+
stream.next_out = buf;
1951+
uLong out_before = stream.total_out;
1952+
int err = inflate(&stream, Z_SYNC_FLUSH);
1953+
if (err != Z_OK && err != Z_STREAM_END) {
1954+
rfbClientLog("rfbClientProcessExtServerCutText. inflate buf failed\n");
1955+
free(buf);
1956+
inflateEnd(&stream);
1957+
return FALSE;
1958+
}
1959+
if ((stream.total_out - out_before) != size) {
1960+
rfbClientLog("rfbClientProcessExtServerCutText. inflate size error\n");
1961+
free(buf);
1962+
inflateEnd(&stream);
1963+
return FALSE;
1964+
}
1965+
if (client->GotXCutTextUTF8)
1966+
client->GotXCutTextUTF8(client, buf, size);
1967+
free(buf);
1968+
1969+
inflateEnd(&stream);
1970+
return TRUE;
1971+
}
1972+
#endif
17901973

17911974

17921975
/*
@@ -2332,12 +2515,20 @@ HandleRFBServerMessage(rfbClient* client)
23322515
case rfbServerCutText:
23332516
{
23342517
char *buffer;
2518+
#ifdef LIBVNCSERVER_HAVE_LIBZ
2519+
int32_t ilen; /* also as a flag, if ilen < 0, it is ext clipboard text */
2520+
#endif
23352521

23362522
if (!ReadFromRFBServer(client, ((char *)&msg) + 1,
23372523
sz_rfbServerCutTextMsg - 1))
23382524
return FALSE;
23392525

2526+
#ifdef LIBVNCSERVER_HAVE_LIBZ
2527+
ilen = rfbClientSwap32IfLE(msg.sct.length);
2528+
msg.sct.length = ilen < 0 ? -ilen : ilen;
2529+
#else
23402530
msg.sct.length = rfbClientSwap32IfLE(msg.sct.length);
2531+
#endif
23412532

23422533
if (msg.sct.length > 1<<20) {
23432534
rfbClientErr("Ignoring too big cut text length sent by server: %u B > 1 MB\n", (unsigned int)msg.sct.length);
@@ -2353,8 +2544,18 @@ HandleRFBServerMessage(rfbClient* client)
23532544

23542545
buffer[msg.sct.length] = 0;
23552546

2547+
#ifdef LIBVNCSERVER_HAVE_LIBZ
2548+
if (ilen < 0 && client->GotXCutTextUTF8) {
2549+
if (!rfbClientProcessExtServerCutText(client, buffer, -ilen)) {
2550+
free(buffer);
2551+
return FALSE;
2552+
}
2553+
} else if (client->GotXCutText)
2554+
client->GotXCutText(client, buffer, msg.sct.length);
2555+
#else
23562556
if (client->GotXCutText)
23572557
client->GotXCutText(client, buffer, msg.sct.length);
2558+
#endif
23582559

23592560
free(buffer);
23602561

rfb/rfbclient.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ typedef char* (*GetPasswordProc)(struct _rfbClient* client);
217217
typedef rfbCredential* (*GetCredentialProc)(struct _rfbClient* client, int credentialType);
218218
typedef rfbBool (*MallocFrameBufferProc)(struct _rfbClient* client);
219219
typedef void (*GotXCutTextProc)(struct _rfbClient* client, const char *text, int textlen);
220+
typedef void (*GotXCutTextUTF8Proc)(struct _rfbClient* client, const char* buffer, int buffer_len);
220221
typedef void (*BellProc)(struct _rfbClient* client);
221222
/**
222223
Called when a cursor shape update was received from the server. The decoded cursor shape
@@ -477,6 +478,14 @@ typedef struct _rfbClient {
477478
* Used for intended dimensions, rfbClient.width and rfbClient.height are used to manage the real framebuffer dimensions.
478479
*/
479480
rfbExtDesktopScreen screen;
481+
482+
#ifdef LIBVNCSERVER_HAVE_LIBZ
483+
uint32_t extendedClipboardServerCapabilities;
484+
#endif
485+
/**
486+
* Callback fired when "Extended Clipboard" UTF-8 text data is received.
487+
*/
488+
GotXCutTextUTF8Proc GotXCutTextUTF8;
480489
} rfbClient;
481490

482491
/* cursor.c */
@@ -596,18 +605,33 @@ extern rfbBool SendKeyEvent(rfbClient* client,uint32_t key, rfbBool down);
596605
*/
597606
extern rfbBool SendExtendedKeyEvent(rfbClient* client, uint32_t keysym, uint32_t keycode, rfbBool down);
598607
/**
599-
* Places a string on the server's clipboard. Use this function if you want to
608+
* Places a Latin-1-encoded string on the server's clipboard. Use this function if you want to
600609
* be able to copy and paste between the server and your application. For
601610
* instance, when your application is notified that the user copied some text
602611
* onto the clipboard, you would call this function to synchronize the server's
603612
* clipboard with your local clipboard.
604613
* @param client The client structure through which to send the client cut text
605614
* message
606-
* @param str The string to send (doesn't need to be NULL terminated)
615+
* @param str The string to send (needs to be Latin-1-encoded, doesn't need to be NULL terminated)
607616
* @param len The length of the string
608617
* @return true if the client cut message was sent successfully, false otherwise
609618
*/
610619
extern rfbBool SendClientCutText(rfbClient* client,char *str, int len);
620+
/**
621+
* Places a UTF-8-encoded string on the server's clipboard if the server supports it.
622+
* Use this function if you want to be able to copy and paste between the server and your application. For
623+
* instance, when your application is notified that the user copied some text
624+
* onto the clipboard, you would call this function to synchronize the server's
625+
* clipboard with your local clipboard. This is the more modern equivalent of
626+
* SendClientCutText().
627+
* @param client The client structure through which to send the client cut text
628+
* message
629+
* @param str The string to send (needs to be UTF-8-encoded, doesn't need to be NULL terminated)
630+
* @param len The length of the string in bytes
631+
* @return true if the message was sent successfully, false otherwise - you might want to
632+
* fall back to SendClientCutText() in this case.
633+
*/
634+
extern rfbBool SendClientCutTextUTF8(rfbClient* client, char *str, int len);
611635
/**
612636
* Handles messages from the RFB server. You must call this function
613637
* intermittently so LibVNCClient can parse messages from the server. For

0 commit comments

Comments
 (0)