diff --git a/configure.ac b/configure.ac index 61d3c9d7f9..9c187806bb 100644 --- a/configure.ac +++ b/configure.ac @@ -433,6 +433,24 @@ AC_DEFINE_IF([ENABLE_LIBLZ4], [test "${want_liblz4}" = "yes"], [1], [Use liblz4 AC_SUBST([want_liblz4]) AC_SUBST([ENABLE_LIBLZ4]) + +want_vnc_server="no" +AC_ARG_ENABLE([vnc-server], + [AS_HELP_STRING([--enable-vnc-server],[Enable VNC server support for Ecore_Evas_X. @<:@default=disabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + want_vnc_server="yes" + else + want_vnc_server="no" + fi + ], + [want_vnc_server="no"]) + +AM_CONDITIONAL([ENABLE_VNC_SERVER], [test "${want_vnc_server}" = "yes"]) +AC_DEFINE_IF([ENABLE_VNC_SERVER], [test "${want_vnc_server}" = "yes"], [1], [Use VNC server support for Ecore_Evas_X]) +AC_SUBST([want_vnc_server]) +AC_SUBST([ENABLE_VNC_SERVER]) + #### Checks for header files # Common Checks (keep names sorted for ease of use): @@ -4622,7 +4640,8 @@ AM_CONDITIONAL([BUILD_ECORE_EVAS_WIN32], # XXX TODO: ecore_evas_x11 -ECORE_EVAS_MODULE([software-x11], [${want_x11_any}]) +ECORE_EVAS_MODULE([software-x11], [${want_x11_any}], + [EFL_OPTIONAL_DEPEND_PKG([ECORE_EVAS], [${want_vnc_server}], [VNC_SERVER], [libvncserver])]) have_ecore_evas_software_xlib="no" have_ecore_evas_software_xcb="no" @@ -4645,7 +4664,8 @@ fi # XXX TODO: ecore_evas_opengl_x11 -ECORE_EVAS_MODULE([opengl-x11], [${want_x11_any_opengl}]) +ECORE_EVAS_MODULE([opengl-x11], [${want_x11_any_opengl}], + [EFL_OPTIONAL_DEPEND_PKG([ECORE_EVAS], [${want_vnc_server}], [VNC_SERVER], [libvncserver])]) have_ecore_evas_opengl_xlib="no" have_ecore_evas_opengl_xcb="no" diff --git a/src/lib/ecore_evas/Ecore_Evas.h b/src/lib/ecore_evas/Ecore_Evas.h index 30e45b1756..d9827ac4bf 100644 --- a/src/lib/ecore_evas/Ecore_Evas.h +++ b/src/lib/ecore_evas/Ecore_Evas.h @@ -2397,6 +2397,42 @@ EAPI void ecore_evas_x11_shape_input_empty(Ecore_Evas *ee); EAPI void ecore_evas_x11_shape_input_reset(Ecore_Evas *ee); EAPI void ecore_evas_x11_shape_input_apply(Ecore_Evas *ee); +/** + * @brief A callback used to accept a new client. + * @param data The callback data + * @param ee The Ecore_Evas + * @param client_host The address of the new client. + * @return @c EINA_TRUE to accep the client, @c EINA_FALSE otherwise. + * @see ecore_evas_vnc_start() + * @since 1.19 + */ +typedef Eina_Bool (*Ecore_Evas_Vnc_Client_Accept_Cb)(void *data, Ecore_Evas *ee, const char *client_host); + +/** + * @brief Starts a VNC server. + * + * @param ee The Ecore_Evas to start the VNC server + * @param addr The address that will be used to bind the VNC server. Use @c NULL to bind to any interface. + * @param port The port number to start the VNC server. Use @c -1 to set the default VNC port (5900) + * @param cb A callback used to accept a new client. If @c NULL all clients will be accepted. + * @param data Data to @a cb + * @return @c EINA_TRUE on success, @c EINA_FALSE otherwise. + * @see ecore_evas_vnc_stop() + * @see Ecore_Evas_Vnc_Client_Accept_Cb() + * @since 1.19 + */ +EAPI Eina_Bool ecore_evas_vnc_start(Ecore_Evas *ee, const char *addr, int port, Ecore_Evas_Vnc_Client_Accept_Cb cb, void *data); + +/** + * @brief Stop a running VNC server + * + * @param ee Ecore_Evas to stop the VNC server + * @return @c EINA_TRUE if the VNC server was stopped, @c EINA_FALSE otherwise. + * @see ecore_evas_vnc_start() + * @since 1.19 + */ +EAPI Eina_Bool ecore_evas_vnc_stop(Ecore_Evas *ee); + /** * @defgroup Ecore_Evas_Ews Ecore_Evas Single Process Windowing System. * @ingroup Ecore_Evas_Group diff --git a/src/lib/ecore_evas/ecore_evas.c b/src/lib/ecore_evas/ecore_evas.c index 72e46992cd..979a6ae947 100644 --- a/src/lib/ecore_evas/ecore_evas.c +++ b/src/lib/ecore_evas/ecore_evas.c @@ -3955,6 +3955,37 @@ ecore_evas_x11_shape_input_apply(Ecore_Evas *ee) iface->shape_input_apply(ee); } +EAPI Eina_Bool +ecore_evas_vnc_start(Ecore_Evas *ee, const char *addr, int port, + Ecore_Evas_Vnc_Client_Accept_Cb cb, void *data) +{ + Ecore_Evas_Interface_X11 *iface; + + if (strcmp(ee->driver, "software_x11")) + return EINA_FALSE; + + iface = (Ecore_Evas_Interface_X11 *)_ecore_evas_interface_get(ee, "x11"); + EINA_SAFETY_ON_NULL_RETURN_VAL(iface, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(iface->vnc_start, EINA_FALSE); + + return iface->vnc_start(ee, addr, port, cb, data); +} + +EAPI Eina_Bool +ecore_evas_vnc_stop(Ecore_Evas *ee) +{ + Ecore_Evas_Interface_X11 *iface; + + if (strcmp(ee->driver, "software_x11")) + return EINA_FALSE; + + iface = (Ecore_Evas_Interface_X11 *)_ecore_evas_interface_get(ee, "x11"); + EINA_SAFETY_ON_NULL_RETURN_VAL(iface, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(iface->vnc_stop, EINA_FALSE); + + return iface->vnc_stop(ee); +} + EAPI Ecore_Evas * ecore_evas_extn_socket_new(int w, int h) { diff --git a/src/lib/ecore_evas/ecore_evas_x11.h b/src/lib/ecore_evas/ecore_evas_x11.h index b349b7f2ac..7d5e809d04 100644 --- a/src/lib/ecore_evas/ecore_evas_x11.h +++ b/src/lib/ecore_evas/ecore_evas_x11.h @@ -17,6 +17,10 @@ struct _Ecore_Evas_Interface_X11 { void (*shape_input_empty)(Ecore_Evas *ee); void (*shape_input_reset)(Ecore_Evas *ee); void (*shape_input_apply)(Ecore_Evas *ee); + Eina_Bool (*vnc_start)(Ecore_Evas *ee, const char *addr, int port, + Ecore_Evas_Vnc_Client_Accept_Cb cb, + void *data); + Eina_Bool (*vnc_stop)(Ecore_Evas *ee); }; struct _Ecore_Evas_Interface_Software_X11 { diff --git a/src/modules/ecore_evas/engines/x/ecore_evas_x.c b/src/modules/ecore_evas/engines/x/ecore_evas_x.c index e3bfaa89f9..88086e2401 100644 --- a/src/modules/ecore_evas/engines/x/ecore_evas_x.c +++ b/src/modules/ecore_evas/engines/x/ecore_evas_x.c @@ -26,6 +26,11 @@ #include "ecore_evas_private.h" #include "ecore_evas_x11.h" +#ifdef ENABLE_VNC_SERVER +# include +# include +#endif + #ifdef EAPI # undef EAPI #endif @@ -131,6 +136,17 @@ struct _Ecore_Evas_Engine_Data_X11 { unsigned long colormap; // store colormap used to create pixmap } pixmap; Eina_Bool destroyed : 1; // X window has been deleted and cannot be used + +#ifdef ENABLE_VNC_SERVER + char *frame_buffer; + rfbScreenInfoPtr vnc_screen; + Ecore_Fd_Handler *vnc_listen_handler; + Ecore_Fd_Handler *vnc_listen6_handler; + Ecore_Evas_Vnc_Client_Accept_Cb accept_cb; + void *accept_cb_data; + int last_w; + int last_h; +#endif }; static Ecore_Evas_Interface_X11 * _ecore_evas_x_interface_x11_new(void); @@ -152,6 +168,123 @@ static void _transparent_do(Ecore_Evas *, int); static void _avoid_damage_do(Ecore_Evas *, int); static void _rotation_do(Ecore_Evas *, int, int); +#ifdef ENABLE_VNC_SERVER + +typedef struct _Ecore_Evas_X11_Vnc_Client_Data { + Ecore_Fd_Handler *handler; +} Ecore_Evas_X11_Vnc_Client_Data; + +static unsigned int _available_seat = 1; + +#define VNC_BITS_PER_SAMPLE (8) +#define VNC_SAMPLES_PER_PIXEL (3) +#define VNC_BYTES_PER_PIXEL (4) + +static void +_ecore_evas_x11_update_vnc_clients(rfbScreenInfoPtr vnc_screen) +{ + rfbClientIteratorPtr itr; + rfbClientRec *client; + + itr = rfbGetClientIterator(vnc_screen); + + //No clients. + if (!itr) return; + + while ((client = rfbClientIteratorNext(itr))) { + rfbBool r; + + r = rfbUpdateClient(client); + + if (!r) + { + Ecore_Evas_X11_Vnc_Client_Data *cdata = client->clientData; + + WRN("Could not update the VNC client on seat '%p'\n", cdata); + } + + //Client disconnected + if (client->sock == -1) rfbClientConnectionGone(client); + } + + rfbReleaseClientIterator(itr); +} + +static void +_ecore_evas_x11_vnc_server_format_setup(Ecore_Evas_Engine_Data_X11 *edata) +{ + int aux; + + //FIXME: Using BGR - Is there a better way to do this? + aux = edata->vnc_screen->serverFormat.redShift; + edata->vnc_screen->serverFormat.redShift = edata->vnc_screen->serverFormat.blueShift; + edata->vnc_screen->serverFormat.blueShift = aux; +} + +static void +_ecore_evas_x11_region_push_hook(Evas *e, int x, int y, int w, int h, + const void *pixels) +{ + Ecore_Evas *ee; + Ecore_Evas_Engine_Data_X11 *edata; + size_t size; + + ee = evas_data_attach_get(e); + edata = ee->engine.data; + + if (!edata->frame_buffer || edata->last_w != ee->w || edata->last_h != ee->h) + { + char *new_fb; + + size = ee->w * ee->h * VNC_BYTES_PER_PIXEL; + new_fb = malloc(size); + EINA_SAFETY_ON_NULL_RETURN(new_fb); + free(edata->frame_buffer); + memcpy(new_fb, pixels, size); + edata->frame_buffer = new_fb; + edata->last_w = ee->w; + edata->last_h = ee->h; + if (edata->vnc_screen) + { + rfbNewFramebuffer(edata->vnc_screen, edata->frame_buffer, ee->w, + ee->h, VNC_BITS_PER_SAMPLE, VNC_SAMPLES_PER_PIXEL, + VNC_BYTES_PER_PIXEL); + _ecore_evas_x11_vnc_server_format_setup(edata); + } + } + else + { + //Partial update + int dy; + const int src_stride = w * VNC_BYTES_PER_PIXEL; + + for (dy = 0; dy < h; dy++) + { + memcpy(edata->frame_buffer + (x * VNC_BYTES_PER_PIXEL) + + ((dy + y) * (edata->last_w * VNC_BYTES_PER_PIXEL)), + (char *)pixels + (dy * src_stride), src_stride); + } + } + + if (edata->vnc_screen) + { + rfbMarkRectAsModified(edata->vnc_screen, x, y, x+w, y+h); + _ecore_evas_x11_update_vnc_clients(edata->vnc_screen); + } +} + +#else + +static void +_ecore_evas_x11_region_push_hook(Evas *e EINA_UNUSED, int x EINA_UNUSED, + int y EINA_UNUSED, int w EINA_UNUSED, + int h EINA_UNUSED, + const void *pixels EINA_UNUSED) +{ +} + +#endif //ENABLE_VNC_SERVER + static void _ecore_evas_x_hints_update(Ecore_Evas *ee) { @@ -2059,6 +2192,16 @@ _ecore_evas_x_free(Ecore_Evas *ee) ecore_timer_del(edata->outdelay); edata->outdelay = NULL; } +#ifdef ENABLE_VNC_SERVER + if (edata->vnc_screen) + { + ecore_main_fd_handler_del(edata->vnc_listen6_handler); + ecore_main_fd_handler_del(edata->vnc_listen_handler); + free(edata->frame_buffer); + rfbScreenCleanup(edata->vnc_screen); + edata->vnc_screen = NULL; + } +#endif free(edata); _ecore_evas_x_shutdown(); ecore_x_shutdown(); @@ -4110,6 +4253,7 @@ ecore_evas_software_x11_new_internal(const char *disp_name, Ecore_X_Window paren einfo->info.screen = NULL; # endif einfo->info.drawable = ee->prop.window; + einfo->func.region_push_hook = _ecore_evas_x11_region_push_hook; if (argb) { @@ -4298,6 +4442,7 @@ ecore_evas_software_x11_pixmap_new_internal(const char *disp_name, Ecore_X_Windo } } + einfo->func.region_push_hook = _ecore_evas_x11_region_push_hook; einfo->info.destination_alpha = argb; if (redraw_debug < 0) @@ -5131,6 +5276,211 @@ _ecore_evas_x11_shape_input_apply(Ecore_Evas *ee) ecore_x_window_shape_input_window_set(ee->prop.window, edata->win_shaped_input); } +#ifdef ENABLE_VNC_SERVER +static Eina_Bool +_ecore_evas_x11_vnc_socket_listen_activity(void *data, + Ecore_Fd_Handler *fd_handler EINA_UNUSED) +{ + rfbProcessNewConnection(data); + return ECORE_CALLBACK_RENEW; +} + +static void +_ecore_evas_x11_client_gone(rfbClientRec *client) +{ + Ecore_Evas_X11_Vnc_Client_Data *cdata = client->clientData; + + EDBG("VNC client on seat '%p' gone", cdata); + + ecore_main_fd_handler_del(cdata->handler); + free(cdata); + _available_seat--; +} + +static Eina_Bool +_ecore_evas_x11_client_activity(void *data, + Ecore_Fd_Handler *fd_handler EINA_UNUSED) +{ + Eina_Bool r = ECORE_CALLBACK_RENEW; + rfbClientRec *client = data; + + rfbProcessClientMessage(client); + + //macro from rfb.h + if (FB_UPDATE_PENDING(client)) + rfbSendFramebufferUpdate(client, client->modifiedRegion); + + //Client disconnected. + if (client->sock == -1) + { + rfbClientConnectionGone(client); + r = ECORE_CALLBACK_DONE; + } + + return r; +} + +static enum rfbNewClientAction +_ecore_evas_x11_vnc_client_connection_new(rfbClientRec *client) +{ + Ecore_Evas *ee; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas_X11_Vnc_Client_Data *cdata; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(_available_seat == UINT_MAX, + RFB_CLIENT_REFUSE); + + ee = client->screen->screenData; + edata = ee->engine.data; + + if (!edata->accept_cb(edata->accept_cb_data, ee, client->host)) + return RFB_CLIENT_REFUSE; + + cdata = calloc(1, sizeof(Ecore_Evas_X11_Vnc_Client_Data)); + EINA_SAFETY_ON_NULL_RETURN_VAL(cdata, RFB_CLIENT_REFUSE); + + cdata->handler = ecore_main_fd_handler_add(client->sock, ECORE_FD_READ, + _ecore_evas_x11_client_activity, + client, NULL, NULL); + EINA_SAFETY_ON_NULL_GOTO(cdata->handler, err_handler); + + client->clientGoneHook = _ecore_evas_x11_client_gone; + client->clientData = cdata; + + EDBG("New VNC client on seat '%u'", _available_seat); + + _available_seat++; + return RFB_CLIENT_ACCEPT; + + err_handler: + free(cdata); + return RFB_CLIENT_REFUSE; +} + +static void +_ecore_evas_x11_vnc_client_keyboard_event(rfbBool down EINA_UNUSED, + rfbKeySym keySym EINA_UNUSED, + rfbClientRec *client EINA_UNUSED) +{ + EDBG("Keyboard event\n"); +} + +static void +_ecore_evas_x11_vnc_client_pointer_event(int buttonMask EINA_UNUSED, + int x EINA_UNUSED, int y EINA_UNUSED, + rfbClientPtr client EINA_UNUSED) +{ + EDBG("Pointer event\n"); +} +#endif + +static Eina_Bool +_ecore_evas_x11_vnc_start(Ecore_Evas *ee, const char *addr, int port, + Ecore_Evas_Vnc_Client_Accept_Cb cb, void *data) +{ +#ifdef ENABLE_VNC_SERVER + Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + Eina_Bool can_listen = EINA_FALSE; + + if (edata->vnc_screen) + return EINA_FALSE; + + edata->vnc_screen = rfbGetScreen(0, NULL, ee->w, ee->h, VNC_BITS_PER_SAMPLE, + VNC_SAMPLES_PER_PIXEL, VNC_BYTES_PER_PIXEL); + EINA_SAFETY_ON_NULL_RETURN_VAL(edata->vnc_screen, EINA_FALSE); + + edata->vnc_screen->newClientHook = _ecore_evas_x11_vnc_client_connection_new; + edata->vnc_screen->kbdAddEvent = _ecore_evas_x11_vnc_client_keyboard_event; + edata->vnc_screen->ptrAddEvent = _ecore_evas_x11_vnc_client_pointer_event; + + //This enables multiple client connections at the same time. + edata->vnc_screen->alwaysShared = TRUE; + edata->vnc_screen->frameBuffer = edata->frame_buffer; + + _ecore_evas_x11_vnc_server_format_setup(edata); + + if (port > 0) + edata->vnc_screen->port = edata->vnc_screen->ipv6port= port; + + if (addr) + { + int err; + + //rfbStringToAddr() does not change the addr contents. + err = rfbStringToAddr((char *)addr, &edata->vnc_screen->listenInterface); + EINA_SAFETY_ON_TRUE_GOTO(err == 0, err_screen); + } + + rfbInitServer(edata->vnc_screen); + if (edata->vnc_screen->listenSock >= 0) + { + edata->vnc_listen_handler = ecore_main_fd_handler_add(edata->vnc_screen->listenSock, + ECORE_FD_READ, + _ecore_evas_x11_vnc_socket_listen_activity, + edata->vnc_screen, NULL, + NULL); + EINA_SAFETY_ON_NULL_GOTO(edata->vnc_listen_handler, err_listen); + can_listen = EINA_TRUE; + } + + if (edata->vnc_screen->listen6Sock >= 0) + { + edata->vnc_listen6_handler = ecore_main_fd_handler_add(edata->vnc_screen->listen6Sock, + ECORE_FD_READ, + _ecore_evas_x11_vnc_socket_listen_activity, + edata->vnc_screen, + NULL, NULL); + EINA_SAFETY_ON_NULL_GOTO(edata->vnc_listen6_handler, err_listen6); + can_listen = EINA_TRUE; + } + + //rfbInitServer() failed and could not setup the sockets. + EINA_SAFETY_ON_FALSE_GOTO(can_listen, err_listen); + + edata->vnc_screen->screenData = ee; + edata->accept_cb_data = data; + edata->accept_cb = cb; + + return EINA_TRUE; + + err_listen6: + ecore_main_fd_handler_del(edata->vnc_listen_handler); + edata->vnc_listen_handler = NULL; + err_listen: + rfbScreenCleanup(edata->vnc_screen); + edata->vnc_screen = NULL; + err_screen: + return EINA_FALSE; +#else + (void)ee; + (void)addr; + (void)port; + (void)cb; + (void)data; + return EINA_FALSE; +#endif +} + +static Eina_Bool +_ecore_evas_x11_vnc_stop(Ecore_Evas *ee) +{ +#ifdef ENABLE_VNC_SERVER + Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + + if (!edata->vnc_screen) + return EINA_FALSE; + + ecore_main_fd_handler_del(edata->vnc_listen6_handler); + ecore_main_fd_handler_del(edata->vnc_listen_handler); + rfbScreenCleanup(edata->vnc_screen); + edata->vnc_screen = NULL; + return EINA_TRUE; +#else + (void)ee; + return EINA_FALSE; +#endif +} + static Ecore_Evas_Interface_X11 * _ecore_evas_x_interface_x11_new(void) { @@ -5151,6 +5501,8 @@ _ecore_evas_x_interface_x11_new(void) iface->shape_input_empty = _ecore_evas_x11_shape_input_empty; iface->shape_input_reset = _ecore_evas_x11_shape_input_reset; iface->shape_input_apply = _ecore_evas_x11_shape_input_apply; + iface->vnc_start = _ecore_evas_x11_vnc_start; + iface->vnc_stop = _ecore_evas_x11_vnc_stop; return iface; } @@ -5205,3 +5557,4 @@ _ecore_evas_x_interface_gl_x11_new(void) return iface; } #endif +