add tdm_hwc_window_set_cursor_image
[platform/core/uifw/libtdm.git] / tools / tdm_test_client.c
index ed88d8a..ab12626 100644 (file)
-/*
-Copyright (C) 2015 Samsung Electronics co., Ltd. All Rights Reserved.
-
-Contact:
-      Changyeon Lee <cyeon.lee@samsung.com>,
-      JunKyeong Kim <jk0430.kim@samsung.com>,
-      Boram Park <boram1288.park@samsung.com>,
-      SooChan Lim <sc1.lim@samsung.com>
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation
-the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice (including the next
-paragraph) shall be included in all copies or substantial portions of the
-Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
-THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-*/
+/**************************************************************************
+ *
+ * libtdm
+ *
+ * Copyright 2015 Samsung Electronics co., Ltd. All Rights Reserved.
+ *
+ * Contact: Eunchul Kim <chulspro.kim@samsung.com>,
+ *          JinYoung Jeon <jy0.jeon@samsung.com>,
+ *          Taeheon Kim <th908.kim@samsung.com>,
+ *          YoungJun Cho <yj44.cho@samsung.com>,
+ *          SooChan Lim <sc1.lim@samsung.com>,
+ *          Boram Park <boram1288.park@samsung.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+**************************************************************************/
 
 #include <stdio.h>
+#include <string.h>
 #include <stdlib.h>
 #include <poll.h>
 #include <errno.h>
 #include <time.h>
+#include <stdint.h>
+#include <png.h>
 
-#include <tdm_client.h>
-#include <tdm_helper.h>
+#include "tdm_client.h"
+#include "tdm_macro.h"
+#include "buffers.h"
 
-static int
-get_time_in_millis(void)
+#define CHECK_V_STEP 0
+
+typedef struct _tdm_test_client_arg {
+       char *output_name;
+       int fps;
+       int sync;
+       int interval;
+       int offset;
+       int enable_fake;
+       int pid;
+       char *vblank_name;
+} tdm_test_client_arg;
+
+typedef struct _tdm_test_client {
+       tdm_test_client_arg args;
+
+       int do_query;
+       int do_vblank;
+       int do_voutput;
+       int waiting;
+
+       tdm_client *client;
+       tdm_client_voutput *voutput;
+       tdm_client_output *output;
+} tdm_test_client;
+
+struct typestrings {
+       int type;
+       const char *string;
+};
+
+struct optstrings {
+       int  type;
+       const char *opt;
+       const char *desc;
+       const char *arg;
+       const char *ex;
+};
+
+enum {
+       OPT_QRY,
+       OPT_TST,
+       OPT_GNR,
+};
+
+static struct typestrings typestrs[] = {
+       {OPT_QRY, "Query"},
+       {OPT_TST, "Test"},
+       {OPT_GNR, "General"},
+};
+
+static struct optstrings optstrs[] = {
+       {OPT_QRY, "qo", "output objects info", "<output_name>", "primary"},
+       {OPT_TST, "v", "vblank test", "<output_name>[,<sync>][@<fps>][~<interval>][+<offset>][*fake][^vblank_name]", "primary,0@60~1+0*1^test"},
+       {OPT_TST, "V", "virtual output test", NULL, NULL},
+};
+
+static void
+usage(char *app_name)
+{
+       int type_size = sizeof(typestrs) / sizeof(struct typestrings);
+       int opt_size = sizeof(optstrs) / sizeof(struct optstrings);
+       int t;
+
+       printf("usage: %s \n\n", app_name);
+
+       for (t = 0; t < type_size; t++) {
+               int o, f = 1;
+
+               for (o = 0; o < opt_size; o++)
+                       if (optstrs[o].type == typestrs[t].type) {
+                               if (f == 1)
+                                       printf(" %s options:\n\n", typestrs[t].string);
+                               printf("\t-%s\t%s\n", optstrs[o].opt, optstrs[o].desc);
+                               if (optstrs[o].arg)
+                                       printf("\t\t  %s\n", optstrs[o].arg);
+                               if (optstrs[o].ex)
+                                       printf("\t\t  ex) %s\n", optstrs[o].ex);
+                               f = 0;
+                       }
+               printf("\n");
+       }
+
+       exit(0);
+}
+
+//"<output_name>"
+static void
+parse_arg_qo(tdm_test_client *data, char *arg)
+{
+       char name[TDM_NAME_LEN];
+       strtostr(name, TDM_NAME_LEN, arg, TDM_DELIM);
+       data->args.output_name = strndup(name, TDM_NAME_LEN);
+}
+
+//"<output_name>[,<sync>][@<fps>][~<interval>][+<offset>][*fake]"
+static void
+parse_arg_v(tdm_test_client *data, char *arg)
+{
+       char *end = arg;
+       char name[TDM_NAME_LEN];
+
+       end = strtostr(name, TDM_NAME_LEN, arg, TDM_DELIM);
+       data->args.output_name = strndup(name, TDM_NAME_LEN);
+
+       if (*end == ',') {
+               arg = end + 1;
+               data->args.sync = strtol(arg, &end, 10);
+       }
+
+       if (*end == '@') {
+               arg = end + 1;
+               data->args.fps = strtol(arg, &end, 10);
+       }
+
+       if (*end == '~') {
+               arg = end + 1;
+               data->args.interval = strtol(arg, &end, 10);
+       }
+
+       if (*end == '+' || *end == '-') {
+               arg = end;
+               data->args.offset = strtol(arg, &end, 10);
+       }
+
+       if (*end == '*') {
+               arg = end + 1;
+               data->args.enable_fake = strtol(arg, &end, 10);
+       }
+
+       if (*end == '^') {
+               char name[TDM_NAME_LEN];
+               arg = end + 1;
+               end = strtostr(name, TDM_NAME_LEN, arg, TDM_DELIM);
+               data->args.vblank_name = strndup(name, TDM_NAME_LEN);
+       }
+}
+
+static void
+parse_args(tdm_test_client *data, int argc, char *argv[])
+{
+       int i;
+
+       if (argc < 2) {
+               usage(argv[0]);
+               exit(0);
+       }
+
+       memset(data, 0, sizeof *data);
+       data->args.interval = 1;
+
+       for (i = 1; i < argc; i++) {
+               if (!strncmp(argv[i] + 1, "qo", 2)) {
+                       data->do_query = 1;
+                       parse_arg_qo(data, argv[++i]);
+               } else if (!strncmp(argv[i] + 1, "v", 1)) {
+                       data->do_vblank = 1;
+                       parse_arg_v(data, argv[++i]);
+               } else if (!strncmp(argv[i] + 1, "V", 1)) {
+                       data->do_voutput = 1;
+               } else {
+                       usage(argv[0]);
+                       exit(0);
+               }
+       }
+}
+
+static double
+get_time(void)
 {
        struct timespec tp;
 
        if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0)
-               return (int)(tp.tv_sec * 1000) + (tp.tv_nsec / 1000000L);
+               return (double)tp.tv_sec + ((double)tp.tv_nsec) / 1000000000.0;
 
        return 0;
 }
 
 static void
-_client_vblank_handler(unsigned int sequence, unsigned int tv_sec,
-                       unsigned int tv_usec, void *user_data)
+_client_vblank_handler(tdm_client_vblank *vblank, tdm_error error, unsigned int sequence,
+                                          unsigned int tv_sec, unsigned int tv_usec, void *user_data)
 {
-       int temp1, temp2;
+       tdm_test_client *data = user_data;
+       double cur, vbl;
+       static double p_vbl = 0;
+
+       data->waiting = 0;
 
-       temp1 = (int)user_data;
-       temp2 = get_time_in_millis();
+       if (error == TDM_ERROR_DPMS_OFF) {
+               printf("exit: dpms off\n");
+               exit(0);
+       }
+
+       if (error != TDM_ERROR_NONE) {
+               printf("exit: error(%d)\n", error);
+               exit(0);
+       }
 
-       printf("%d ms\n", temp2 - temp1);
+       cur = get_time();
+       vbl = (double)tv_sec + ((double)tv_usec) / 1000000.0;
+
+       printf("vblank              : %.6f us vbl(%.6f)\n", vbl - p_vbl, vbl);
+
+       if (cur - vbl > 0.002) /* 2ms */
+               printf("kernel -> tdm-client: %.0f us\n", (cur - vbl) * 1000000.0);
+
+       p_vbl = vbl;
 }
 
+static char *conn_str[3] = {"disconnected", "connected", "mode_setted"};
+static char *dpms_str[4] = {"on", "standy", "suspend", "off"};
 
-int
-main(int argc, char *argv[])
+static void
+_client_output_handler(tdm_client_output *output, tdm_output_change_type type,
+                                          tdm_value value, void *user_data)
 {
-       tdm_client *client;
-       tdm_client_error error;
+       if (type == TDM_OUTPUT_CHANGE_CONNECTION)
+               printf("output %s.\n", conn_str[value.u32]);
+       else if (type == TDM_OUTPUT_CHANGE_DPMS)
+               printf("dpms %s.\n", dpms_str[value.u32]);
+}
+
+static void
+do_query(tdm_test_client *data)
+{
+       tdm_client_output *output;
+       tdm_output_conn_status status;
+       tdm_output_dpms dpms;
+       unsigned int refresh;
+       tdm_error error;
+
+       output = tdm_client_get_output(data->client, NULL, &error);
+       if (error != TDM_ERROR_NONE) {
+               printf("tdm_client_get_output failed\n");
+               return;
+       }
+
+       error = tdm_client_output_get_conn_status(output, &status);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+       error = tdm_client_output_get_dpms(output, &dpms);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+       error = tdm_client_output_get_refresh_rate(output, &refresh);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+
+       printf("tdm_output \"%s\"\n", data->args.output_name);
+       printf("\tstatus : %s\n", conn_str[status]);
+       printf("\tdpms : %s\n", dpms_str[dpms]);
+       printf("\trefresh : %d\n", refresh);
+}
+
+static void
+do_vblank(tdm_test_client *data)
+{
+       tdm_client_output *output;
+       tdm_client_vblank *vblank = NULL;
+       tdm_error error;
        int fd = -1;
        struct pollfd fds;
 
-       client = tdm_client_create(&error);
-       if (error != TDM_CLIENT_ERROR_NONE) {
-               printf("tdm_client_create failed\n");
-               exit(1);
+       output = tdm_client_get_output(data->client, data->args.output_name, &error);
+       if (error != TDM_ERROR_NONE) {
+               printf("tdm_client_get_output failed\n");
+               return;
+       }
+
+       error = tdm_client_output_add_change_handler(output, _client_output_handler, NULL);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+
+       vblank = tdm_client_output_create_vblank(output, &error);
+       if (error != TDM_ERROR_NONE) {
+               printf("tdm_client_output_create_vblank failed\n");
+               return;
+       }
+
+       error = tdm_client_vblank_set_name(vblank, data->args.vblank_name);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+       error = tdm_client_vblank_set_enable_fake(vblank, data->args.enable_fake);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+       error = tdm_client_vblank_set_sync(vblank, data->args.sync);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
+       if (data->args.fps > 0) {
+               error = tdm_client_vblank_set_fps(vblank, data->args.fps);
+               TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
        }
+       error = tdm_client_vblank_set_offset(vblank, data->args.offset);
+       TDM_WARNING_IF_FAIL(error == TDM_ERROR_NONE);
 
-       error = tdm_client_get_fd(client, &fd);
-       if (error != TDM_CLIENT_ERROR_NONE || fd < 0) {
+       error = tdm_client_get_fd(data->client, &fd);
+       if (error != TDM_ERROR_NONE || fd < 0) {
                printf("tdm_client_get_fd failed\n");
                goto done;
        }
@@ -87,30 +346,410 @@ main(int argc, char *argv[])
        while (1) {
                int ret;
 
-               error = tdm_client_wait_vblank(client, "unknown-0", 1, 0,
-                                              _client_vblank_handler,
-                                              (void*)get_time_in_millis());
-               if (error != TDM_CLIENT_ERROR_NONE) {
-                       printf("tdm_client_wait_vblank failed\n");
-                       goto done;
+               if (!data->waiting) {
+                       error = tdm_client_vblank_wait(vblank, data->args.interval,
+                                                                                  _client_vblank_handler, data);
+                       if (error == TDM_ERROR_DPMS_OFF) {
+                               printf("tdm_client_vblank_wait failed (dpms off)\n");
+                               goto done;
+                       }
+                       if (error != TDM_ERROR_NONE) {
+                               printf("tdm_client_vblank_wait failed (error: %d)\n", error);
+                               goto done;
+                       }
+                       data->waiting = 1;
                }
 
-               ret = poll(&fds, 1, -1);
-               if (ret < 0) {
-                       if (errno == EBUSY)  /* normal case */
-                               continue;
-                       else {
-                               printf("poll failed: %m\n");
+               if (!data->args.sync) {
+                       ret = poll(&fds, 1, -1);
+                       if (ret < 0) {
+                               if (errno == EINTR || errno == EAGAIN)  /* normal case */
+                                       continue;
+                               else {
+                                       printf("poll failed: %m\n");
+                                       goto done;
+                               }
+                       }
+
+                       error = tdm_client_handle_events(data->client);
+                       if (error != TDM_ERROR_NONE) {
+                               printf("tdm_client_handle_events failed\n");
                                goto done;
                        }
                }
+       }
+
+done:
+       if (vblank)
+               tdm_client_vblank_destroy(vblank);
+}
+
+#define PNG_DEPTH 8
+
+void
+_tdm_client_get_buffer_full_size(tbm_surface_h buffer, int *buffer_w, int *buffer_h)
+{
+       tbm_surface_info_s info;
+       int ret;
+
+       TDM_RETURN_IF_FAIL(buffer != NULL);
+
+       ret = tbm_surface_get_info(buffer, &info);
+       TDM_RETURN_IF_FAIL(ret == TBM_SURFACE_ERROR_NONE);
+
+       if (buffer_w) {
+               if (IS_RGB(info.format))
+                       *buffer_w = info.planes[0].stride >> 2;
+               else
+                       *buffer_w = info.planes[0].stride;
+       }
+
+       if (buffer_h)
+               *buffer_h = info.planes[0].size / info.planes[0].stride;
+}
+
+static void
+_tdm_client_dump_png(const char *file, const void *data, int width,
+                                        int height)
+{
+       FILE *fp;
+
+       fp = fopen(file, "wb");
+       TDM_RETURN_IF_FAIL(fp != NULL);
+
+       png_structp pPngStruct =
+               png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+       if (!pPngStruct) {
+               fclose(fp);
+               return;
+       }
+
+       png_infop pPngInfo = png_create_info_struct(pPngStruct);
+       if (!pPngInfo) {
+               png_destroy_write_struct(&pPngStruct, NULL);
+               fclose(fp);
+               return;
+       }
+
+       png_init_io(pPngStruct, fp);
+       png_set_IHDR(pPngStruct,
+                                pPngInfo,
+                                width,
+                                height,
+                                PNG_DEPTH,
+                                PNG_COLOR_TYPE_RGBA,
+                                PNG_INTERLACE_NONE,
+                                PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+       png_set_bgr(pPngStruct);
+       png_write_info(pPngStruct, pPngInfo);
+
+       const int pixel_size = 4;       // RGBA
+       png_bytep *row_pointers =
+               png_malloc(pPngStruct, height * sizeof(png_byte *));
+       if (!row_pointers) {
+               png_destroy_write_struct(&pPngStruct, &pPngInfo);
+               fclose(fp);
+               return;
+       }
+
+       unsigned int *blocks = (unsigned int *)data;
+       int y = 0;
+       int x = 0;
+
+       for (; y < height; ++y) {
+               png_bytep row =
+                       png_malloc(pPngStruct, sizeof(png_byte) * width * pixel_size);
+               if (!row) {
+                       for (x = 0; x < y; x++)
+                               png_free(pPngStruct, row_pointers[x]);
+                       png_free(pPngStruct, row_pointers);
+                       png_destroy_write_struct(&pPngStruct, &pPngInfo);
+                       fclose(fp);
+                       return;
+               }
+
+               row_pointers[y] = (png_bytep)row;
+               for (x = 0; x < width; ++x) {
+                       unsigned int curBlock = blocks[y * width + x];
+                       row[x * pixel_size] = (curBlock & 0xFF);
+                       row[1 + x * pixel_size] = (curBlock >> 8) & 0xFF;
+                       row[2 + x * pixel_size] = (curBlock >> 16) & 0xFF;
+                       row[3 + x * pixel_size] = (curBlock >> 24) & 0xFF;
+               }
+       }
+
+       png_write_image(pPngStruct, row_pointers);
+       png_write_end(pPngStruct, pPngInfo);
+
+       for (y = 0; y < height; y++)
+               png_free(pPngStruct, row_pointers[y]);
+       png_free(pPngStruct, row_pointers);
+
+       png_destroy_write_struct(&pPngStruct, &pPngInfo);
+
+       fclose(fp);
+}
+
+void
+_tdm_client_dump_buffer(tbm_surface_h buffer, const char *file)
+{
+       char temp[TDM_PATH_LEN] = {0,};
+       tbm_surface_info_s info;
+       int len, ret;
+       const char *ext;
+       int bo_cnt;
+       int bw, bh;
+       char *dot, *p = temp;
+       const char *file_exts[2] = {"png", "raw"};
+
+       TDM_RETURN_IF_FAIL(buffer != NULL);
+       TDM_RETURN_IF_FAIL(file != NULL);
+
+       ret = tbm_surface_map(buffer, TBM_OPTION_READ, &info);
+       TDM_RETURN_IF_FAIL(ret == TBM_SURFACE_ERROR_NONE);
+
+       if (IS_RGB(info.format))
+               ext = file_exts[0];
+       else
+               ext = file_exts[1];
+
+       dot = strrchr(file, '.');
+       if (!dot || strlen(dot + 1) != 3 || strncmp(dot + 1, ext, 3)) {
+               len = strnlen(file, TDM_PATH_LEN - 5);
+               strncat(p, file, len);
+               p += len;
+               *(p++) = '.';
+               strncat(p, ext, 3);
+               p += 3;
+               *p = '\0';
+       } else {
+               len = strnlen(file, TDM_PATH_LEN - 1);
+               strncat(p, file, len);
+               p += len;
+               *p = '\0';
+       }
+
+       _tdm_client_get_buffer_full_size(buffer, &bw, &bh);
+
+       bo_cnt = tbm_surface_internal_get_num_bos(buffer);
+       TDM_DBG("buffer: bo_cnt(%d) %dx%d(%dx%d) %c%c%c%c, plane: (%p+%d, %d,%d) (%p+%d, %d,%d) (%p+%d, %d,%d)",
+                       bo_cnt, bw, bh, info.width, info.height, FOURCC_STR(info.format),
+                       info.planes[0].ptr, info.planes[0].offset, info.planes[0].stride, info.planes[0].size,
+                       info.planes[1].ptr, info.planes[1].offset, info.planes[1].stride, info.planes[1].size,
+                       info.planes[2].ptr, info.planes[2].offset, info.planes[2].stride, info.planes[2].size);
+
+       _tdm_client_dump_png(temp, info.planes[0].ptr, bw, bh);
+
+       tbm_surface_unmap(buffer);
+
+       printf("dump %s\n", temp);
+}
+
+static void
+_dump_buffer(tbm_surface_h buffer, int count)
+{
+       char temp[TDM_PATH_LEN] = {0,};
+
+       snprintf(temp, TDM_PATH_LEN, "/tmp/%c%c%c%c_%dx%d_%d",
+               FOURCC_STR(tbm_surface_get_format(buffer)),
+               tbm_surface_get_width(buffer),
+               tbm_surface_get_height(buffer),
+               count);
+       _tdm_client_dump_buffer(buffer, temp);
+}
+
+static void
+_voutput_commit(tdm_client_voutput *voutput, tbm_surface_h buffer, void *user_data)
+{
+       tdm_test_client *data = (tdm_test_client *)user_data;
+       static int count = 0;
+
+       TDM_EXIT_IF_FAIL(data != NULL);
+       TDM_EXIT_IF_FAIL(buffer != NULL);
+
+       if ((count < 10) || (count >= 31 && count <= 40))
+               _dump_buffer(buffer, count);
+       count++;
+
+       if (count == 30) {
+               printf("client: %d commited(%p), mode change request to index 1\n", count, buffer);
+               tdm_client_voutput_set_mode(data->voutput, 1);
+       } else if (count == 50) {
+               printf("client: %d commited(%p), disconnect\n", count, buffer);
+               tdm_client_voutput_disconnect(data->voutput);
+       } else {
+               printf("client: %d commited(%p)\n", count, buffer);
+       }
+
+       tdm_client_voutput_commit_done(voutput);
+}
+
+static void
+_voutput_output_handler(tdm_client_output *output, tdm_output_change_type type,
+                                          tdm_value value, void *user_data)
+{
+       tdm_client_voutput *voutput = NULL;
+       tdm_output_conn_status status;
+       tdm_test_client *data;
+       unsigned int width, height;
+
+       data = (tdm_test_client *) user_data;
+       TDM_RETURN_IF_FAIL(data != NULL);
+       voutput = data->voutput;
+       TDM_RETURN_IF_FAIL(voutput != NULL);
+
+       if (type == TDM_OUTPUT_CHANGE_CONNECTION) {
+               status = (tdm_output_conn_status)value.u32;
+               printf("output %s.\n", conn_str[value.u32]);
+
+               if (status == TDM_OUTPUT_CONN_STATUS_DISCONNECTED) {
+                       printf("client: disconnected, destroy voutput\n");
+                       tdm_client_output_remove_change_handler(output, _voutput_output_handler, data);
+#if CHECK_V_STEP
+                       printf("press enter to continuet\n");
+                       getchar();
+#endif
+                       tdm_client_voutput_destroy(voutput);
+               } else if (status == TDM_OUTPUT_CONN_STATUS_CONNECTED) {
+                       printf("client: connected\n");
+               } else if (status == TDM_OUTPUT_CONN_STATUS_MODE_SETTED) {
+                       tdm_client_output_get_mode(output, &width, &height);
+                       printf("client: mode setted(%dx%d)\n", width, height);
+#if CHECK_V_STEP
+                       printf("press enter to continuet\n");
+                       getchar();
+#endif
+               }
+       } else if (type == TDM_OUTPUT_CHANGE_DPMS) {
+               printf("output %s.\n", dpms_str[value.u32]);
+       }
+}
 
-               error = tdm_client_handle_events(client);
-               if (error != TDM_CLIENT_ERROR_NONE)
-                       printf("tdm_client_handle_events failed\n");
+static void
+_voutput_make_available_mode(tdm_client_output_mode *modes, int count)
+{
+       int i;
+       for (i = 0 ; i < count; i++) {
+               modes[i].clock = 25200;
+               modes[i].hdisplay = 640 * (count - i);
+               modes[i].hsync_start = 656;
+               modes[i].hsync_end = 752;
+               modes[i].htotal = 800;
+               modes[i].hskew = 0;
+               modes[i].vdisplay = 480 * (count - i);
+               modes[i].vsync_start = 490;
+               modes[i].vsync_end = 492;
+               modes[i].vtotal = 525;
+               modes[i].vscan = 0;
+               modes[i].vrefresh = 30;
+               modes[i].flags = 0;
+               modes[i].type = 0;
+               snprintf(modes[i].name, TDM_NAME_LEN, "%dx%d_%d", modes[i].hdisplay, modes[i].vdisplay, i);
        }
+}
+
+static void
+do_voutput(tdm_test_client *data)
+{
+       tdm_client_voutput *voutput = NULL;
+       tdm_client_output *output = NULL;
+       tdm_client_output_mode modes[2];
+       tdm_error ret = TDM_ERROR_NONE;
+
+       printf("virtual output test - client\n");
+
+       voutput = tdm_client_create_voutput(data->client, "virtual-test", &ret);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       ret = tdm_client_voutput_add_commit_handler(voutput, _voutput_commit, data);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       output = tdm_client_voutput_get_client_output(voutput, &ret);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       ret = tdm_client_output_add_change_handler(output, _voutput_output_handler, data);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       ret = tdm_client_voutput_set_physical_size(voutput, 300, 200);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       _voutput_make_available_mode(modes, 2);
+       ret = tdm_client_voutput_set_available_modes(voutput, modes, 2);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+#if CHECK_V_STEP
+       printf("virtual output test - press enter to connect\n");
+       getchar();
+#endif
+       ret = tdm_client_voutput_connect(voutput);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       data->voutput = voutput;
+       data->output = output;
+
+       while (1) {
+               tdm_client_handle_events_timeout(data->client, 1000);
+       }
+
+done:
+       if (voutput)
+               tdm_client_voutput_destroy(voutput);
+}
+
+static tdm_test_client ttc_data;
+
+int
+main(int argc, char *argv[])
+{
+       tdm_test_client *data = &ttc_data;
+       tdm_error error;
+
+       /* for testing */
+       const char *xdg = (const char*)getenv("XDG_RUNTIME_DIR");
+       if (!xdg) {
+               char buf[32];
+               snprintf(buf, sizeof(buf), "/run");
+               int ret = setenv("XDG_RUNTIME_DIR", (const char*)buf, 1);
+               if (ret != 0)
+                       exit(0);
+       }
+
+       /* for tbm_bufmgr_init */
+       const char *s  = (const char*)getenv("TBM_DISPLAY_SERVER");
+       if (!s) {
+               char buf[32];
+               snprintf(buf, sizeof(buf), "1");
+               int ret = setenv("TBM_DISPLAY_SERVER", (const char*)buf, 1);
+               if (ret != 0)
+                       exit(0);
+       }
+
+       parse_args(data, argc, argv);
+
+       printf("sync(%d) fps(%d) interval(%d) offset(%d) enable_fake(%d) pid(%d)\n",
+                  data->args.sync, data->args.fps, data->args.interval,
+                  data->args.offset, data->args.enable_fake, data->args.pid);
+
+       data->client = tdm_client_create(&error);
+       if (error != TDM_ERROR_NONE) {
+               printf("tdm_client_create failed\n");
+               goto done;
+       }
+
+       if (data->do_query)
+               do_query(data);
+       if (data->do_vblank)
+               do_vblank(data);
+       if (data->do_voutput)
+               do_voutput(data);
 
 done:
-       tdm_client_destroy(client);
+       if (data->args.output_name)
+               free(data->args.output_name);
+       if (data->args.vblank_name)
+               free(data->args.vblank_name);
+       if (data->client)
+               tdm_client_destroy(data->client);
+
        return 0;
 }