Package version up to 2.7.1
[platform/core/uifw/libtdm.git] / src / tdm_event_loop.c
index e0791ab..5117c27 100644 (file)
@@ -1,90 +1,86 @@
 /**************************************************************************
-
-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 <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, 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.
-
+ *
+ * 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.
+ *
 **************************************************************************/
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
-#include "tdm.h"
 #include "tdm_private.h"
-#include "tdm_list.h"
 
-#include <wayland-server-core.h>
-
-typedef struct _tdm_event_loop_source_base
-{
+typedef struct _tdm_event_loop_source_base {
+       struct list_head link;
+       tdm_private_display *private_display;
        struct wl_event_source *wl_source;
 } tdm_event_loop_source_base;
 
-typedef struct _tdm_event_loop_source_fd
-{
+typedef struct _tdm_event_loop_source_fd {
        tdm_event_loop_source_base base;
-       tdm_private_display *private_display;
        tdm_event_loop_fd_handler func;
        void *user_data;
 } tdm_event_loop_source_fd;
 
-typedef struct _tdm_event_loop_source_timer
-{
+typedef struct _tdm_event_loop_source_timer {
        tdm_event_loop_source_base base;
-       tdm_private_display *private_display;
        tdm_event_loop_timer_handler func;
        void *user_data;
 } tdm_event_loop_source_timer;
 
+static tdm_private_loop *keep_private_loop;
+
 static tdm_error
 _tdm_event_loop_main_fd_handler(int fd, tdm_event_loop_mask mask, void *user_data)
 {
-       tdm_private_display *private_display = (tdm_private_display*)user_data;
-       tdm_private_loop *private_loop;
+       tdm_private_module *private_module = (tdm_private_module*)user_data;
+       tdm_private_display *private_display;
        tdm_func_display *func_display;
        tdm_error ret;
 
-       TDM_RETURN_VAL_IF_FAIL(private_display != NULL, TDM_ERROR_OPERATION_FAILED);
-       TDM_RETURN_VAL_IF_FAIL(private_display->private_loop != NULL, TDM_ERROR_OPERATION_FAILED);
+       TDM_RETURN_VAL_IF_FAIL(TDM_MUTEX_IS_LOCKED(), TDM_ERROR_OPERATION_FAILED);
+       TDM_RETURN_VAL_IF_FAIL(private_module != NULL, TDM_ERROR_OPERATION_FAILED);
 
-       private_loop = private_display->private_loop;
-
-       if (tdm_debug_thread)
-               TDM_INFO("backend fd(%d) event happens", private_loop->backend_fd);
+       if (tdm_debug_module & TDM_DEBUG_EVENT)
+               TDM_INFO("backend fd(%d) event happens", private_module->fd);
 
-       func_display = &private_display->func_display;
+       func_display = &private_module->func_display;
        if (!func_display->display_handle_events)
                return TDM_ERROR_NONE;
 
-       ret = func_display->display_handle_events(private_display->bdata);
+       private_display = private_module->private_display;
+       private_display->current_module = private_module;
+       ret = func_display->display_handle_events(private_module->bdata);
+       private_display->current_module = NULL;
 
        return ret;
 }
@@ -95,17 +91,17 @@ tdm_event_loop_init(tdm_private_display *private_display)
        tdm_private_loop *private_loop;
        tdm_error ret;
 
+       TDM_RETURN_VAL_IF_FAIL(TDM_MUTEX_IS_LOCKED(), TDM_ERROR_OPERATION_FAILED);
+
        if (private_display->private_loop)
                return TDM_ERROR_NONE;
 
-       private_loop = calloc(1, sizeof *private_loop);
+       private_loop = calloc(1, sizeof * private_loop);
        if (!private_loop) {
                TDM_ERR("alloc failed");
                return TDM_ERROR_OUT_OF_MEMORY;
        }
 
-       private_loop->backend_fd = -1;
-
        private_loop->wl_display = wl_display_create();
        if (!private_loop->wl_display) {
                TDM_ERR("creating a wayland display failed");
@@ -122,6 +118,8 @@ tdm_event_loop_init(tdm_private_display *private_display)
                return TDM_ERROR_OPERATION_FAILED;
        }
 
+       LIST_INITHEAD(&private_loop->source_list);
+
        private_loop->dpy = private_display;
        private_display->private_loop = private_loop;
 
@@ -134,6 +132,8 @@ tdm_event_loop_init(tdm_private_display *private_display)
                return TDM_ERROR_OPERATION_FAILED;
        }
 
+       keep_private_loop = private_loop;
+
        TDM_INFO("event loop fd(%d)", wl_event_loop_get_fd(private_loop->wl_loop));
 
        return TDM_ERROR_NONE;
@@ -142,62 +142,92 @@ tdm_event_loop_init(tdm_private_display *private_display)
 INTERN void
 tdm_event_loop_deinit(tdm_private_display *private_display)
 {
+       tdm_private_module *private_module = NULL;
+       tdm_event_loop_source_base *source = NULL, *ss = NULL;
+
+       TDM_RETURN_IF_FAIL(TDM_MUTEX_IS_LOCKED());
+
        if (!private_display->private_loop)
                return;
 
-       tdm_thread_deinit(private_display->private_loop);
+       if (tdm_thread_is_running())
+               TDM_ERR("thread is still running. tdm_event_loop_stop SHOULD be called");
+
        tdm_server_deinit(private_display->private_loop);
 
-       if (private_display->private_loop->backend_source)
-               tdm_event_loop_source_remove(private_display->private_loop->backend_source);
+       LIST_FOR_EACH_ENTRY(private_module, &private_display->module_list, link) {
+               if (private_module->event_source)
+                       tdm_event_loop_source_remove(private_module->event_source);
+               private_module->event_source = NULL;
+               private_module->fd = -1;
+       }
+
+       LIST_FOR_EACH_ENTRY_SAFE(source, ss, &private_display->private_loop->source_list, link) {
+               tdm_event_loop_source_remove(source);
+       }
 
-       if (private_display->private_loop->wl_display)
-               wl_display_destroy(private_display->private_loop->wl_display);
+#if WAYLAND_VERSION_MAJOR >= 1 && WAYLAND_VERSION_MINOR >= 15
+       wl_display_destroy_clients(private_display->private_loop->wl_display);
+#endif
+
+       wl_display_destroy(private_display->private_loop->wl_display);
 
        free(private_display->private_loop);
        private_display->private_loop = NULL;
+       keep_private_loop = NULL;
+
+       TDM_INFO("event loop deinit done");
+}
+
+INTERN void
+tdm_event_loop_stop(tdm_private_display *private_display)
+{
+       TDM_RETURN_IF_FAIL(TDM_MUTEX_IS_LOCKED());
+
+       /* after tdm_thread_deinit, we don't worry about thread things because it's finalized */
+       tdm_thread_deinit(private_display->private_loop);
 }
 
 INTERN void
 tdm_event_loop_create_backend_source(tdm_private_display *private_display)
 {
        tdm_private_loop *private_loop = private_display->private_loop;
-       tdm_func_display *func_display;
+       tdm_private_module *private_module = NULL;
        tdm_error ret;
        int fd = -1;
 
+       TDM_RETURN_IF_FAIL(TDM_MUTEX_IS_LOCKED());
        TDM_RETURN_IF_FAIL(private_loop != NULL);
 
-       func_display = &private_display->func_display;
-       if (!func_display->display_get_fd) {
-               TDM_INFO("TDM backend module won't offer a display fd");
-               return;
-       }
+       LIST_FOR_EACH_ENTRY(private_module, &private_display->module_list, link) {
+               tdm_func_display *func_display = &private_module->func_display;
 
-       ret = func_display->display_get_fd(private_display->bdata, &fd);
-       if (fd < 0) {
-               TDM_WRN("TDM backend module returns fd(%d)", fd);
-               return;
-       }
+               if (!func_display->display_get_fd) {
+                       TDM_INFO("TDM backend module won't offer a display fd");
+                       private_module->event_source = NULL;
+                       private_module->fd = -1;
+                       continue;
+               }
 
-       if (!func_display->display_handle_events) {
-               TDM_ERR("no display_handle_events function");
-               return;
-       }
+               ret = func_display->display_get_fd(private_module->bdata, &fd);
 
-       private_loop->backend_source =
-               tdm_event_loop_add_fd_handler(private_display, fd,
-                                             TDM_EVENT_LOOP_READABLE,
-                                             _tdm_event_loop_main_fd_handler,
-                                             private_display, &ret);
-       if (!private_loop->backend_source) {
-               TDM_ERR("no backend fd(%d) source", fd);
-               return;
-       }
+               assert(ret == TDM_ERROR_NONE && fd >= 0);
+               assert(func_display->display_handle_events);
+
+               private_module->event_source =
+                       tdm_event_loop_add_fd_handler(private_display, fd,
+                                                                                 TDM_EVENT_LOOP_READABLE,
+                                                                                 _tdm_event_loop_main_fd_handler,
+                                                                                 private_module, &ret);
+               if (!private_module->event_source) {
+                       TDM_ERR("no backend fd(%d) source", fd);
+                       return;
+               }
 
-       private_loop->backend_fd = fd;
+               private_module->fd = fd;
 
-       TDM_INFO("backend fd(%d) source created", private_loop->backend_fd);
+               TDM_INFO("backend fd(%d) source created", private_module->fd);
+       }
 }
 
 INTERN int
@@ -205,6 +235,8 @@ tdm_event_loop_get_fd(tdm_private_display *private_display)
 {
        tdm_private_loop *private_loop = private_display->private_loop;
 
+       /* DON'T check TDM_MUTEX_IS_LOCKED here */
+
        TDM_RETURN_VAL_IF_FAIL(private_loop->wl_loop != NULL, -1);
 
        return wl_event_loop_get_fd(private_loop->wl_loop);
@@ -215,11 +247,19 @@ tdm_event_loop_dispatch(tdm_private_display *private_display)
 {
        tdm_private_loop *private_loop = private_display->private_loop;
 
+       /* DON'T check TDM_MUTEX_IS_LOCKED here */
+
        TDM_RETURN_VAL_IF_FAIL(private_loop->wl_loop != NULL, TDM_ERROR_OPERATION_FAILED);
 
-       if (tdm_debug_thread)
+       if (tdm_debug_module & TDM_DEBUG_EVENT)
                TDM_INFO("dispatch");
 
+       if (tdm_thread_is_running() &&
+               tdm_thread_in_display_thread(syscall(SYS_gettid))) {
+               TDM_NEVER_GET_HERE();
+               return TDM_ERROR_OPERATION_FAILED;
+       }
+
        /* Don't set timeout to -1. It can make deadblock by two mutex locks.
         * If need to set -1, use poll() and call tdm_event_loop_dispatch() after
         * escaping polling.
@@ -227,6 +267,8 @@ tdm_event_loop_dispatch(tdm_private_display *private_display)
        if (wl_event_loop_dispatch(private_loop->wl_loop, 0) < 0)
                TDM_ERR("dispatch failed");
 
+       wl_display_flush_clients(private_loop->wl_display);
+
        return TDM_ERROR_NONE;
 }
 
@@ -236,8 +278,16 @@ tdm_event_loop_flush(tdm_private_display *private_display)
 {
        tdm_private_loop *private_loop = private_display->private_loop;
 
+       /* DON'T check TDM_MUTEX_IS_LOCKED here */
+
        TDM_RETURN_IF_FAIL(private_loop->wl_display != NULL);
 
+       if (tdm_thread_is_running() &&
+               tdm_thread_in_display_thread(syscall(SYS_gettid))) {
+               TDM_NEVER_GET_HERE();
+               return;
+       }
+
        wl_display_flush_clients(private_loop->wl_display);
 }
 
@@ -245,11 +295,16 @@ static int
 _tdm_event_loop_fd_func(int fd, uint32_t wl_mask, void *data)
 {
        tdm_event_loop_source_fd *fd_source = (tdm_event_loop_source_fd*)data;
+       tdm_private_display *private_display;
        tdm_event_loop_mask mask = 0;
 
+       /* DON'T check TDM_MUTEX_IS_LOCKED here */
+
        TDM_RETURN_VAL_IF_FAIL(fd_source, 1);
        TDM_RETURN_VAL_IF_FAIL(fd_source->func, 1);
 
+       private_display = fd_source->base.private_display;
+
        if (wl_mask & WL_EVENT_READABLE)
                mask |= TDM_EVENT_LOOP_READABLE;
        if (wl_mask & WL_EVENT_WRITABLE)
@@ -259,15 +314,17 @@ _tdm_event_loop_fd_func(int fd, uint32_t wl_mask, void *data)
        if (wl_mask & WL_EVENT_ERROR)
                mask |= TDM_EVENT_LOOP_ERROR;
 
+       _pthread_mutex_lock(&private_display->lock);
        fd_source->func(fd, mask, fd_source->user_data);
+       _pthread_mutex_unlock(&private_display->lock);
 
        return 1;
 }
 
-EXTERN tdm_event_loop_source*
+EXTERN tdm_event_loop_source *
 tdm_event_loop_add_fd_handler(tdm_display *dpy, int fd, tdm_event_loop_mask mask,
-                              tdm_event_loop_fd_handler func, void *user_data,
-                              tdm_error *error)
+                                                         tdm_event_loop_fd_handler func, void *user_data,
+                                                         tdm_error *error)
 {
        tdm_private_display *private_display;
        tdm_private_loop *private_loop;
@@ -275,6 +332,7 @@ tdm_event_loop_add_fd_handler(tdm_display *dpy, int fd, tdm_event_loop_mask mask
        uint32_t wl_mask = 0;
        tdm_error ret;
 
+       TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(TDM_MUTEX_IS_LOCKED(), TDM_ERROR_OPERATION_FAILED, NULL);
        TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(dpy, TDM_ERROR_INVALID_PARAMETER, NULL);
        TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(fd >= 0, TDM_ERROR_INVALID_PARAMETER, NULL);
        TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(func, TDM_ERROR_INVALID_PARAMETER, NULL);
@@ -292,9 +350,13 @@ tdm_event_loop_add_fd_handler(tdm_display *dpy, int fd, tdm_event_loop_mask mask
        if (mask & TDM_EVENT_LOOP_WRITABLE)
                wl_mask |= WL_EVENT_WRITABLE;
 
+       fd_source->func = func;
+       fd_source->user_data = user_data;
+
+       fd_source->base.private_display = private_display;
        fd_source->base.wl_source =
                wl_event_loop_add_fd(private_loop->wl_loop,
-                                    fd, wl_mask, _tdm_event_loop_fd_func, fd_source);
+                                                        fd, wl_mask, _tdm_event_loop_fd_func, fd_source);
        if (!fd_source->base.wl_source) {
                if (error)
                        *error = TDM_ERROR_OUT_OF_MEMORY;
@@ -302,14 +364,12 @@ tdm_event_loop_add_fd_handler(tdm_display *dpy, int fd, tdm_event_loop_mask mask
                return NULL;
        }
 
-       fd_source->private_display = private_display;
-       fd_source->func = func;
-       fd_source->user_data = user_data;
+       LIST_ADDTAIL(&fd_source->base.link, &private_loop->source_list);
 
        if (error)
                *error = TDM_ERROR_NONE;
 
-       return (tdm_event_loop_source*)fd_source;
+       return (tdm_event_loop_source *)fd_source;
 }
 
 EXTERN tdm_error
@@ -318,6 +378,7 @@ tdm_event_loop_source_fd_update(tdm_event_loop_source *source, tdm_event_loop_ma
        tdm_event_loop_source_fd *fd_source = source;
        uint32_t wl_mask = 0;
 
+       TDM_RETURN_VAL_IF_FAIL(TDM_MUTEX_IS_LOCKED(), TDM_ERROR_OPERATION_FAILED);
        TDM_RETURN_VAL_IF_FAIL(fd_source, TDM_ERROR_INVALID_PARAMETER);
 
        if (mask & TDM_EVENT_LOOP_READABLE)
@@ -337,24 +398,36 @@ static int
 _tdm_event_loop_timer_func(void *data)
 {
        tdm_event_loop_source_timer *timer_source = (tdm_event_loop_source_timer*)data;
+       tdm_private_display *private_display;
+
+       /* DON'T check TDM_MUTEX_IS_LOCKED here */
 
        TDM_RETURN_VAL_IF_FAIL(timer_source, 1);
        TDM_RETURN_VAL_IF_FAIL(timer_source->func, 1);
 
+       private_display = timer_source->base.private_display;
+
+       /* TDM event_loop function is actually for TDM backend module. When we call the
+        * backend's functions, we have to lock the mutex. TDM backend shouldn't consider
+        * mutex things.
+        */
+       _pthread_mutex_lock(&private_display->lock);
        timer_source->func(timer_source->user_data);
+       _pthread_mutex_unlock(&private_display->lock);
 
        return 1;
 }
 
-EXTERN tdm_event_loop_source*
+EXTERN tdm_event_loop_source *
 tdm_event_loop_add_timer_handler(tdm_display *dpy, tdm_event_loop_timer_handler func,
-                                 void *user_data, tdm_error *error)
+                                                                void *user_data, tdm_error *error)
 {
        tdm_private_display *private_display;
        tdm_private_loop *private_loop;
        tdm_event_loop_source_timer *timer_source;
        tdm_error ret;
 
+       TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(TDM_MUTEX_IS_LOCKED(), TDM_ERROR_OPERATION_FAILED, NULL);
        TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(dpy, TDM_ERROR_INVALID_PARAMETER, NULL);
        TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(func, TDM_ERROR_INVALID_PARAMETER, NULL);
 
@@ -366,9 +439,13 @@ tdm_event_loop_add_timer_handler(tdm_display *dpy, tdm_event_loop_timer_handler
        timer_source = calloc(1, sizeof(tdm_event_loop_source_timer));
        TDM_RETURN_VAL_IF_FAIL_WITH_ERROR(timer_source, TDM_ERROR_OUT_OF_MEMORY, NULL);
 
+       timer_source->func = func;
+       timer_source->user_data = user_data;
+
+       timer_source->base.private_display = private_display;
        timer_source->base.wl_source =
                wl_event_loop_add_timer(private_loop->wl_loop,
-                                       _tdm_event_loop_timer_func, timer_source);
+                                                               _tdm_event_loop_timer_func, timer_source);
        if (!timer_source->base.wl_source) {
                if (error)
                        *error = TDM_ERROR_OUT_OF_MEMORY;
@@ -376,21 +453,20 @@ tdm_event_loop_add_timer_handler(tdm_display *dpy, tdm_event_loop_timer_handler
                return NULL;
        }
 
-       timer_source->private_display = private_display;
-       timer_source->func = func;
-       timer_source->user_data = user_data;
+       LIST_ADDTAIL(&timer_source->base.link, &private_loop->source_list);
 
        if (error)
                *error = TDM_ERROR_NONE;
 
-       return (tdm_event_loop_source*)timer_source;
+       return (tdm_event_loop_source *)timer_source;
 }
 
 EXTERN tdm_error
-tdm_event_loop_source_timer_update(tdm_event_loop_source *source, int ms_delay)
+tdm_event_loop_source_timer_update(tdm_event_loop_source *source, unsigned int ms_delay)
 {
        tdm_event_loop_source_timer *timer_source = source;
 
+       TDM_RETURN_VAL_IF_FAIL(TDM_MUTEX_IS_LOCKED(), TDM_ERROR_OPERATION_FAILED);
        TDM_RETURN_VAL_IF_FAIL(timer_source, TDM_ERROR_INVALID_PARAMETER);
 
        if (wl_event_source_timer_update(timer_source->base.wl_source, ms_delay) < 0) {
@@ -406,10 +482,203 @@ tdm_event_loop_source_remove(tdm_event_loop_source *source)
 {
        tdm_event_loop_source_base *base = (tdm_event_loop_source_base*)source;
 
-       if (!base)
+       TDM_RETURN_IF_FAIL(TDM_MUTEX_IS_LOCKED());
+
+       if (!base || !keep_private_loop)
                return;
 
+       LIST_DEL(&base->link);
+
        wl_event_source_remove(base->wl_source);
 
        free(source);
 }
+
+/* LCOV_EXCL_START */
+static void
+_trace_cb_client_destroy(struct wl_listener *listener, void *data)
+{
+       struct wl_client *client = (struct wl_client *) data;
+       struct timespec tp;
+       unsigned int time;
+       pid_t pid = -1;
+       const char *proc_name;
+       char temp[512] = { 0, }, *p = temp;
+       int len = sizeof(temp), *l = &len;
+
+       wl_client_get_credentials(client, &pid, NULL, NULL);
+       proc_name = tdm_server_get_client_name(pid);
+
+       clock_gettime(CLOCK_MONOTONIC, &tp);
+       time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000);
+
+       TDM_SNPRINTF(p, l, "[%10.3f] Server           [PID:%d] client destroying", time / 1000.0, pid);
+       TDM_SNPRINTF(p, l, ", cmd: %s", proc_name ? proc_name : "Unknown");
+
+       TDM_INFO("%s", temp);
+
+       wl_list_remove(&listener->link);
+       free(listener);
+}
+
+static void
+_trace_reg_client_destroy_listener(struct wl_client *client)
+{
+       struct wl_listener *listener;
+
+       listener = wl_client_get_destroy_listener(client, _trace_cb_client_destroy);
+       if (listener)
+               return;
+
+       listener = calloc(1, sizeof(struct wl_listener));
+       TDM_RETURN_IF_FAIL(listener != NULL);
+
+       listener->notify = _trace_cb_client_destroy;
+       wl_client_add_destroy_listener(client, listener);
+}
+
+static const char *
+_trace_get_next_argument(const char *signature,
+                                                struct argument_details *details)
+{
+       details->nullable = 0;
+       for (; *signature; ++signature) {
+               switch (*signature) {
+               case 'i':
+               case 'u':
+               case 'f':
+               case 's':
+               case 'o':
+               case 'n':
+               case 'a':
+               case 'h':
+                       details->type = *signature;
+                       return signature + 1;
+               case '?':
+                       details->nullable = 1;
+                       break;
+               default:
+                       return NULL;
+               }
+       }
+       details->type = '\0';
+       return signature;
+}
+
+static struct wl_protocol_logger *_trace_protocol_logger;
+
+static void
+_trace_protocol_logger_cb(void *user_data,
+                                                 enum wl_protocol_logger_type direction,
+                                                 const struct wl_protocol_logger_message *message)
+{
+       int i;
+       struct argument_details arg;
+       struct wl_client *client = wl_resource_get_client(message->resource);
+       const char *signature = message->message->signature;
+       struct timespec tp;
+       unsigned int time;
+       pid_t pid = -1;
+       const char *proc_name;
+       char temp[512] = { 0, }, *p = temp;
+       int len = sizeof(temp), *l = &len;
+       int send;
+
+       if (client) {
+               _trace_reg_client_destroy_listener(client);
+               wl_client_get_credentials(client, &pid, NULL, NULL);
+       }
+
+       proc_name = tdm_server_get_client_name(pid);
+
+       clock_gettime(CLOCK_MONOTONIC, &tp);
+       time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000);
+
+       send = (direction == WL_PROTOCOL_LOGGER_EVENT) ? 1 : 0;
+
+       TDM_SNPRINTF(p, l, "[%10.3f] %s%d%s%s@%u.%s(",
+                                time / 1000.0,
+                                send ? "Server -> Client [PID:" : "Server <- Client [PID:",
+                                pid, "] ",
+                                wl_resource_get_class(message->resource),
+                                wl_resource_get_id(message->resource),
+                                message->message->name);
+
+       for (i = 0; i < message->arguments_count; i++) {
+               signature = _trace_get_next_argument(signature, &arg);
+               TDM_RETURN_IF_FAIL(signature != NULL);
+
+               if (i > 0)
+                       TDM_SNPRINTF(p, l, ", ");
+
+               switch (arg.type) {
+               case 'u':
+                       TDM_SNPRINTF(p, l, "%u", message->arguments[i].u);
+                       break;
+               case 'i':
+                       TDM_SNPRINTF(p, l, "%d", message->arguments[i].i);
+                       break;
+               case 'f':
+                       TDM_SNPRINTF(p, l, "%f",
+                                                wl_fixed_to_double(message->arguments[i].f));
+                       break;
+               case 's':
+                       TDM_SNPRINTF(p, l, "\"%s\"", message->arguments[i].s);
+                       break;
+               case 'o':
+                       if (message->arguments[i].o)
+                               TDM_SNPRINTF(p, l, "%s@%u",
+                                                        wl_resource_get_class((struct wl_resource *) message->arguments[i].o),
+                                                        wl_resource_get_id((struct wl_resource *) message->arguments[i].o));
+                       else
+                               TDM_SNPRINTF(p, l, "nil");
+                       break;
+               case 'n':
+                       TDM_SNPRINTF(p, l, "new id %s@",
+                                                (message->message->types[i]) ? message->message->types[i]->name : "[unknown]");
+                       if (message->arguments[i].n != 0)
+                               TDM_SNPRINTF(p, l, "%u", message->arguments[i].n);
+                       else
+                               TDM_SNPRINTF(p, l, "nil");
+                       break;
+               case 'a':
+                       TDM_SNPRINTF(p, l, "array");
+                       break;
+               case 'h':
+                       TDM_SNPRINTF(p, l, "fd %d", message->arguments[i].h);
+                       break;
+               default:
+                       return;
+               }
+       }
+
+       TDM_SNPRINTF(p, l, "), cmd: %s", proc_name ? proc_name : "Unknown");
+
+       TDM_INFO("%s", temp);
+}
+
+INTERN tdm_error
+tdm_event_loop_trace_enable(tdm_private_display * private_display,
+                                                       unsigned int enable)
+{
+       tdm_private_loop *private_loop = private_display->private_loop;
+
+       TDM_RETURN_VAL_IF_FAIL(private_loop->wl_display != NULL, TDM_ERROR_NONE);
+
+       if (!enable) {
+               if (_trace_protocol_logger) {
+                       wl_protocol_logger_destroy(_trace_protocol_logger);
+                       _trace_protocol_logger = NULL;
+               }
+               return TDM_ERROR_NONE;
+       }
+
+       if (_trace_protocol_logger)
+               wl_protocol_logger_destroy(_trace_protocol_logger);
+
+       _trace_protocol_logger =
+               wl_display_add_protocol_logger(private_loop->wl_display, _trace_protocol_logger_cb, NULL);
+
+       return TDM_ERROR_NONE;
+}
+/* LCOV_EXCL_STOP */