diff options
author | Anatolii Nikulin <nikulin.a@samsung.com> | 2016-05-19 17:26:33 +0300 |
---|---|---|
committer | Anatolii Nikulin <nikulin.a@samsung.com> | 2016-05-20 12:12:11 +0300 |
commit | 5766e64da9d26d0fe2fa7fb6e90476828c0cec55 (patch) | |
tree | 3cda5cf848e945ed4cabea608b74423133c07972 | |
parent | 302ea17d2146d79de975d74eaa84b3488bcd634f (diff) | |
download | swap-probe-5766e64da9d26d0fe2fa7fb6e90476828c0cec55.tar.gz swap-probe-5766e64da9d26d0fe2fa7fb6e90476828c0cec55.tar.bz2 swap-probe-5766e64da9d26d0fe2fa7fb6e90476828c0cec55.zip |
[IMPROVE] Implement screenshot and orientation event featuressubmit/tizen/20160526.101038accepted/tizen/wearable/20160602.021524accepted/tizen/tv/20160602.021543accepted/tizen/mobile/20160602.021511accepted/tizen/ivi/20160602.021551accepted/tizen/common/20160526.150514
We take screenshot using Wayland API,
for orientation events we use Tizen sensors API
Change-Id: Ib80b322d61d41e4f952d0d0ece513ed30e4898d2
Signed-off-by: Anatolii Nikulin <nikulin.a@samsung.com>
-rw-r--r-- | Makefile | 8 | ||||
-rwxr-xr-x | helper/dacapture.c | 710 | ||||
-rw-r--r-- | helper/wayland-api.c | 26 | ||||
-rw-r--r-- | helper/wayland-api.h | 68 | ||||
-rw-r--r-- | include/dacapture.h | 6 | ||||
-rwxr-xr-x | include/dahelper.h | 3 | ||||
-rw-r--r-- | include/orientation.h | 27 | ||||
-rw-r--r-- | packaging/swap-probe.spec | 18 | ||||
-rw-r--r-- | probe_capi/capi_appfw.c | 5 | ||||
-rw-r--r-- | probe_event/api_names.txt | 4 | ||||
-rwxr-xr-x | probe_event/da_event.c | 37 | ||||
-rw-r--r-- | probe_event/orientation.c | 161 |
12 files changed, 647 insertions, 426 deletions
@@ -44,6 +44,7 @@ INCLUDE_CPPFLAGS = \ -I/usr/include/vconf \ -I/usr/lib/dbus-1.0/include \ -I/usr/include/efl-1 \ + -I/usr/include/sensor/ \ -I/usr/include/eo-1 \ WARN_CFLAGS = -g \ @@ -107,11 +108,12 @@ PROBE_SRCS = \ ./probe_file/da_io_posix.c \ ./probe_file/da_io_stdc.c \ -ifeq ($(X11_SUPPORT),y) +ifeq ($(WAYLAND_SUPPORT),y) +UTILITY_SRCS += ./helper/wayland-api.c UTILITY_SRCS += ./helper/dacapture.c PROBE_SRCS += ./probe_event/orientation.c -CFLAGS += -DX11_SUPPORT -endif # X11_SUPPORT +CFLAGS += -DWAYLAND_SUPPORT +endif # WAYLAND_SUPPORT DUMMY_SRCS = ./custom_chart/da_chart_dummy.c CAPI_SRCS = $(COMMON_SRCS) \ diff --git a/helper/dacapture.c b/helper/dacapture.c index e13a0ae..d61bf26 100755 --- a/helper/dacapture.c +++ b/helper/dacapture.c @@ -9,6 +9,7 @@ * Woojin Jung <woojin2.jung@samsung.com> * Juyoung Kim <j0.kim@samsung.com> * Anastasia Lyupa <a.lyupa@samsung.com> + * Anatolii Nukulin <nikulin.a@samsung.com> * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the @@ -39,260 +40,356 @@ #include <sys/types.h> // for stat, getpid #include <sys/stat.h> // fot stat, chmod #include <unistd.h> // fot stat, getpid -#include <sys/shm.h> // for shmget, shmat #include <pthread.h> // for mutex - -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <X11/extensions/XShm.h> +#include <sys/param.h> // MIN, MAX +#include <sys/mman.h> // mmap #include <Ecore.h> #include <Evas.h> #include <Evas_Engine_Buffer.h> +#include <wayland-client.h> +#include "wayland-api.h" #include "real_functions.h" #include "daprobe.h" #include "dahelper.h" #include "binproto.h" -#define MAX_HEIGHT 720 +#define MAX_HEIGHT 720 #define CAPTURE_TIMEOUT 2.0 +#define MAX_PATH_LENGTH 256 + +struct screenshot_data { + struct wl_shm *shm; + struct screenshooter *screenshooter; + struct wl_list output_list; + int min_x, min_y, max_x, max_y; + int buffer_copy_done; +}; + +struct screenshooter_output { + struct wl_output *output; + struct wl_buffer *buffer; + int width, height, offset_x, offset_y; + void *data; + struct wl_list link; +}; + +static void +display_handle_geometry(void __attribute__((unused)) *data, + struct wl_output *wl_output, + int x, + int y, + int __attribute__((unused)) physical_width, + int __attribute__((unused)) physical_height, + int __attribute__((unused)) subpixel, + const char __attribute__((unused)) *make, + const char __attribute__((unused)) *model, + int __attribute__((unused)) transform) +{ + struct screenshooter_output *output; + + output = wl_output_get_user_data(wl_output); -typedef struct _screenshot_data + if (wl_output == output->output) { + output->offset_x = x; + output->offset_y = y; + } +} + +static void +display_handle_mode(void __attribute__((unused)) *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int __attribute__((unused)) refresh) { - XImage* ximage; - Display* dpy; - XShmSegmentInfo x_shm_info; -} screenshot_data; + struct screenshooter_output *output; -/* -int convert_image( void* srcbuf, void* dstbuf, - pixman_format_code_t src_format, - pixman_format_code_t dst_format, - int src_width, int src_height, - int dst_width, int dst_height, - int rotate) + output = wl_output_get_user_data(wl_output); + + if ((wl_output == output->output) && + (flags & WL_OUTPUT_MODE_CURRENT)) { + output->width = width; + output->height = height; + } +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode, + NULL, + NULL +}; + +static void +screenshot_done(void *data, + struct screenshooter __attribute__((unused)) *screenshooter) { - pixman_image_t * src_img; - pixman_image_t * dst_img; - pixman_transform_t transform; - - int src_stride, dst_stride; - int src_bpp; - int dst_bpp; - pixman_op_t op; - int rotate_step; - int ret = False; - - return_val_if_fail (srcbuf != NULL, False); - return_val_if_fail (dstbuf != NULL, False); - return_val_if_fail (rotate <= 360 && rotate >= -360, False); - - op = PIXMAN_OP_SRC; - - src_bpp = PIXMAN_FORMAT_BPP (src_format) / 8; - return_val_if_fail (src_bpp > 0, False); - - dst_bpp = PIXMAN_FORMAT_BPP (dst_format) / 8; - return_val_if_fail (dst_bpp > 0, False); - - rotate_step = (rotate + 360) / 90 % 4; - - src_stride = src_width * src_bpp; - dst_stride = dst_width * dst_bpp; - - src_img = pixman_image_create_bits (src_format, src_width, src_height, srcbuf, src_stride); - dst_img = pixman_image_create_bits (dst_format, dst_width, dst_height, dstbuf, dst_stride); - - goto_if_fail (src_img != NULL, CANT_CONVERT); - goto_if_fail (dst_img != NULL, CANT_CONVERT); - - pixman_transform_init_identity (&transform); - - if (rotate_step > 0) - { - int c, s, tx = 0, ty = 0; - switch (rotate_step) - { - case 1: - // 270 degrees - c = 0; - s = -pixman_fixed_1; - ty = pixman_int_to_fixed (dst_width); - break; - case 2: - // 180 degrees - c = -pixman_fixed_1; - s = 0; - tx = pixman_int_to_fixed (dst_width); - ty = pixman_int_to_fixed (dst_height); - break; - case 3: - // 90 degrees - c = 0; - s = pixman_fixed_1; - tx = pixman_int_to_fixed (dst_height); - break; - default: - // 0 degrees - c = 0; - s = 0; - break; + struct screenshot_data *sdata = data; + sdata->buffer_copy_done = 1; +} + +static const struct screenshooter_listener screenshooter_listener = { + screenshot_done +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t __attribute__((unused)) version) +{ + struct screenshot_data *sdata = data; + + if (strcmp(interface, "wl_output") == 0) { + struct screenshooter_output *output = malloc(sizeof(*output)); + + if (output) { + PRINTMSG("allocate %p", output); + output->output = wl_registry_bind(registry, name, + &wl_output_interface, + 1); + wl_list_insert(&sdata->output_list, &output->link); + wl_output_add_listener(output->output, + &output_listener, + output); } - pixman_transform_rotate (&transform, NULL, c, s); - pixman_transform_translate (&transform, NULL, tx, ty); + } else if (strcmp(interface, "wl_shm") == 0) { + sdata->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + } else if (strcmp(interface, "screenshooter") == 0) { + sdata->screenshooter = wl_registry_bind(registry, name, + &screenshooter_interface, + 1); } +} + +static const struct wl_registry_listener registry_listener = { + handle_global, + NULL +}; - pixman_image_set_transform (src_img, &transform); +static struct wl_buffer * +create_shm_buffer(struct wl_shm *shm, int width, int height, void **data_out) +{ + char filename[] = "/tmp/wayland-shm-XXXXXX"; + struct wl_shm_pool *pool; + struct wl_buffer *buffer; + int fd, size, stride; + void *data; - pixman_image_composite (op, src_img, NULL, dst_img, - 0, 0, 0, 0, 0, 0, dst_width, dst_height); + stride = width * 4; + size = stride * height; - ret = True; + fd = mkstemp(filename); + if (fd < 0) + return NULL; -CANT_CONVERT: - if (src_img) - pixman_image_unref (src_img); - if (dst_img) - pixman_image_unref (dst_img); + if (ftruncate(fd, size) < 0) { + close(fd); + return NULL; + } - return ret; + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + unlink(filename); + + if (data == MAP_FAILED) { + close(fd); + return NULL; + } + + pool = wl_shm_create_pool(shm, fd, size); + close(fd); + buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, + WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + *data_out = data; + + return buffer; } -*/ -static char* captureScreenShotX(int* pwidth, int* pheight, screenshot_data* sdata) +static void *__screenshot_to_buf(struct screenshot_data *sdata, int width, + int height) { - static Display *(*__XOpenDisplay_p)(_Xconst char *); - static Bool (*__XShmGetImage_p)(Display *, Drawable, XImage *, int, int, - unsigned long); - static XImage *(*__XShmCreateImage_p)(Display *, Visual *, unsigned int, - int, char *, XShmSegmentInfo *, unsigned int, unsigned int); - static Bool (*__XShmAttach_p)(Display *, XShmSegmentInfo *); - static Bool (*__XShmDetach_p)(Display *, XShmSegmentInfo *); - static int (*__XSync_p)(Display *, Bool); - Window root; -// Atom atom_rotation; - - rtld_default_set_once(__XOpenDisplay_p, "XOpenDisplay"); - rtld_default_set_once(__XShmGetImage_p, "XShmGetImage"); - rtld_default_set_once(__XShmCreateImage_p, "XShmCreateImage"); - rtld_default_set_once(__XShmAttach_p, "XShmAttach"); - rtld_default_set_once(__XShmDetach_p, "XShmDetach"); - rtld_default_set_once(__XSync_p, "XSync"); - - sdata->dpy = __XOpenDisplay_p(NULL); - if(unlikely(sdata->dpy == NULL)) - { - // XOpenDisplay failed! + int output_stride, buffer_stride, i; + void *data, *d, *s; + struct screenshooter_output *output, *next; + + buffer_stride = width * 4; + data = malloc(buffer_stride * height); + if (!data) return NULL; - } - *pwidth = DisplayWidth(sdata->dpy, DefaultScreen(sdata->dpy)); - *pheight = DisplayHeight(sdata->dpy, DefaultScreen(sdata->dpy)); - - root = RootWindow(sdata->dpy, DefaultScreen(sdata->dpy)); - - sdata->ximage = __XShmCreateImage_p(sdata->dpy, DefaultVisualOfScreen (DefaultScreenOfDisplay (sdata->dpy)), 24, - ZPixmap, NULL, &sdata->x_shm_info, (unsigned int)*pwidth, (unsigned int)*pheight); - - if(sdata->ximage != NULL) - { - sdata->x_shm_info.shmid = shmget(IPC_PRIVATE, sdata->ximage->bytes_per_line * sdata->ximage->height, IPC_CREAT | 0777); - sdata->x_shm_info.shmaddr = sdata->ximage->data = shmat(sdata->x_shm_info.shmid, 0, 0); - sdata->x_shm_info.readOnly = False; - - if(__XShmAttach_p(sdata->dpy, &sdata->x_shm_info)) - { - if(__XShmGetImage_p(sdata->dpy, root, sdata->ximage, 0, 0, AllPlanes)) - { - __XSync_p (sdata->dpy, False); - return sdata->ximage->data; - } - else - { - ; // XShmGetImage failed ! - } - - __XShmDetach_p(sdata->dpy, &sdata->x_shm_info); - } - else - { - ; // XShmAttach failed ! + wl_list_for_each_safe(output, next, &sdata->output_list, link) { + output_stride = output->width * 4; + s = output->data; + d = data + (output->offset_y - sdata->min_y) * buffer_stride + + (output->offset_x - sdata->min_x) * 4; + + for (i = 0; i < output->height; i++) { + memcpy(d, s, output_stride); + d += buffer_stride; + s += output_stride; } - shmdt (sdata->x_shm_info.shmaddr); - shmctl (sdata->x_shm_info.shmid, IPC_RMID, NULL); - XDestroyImage(sdata->ximage); - sdata->ximage = NULL; + wl_list_remove(&output->link); + free(output); } - else - { - ; // XShmCreateImage failed! + + return data; +} + +static int +set_buffer_size(struct screenshot_data *sdata, int *width, int *height) +{ + struct screenshooter_output *output; + + sdata->min_x = sdata->min_y = INT_MAX; + sdata->max_x = sdata->max_y = INT_MIN; + int position = 0; + + wl_list_for_each_reverse(output, &sdata->output_list, link) { + output->offset_x = position; + position += output->width; } - return NULL; + wl_list_for_each(output, &sdata->output_list, link) { + sdata->min_x = MIN(sdata->min_x, output->offset_x); + sdata->min_y = MIN(sdata->min_y, output->offset_y); + sdata->max_x = MAX(sdata->max_x, + output->offset_x + output->width); + sdata->max_y = MAX(sdata->max_y, + output->offset_y + output->height); + } + + if (sdata->max_x <= sdata->min_x || sdata->max_y <= sdata->min_y) + return -1; + + *width = sdata->max_x - sdata->min_x; + *height = sdata->max_y - sdata->min_y; + + return 0; } -static void releaseScreenShotX(screenshot_data* sdata) +static void *__capture_screnshot_wayland(int *width, int *height) { - static int (*__XCloseDisplay_p)(Display *); - static Bool (*__XShmDetach_p)(Display *, XShmSegmentInfo *); - - rtld_default_set_once(__XCloseDisplay_p, "XCloseDisplay"); - rtld_default_set_once(__XShmDetach_p, "XShmDetach"); - - if(sdata->ximage) - { - __XShmDetach_p (sdata->dpy, &sdata->x_shm_info); - shmdt (sdata->x_shm_info.shmaddr); - shmctl (sdata->x_shm_info.shmid, IPC_RMID, NULL); - XDestroyImage(sdata->ximage); + struct wl_display *display = NULL; + struct wl_registry *registry; + struct screenshooter_output *output; + void *buf = NULL; + struct screenshot_data *sdata; + const char *wayland_socket = NULL; + + wayland_socket = getenv("WAYLAND_SOCKET"); + if (!wayland_socket) + wayland_socket = getenv("WAYLAND_DISPLAY"); + + if (!wayland_socket) { + PRINTERR("must be launched by wayland"); + return NULL; + } + + sdata = malloc(sizeof(*sdata)); + if (!sdata) + return NULL; + + sdata->shm = NULL; + sdata->screenshooter = NULL; + wl_list_init(&sdata->output_list); + sdata->min_x = 0; + sdata->min_y = 0; + sdata->max_x = 0; + sdata->max_y = 0; + sdata->buffer_copy_done = 0; + + display = wl_display_connect(wayland_socket); + if (display == NULL) + goto out; + + /* wl_list_init(&output_list); */ + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, sdata); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (sdata->screenshooter == NULL) { + PRINTERR("display doesn't support screenshooter"); + return NULL; } - else { } - if(sdata->dpy) - { - __XCloseDisplay_p(sdata->dpy); + screenshooter_add_listener(sdata->screenshooter, &screenshooter_listener, + sdata); + + if (set_buffer_size(sdata, width, height)) + return NULL; + + + wl_list_for_each(output, &sdata->output_list, link) { + output->buffer = create_shm_buffer(sdata->shm, output->width, + output->height, &output->data); + screenshooter_shoot(sdata->screenshooter, + output->output, + output->buffer); + sdata->buffer_copy_done = 0; + while (!sdata->buffer_copy_done) { + wl_display_roundtrip(display); + } } + + buf = __screenshot_to_buf(sdata, *width, *height); + +out: + if (display) + wl_display_disconnect(display); + free(sdata); + return buf; } static Evas* create_canvas(int width, int height) { - static void (*__evas_output_method_set_p)(Evas *e, int render_method); static void (*__evas_output_size_set_p)(Evas *e, int w, int h); - static void (*__evas_output_viewport_set_p)(Evas *e, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h); + static void (*__evas_output_viewport_set_p)(Evas *e, Evas_Coord x, + Evas_Coord y, Evas_Coord w, + Evas_Coord h); static int (*__evas_render_method_lookup_p)(const char *name); static Evas_Engine_Info *(*__evas_engine_info_get_p)(const Evas *e); - static Eina_Bool (*__evas_engine_info_set_p)(const Evas *e, Evas_Engine_Info *info); + static Eina_Bool (*__evas_engine_info_set_p)(const Evas *e, + Evas_Engine_Info *info); static Evas *(*__evas_new_p)(void); static void (*__evas_free_p)(Evas *e); Evas *canvas; Evas_Engine_Info_Buffer *einfo; int method; void *pixels; - - rtld_default_set_once(__evas_output_method_set_p, "evas_output_method_set"); - rtld_default_set_once(__evas_output_size_set_p, "evas_output_size_set"); - rtld_default_set_once(__evas_output_viewport_set_p, "evas_output_viewport_set"); - rtld_default_set_once(__evas_render_method_lookup_p, "evas_render_method_lookup"); - rtld_default_set_once(__evas_engine_info_get_p, "evas_engine_info_get"); - rtld_default_set_once(__evas_engine_info_set_p, "evas_engine_info_set"); + Eina_Bool ret; + + rtld_default_set_once(__evas_output_method_set_p, + "evas_output_method_set"); + rtld_default_set_once(__evas_output_size_set_p, + "evas_output_size_set"); + rtld_default_set_once(__evas_output_viewport_set_p, + "evas_output_viewport_set"); + rtld_default_set_once(__evas_render_method_lookup_p, + "evas_render_method_lookup"); + rtld_default_set_once(__evas_engine_info_get_p, + "evas_engine_info_get"); + rtld_default_set_once(__evas_engine_info_set_p, + "evas_engine_info_set"); rtld_default_set_once(__evas_new_p, "evas_new"); rtld_default_set_once(__evas_free_p, "evas_free"); method = __evas_render_method_lookup_p("buffer"); - if (unlikely(method <= 0)) - { - //fputs("ERROR: evas was not compiled with 'buffer' engine!\n", stderr); + if (unlikely(method <= 0)) { + PRINTERR("evas was not compiled with 'buffer' engine!"); return NULL; } canvas = __evas_new_p(); - if (unlikely(canvas == NULL)) - { - //fputs("ERROR: could not instantiate new evas canvas.\n", stderr); + if (unlikely(canvas == NULL)) { + PRINTERR("could not instantiate new evas canvas"); return NULL; } @@ -301,9 +398,8 @@ static Evas* create_canvas(int width, int height) __evas_output_viewport_set_p(canvas, 0, 0, width, height); einfo = (Evas_Engine_Info_Buffer *)__evas_engine_info_get_p(canvas); - if (unlikely(einfo == NULL)) - { - //fputs("ERROR: could not get evas engine info!\n", stderr); + if (unlikely(einfo == NULL)) { + PRINTERR("could not get evas engine info!"); __evas_free_p(canvas); return NULL; } @@ -311,7 +407,7 @@ static Evas* create_canvas(int width, int height) // ARGB32 is sizeof(int), that is 4 bytes, per pixel pixels = real_malloc(width * height * sizeof(int)); if (unlikely(pixels == NULL)) { - //fputs("ERROR: could not allocate canvas pixels!\n", stderr); + PRINTERR("could not allocate canvas pixels!"); __evas_free_p(canvas); return NULL; } @@ -324,8 +420,9 @@ static Evas* create_canvas(int width, int height) einfo->info.func.new_update_region = NULL; einfo->info.func.free_update_region = NULL; - if (unlikely(__evas_engine_info_set_p(canvas,(Evas_Engine_Info*)einfo) == EINA_FALSE)) { - PRINTMSG("ERROR: could not set evas engine info!\n"); + ret = __evas_engine_info_set_p(canvas,(Evas_Engine_Info*)einfo); + if (unlikely(ret == EINA_FALSE)) { + PRINTERR("could not set evas engine info!"); __evas_free_p(canvas); return NULL; } @@ -339,13 +436,13 @@ static void destroy_canvas(Evas* canvas) static void (*__evas_free_p)(Evas *e); Evas_Engine_Info_Buffer *einfo; - rtld_default_set_once(__evas_engine_info_get_p, "evas_engine_info_get"); + rtld_default_set_once(__evas_engine_info_get_p, + "evas_engine_info_get"); rtld_default_set_once(__evas_free_p, "evas_free"); einfo = (Evas_Engine_Info_Buffer *)__evas_engine_info_get_p(canvas); - if (unlikely(einfo == NULL)) - { - //fputs("ERROR: could not get evas engine info!\n", stderr); + if (unlikely(einfo == NULL)) { + PRINTERR("could not get evas engine info!"); __evas_free_p(canvas); return; } @@ -356,117 +453,125 @@ static void destroy_canvas(Evas* canvas) int captureScreen() { - static void (*__evas_object_resize_p)(Evas_Object *obj, Evas_Coord w, Evas_Coord h); - static void (*__evas_object_image_data_set_p)(Evas_Object *obj, void *data); - static void (*__evas_object_image_size_set_p)(Evas_Object *obj, int w, int h); - static void (*__evas_object_image_fill_set_p)(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h); - static void (*__evas_object_image_data_update_add_p)(Evas_Object *obj, int x, int y, int w, int h); - static Eina_Bool (*__evas_object_image_save_p)(Evas_Object *obj, const char *file, const char *key, const char *flags); + static void (*__evas_object_resize_p)(Evas_Object *obj, + Evas_Coord w, + Evas_Coord h); + static void (*__evas_object_image_data_set_p)(Evas_Object *obj, + void *data); + static void (*__evas_object_image_size_set_p)(Evas_Object *obj, + int w, int h); + static void (*__evas_object_image_fill_set_p)(Evas_Object *obj, + Evas_Coord x, + Evas_Coord y, + Evas_Coord w, + Evas_Coord h); + static void (*__evas_object_image_data_update_add_p)(Evas_Object *obj, + int x, int y, + int w, int h); + static Eina_Bool (*__evas_object_image_save_p)(Evas_Object *obj, + const char *file, + const char *key, + const char *flags); static Evas_Object *(*__evas_object_image_add_p)(Evas *e); static pthread_mutex_t captureScreenLock = PTHREAD_MUTEX_INITIALIZER; char dstpath[MAX_PATH_LENGTH]; - char* scrimage; - int width, height; - Evas* ev = NULL; - Evas_Object* img; - screenshot_data sdata; - int ret = 0; - - rtld_default_set_once(__evas_object_resize_p, "evas_object_resize"); - rtld_default_set_once(__evas_object_image_data_set_p, "evas_object_image_data_set"); - rtld_default_set_once(__evas_object_image_size_set_p, "evas_object_image_size_set"); - rtld_default_set_once(__evas_object_image_fill_set_p, "evas_object_image_fill_set"); - rtld_default_set_once(__evas_object_image_data_update_add_p, "evas_object_image_data_update_add"); - rtld_default_set_once(__evas_object_image_save_p, "evas_object_image_save"); - rtld_default_set_once(__evas_object_image_add_p, "evas_object_image_add"); + char *scrimage; + int width, height, err; + Evas *ev = NULL; + Evas_Object *img; + int ret = -1; + + rtld_default_set_once(__evas_object_resize_p, + "evas_object_resize"); + rtld_default_set_once(__evas_object_image_data_set_p, + "evas_object_image_data_set"); + rtld_default_set_once(__evas_object_image_size_set_p, + "evas_object_image_size_set"); + rtld_default_set_once(__evas_object_image_fill_set_p, + "evas_object_image_fill_set"); + rtld_default_set_once(__evas_object_image_data_update_add_p, + "evas_object_image_data_update_add"); + rtld_default_set_once(__evas_object_image_save_p, + "evas_object_image_save"); + rtld_default_set_once(__evas_object_image_add_p, + "evas_object_image_add"); pthread_mutex_lock(&captureScreenLock); inc_current_event_index(); - sdata.ximage = NULL; - scrimage = captureScreenShotX(&width, &height, &sdata); - if(scrimage != NULL) - { - ev = create_canvas(width, height); - if(likely(ev != NULL)) - { - snprintf(dstpath, sizeof(dstpath), - SCREENSHOT_DIRECTORY "/%d_%d.png", getpid(), - getCurrentEventIndex()); - - // make image buffer - if((img = __evas_object_image_add_p(ev)) != NULL) - { - //image buffer set - __evas_object_image_data_set_p(img, NULL); - __evas_object_image_size_set_p(img, width, height); - __evas_object_image_data_set_p(img, scrimage); - - // resize image - if(height > MAX_HEIGHT) - { - width = width * MAX_HEIGHT / height; - height = MAX_HEIGHT; - __evas_object_resize_p(img, width, height); - __evas_object_image_fill_set_p(img, 0, 0, width, height); - } - __evas_object_image_data_update_add_p(img, 0, 0, width, height); - - //save file - if(__evas_object_image_save_p(img, dstpath, NULL, "compress=5") != 0) - { - if (chmod(dstpath, 0777) == -1) - PRINTWRN("cannot chmod -R777 <%s>", dstpath); - - /* welcome to the hell */ - log_t log; - PREPARE_LOCAL_BUF_THOUGH((char *)&log); - - /* skip header */ - LOCAL_BUF += offsetof(log_t, data); - /* pack screenshot name */ - BUF_PTR = pack_string(LOCAL_BUF, dstpath); /* file name */ - LOCAL_BUF = BUF_PTR; - - /* pack probe */ - PACK_COMMON_BEGIN(MSG_PROBE_SCREENSHOT, API_ID_captureScreen, "", 0); - PACK_COMMON_END_THOUGH('d', 0, 0, 0); - PACK_SCREENSHOT(dstpath, getOrientation()); - SET_MSG_LEN(); - log.length = GET_MSG_LEN() + MSG_HDR_LEN + strlen(dstpath) + 1; - - /* send all message */ - printLog(&log, APP_MSG_IMAGE); - } - else - { - // captureScreen : evas_object_image_save failed - ret = -1; - } - } - else - { - // captureScreen : evas_object_image_add failed - ret = -1; - } - } - else - { - // captureScreen : create canvas failed - ret = -1; - } + scrimage = __capture_screnshot_wayland(&width, &height); + if (unlikely(scrimage == NULL)) { + ret = -1; + goto out; } - else - { - // captureScreen : captureScreenShotX failed + + ev = create_canvas(width, height); + if (unlikely(ev == NULL)) { ret = -1; + goto out_image; + } + + snprintf(dstpath, sizeof(dstpath), + SCREENSHOT_DIRECTORY "/%d_%d.png", getpid(), + getCurrentEventIndex()); + + // make image buffer + img = __evas_object_image_add_p(ev); + if (unlikely(img == NULL)) { + ret = -1; + goto out_canvas; + } + + //image buffer set + __evas_object_image_data_set_p(img, NULL); + __evas_object_image_size_set_p(img, width, height); + __evas_object_image_data_set_p(img, scrimage); + + // resize image + if (height > MAX_HEIGHT) { + width = width * MAX_HEIGHT / height; + height = MAX_HEIGHT; + __evas_object_resize_p(img, width, height); + __evas_object_image_fill_set_p(img, 0, 0, width, height); + } + __evas_object_image_data_update_add_p(img, 0, 0, width, height); + + //save image to png file + err = __evas_object_image_save_p(img, dstpath, NULL, "compress=5"); + if (err != 0) { + if (chmod(dstpath, 0777) == -1) + PRINTWRN("cannot chmod -R777 <%s>", dstpath); + + /* welcome to the hell */ + log_t log; + PREPARE_LOCAL_BUF_THOUGH((char *)&log); + + /* skip header */ + LOCAL_BUF += offsetof(log_t, data); + /* pack screenshot name */ + BUF_PTR = pack_string(LOCAL_BUF, dstpath); /* file name */ + LOCAL_BUF = BUF_PTR; + + /* pack probe */ + PACK_COMMON_BEGIN(MSG_PROBE_SCREENSHOT, API_ID_captureScreen, "", 0); + PACK_COMMON_END_THOUGH('d', 0, 0, 0); + PACK_SCREENSHOT(dstpath, current_angle_get()); + SET_MSG_LEN(); + log.length = GET_MSG_LEN() + MSG_HDR_LEN + strlen(dstpath) + 1; + + /* send all message */ + printLog(&log, APP_MSG_IMAGE); + ret = 0; } // release resources - releaseScreenShotX(&sdata); - if(ev) - destroy_canvas(ev); +out_canvas: + destroy_canvas(ev); +out_image: + free(scrimage); + +out: pthread_mutex_unlock(&captureScreenLock); return ret; } @@ -484,10 +589,11 @@ static Eina_Bool _captureTimer(void __unused * data) int activateCaptureTimer() { - static Ecore_Timer *(*__ecore_timer_add_p)(double in, Ecore_Task_Cb func, const void *data); + static Ecore_Timer *(*__ecore_timer_add_p)(double in, + Ecore_Task_Cb func, + const void *data); rtld_default_set_once(__ecore_timer_add_p, "ecore_timer_add"); - __ecore_timer_add_p(CAPTURE_TIMEOUT, _captureTimer, NULL); return 0; } diff --git a/helper/wayland-api.c b/helper/wayland-api.c new file mode 100644 index 0000000..5a3a8fb --- /dev/null +++ b/helper/wayland-api.c @@ -0,0 +1,26 @@ +#include <stdlib.h> +#include <stdint.h> +#include "wayland-util.h" + +extern const struct wl_interface wl_buffer_interface; +extern const struct wl_interface wl_output_interface; + +static const struct wl_interface *types[] = { + &wl_output_interface, + &wl_buffer_interface, +}; + +static const struct wl_message screenshooter_requests[] = { + { "shoot", "oo", types + 0 }, +}; + +static const struct wl_message screenshooter_events[] = { + { "done", "", types + 0 }, +}; + +WL_EXPORT const struct wl_interface screenshooter_interface = { + "screenshooter", 1, + 1, screenshooter_requests, + 1, screenshooter_events, +}; + diff --git a/helper/wayland-api.h b/helper/wayland-api.h new file mode 100644 index 0000000..5518f2b --- /dev/null +++ b/helper/wayland-api.h @@ -0,0 +1,68 @@ +#ifndef SCREENSHOOTER_CLIENT_PROTOCOL_H +#define SCREENSHOOTER_CLIENT_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stddef.h> +#include "wayland-client.h" + +struct wl_client; +struct wl_resource; + +struct screenshooter; +struct wl_buffer; +struct wl_output; + +extern const struct wl_interface screenshooter_interface; + +struct screenshooter_listener { + /** + * done - (none) + */ + void (*done)(void *data, + struct screenshooter *screenshooter); +}; + +static inline int +screenshooter_add_listener(struct screenshooter *screenshooter, + const struct screenshooter_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) screenshooter, + (void (**)(void)) listener, data); +} + +#define SCREENSHOOTER_SHOOT 0 + +static inline void +screenshooter_set_user_data(struct screenshooter *screenshooter, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) screenshooter, user_data); +} + +static inline void * +screenshooter_get_user_data(struct screenshooter *screenshooter) +{ + return wl_proxy_get_user_data((struct wl_proxy *) screenshooter); +} + +static inline void +screenshooter_destroy(struct screenshooter *screenshooter) +{ + wl_proxy_destroy((struct wl_proxy *) screenshooter); +} + +static inline void +screenshooter_shoot(struct screenshooter *screenshooter, struct wl_output *output, struct wl_buffer *buffer) +{ + wl_proxy_marshal((struct wl_proxy *) screenshooter, + SCREENSHOOTER_SHOOT, output, buffer); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/dacapture.h b/include/dacapture.h index ef9634c..bcf3d67 100644 --- a/include/dacapture.h +++ b/include/dacapture.h @@ -27,7 +27,7 @@ #define _DACAPTURE_H_ -#ifdef X11_SUPPORT +#ifdef WAYLAND_SUPPORT #include <Evas.h> @@ -38,7 +38,7 @@ void _cb_render_post(void *data, Evas *e, void *eventinfo); int initialize_screencapture(); int finalize_screencapture(); -#else /* X11_SUPPORT */ +#else /* WAYLAND_SUPPORT */ #include "daprobe.h" @@ -68,7 +68,7 @@ static inline int finalize_screencapture() return -1; } -#endif /* X11_SUPPORT */ +#endif /* WAYLAND_SUPPORT */ #endif /* _DACAPTURE_H_ */ diff --git a/include/dahelper.h b/include/dahelper.h index 76e61cb..64f21c1 100755 --- a/include/dahelper.h +++ b/include/dahelper.h @@ -179,7 +179,8 @@ char *real_abs_path(int fd, char *buffer, size_t bufsiz); // event related functions int initialize_event(); int finalize_event(); -int getOrientation(); +int current_angle_get(); +void current_angle_set(int angle); void on_orientation_changed(int angle, bool capi); // query functions diff --git a/include/orientation.h b/include/orientation.h index 68d6ccd..8e5da8f 100644 --- a/include/orientation.h +++ b/include/orientation.h @@ -26,31 +26,4 @@ #ifndef _ORIENTATION_H_ #define _ORIENTATION_H_ - -#ifdef X11_SUPPORT - -#include <Ecore.h> - -Ecore_Event_Handler *register_orientation_event_listener(); -void unregister_orientation_event_listener(Ecore_Event_Handler *handler); - -#else /* X11_SUPPORT */ - -#include "daprobe.h" - -static inline Ecore_Event_Handler *register_orientation_event_listener() -{ - PRINTERR("X11 isn't support, orientation_event unavailable\n"); - return NULL; -} - -static inline -void unregister_orientation_event_listener(Ecore_Event_Handler __unused *handler) -{ - PRINTERR("X11 isn't support, orientation_event unavailable\n"); -} - -#endif /* X11_SUPPORT */ - - #endif /* _ORIENTATION_H_ */ diff --git a/packaging/swap-probe.spec b/packaging/swap-probe.spec index 1234cd8..febdc56 100644 --- a/packaging/swap-probe.spec +++ b/packaging/swap-probe.spec @@ -8,11 +8,11 @@ Source: %{name}_%{version}.tar.gz # setup config -%define X11_SUPPORT 0 +%define WAYLAND_SUPPORT 0 -%if "%{_with_x}" == "1" -%define X11_SUPPORT 1 -%endif # _with_x +%if "%{_with_wayland}" == "1" +%define WAYLAND_SUPPORT 1 +%endif # _with_wayland # unsupport arch @@ -36,17 +36,13 @@ BuildRequires: python-accel-armv7l-cross-arm BuildRequires: python-accel-aarch64-cross-aarch64 %endif # aarch64 +BuildRequires: pkgconfig(sensor) # graphic support BuildRequires: pkgconfig(gles20) BuildRequires: pkgconfig(wayland-egl) BuildRequires: pkgconfig(egl) -%if %{X11_SUPPORT} -BuildRequires: libXext-devel -%endif # X11_SUPPORT - - Provides: swap-probe @@ -83,9 +79,7 @@ This tool will be installed in target %setup -q -n %{name}_%{version} %build -%if %{X11_SUPPORT} -export X11_SUPPORT=y -%endif +export WAYLAND_SUPPORT=y make rmheaders make headers diff --git a/probe_capi/capi_appfw.c b/probe_capi/capi_appfw.c index 653038a..b2eefe9 100644 --- a/probe_capi/capi_appfw.c +++ b/probe_capi/capi_appfw.c @@ -36,7 +36,6 @@ #include "dahelper.h" #include "probeinfo.h" #include "binproto.h" -#include "orientation.h" #include "real_functions.h" #include "common_probe_init.h" @@ -116,12 +115,10 @@ static void _ui_dalc_app_control(app_control_h handle, void *user_data) int PROBE_NAME(ui_app_main)(int argc, char **argv, ui_app_lifecycle_callback_s *callback, void *user_data) { static int (*ui_app_mainp)(int argc, char **argv, ui_app_lifecycle_callback_s *callback, void *user_data); - Ecore_Event_Handler* handler; int ret; GET_REAL_FUNCP_RTLD_DEFAULT(ui_app_main, ui_app_mainp); - handler = register_orientation_event_listener(); uiAppCallback.create = callback->create; uiAppCallback.terminate = callback->terminate; uiAppCallback.pause = callback->pause; @@ -143,8 +140,6 @@ int PROBE_NAME(ui_app_main)(int argc, char **argv, ui_app_lifecycle_callback_s * ret = ui_app_mainp(argc, argv, callback, user_data); - unregister_orientation_event_listener(handler); - callback->create = uiAppCallback.create; callback->terminate = uiAppCallback.terminate; callback->pause = uiAppCallback.pause; diff --git a/probe_event/api_names.txt b/probe_event/api_names.txt index d2bb27b..517456b 100644 --- a/probe_event/api_names.txt +++ b/probe_event/api_names.txt @@ -42,9 +42,9 @@ Ecore_Event_Handler *register_orientation_event_listener(); Ecor void unregister_orientation_event_listener(Ecore_Event_Handler *handler); void unregister_orientation_event_listener(Ecore_Event_Handler *handler); 0 #filename "orientation.c" -#lib "/usr/lib/libecore_x.so.1" +#lib "/usr/lib/libecore_wayland.so.1" #feature --- -ecore_x_init; ecore_x_init; 3 +ecore_wl_init; ecore_wl_init; 3 #filename "da_event.c" #lib "???" diff --git a/probe_event/da_event.c b/probe_event/da_event.c index 44c7044..43fbb2c 100755 --- a/probe_event/da_event.c +++ b/probe_event/da_event.c @@ -43,8 +43,7 @@ #include "binproto.h" #include "real_functions.h" -static int external_angle = 0; -static int internal_angle = 0; +static int __current_angle = 0; // ==================================================================== // initialize and finalize event @@ -52,15 +51,6 @@ static int internal_angle = 0; int initialize_event() { - static bool inited = false; - static app_device_orientation_e (*__app_get_device_orientation_p)(void); - - if (inited) - return 0; - - rtld_default_set_once(__app_get_device_orientation_p, "app_get_device_orientation"); - - external_angle = internal_angle = __app_get_device_orientation_p(); return 0; } @@ -69,7 +59,6 @@ int finalize_event() return 0; } - // =================================================================== // orientation related functions // =================================================================== @@ -78,8 +67,7 @@ static int convert_angle(int angle) { int os = _OS_NONE; - switch(angle) - { + switch(angle) { case 0: os = _OS_PORTRAIT; break; @@ -101,13 +89,9 @@ static int convert_angle(int angle) void on_orientation_changed(int angle, bool capi) { - initialize_event(); + __current_angle = angle; - internal_angle = angle; - external_angle = internal_angle; - - if(isOptionEnabled(OPT_EVENT)) - { + if (isOptionEnabled(OPT_EVENT)) { inc_current_event_index(); PREPARE_LOCAL_BUF(); @@ -115,16 +99,19 @@ void on_orientation_changed(int angle, bool capi) API_ID_on_orientation_changed, "dd", angle, (uint32_t)capi); PACK_COMMON_END('v', 0, 0, 0); - PACK_UIEVENT(_EVENT_ORIENTATION, 0, 0, 0, "", convert_angle(external_angle)); + PACK_UIEVENT(_EVENT_ORIENTATION, 0, 0, 0, "", + convert_angle(__current_angle)); FLUSH_LOCAL_BUF(); } SCREENSHOT_SET(); } -int getOrientation() +int current_angle_get() { - initialize_event(); - - return external_angle; + return __current_angle; +} +void current_angle_set(int angle) +{ + __current_angle = angle; } diff --git a/probe_event/orientation.c b/probe_event/orientation.c index 0750cda..47d5e70 100644 --- a/probe_event/orientation.c +++ b/probe_event/orientation.c @@ -1,13 +1,14 @@ /* * DA probe * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. + * Copyright (c) 2000 - 2016 Samsung Electronics Co., Ltd. All rights reserved. * * Contact: * * Jaewon Lim <jaewon81.lim@samsung.com> * Woojin Jung <woojin2.jung@samsung.com> * Juyoung Kim <j0.kim@samsung.com> + * Anatolii Nikulin <nikulin.a@samsung.com> * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the @@ -28,82 +29,150 @@ * */ -#include <Ecore.h> -#include <Ecore_X.h> +#include <vconf.h> +#include <sensor_internal.h> #include "daprobe.h" #include "dahelper.h" #include "common_probe_init.h" +struct rotation_s { + int handle; + int angle; + int auto_rotate; +}; -static Eina_Bool _da_onclientmessagereceived(void __unused *pData, - int __unused type, - void *pEvent) +static struct rotation_s rotation; +static int rotation_initialized = 0; + +static int __get_angle(sensor_data_t *data) { - static Ecore_X_Atom *__ECORE_X_ATOM_E_WINDOW_ROTATION_CHANGE_PREPARE_P = NULL; - Ecore_X_Event_Client_Message *pClientEvent; + int event, m; + + m = 0; + if (data->value_count > 0) { + event = (int)data->values[0]; + } else { + PRINTERR("Failed to get sensor data"); + return 0; + } - rtld_default_set_once(__ECORE_X_ATOM_E_WINDOW_ROTATION_CHANGE_PREPARE_P, "ECORE_X_ATOM_E_WINDOW_ROTATION_CHANGE_PREPARE"); + switch (event) { + case AUTO_ROTATION_DEGREE_0: + m = 0; + break; - pClientEvent = (Ecore_X_Event_Client_Message*)pEvent; + case AUTO_ROTATION_DEGREE_90: + m = 90; + break; - if (pClientEvent != NULL) { + case AUTO_ROTATION_DEGREE_180: + m = 180; + break; - //This code from ecore_x - //So I don't know what 32 does mean - if (pClientEvent->format != 32) - return ECORE_CALLBACK_PASS_ON; + case AUTO_ROTATION_DEGREE_270: + m = 270; + break; - if (pClientEvent->message_type == - *__ECORE_X_ATOM_E_WINDOW_ROTATION_CHANGE_PREPARE_P) { - int orientation = (int)pClientEvent->data.l[1]; - on_orientation_changed(orientation, false); - } + default: + break; } - return ECORE_CALLBACK_RENEW; + return m; +} + +static void __auto_rotate_screen_cb(keynode_t *key, + void __attribute__((unused)) *data) +{ + rotation.auto_rotate = vconf_keynode_get_bool(key); } -Ecore_Event_Handler *register_orientation_event_listener() +static void __rotation_changed_cb(sensor_t __attribute__((unused)) sensor, + unsigned int event_type, + sensor_data_t *data, + void __attribute__((unused)) *user_data) { - static Ecore_Event_Handler *(*__ecore_event_handler_add_p)(int type, Ecore_Event_Handler_Cb func, const void *data) = NULL; - static int *__ECORE_X_EVENT_CLIENT_MESSAGE_P = NULL; - Ecore_Event_Handler *handler; + if (event_type != AUTO_ROTATION_CHANGE_STATE_EVENT) + return; - rtld_default_set_once(__ecore_event_handler_add_p, "ecore_event_handler_add"); - rtld_default_set_once(__ECORE_X_EVENT_CLIENT_MESSAGE_P, "ECORE_X_EVENT_CLIENT_MESSAGE"); + rotation.angle = __get_angle(data); + if (!rotation.auto_rotate) + return; - handler = __ecore_event_handler_add_p(*__ECORE_X_EVENT_CLIENT_MESSAGE_P, - _da_onclientmessagereceived, NULL); + on_orientation_changed(rotation.angle, false); - return handler; } -void unregister_orientation_event_listener(Ecore_Event_Handler *handler) + +static int __rotate_event_init(void) { - static void *(*__ecore_event_handler_del_p)(Ecore_Event_Handler *event_handler) = NULL; + bool r; + sensor_data_t data; + sensor_t sensor = sensord_get_sensor(AUTO_ROTATION_SENSOR); + + rotation.angle = 0; + rotation.handle = sensord_connect(sensor); + if (rotation.handle < 0) { + PRINTERR("Failed to connect sensord"); + return -1; + } + + r = sensord_register_event(rotation.handle, + AUTO_ROTATION_CHANGE_STATE_EVENT, + SENSOR_INTERVAL_NORMAL, + 0, + __rotation_changed_cb, + NULL); + if (!r) { + PRINTERR("Failed to register event"); + sensord_disconnect(rotation.handle); + return -1; + } + + r = sensord_start(rotation.handle, 0); + if (!r) { + PRINTERR("Failed to start sensord"); + sensord_unregister_event(rotation.handle, + AUTO_ROTATION_CHANGE_STATE_EVENT); + sensord_disconnect(rotation.handle); + return -1; + } - rtld_default_set_once(__ecore_event_handler_del_p, "ecore_event_handler_del"); + rotation.auto_rotate = 0; + vconf_get_bool(VCONFKEY_SETAPPL_AUTO_ROTATE_SCREEN_BOOL, + &rotation.auto_rotate); + + if (vconf_notify_key_changed(VCONFKEY_SETAPPL_AUTO_ROTATE_SCREEN_BOOL, + __auto_rotate_screen_cb, NULL) != 0) + PRINTERR("Failed to register callback for %s", + VCONFKEY_SETAPPL_AUTO_ROTATE_SCREEN_BOOL); + + r = sensord_get_data(rotation.handle, AUTO_ROTATION_SENSOR, &data); + if (!r) { + PRINTERR("sensord_get_data failed"); + rotation.angle = 0; + } else { + rotation.angle = __get_angle(&data); + } + current_angle_set(rotation.angle); + rotation_initialized = 1; - if (handler) - __ecore_event_handler_del_p(handler); + return 0; } -EAPI int PROBE_NAME(ecore_x_init)(const char *name) +EAPI int PROBE_NAME(ecore_wl_init)(const char *name) { - static Ecore_Event_Handler *event_handler = NULL; - int res = 0; - static int (*ecore_x_initp)(const char *name); - PRINTMSG("(%s)", name); + static int (*ecore_wl_initp)(const char *name); - rtld_default_set_once(ecore_x_initp, "ecore_x_init"); - res = ecore_x_initp(name); + PRINTMSG("(%s)", name); + rtld_default_set_once(ecore_wl_initp, "ecore_wl_init"); + res = ecore_wl_initp(name); - if (event_handler == NULL) { - event_handler = register_orientation_event_listener(); - if (event_handler == NULL) - PRINTERR("Fail to init event listener"); + if (!rotation_initialized) { + if (__rotate_event_init() < 0) + PRINTERR("Failed to initialize rotation"); } + return res; } |