Skip to content

Commit 1776764

Browse files
examples: add an XCB-based X11 example
* examples/x11vncserver: add X11 example * examples/x11vncserver: add comments abot compiling * add `x11vncserver.c` in the CMake build system * use `FindX11.cmake` and clear `NOTFOUND` * add some comments * add some comments * rename x11vncserver to x11 * Need CMake 3.24.0 to find X11 libraries * Need CMake 3.24.0 to find XCB libraries
1 parent c8af4e9 commit 1776764

3 files changed

Lines changed: 198 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ examples/simple
3434
examples/simple15
3535
examples/storepasswd
3636
examples/vncev
37+
examples/x11
3738
test/blooptest
3839
test/cargstest
3940
test/copyrecttest

CMakeLists.txt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ option(WITH_24BPP "Allow 24 bpp" ON)
5959
option(WITH_IPv6 "Enable IPv6 Support" ON)
6060
option(WITH_WEBSOCKETS "Build with websockets support" ON)
6161
option(WITH_SASL "Build with SASL support" ON)
62+
option(WITH_XCB "Build with XCB support" ON)
6263
option(WITH_EXAMPLES "Build examples" ON)
6364
option(WITH_TESTS "Build tests" ON)
6465

@@ -72,6 +73,10 @@ if(WITH_LZO)
7273
find_package(LZO)
7374
endif()
7475

76+
if(WITH_XCB)
77+
find_package(X11) # Need CMake 3.24.0 to find XCB libraries. see https://cmake.org/cmake/help/v3.24/module/FindX11.html
78+
endif()
79+
7580
if(WITH_JPEG)
7681
find_package(JPEG)
7782
if(JPEG_FOUND)
@@ -585,6 +590,18 @@ if(ANDROID)
585590
)
586591
endif(ANDROID)
587592

593+
if(X11_xcb_FOUND AND X11_xcb_xtest_FOUND AND X11_xcb_keysyms_FOUND)
594+
set(LIBVNCSERVER_EXAMPLES
595+
${LIBVNCSERVER_EXAMPLES}
596+
x11
597+
)
598+
else()
599+
# clear NOTFOUND
600+
unset(X11_xcb_LIB CACHE)
601+
unset(X11_xcb_xtest_LIB CACHE)
602+
unset(X11_xcb_keysyms_LIB CACHE)
603+
endif(X11_xcb_FOUND AND X11_xcb_xtest_FOUND AND X11_xcb_keysyms_FOUND)
604+
588605
set(LIBVNCCLIENT_EXAMPLES
589606
backchannel
590607
ppmtest
@@ -630,7 +647,7 @@ if(WITH_EXAMPLES)
630647
add_executable(examples_${e} ${LIBVNCSRVEXAMPLE_DIR}/${e}.c)
631648
set_target_properties(examples_${e} PROPERTIES OUTPUT_NAME ${e})
632649
set_target_properties(examples_${e} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples/server)
633-
target_link_libraries(examples_${e} vncserver ${CMAKE_THREAD_LIBS_INIT} ${CARBON_LIBRARY} ${IOKIT_LIBRARY} ${IOSURFACE_LIBRARY})
650+
target_link_libraries(examples_${e} vncserver ${CMAKE_THREAD_LIBS_INIT} ${CARBON_LIBRARY} ${IOKIT_LIBRARY} ${IOSURFACE_LIBRARY} ${X11_xcb_LIB} ${X11_xcb_xtest_LIB} ${X11_xcb_keysyms_LIB})
634651
endforeach(e ${LIBVNCSERVER_EXAMPLES})
635652

636653
foreach(e ${LIBVNCCLIENT_EXAMPLES})

examples/server/x11.c

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Compile with LIBS := -lvncserver -lxcb -lxcb-xtest -lxcb-keysyms
2+
// Need CMake 3.24.0 to find these libraries. see https://cmake.org/cmake/help/v3.24/module/FindX11.html
3+
// XWayland not support to read screen, because wayland not allow it.
4+
// Read screen in wayland need use XDG desktop portals' interface `org.freedesktop.portal.Screenshot` and `org.freedesktop.portal.ScreenCast`
5+
// Under some environment, this code not work well, see https://github.com/LibVNC/libvncserver/pull/503#issuecomment-1064472566
6+
7+
#include <rfb/rfb.h>
8+
#include <xcb/xcb.h>
9+
#include <xcb/xtest.h>
10+
#include <xcb/xcb_keysyms.h>
11+
12+
void dirty_copy(rfbScreenInfoPtr rfbScreen, const uint8_t* data, int width, int height, int nbytes);
13+
void convert_bgrx_to_rgb(const uint8_t* in, int16_t width, int32_t height, uint8_t* buff);
14+
void get_window_size(xcb_connection_t* conn, xcb_window_t window, int16_t* width, int16_t* height);
15+
void get_window_image(xcb_connection_t* conn, xcb_window_t window, uint8_t* buff);
16+
void send_keycode(xcb_connection_t *conn, xcb_keycode_t keycode, int press);
17+
void send_keysym(xcb_connection_t *conn, xcb_keysym_t keysym, int press);
18+
void send_button(xcb_connection_t *conn, xcb_button_t button, int press);
19+
void send_motion(xcb_connection_t *conn, int16_t x, int16_t y);
20+
21+
// global
22+
xcb_connection_t* conn;
23+
24+
static void keyCallback(rfbBool down, rfbKeySym keySym, rfbClientPtr client)
25+
{
26+
(void)(client);
27+
send_keysym(conn, keySym, (int)down);
28+
}
29+
30+
#define VNC_BUTTON_MASK_LEFT rfbButton1Mask
31+
#define VNC_BUTTON_MASK_MIDDLE rfbButton2Mask
32+
#define VNC_BUTTON_MASK_RIGHT rfbButton3Mask
33+
#define VNC_BUTTON_MASK_UP rfbWheelUpMask
34+
#define VNC_BUTTON_MASK_DOWN rfbWheelDownMask
35+
36+
#define X11_BUTTON_LEFT XCB_BUTTON_INDEX_1
37+
#define X11_BUTTON_MIDDLE XCB_BUTTON_INDEX_2
38+
#define X11_BUTTON_RIGHT XCB_BUTTON_INDEX_3
39+
#define X11_BUTTON_UP XCB_BUTTON_INDEX_4
40+
#define X11_BUTTON_DOWN XCB_BUTTON_INDEX_5
41+
42+
static void mouseCallback(int buttonMask, int x, int y, rfbClientPtr client)
43+
{
44+
(void)(client);
45+
46+
send_button(conn, X11_BUTTON_LEFT, !!(buttonMask & VNC_BUTTON_MASK_LEFT));
47+
send_button(conn, X11_BUTTON_MIDDLE, !!(buttonMask & VNC_BUTTON_MASK_MIDDLE));
48+
send_button(conn, X11_BUTTON_RIGHT, !!(buttonMask & VNC_BUTTON_MASK_RIGHT));
49+
send_button(conn, X11_BUTTON_UP, !!(buttonMask & VNC_BUTTON_MASK_UP));
50+
send_button(conn, X11_BUTTON_DOWN, !!(buttonMask & VNC_BUTTON_MASK_DOWN));
51+
52+
send_motion(conn, (int16_t)x, (int16_t)y);
53+
}
54+
55+
int main(int argc, char* argv[])
56+
{
57+
conn = xcb_connect(NULL, NULL);
58+
const xcb_setup_t* setup = xcb_get_setup(conn);
59+
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
60+
xcb_screen_t* screen = iter.data;
61+
xcb_window_t root = screen->root;
62+
63+
int16_t width;
64+
int16_t height;
65+
get_window_size(conn, root, &width, &height);
66+
void* frameBuffer = malloc(3UL * width * height);
67+
68+
rfbScreenInfoPtr rfbScreen = rfbGetScreen(&argc, argv, (int)width, (int)height, 8, 3, 3);
69+
rfbScreen->desktopName = "LibVNCServer X11 Example";
70+
rfbScreen->frameBuffer = (char*)malloc(3UL * width * height);
71+
rfbScreen->alwaysShared = TRUE;
72+
rfbScreen->kbdAddEvent = keyCallback;
73+
rfbScreen->ptrAddEvent = mouseCallback;
74+
rfbInitServer(rfbScreen);
75+
rfbRunEventLoop(rfbScreen, 10000, TRUE);
76+
77+
while (TRUE)
78+
{
79+
get_window_image(conn, root, (uint8_t*)frameBuffer);
80+
dirty_copy(rfbScreen, (uint8_t*)frameBuffer, (int)width, (int)height, 3);
81+
}
82+
83+
free(rfbScreen->frameBuffer);
84+
free(frameBuffer);
85+
xcb_disconnect(conn);
86+
return EXIT_SUCCESS;
87+
}
88+
89+
void dirty_copy(rfbScreenInfoPtr rfbScreen, const uint8_t* data, int width, int height, int nbytes)
90+
{
91+
// check dirty by line, because it is convenient to copy
92+
for (int y = 0; y < height; y++)
93+
{
94+
rfbBool dirty = FALSE;
95+
for (int x = 0; x < width; x++)
96+
{
97+
const void* s1 = &rfbScreen->frameBuffer[(y*width+x)*nbytes];
98+
const void* s2 = &data[(y*width+x)*nbytes];
99+
if (memcmp(s1, s2, nbytes) != 0)
100+
{
101+
dirty = TRUE;
102+
break;
103+
}
104+
}
105+
106+
if (dirty)
107+
{
108+
memcpy(&rfbScreen->frameBuffer[y*width*nbytes], &data[y*width*nbytes], width*nbytes);
109+
rfbMarkRectAsModified(rfbScreen, 0, y, width, y+1);
110+
}
111+
}
112+
}
113+
114+
void convert_bgrx_to_rgb(const uint8_t* in, int16_t width, int32_t height, uint8_t* buff)
115+
{
116+
for (int16_t y = 0; y < height; y++)
117+
{
118+
for(int16_t x = 0; x < width; x++)
119+
{
120+
buff[(y*width+x)*3] = in[(y*width+x)*4 + 2];
121+
buff[(y*width+x)*3 + 1] = in[(y*width+x)*4 + 1];
122+
buff[(y*width+x)*3 + 2] = in[(y*width+x)*4];
123+
}
124+
}
125+
}
126+
127+
void get_window_size(xcb_connection_t* conn, xcb_window_t window, int16_t* width, int16_t* height)
128+
{
129+
xcb_get_geometry_cookie_t cookie = xcb_get_geometry(conn, window);
130+
xcb_get_geometry_reply_t* reply = xcb_get_geometry_reply(conn, cookie, NULL);
131+
132+
*width = reply->width;
133+
*height = reply->height;
134+
free(reply);
135+
}
136+
137+
void get_window_image(xcb_connection_t* conn, xcb_window_t window, uint8_t* buff)
138+
{
139+
int16_t width = 0;
140+
int16_t height = 0;
141+
get_window_size(conn, window, &width, &height);
142+
143+
// will failed in wayland, xcb_get_image_data will return NULL, convert_bgrx_to_rgb will abort
144+
xcb_get_image_cookie_t cookie = xcb_get_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, window, 0, 0, width, height, UINT32_MAX);
145+
xcb_get_image_reply_t* reply = xcb_get_image_reply(conn, cookie, NULL);
146+
convert_bgrx_to_rgb(xcb_get_image_data(reply), width, height, buff);
147+
free(reply);
148+
}
149+
150+
151+
void send_keycode(xcb_connection_t *conn, xcb_keycode_t keycode, int press)
152+
{
153+
xcb_test_fake_input(conn, press ? XCB_KEY_PRESS : XCB_KEY_RELEASE, keycode, XCB_CURRENT_TIME, XCB_NONE, 0, 0, 0);
154+
xcb_flush(conn);
155+
}
156+
157+
158+
void send_keysym(xcb_connection_t *conn, xcb_keysym_t keysym, int press)
159+
{
160+
xcb_key_symbols_t* symbols = xcb_key_symbols_alloc(conn);
161+
xcb_keycode_t* code = xcb_key_symbols_get_keycode(symbols, keysym);
162+
for (; code != NULL && *code != XCB_NO_SYMBOL; code++)
163+
{
164+
send_keycode(conn, *code, press);
165+
}
166+
xcb_key_symbols_free(symbols);
167+
}
168+
169+
void send_button(xcb_connection_t *conn, xcb_button_t button, int press)
170+
{
171+
xcb_test_fake_input(conn, press ? XCB_BUTTON_PRESS : XCB_BUTTON_RELEASE, button, XCB_CURRENT_TIME, XCB_NONE, 0, 0, 0);
172+
xcb_flush(conn);
173+
}
174+
175+
void send_motion(xcb_connection_t *conn, int16_t x, int16_t y)
176+
{
177+
xcb_test_fake_input(conn, XCB_MOTION_NOTIFY, 0, XCB_CURRENT_TIME, XCB_NONE, x, y, 0);
178+
xcb_flush(conn);
179+
}

0 commit comments

Comments
 (0)