diff --git a/CMakeLists.txt b/CMakeLists.txt index fa409f516c..5a06a51ad9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,6 +281,9 @@ OCV_OPTION(WITH_GTK "Include GTK support" ON OCV_OPTION(WITH_GTK_2_X "Use GTK version 2" OFF VISIBLE_IF UNIX AND NOT APPLE AND NOT ANDROID VERIFY HAVE_GTK AND NOT HAVE_GTK3) +OCV_OPTION(WITH_WAYLAND "Include Wayland support" OFF + VISIBLE_IF UNIX AND NOT APPLE AND NOT ANDROID + VERIFY HAVE_WAYLAND) OCV_OPTION(WITH_IPP "Include Intel IPP support" (NOT MINGW AND NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF (X86_64 OR X86) AND NOT WINRT AND NOT IOS VERIFY HAVE_IPP) @@ -1260,6 +1263,24 @@ endif(WIN32) status("") status(" GUI: " "${OPENCV_HIGHGUI_BUILTIN_BACKEND}") +if(WITH_WAYLAND OR HAVE_WAYLAND) + if(HAVE_WAYLAND_CLIENT) + status(" Wayland Client:" "YES (ver ${WAYLAND_CLIENT_VERSION})") + endif() + if(HAVE_WAYLAND_CURSOR) + status(" Wayland Cursor:" "YES (ver ${WAYLAND_CURSOR_VERSION})") + endif() + if(HAVE_WAYLAND_PROTOCOL) + status(" Wayland Protocol:" "YES (ver ${WAYLAND_PROTOCOL_VERSION})") + endif() + if(HAVE_WAYLAND_EGL) + status(" Wayland EGL:" "YES (ver ${WAYLAND_EGL_VERSION})") + endif() + if(HAVE_XKBCOMMON) + status(" Xkbcommon:" "YES (ver ${XKBCOMMON_VERSION})") + endif() +endif() + if(WITH_QT OR HAVE_QT) if(HAVE_QT) status(" QT:" "YES (ver ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH} ${QT_EDITION})") diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index b78bb98162..0ed2ad59b1 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -146,6 +146,7 @@ ocv_create_module(${extra_libs}) ocv_target_link_libraries(${the_module} PRIVATE "${ZLIB_LIBRARIES}" "${OPENCL_LIBRARIES}" "${VA_LIBRARIES}" "${OPENGL_LIBRARIES}" + "${GLX_LIBRARIES}" "${LAPACK_LIBRARIES}" "${CPUFEATURES_LIBRARIES}" "${HALIDE_LIBRARIES}" "${ITT_LIBRARIES}" "${OPENCV_HAL_LINKER_LIBS}" diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 65d24e0ab0..201f2e7cf1 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -49,7 +49,31 @@ list(REMOVE_ITEM highgui_ext_hdrs "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${n set(OPENCV_HIGHGUI_BUILTIN_BACKEND "") -if(HAVE_QT) +if(WITH_WAYLAND AND HAVE_WAYLAND) + set(OPENCV_HIGHGUI_BUILTIN_BACKEND "Wayland") + add_definitions(-DHAVE_WAYLAND) + + set(CMAKE_INCLUDE_CURRENT_DIR ON) + + if (HAVE_WAYLAND_PROTOCOLS) + ocv_wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml + xdg-shell-client-protocol) + endif() + + list(APPEND highgui_srcs + ${CMAKE_CURRENT_LIST_DIR}/src/window_wayland.cpp + ${WAYLAND_PROTOCOL_SOURCES} + ) + list(APPEND HIGHGUI_LIBRARIES "${WAYLAND_CLIENT_LINK_LIBRARIES};${WAYLAND_CURSOR_LINK_LIBRARIES};${XKBCOMMON_LINK_LIBRARIES}") + + if(HAVE_WAYLAND_EGL) + if(WAYLAND_EGL_LIBRARIES) + list(APPEND HIGHGUI_LIBRARIES "${WAYLAND_EGL_LIBRARIES}") + endif() + endif() + +elseif(HAVE_QT) set(OPENCV_HIGHGUI_BUILTIN_BACKEND "QT${QT_VERSION_MAJOR}") add_definitions(-DHAVE_QT) diff --git a/modules/highgui/cmake/detect_wayland.cmake b/modules/highgui/cmake/detect_wayland.cmake new file mode 100644 index 0000000000..24e70d1b85 --- /dev/null +++ b/modules/highgui/cmake/detect_wayland.cmake @@ -0,0 +1,35 @@ +# --- Wayland --- +macro(ocv_wayland_generate protocol_file output_file) + add_custom_command(OUTPUT ${output_file}.h + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header < ${protocol_file} > ${output_file}.h + DEPENDS ${protocol_file}) + add_custom_command(OUTPUT ${output_file}.c + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} private-code < ${protocol_file} > ${output_file}.c + DEPENDS ${protocol_file}) + list(APPEND WAYLAND_PROTOCOL_SOURCES ${output_file}.h ${output_file}.c) +endmacro() + +ocv_clear_vars(HAVE_WAYLAND_CLIENT HAVE_WAYLAND_CURSOR HAVE_XKBCOMMON HAVE_WAYLAND_PROTOCOLS) +if(WITH_WAYLAND) + ocv_check_modules(WAYLAND_CLIENT wayland-client) + if(WAYLAND_CLIENT_FOUND) + set(HAVE_WAYLAND_CLIENT ON) + endif() + ocv_check_modules(WAYLAND_CURSOR wayland-cursor) + if(WAYLAND_CURSOR_FOUND) + set(HAVE_WAYLAND_CURSOR ON) + endif() + ocv_check_modules(XKBCOMMON xkbcommon) + if(XKBCOMMON_FOUND) + set(HAVE_XKBCOMMON ON) + endif() + ocv_check_modules(WAYLAND_PROTOCOLS wayland-protocols>=1.13) + if(HAVE_WAYLAND_PROTOCOLS) + pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir) + find_host_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner REQUIRED) + endif() + + if(HAVE_WAYLAND_CLIENT AND HAVE_WAYLAND_CURSOR AND HAVE_XKBCOMMON AND HAVE_WAYLAND_PROTOCOLS) + set(HAVE_WAYLAND TRUE) + endif() +endif() diff --git a/modules/highgui/cmake/init.cmake b/modules/highgui/cmake/init.cmake index 0b4178c868..49d4799b30 100644 --- a/modules/highgui/cmake/init.cmake +++ b/modules/highgui/cmake/init.cmake @@ -38,6 +38,7 @@ endmacro() add_backend("gtk" WITH_GTK) add_backend("win32ui" WITH_WIN32UI) +add_backend("wayland" WITH_WAYLAND) # TODO cocoa # TODO qt # TODO opengl diff --git a/modules/highgui/src/precomp.hpp b/modules/highgui/src/precomp.hpp index 0d26b957ad..ce9efe4153 100644 --- a/modules/highgui/src/precomp.hpp +++ b/modules/highgui/src/precomp.hpp @@ -130,6 +130,7 @@ void setWindowTitle_W32(const cv::String& name, const cv::String& title); void setWindowTitle_GTK(const cv::String& name, const cv::String& title); void setWindowTitle_QT(const cv::String& name, const cv::String& title); void setWindowTitle_COCOA(const cv::String& name, const cv::String& title); +void setWindowTitle_WAYLAND(const cv::String& name, const cv::String& title); int pollKey_W32(); diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index 81d205a69a..dd70fd2456 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -613,6 +613,8 @@ void cv::setWindowTitle(const String& winname, const String& title) return setWindowTitle_QT(winname, title); #elif defined (HAVE_COCOA) return setWindowTitle_COCOA(winname, title); +#elif defined (HAVE_WAYLAND) + return setWindowTitle_WAYLAND(winname, title); #else CV_Error(Error::StsNotImplemented, "The function is not implemented. " "Rebuild the library with Windows, GTK+ 2.x or Cocoa support. " @@ -1226,6 +1228,7 @@ int cv::createButton(const String&, ButtonCallback, void*, int , bool ) #elif defined (HAVE_GTK) // see window_gtk.cpp #elif defined (HAVE_COCOA) // see window_cocoa.mm #elif defined (HAVE_QT) // see window_QT.cpp +#elif defined (HAVE_WAYLAND) // see window_wayland.cpp #elif defined (WINRT) && !defined (WINRT_8_0) // see window_winrt.cpp #else diff --git a/modules/highgui/src/window_wayland.cpp b/modules/highgui/src/window_wayland.cpp new file mode 100644 index 0000000000..69231c0072 --- /dev/null +++ b/modules/highgui/src/window_wayland.cpp @@ -0,0 +1,2476 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "precomp.hpp" + +#ifndef _WIN32 +#if defined (HAVE_WAYLAND) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "xdg-shell-client-protocol.h" + +#include +#include +#include +#include "opencv2/core/utils/logger.hpp" + +/* */ +/* OpenCV highgui internals */ +/* */ +class cv_wl_display; + +class cv_wl_mouse; + +class cv_wl_keyboard; + +class cv_wl_input; + +class cv_wl_buffer; + +struct cv_wl_cursor; + +class cv_wl_cursor_theme; + +class cv_wl_widget; + +class cv_wl_titlebar; + +class cv_wl_viewer; + +class cv_wl_trackbar; + +class cv_wl_window; + +class cv_wl_core; + +using std::weak_ptr; +using std::shared_ptr; +using namespace cv::Error; +namespace ch = std::chrono; + +#define throw_system_error(errmsg, errnum) \ + CV_Error_(StsInternal, ("%s: %s", errmsg, strerror(errnum))); + +// workaround for Wayland macro not compiling in C++ +#define WL_ARRAY_FOR_EACH(pos, array, type) \ + for ((pos) = (type)(array)->data; \ + (const char*)(pos) < ((const char*)(array)->data + (array)->size); \ + (pos)++) + +static int xkb_keysym_to_ascii(xkb_keysym_t keysym) { + return static_cast(keysym & 0xff); +} + + +static void draw_xrgb8888(void *d, uint8_t a, uint8_t r, uint8_t g, uint8_t b) { + *((uint32_t *) d) = ((a << 24) | (r << 16) | (g << 8) | b); +} + +static void write_mat_to_xrgb8888(cv::Mat const &img, void *data) { + CV_Assert(data != nullptr); + CV_Assert(img.isContinuous()); + + for (int y = 0; y < img.rows; y++) { + for (int x = 0; x < img.cols; x++) { + auto p = img.at(y, x); + draw_xrgb8888((char *) data + (y * img.cols + x) * 4, 0x00, p[2], p[1], p[0]); + } + } +} + +class epoller { +public: + epoller() : epoll_fd_(epoll_create1(EPOLL_CLOEXEC)) { + if (epoll_fd_ < 0) + throw_system_error("Failed to create epoll fd", errno) + } + + ~epoller() { + close(epoll_fd_); + } + + void add(int fd, int events = EPOLLIN) const { + this->ctl(EPOLL_CTL_ADD, fd, events); + } + + void modify(int fd, int events) const { + this->ctl(EPOLL_CTL_MOD, fd, events); + } + + void remove(int fd) const { + this->ctl(EPOLL_CTL_DEL, fd, 0); + } + + void ctl(int op, int fd, int events) const { + struct epoll_event event{0, {nullptr}}; + event.events = events; + event.data.fd = fd; + int ret = epoll_ctl(epoll_fd_, op, fd, &event); + if (ret < 0) + throw_system_error("epoll_ctl", errno) + } + + std::vector wait(int timeout = -1, int max_events = 16) const { + std::vector events(max_events); + int event_num = epoll_wait(epoll_fd_, + static_cast(events.data()), static_cast(events.size()), timeout); + if (event_num < 0) + throw_system_error("epoll_wait", errno) + events.erase(events.begin() + event_num, events.end()); + return events; + } + + epoller(epoller const &) = delete; + + epoller &operator=(epoller const &) = delete; + + epoller(epoller &&) = delete; + + epoller &operator=(epoller &&) = delete; + +private: + int epoll_fd_; +}; + +class cv_wl_display { +public: + cv_wl_display(); + + explicit cv_wl_display(std::string const &disp); + + ~cv_wl_display(); + + int dispatch(); + + int dispatch_pending(); + + int flush(); + + int run_once(); + + struct wl_shm *shm(); + + weak_ptr input(); + + uint32_t formats() const; + + struct wl_surface *get_surface(); + + struct xdg_surface *get_shell_surface(struct wl_surface *surface); + +private: + epoller poller_; + struct wl_display *display_{}; + struct wl_registry *registry_{}; + struct wl_registry_listener reg_listener_{ + &handle_reg_global, &handle_reg_remove + }; + struct wl_compositor *compositor_ = nullptr; + struct wl_shm *shm_ = nullptr; + struct wl_shm_listener shm_listener_{&handle_shm_format}; + struct xdg_wm_base *xdg_wm_base_ = nullptr; + struct xdg_wm_base_listener xdg_wm_base_listener_{&handle_shell_ping}; + shared_ptr input_; + uint32_t formats_ = 0; + bool buffer_scale_enable_{}; + + void init(const char *display); + + static void + handle_reg_global(void *data, struct wl_registry *reg, uint32_t name, const char *iface, uint32_t version); + + static void handle_reg_remove(void *data, struct wl_registry *wl_registry, uint32_t name); + + static void handle_shm_format(void *data, struct wl_shm *wl_shm, uint32_t format); + + static void handle_shell_ping(void *data, struct xdg_wm_base *shell, uint32_t serial); +}; + +class cv_wl_mouse { +public: + enum button { + NONE = 0, + LBUTTON = BTN_LEFT, + RBUTTON = BTN_RIGHT, + MBUTTON = BTN_MIDDLE, + }; + + explicit cv_wl_mouse(struct wl_pointer *pointer); + + ~cv_wl_mouse(); + + void set_cursor(uint32_t serial, struct wl_surface *surface, int32_t hotspot_x, int32_t hotspot_y); + +private: + struct wl_pointer *pointer_; + struct wl_pointer_listener pointer_listener_{ + &handle_pointer_enter, &handle_pointer_leave, + &handle_pointer_motion, &handle_pointer_button, + &handle_pointer_axis, &handle_pointer_frame, + &handle_pointer_axis_source, &handle_pointer_axis_stop, + &handle_pointer_axis_discrete + }; + cv_wl_window *focus_window_{}; + + static void + handle_pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy); + + static void + handle_pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface); + + static void + handle_pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy); + + static void + handle_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, + uint32_t state); + + static void + handle_pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); + + static void handle_pointer_frame(void *data, struct wl_pointer *wl_pointer) { + CV_UNUSED(data); + CV_UNUSED(wl_pointer); + } + + static void handle_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) { + CV_UNUSED(data); + CV_UNUSED(wl_pointer); + CV_UNUSED(axis_source); + } + + static void handle_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { + CV_UNUSED(data); + CV_UNUSED(wl_pointer); + CV_UNUSED(time); + CV_UNUSED(axis); + } + + static void + handle_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + CV_UNUSED(data); + CV_UNUSED(wl_pointer); + CV_UNUSED(axis); + CV_UNUSED(discrete); + } +}; + +class cv_wl_keyboard { +public: + enum { + MOD_SHIFT_MASK = 0x01, + MOD_ALT_MASK = 0x02, + MOD_CONTROL_MASK = 0x04 + }; + + explicit cv_wl_keyboard(struct wl_keyboard *keyboard); + + ~cv_wl_keyboard(); + + uint32_t get_modifiers() const; + + std::queue get_key_queue(); + +private: + struct { + struct xkb_context *ctx; + struct xkb_keymap *keymap; + struct xkb_state *state; + xkb_mod_mask_t control_mask; + xkb_mod_mask_t alt_mask; + xkb_mod_mask_t shift_mask; + } xkb_{nullptr, nullptr, nullptr, 0, 0, 0}; + struct wl_keyboard *keyboard_ = nullptr; + struct wl_keyboard_listener keyboard_listener_{ + &handle_kb_keymap, &handle_kb_enter, &handle_kb_leave, + &handle_kb_key, &handle_kb_modifiers, &handle_kb_repeat + }; + uint32_t modifiers_{}; + std::queue key_queue_; + + static void handle_kb_keymap(void *data, struct wl_keyboard *kb, uint32_t format, int fd, uint32_t size); + + static void handle_kb_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, + struct wl_array *keys); + + static void handle_kb_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface); + + static void handle_kb_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, + uint32_t state); + + static void handle_kb_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group); + + static void handle_kb_repeat(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay); +}; + +class cv_wl_input { +public: + explicit cv_wl_input(struct wl_seat *seat); + + ~cv_wl_input(); + + struct wl_seat *seat() const { return seat_; } + + weak_ptr mouse(); + + weak_ptr keyboard(); + +private: + struct wl_seat *seat_; + struct wl_seat_listener seat_listener_{ + &handle_seat_capabilities, &handle_seat_name + }; + shared_ptr mouse_; + shared_ptr keyboard_; + + static void handle_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps); + + static void handle_seat_name(void *data, struct wl_seat *wl_seat, const char *name); +}; + +class cv_wl_buffer { +public: + cv_wl_buffer(); + + ~cv_wl_buffer(); + + void destroy(); + + void busy(bool busy = true); + + bool is_busy() const; + + cv::Size size() const; + + bool is_allocated() const; + + char *data(); + + void create_shm(struct wl_shm *shm, cv::Size size, uint32_t format); + + void attach_to_surface(struct wl_surface *surface, int32_t x, int32_t y); + +private: + int fd_ = -1; + bool busy_ = false; + cv::Size size_{0, 0}; + struct wl_buffer *buffer_ = nullptr; + struct wl_buffer_listener buffer_listener_{ + &handle_buffer_release + }; + void *shm_data_ = nullptr; + + static int create_tmpfile(std::string const &tmpname); + + static int create_anonymous_file(off_t size); + + static void handle_buffer_release(void *data, struct wl_buffer *buffer); +}; + +struct cv_wl_cursor { +public: + friend cv_wl_cursor_theme; + + ~cv_wl_cursor(); + + std::string const &name() const; + + void set_to_mouse(cv_wl_mouse &mouse, uint32_t serial); + + void commit(int image_index = 0); + +private: + std::string name_; + struct wl_cursor *cursor_; + struct wl_surface *surface_; + struct wl_callback *frame_callback_ = nullptr; + struct wl_callback_listener frame_listener_{ + &handle_cursor_frame + }; + + cv_wl_cursor(weak_ptr const &, struct wl_cursor *, std::string); + + static void handle_cursor_frame(void *data, struct wl_callback *cb, uint32_t time); +}; + +class cv_wl_cursor_theme { +public: + cv_wl_cursor_theme(weak_ptr const &display, std::string const &theme, int size = 32); + + ~cv_wl_cursor_theme(); + + int size() const; + + std::string const &name() const; + + weak_ptr get_cursor(std::string const &name); + +private: + int size_; + std::string name_; + weak_ptr display_; + struct wl_cursor_theme *cursor_theme_ = nullptr; + + std::unordered_map> cursors_; +}; + +/* + * height_for_width widget management + */ +class cv_wl_widget { +public: + explicit cv_wl_widget(cv_wl_window *window) + : window_(window) { + } + + virtual ~cv_wl_widget() = default; + + /* Return the size the last time when we did drawing */ + /* draw method must update the last_size_ */ + virtual cv::Size get_last_size() const { + return last_size_; + } + + virtual void get_preferred_width(int &minimum, int &natural) const = 0; + + virtual void get_preferred_height_for_width(int width, int &minimum, int &natural) const = 0; + + virtual void on_mouse(int event, cv::Point const &p, int flag) { + CV_UNUSED(event); + CV_UNUSED(p); + CV_UNUSED(flag); + } + + /* Return: The area widget rendered, if not rendered at all, set as width=height=0 */ + virtual cv::Rect draw(void *data, cv::Size const &, bool force) = 0; + +protected: + cv::Size last_size_{0, 0}; + cv_wl_window *window_; +}; + +class cv_wl_titlebar : public cv_wl_widget { +public: + enum { + btn_width = 24, + btn_margin = 5, + btn_max_x = 8, + btn_max_y = 8, + titlebar_min_width = btn_width * 3 + btn_margin, + titlebar_min_height = 24 + }; + + explicit cv_wl_titlebar(cv_wl_window *window); + + void get_preferred_width(int &minimum, int &natural) const override; + + void get_preferred_height_for_width(int width, int &minimum, int &natural) const override; + + void on_mouse(int event, cv::Point const &p, int flag) override; + + void calc_button_geometry(cv::Size const &size); + + cv::Rect draw(void *data, cv::Size const &size, bool force) override; + +private: + cv::Mat buf_; + cv::Rect btn_close_, btn_max_, btn_min_; + cv::Scalar const line_color_ = CV_RGB(0xff, 0xff, 0xff); + cv::Scalar const bg_color_ = CV_RGB(0x2d, 0x2d, 0x2d); + cv::Scalar const border_color_ = CV_RGB(0x53, 0x63, 0x53); + + std::string last_title_; + + struct { + int face = cv::FONT_HERSHEY_TRIPLEX; + double scale = 0.4; + int thickness = 1; + int baseline = 0; + } title_; +}; + +class cv_wl_viewer : public cv_wl_widget { +public: + enum { + MOUSE_CALLBACK_MIN_INTERVAL_MILLISEC = 15 + }; + + cv_wl_viewer(cv_wl_window *, int flags); + + int get_flags() const { return flags_; } + + void set_image(cv::Mat const &image); + + void set_mouse_callback(CvMouseCallback callback, void *param); + + void get_preferred_width(int &minimum, int &natural) const override; + + void get_preferred_height_for_width(int width, int &minimum, int &natural) const override; + + void on_mouse(int event, cv::Point const &p, int flag) override; + + cv::Rect draw(void *data, cv::Size const &, bool force) override; + +private: + int flags_; + cv::Mat image_; + cv::Rect last_img_area_; + bool image_changed_ = false; + + void *param_ = nullptr; + CvMouseCallback callback_ = nullptr; +}; + +class cv_wl_trackbar : public cv_wl_widget { +public: + cv_wl_trackbar(cv_wl_window *window, std::string name, + int *value, int count, CvTrackbarCallback2 on_change, void *data); + + std::string const &name() const; + + int get_pos() const; + + void set_pos(int value); + + void set_max(int maxval); + + void get_preferred_width(int &minimum, int &natural) const override; + + void get_preferred_height_for_width(int width, int &minimum, int &natural) const override; + + void on_mouse(int event, cv::Point const &p, int flag) override; + + cv::Rect draw(void *data, cv::Size const &size, bool force) override; + +private: + std::string name_; + int count_; + cv::Size size_; + + struct { + int *value; + void *data; + CvTrackbarCallback2 callback; + + void update(int v) const { if (value) *value = v; } + + void call(int v) const { if (callback) callback(v, data); } + } on_change_{}; + + struct { + cv::Scalar bg = CV_RGB(0xa4, 0xa4, 0xa4); + cv::Scalar fg = CV_RGB(0xf0, 0xf0, 0xf0); + } color_; + + struct { + int fontface = cv::FONT_HERSHEY_COMPLEX_SMALL; + double fontscale = 0.6; + int font_thickness = 1; + cv::Size text_size; + cv::Point text_orig; + + int margin = 10, thickness = 5; + cv::Point right, left; + + int length() const { return right.x - left.x; } + } bar_; + + struct { + int value = 0; + int radius = 7; + cv::Point pos; + bool drag = false; + } slider_; + + bool slider_moved_ = true; + cv::Mat data_; + + void prepare_to_draw(); +}; + +struct cv_wl_mouse_callback { + bool drag = false; + cv::Point last{0, 0}; + cv_wl_mouse::button button = cv_wl_mouse::button::NONE; + + void reset() { + drag = false; + last = cv::Point(0, 0); + button = cv_wl_mouse::button::NONE; + } +}; + +struct cv_wl_window_state { + cv_wl_window_state() { + reset(); + } + + void reset() { + maximized = fullscreen = resizing = focused = false; + } + + cv::Size prev_size_{0, 0}; + bool maximized{}, fullscreen{}, resizing{}, focused{}; +}; + +class cv_wl_window { +public: + enum { + DEFAULT_CURSOR_SIZE = 32 + }; + + cv_wl_window(shared_ptr const &display, std::string title, int flags); + + ~cv_wl_window(); + + cv::Size get_size() const; + + std::string const &get_title() const; + + void set_title(std::string const &title); + + cv_wl_window_state const &state() const; + + void show_image(cv::Mat const &image); + + void create_trackbar(std::string const &name, int *value, int count, CvTrackbarCallback2 on_change, void *userdata); + + weak_ptr get_trackbar(std::string const &) const; + + void mouse_enter(cv::Point const &p, uint32_t serial); + + void mouse_leave(); + + void mouse_motion(uint32_t time, cv::Point const &p); + + void mouse_button(uint32_t time, uint32_t button, wl_pointer_button_state state, uint32_t serial); + + void update_cursor(cv::Point const &p, bool grab = false); + + void interactive_move(); + + void set_mouse_callback(CvMouseCallback on_mouse, void *param); + + void set_minimized(); + + void set_maximized(bool maximize = true); + + void show(cv::Size const &new_size = cv::Size(0, 0)); + +private: + cv::Size size_{640, 480}; + std::string title_; + + shared_ptr display_; + struct wl_surface *surface_; + struct xdg_surface *xdg_surface_; + struct xdg_surface_listener xdgsurf_listener_{ + &handle_surface_configure, + }; + struct xdg_toplevel *xdg_toplevel_; + struct xdg_toplevel_listener xdgtop_listener_{ + &handle_toplevel_configure, &handle_toplevel_close + }; + bool wait_for_configure_ = true; + + /* double buffered */ + std::array buffers_; + + bool next_frame_ready_ = true; /* we can now commit a new buffer */ + struct wl_callback *frame_callback_ = nullptr; + struct wl_callback_listener frame_listener_{ + &handle_frame_callback + }; + + cv_wl_window_state state_; + struct { + bool repaint_request = false; /* we need to redraw as soon as possible (some states are changed) */ + bool resize_request = false; + cv::Size size{0, 0}; + } pending_; + + shared_ptr viewer_; + std::vector> widgets_; + std::vector widget_geometries_; + + cv_wl_mouse_callback on_mouse_; + + uint32_t mouse_enter_serial_{}; + uint32_t mouse_button_serial_{}; + struct { + std::string current_name; + cv_wl_cursor_theme theme; + } cursor_; + + cv_wl_buffer *next_buffer(); + + void commit_buffer(cv_wl_buffer *buffer, cv::Rect const &); + + void deliver_mouse_event(int event, cv::Point const &p, int flag); + + std::tuple> manage_widget_geometry(cv::Size const &new_size); + + static void handle_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial); + + static void handle_toplevel_configure(void *, struct xdg_toplevel *, int32_t, int32_t, struct wl_array *); + + static void handle_toplevel_close(void *data, struct xdg_toplevel *surface); + + static void handle_frame_callback(void *data, struct wl_callback *cb, uint32_t time); +}; + +class cv_wl_core { +public: + cv_wl_core(); + + ~cv_wl_core(); + + void init(); + + cv_wl_display &display(); + + std::vector get_window_names() const; + + shared_ptr get_window(std::string const &name); + + void *get_window_handle(std::string const &name); + + std::string const &get_window_name(void *handle); + + bool create_window(std::string const &name, int flags); + + bool destroy_window(std::string const &name); + + void destroy_all_windows(); + +private: + shared_ptr display_; + std::map> windows_; + std::map handles_; +}; + + +/* + * cv_wl_display implementation + */ +cv_wl_display::cv_wl_display() { + init(nullptr); +} + +cv_wl_display::cv_wl_display(std::string const &display) { + init(display.empty() ? nullptr : display.c_str()); +} + +cv_wl_display::~cv_wl_display() { + wl_shm_destroy(shm_); + xdg_wm_base_destroy(xdg_wm_base_); + wl_compositor_destroy(compositor_); + wl_registry_destroy(registry_); + wl_display_flush(display_); + input_.reset(); + wl_display_disconnect(display_); +} + +void cv_wl_display::init(const char *display) { + display_ = wl_display_connect(display); + if (!display_) + throw_system_error("Could not connect to display", errno) + + registry_ = wl_display_get_registry(display_); + wl_registry_add_listener(registry_, ®_listener_, this); + wl_display_roundtrip(display_); + if (!compositor_ || !shm_ || !xdg_wm_base_ || !input_) + CV_Error(StsInternal, "Compositor doesn't have required interfaces"); + + wl_display_roundtrip(display_); + if (!(formats_ & (1 << WL_SHM_FORMAT_XRGB8888))) + CV_Error(StsInternal, "WL_SHM_FORMAT_XRGB32 not available"); + + poller_.add( + wl_display_get_fd(display_), + EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP + ); +} + +int cv_wl_display::dispatch() { + return wl_display_dispatch(display_); +} + +int cv_wl_display::dispatch_pending() { + return wl_display_dispatch_pending(display_); +} + +int cv_wl_display::flush() { + return wl_display_flush(display_); +} + +int cv_wl_display::run_once() { + + auto d = this->display_; + + while (wl_display_prepare_read(d) != 0) { + wl_display_dispatch_pending(d); + } + wl_display_flush(d); + + wl_display_read_events(d); + return wl_display_dispatch_pending(d); +} + +struct wl_shm *cv_wl_display::shm() { + return shm_; +} + +weak_ptr cv_wl_display::input() { + return input_; +} + +uint32_t cv_wl_display::formats() const { + return formats_; +} + +struct wl_surface *cv_wl_display::get_surface() { + return wl_compositor_create_surface(compositor_); +} + +struct xdg_surface *cv_wl_display::get_shell_surface(struct wl_surface *surface) { + return xdg_wm_base_get_xdg_surface(xdg_wm_base_, surface); +} + +void cv_wl_display::handle_reg_global(void *data, struct wl_registry *reg, uint32_t name, const char *iface, + uint32_t version) { + std::string const interface = iface; + auto *display = reinterpret_cast(data); + + if (interface == wl_compositor_interface.name) { + if (version >= 3) { + display->compositor_ = static_cast( + wl_registry_bind(reg, name, + &wl_compositor_interface, + std::min(static_cast(3), version))); + display->buffer_scale_enable_ = true; + } else { + display->compositor_ = static_cast( + wl_registry_bind(reg, name, + &wl_compositor_interface, + std::min(static_cast(2), version))); + } + + } else if (interface == wl_shm_interface.name) { + display->shm_ = static_cast( + wl_registry_bind(reg, name, + &wl_shm_interface, + std::min(static_cast(1), version))); + wl_shm_add_listener(display->shm_, &display->shm_listener_, display); + } else if (interface == xdg_wm_base_interface.name) { + display->xdg_wm_base_ = static_cast( + wl_registry_bind(reg, name, + &xdg_wm_base_interface, + std::min(static_cast(3), version))); + xdg_wm_base_add_listener(display->xdg_wm_base_, &display->xdg_wm_base_listener_, display); + } else if (interface == wl_seat_interface.name) { + auto *seat = static_cast( + wl_registry_bind(reg, name, + &wl_seat_interface, + std::min(static_cast(4), version))); + display->input_ = std::make_shared(seat); + } +} + +void cv_wl_display::handle_reg_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { + CV_UNUSED(data); + CV_UNUSED(wl_registry); + CV_UNUSED(name); +} + +void cv_wl_display::handle_shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { + CV_UNUSED(wl_shm); + auto *display = reinterpret_cast(data); + display->formats_ |= (1 << format); +} + +void cv_wl_display::handle_shell_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { + CV_UNUSED(data); + xdg_wm_base_pong(shell, serial); +} + + +/* + * cv_wl_mouse implementation + */ +cv_wl_mouse::cv_wl_mouse(struct wl_pointer *pointer) + : pointer_(pointer) { + wl_pointer_add_listener(pointer_, &pointer_listener_, this); +} + +cv_wl_mouse::~cv_wl_mouse() { + wl_pointer_destroy(pointer_); +} + +void cv_wl_mouse::set_cursor(uint32_t serial, struct wl_surface *surface, int32_t hotspot_x, int32_t hotspot_y) { + wl_pointer_set_cursor(pointer_, serial, surface, hotspot_x, hotspot_y); +} + +void cv_wl_mouse::handle_pointer_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { + CV_UNUSED(pointer); + int x = wl_fixed_to_int(sx); + int y = wl_fixed_to_int(sy); + auto *mouse = reinterpret_cast(data); + auto *window = reinterpret_cast(wl_surface_get_user_data(surface)); + + mouse->focus_window_ = window; + mouse->focus_window_->mouse_enter(cv::Point(x, y), serial); +} + +void cv_wl_mouse::handle_pointer_leave(void *data, + struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { + CV_UNUSED(pointer); + CV_UNUSED(serial); + CV_UNUSED(surface); + auto *mouse = reinterpret_cast(data); + + mouse->focus_window_->mouse_leave(); + mouse->focus_window_ = nullptr; +} + +void cv_wl_mouse::handle_pointer_motion(void *data, + struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { + CV_UNUSED(pointer); + CV_UNUSED(time); + int x = wl_fixed_to_int(sx); + int y = wl_fixed_to_int(sy); + auto *mouse = reinterpret_cast(data); + + mouse->focus_window_->mouse_motion(time, cv::Point(x, y)); +} + +void cv_wl_mouse::handle_pointer_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + CV_UNUSED(data); + CV_UNUSED(wl_pointer); + auto *mouse = reinterpret_cast(data); + + mouse->focus_window_->mouse_button( + time, button, + static_cast(state), + serial + ); +} + +void cv_wl_mouse::handle_pointer_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + CV_UNUSED(data); + CV_UNUSED(wl_pointer); + CV_UNUSED(time); + CV_UNUSED(axis); + CV_UNUSED(value); + /* TODO: Support scroll events */ +} + + +/* + * cv_wl_keyboard implementation + */ +cv_wl_keyboard::cv_wl_keyboard(struct wl_keyboard *keyboard) + : keyboard_(keyboard) { + xkb_.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!xkb_.ctx) + CV_Error(StsNoMem, "Failed to create xkb context"); + wl_keyboard_add_listener(keyboard_, &keyboard_listener_, this); +} + +cv_wl_keyboard::~cv_wl_keyboard() { + if (xkb_.state) + xkb_state_unref(xkb_.state); + if (xkb_.keymap) + xkb_keymap_unref(xkb_.keymap); + if (xkb_.ctx) + xkb_context_unref(xkb_.ctx); + wl_keyboard_destroy(keyboard_); +} + +uint32_t cv_wl_keyboard::get_modifiers() const { + return modifiers_; +} + +std::queue cv_wl_keyboard::get_key_queue() { + return std::move(key_queue_); +} + +void cv_wl_keyboard::handle_kb_keymap(void *data, struct wl_keyboard *kb, uint32_t format, int fd, uint32_t size) { + CV_UNUSED(kb); + auto *keyboard = reinterpret_cast(data); + + try { + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) + CV_Error(StsInternal, "XKB_V1 keymap format unavailable"); + + char *map_str = (char *) mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_str == MAP_FAILED) + CV_Error(StsInternal, "Failed to mmap keymap"); + + keyboard->xkb_.keymap = xkb_keymap_new_from_string( + keyboard->xkb_.ctx, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_str, size); + if (!keyboard->xkb_.keymap) + CV_Error(StsInternal, "Failed to compile keymap"); + + keyboard->xkb_.state = xkb_state_new(keyboard->xkb_.keymap); + if (!keyboard->xkb_.state) + CV_Error(StsNoMem, "Failed to create XKB state"); + + keyboard->xkb_.control_mask = + 1 << xkb_keymap_mod_get_index(keyboard->xkb_.keymap, "Control"); + keyboard->xkb_.alt_mask = + 1 << xkb_keymap_mod_get_index(keyboard->xkb_.keymap, "Mod1"); + keyboard->xkb_.shift_mask = + 1 << xkb_keymap_mod_get_index(keyboard->xkb_.keymap, "Shift"); + } catch (std::exception &e) { + if (keyboard->xkb_.keymap) + xkb_keymap_unref(keyboard->xkb_.keymap); + std::cerr << "OpenCV Error: " << e.what() << std::endl; + } + + close(fd); +} + +void +cv_wl_keyboard::handle_kb_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) { + CV_UNUSED(data); + CV_UNUSED(keyboard); + CV_UNUSED(serial); + CV_UNUSED(surface); + CV_UNUSED(keys); +} + +void +cv_wl_keyboard::handle_kb_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { + CV_UNUSED(data); + CV_UNUSED(keyboard); + CV_UNUSED(serial); + CV_UNUSED(surface); +} + +void +cv_wl_keyboard::handle_kb_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) { + CV_UNUSED(keyboard); + CV_UNUSED(serial); + CV_UNUSED(time); + auto *kb = reinterpret_cast(data); + xkb_keycode_t keycode = key + 8; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + xkb_keysym_t keysym = xkb_state_key_get_one_sym(kb->xkb_.state, keycode); + kb->key_queue_.push(xkb_keysym_to_ascii(keysym)); + } +} + +void cv_wl_keyboard::handle_kb_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { + CV_UNUSED(keyboard); + CV_UNUSED(serial); + auto *kb = reinterpret_cast(data); + + if (!kb->xkb_.keymap) + return; + + xkb_state_update_mask( + kb->xkb_.state, mods_depressed, + mods_latched, mods_locked, 0, 0, group + ); + + xkb_mod_mask_t mask = xkb_state_serialize_mods( + kb->xkb_.state, + static_cast( + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED) + ); + + kb->modifiers_ = 0; + if (mask & kb->xkb_.control_mask) + kb->modifiers_ |= cv_wl_keyboard::MOD_CONTROL_MASK; + if (mask & kb->xkb_.alt_mask) + kb->modifiers_ |= cv_wl_keyboard::MOD_ALT_MASK; + if (mask & kb->xkb_.shift_mask) + kb->modifiers_ |= cv_wl_keyboard::MOD_SHIFT_MASK; +} + +void cv_wl_keyboard::handle_kb_repeat(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { + CV_UNUSED(data); + CV_UNUSED(wl_keyboard); + CV_UNUSED(rate); + CV_UNUSED(delay); +} + + +/* + * cv_wl_input implementation + */ +cv_wl_input::cv_wl_input(struct wl_seat *seat) + : seat_(seat) { + wl_seat_add_listener(seat_, &seat_listener_, this); +} + +cv_wl_input::~cv_wl_input() { + mouse_.reset(); + keyboard_.reset(); + wl_seat_destroy(seat_); +} + +weak_ptr cv_wl_input::mouse() { + return mouse_; +} + +weak_ptr cv_wl_input::keyboard() { + return keyboard_; +} + +void cv_wl_input::handle_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps) { + CV_UNUSED(wl_seat); + auto *input = reinterpret_cast(data); + + if (caps & WL_SEAT_CAPABILITY_POINTER) { + struct wl_pointer *pointer = wl_seat_get_pointer(input->seat_); + input->mouse_ = std::make_shared(pointer); + } + + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard(input->seat_); + input->keyboard_ = std::make_shared(keyboard); + } +} + +void cv_wl_input::handle_seat_name(void *data, struct wl_seat *wl_seat, const char *name) { + CV_UNUSED(data); + CV_UNUSED(wl_seat); + CV_UNUSED(name); +} + + +/* + * cv_wl_buffer implementation + */ +cv_wl_buffer::cv_wl_buffer() += default; + +cv_wl_buffer::~cv_wl_buffer() { + this->destroy(); +} + +void cv_wl_buffer::destroy() { + if (buffer_) { + wl_buffer_destroy(buffer_); + buffer_ = nullptr; + } + + if (fd_ >= 0) { + close(fd_); + fd_ = -1; + } + + if (shm_data_ && shm_data_ != MAP_FAILED) { + munmap(shm_data_, size_.area() * 4); + shm_data_ = nullptr; + } + + size_.width = size_.height = 0; +} + +void cv_wl_buffer::busy(bool busy) { + busy_ = busy; +} + +bool cv_wl_buffer::is_busy() const { + return busy_; +} + +cv::Size cv_wl_buffer::size() const { + return size_; +} + +bool cv_wl_buffer::is_allocated() const { + return buffer_ && shm_data_; +} + +char *cv_wl_buffer::data() { + return (char *) shm_data_; +} + +void cv_wl_buffer::create_shm(struct wl_shm *shm, cv::Size size, uint32_t format) { + this->destroy(); + + size_ = size; + int stride = size_.width * 4; + int buffer_size = stride * size_.height; + + fd_ = cv_wl_buffer::create_anonymous_file(buffer_size); + + shm_data_ = mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0); + if (shm_data_ == MAP_FAILED) { + int errno_ = errno; + this->destroy(); + throw_system_error("failed to map shm", errno_) + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd_, buffer_size); + buffer_ = wl_shm_pool_create_buffer(pool, 0, size_.width, size_.height, stride, format); + wl_buffer_add_listener(buffer_, &buffer_listener_, this); + wl_shm_pool_destroy(pool); +} + +void cv_wl_buffer::attach_to_surface(struct wl_surface *surface, int32_t x, int32_t y) { + wl_surface_attach(surface, buffer_, x, y); + this->busy(); +} + +int cv_wl_buffer::create_tmpfile(std::string const &tmpname) { + std::vector filename(tmpname.begin(), tmpname.end()); + filename.push_back('\0'); + + int fd = mkostemp(filename.data(), O_CLOEXEC); + if (fd >= 0) + unlink(filename.data()); + else + CV_Error_(StsInternal, + ("Failed to create a tmp file: %s: %s", + tmpname.c_str(), strerror(errno))); + return fd; +} + +int cv_wl_buffer::create_anonymous_file(off_t size) { + auto path = getenv("XDG_RUNTIME_DIR") + std::string("/opencv-shared-XXXXXX"); + int fd = create_tmpfile(path); + + int ret = posix_fallocate(fd, 0, size); + if (ret != 0) { + close(fd); + throw_system_error("Failed to fallocate shm", errno) + } + + return fd; +} + +void cv_wl_buffer::handle_buffer_release(void *data, struct wl_buffer *buffer) { + CV_UNUSED(buffer); + auto *cvbuf = reinterpret_cast(data); + + cvbuf->busy(false); +} + + +/* + * cv_wl_cursor implementation + */ +cv_wl_cursor::cv_wl_cursor(weak_ptr const &display, struct wl_cursor *cursor, std::string name) + : name_(std::move(name)), cursor_(cursor) { + surface_ = display.lock()->get_surface(); +} + +cv_wl_cursor::~cv_wl_cursor() { + if (frame_callback_) + wl_callback_destroy(frame_callback_); + wl_surface_destroy(surface_); +} + +std::string const &cv_wl_cursor::name() const { + return name_; +} + +void cv_wl_cursor::set_to_mouse(cv_wl_mouse &mouse, uint32_t serial) { + auto *cursor_img = cursor_->images[0]; + mouse.set_cursor(serial, surface_, static_cast(cursor_img->hotspot_x), + static_cast(cursor_img->hotspot_y)); +} + +void cv_wl_cursor::commit(int image_index) { + auto *cursor_img = cursor_->images[image_index]; + auto *cursor_buffer = wl_cursor_image_get_buffer(cursor_img); + + if (cursor_->image_count > 1) { + if (frame_callback_) + wl_callback_destroy(frame_callback_); + + frame_callback_ = wl_surface_frame(surface_); + wl_callback_add_listener(frame_callback_, &frame_listener_, this); + } + + wl_surface_attach(surface_, cursor_buffer, 0, 0); + wl_surface_damage(surface_, 0, 0, static_cast(cursor_img->width), + static_cast(cursor_img->height)); + wl_surface_commit(surface_); +} + +void cv_wl_cursor::handle_cursor_frame(void *data, struct wl_callback *cb, uint32_t time) { + CV_UNUSED(cb); + auto *cursor = (struct cv_wl_cursor *) data; + int image_index = wl_cursor_frame(cursor->cursor_, time); + + cursor->commit(image_index); +} + + +/* + * cv_wl_cursor_theme implementation + */ +cv_wl_cursor_theme::cv_wl_cursor_theme(weak_ptr const &display, std::string const &theme, int size) + : size_(size), name_(theme), display_(display), cursors_() { + cursor_theme_ = wl_cursor_theme_load(theme.c_str(), size, display.lock()->shm()); + if (!cursor_theme_) + CV_Error_(StsInternal, ("Couldn't load cursor theme: %s", theme.c_str())); +} + +cv_wl_cursor_theme::~cv_wl_cursor_theme() { + if (cursor_theme_) + wl_cursor_theme_destroy(cursor_theme_); +} + +int cv_wl_cursor_theme::size() const { + return size_; +} + +std::string const &cv_wl_cursor_theme::name() const { + return name_; +} + +weak_ptr cv_wl_cursor_theme::get_cursor(std::string const &name) { + if (cursors_.count(name) == 1) + return cursors_[name]; + + auto *wlcursor = wl_cursor_theme_get_cursor(cursor_theme_, name.c_str()); + if (!wlcursor) + CV_Error_(StsInternal, ("Couldn't load cursor: %s", name.c_str())); + + auto cursor = + shared_ptr(new cv_wl_cursor(display_, wlcursor, name)); + if (!cursor) + CV_Error_(StsInternal, ("Couldn't allocate memory for cursor: %s", name.c_str())); + + cursors_[name] = cursor; + + return cursor; +} + + +/* + * cv_wl_titlebar implementation + */ +cv_wl_titlebar::cv_wl_titlebar(cv_wl_window *window) : cv_wl_widget(window) { + CV_UNUSED(window); +} + +void cv_wl_titlebar::get_preferred_width(int &minimum, int &natural) const { + minimum = natural = titlebar_min_width; +} + +void cv_wl_titlebar::get_preferred_height_for_width(int width, int &minimum, int &natural) const { + CV_UNUSED(width); + minimum = natural = titlebar_min_height; +} + +void cv_wl_titlebar::on_mouse(int event, cv::Point const &p, int flag) { + CV_UNUSED(flag); + if (event == cv::EVENT_LBUTTONDOWN) { + if (btn_close_.contains(p)) { + exit(EXIT_SUCCESS); + } else if (btn_max_.contains(p)) { + window_->set_maximized(!window_->state().maximized); + } else if (btn_min_.contains(p)) { + window_->set_minimized(); + } else { + window_->update_cursor(p, true); + window_->interactive_move(); + } + } +} + +void cv_wl_titlebar::calc_button_geometry(cv::Size const &size) { + /* Basic button geoemetries */ + cv::Size btn_size = cv::Size(btn_width, size.height); + btn_close_ = cv::Rect(cv::Point(size.width - 5 - btn_size.width, 0), btn_size); + btn_max_ = cv::Rect(cv::Point(btn_close_.x - btn_size.width, 0), btn_size); + btn_min_ = cv::Rect(cv::Point(btn_max_.x - btn_size.width, 0), btn_size); +} + +cv::Rect cv_wl_titlebar::draw(void *data, cv::Size const &size, bool force) { + auto damage = cv::Rect(0, 0, 0, 0); + + if (force || last_size_ != size || last_title_ != window_->get_title()) { + buf_ = cv::Mat(size, CV_8UC3, bg_color_); + this->calc_button_geometry(size); + + auto const margin = cv::Point(btn_max_x, btn_max_y); + auto const btn_cls = cv::Rect(btn_close_.tl() + margin, btn_close_.br() - margin); + auto const btn_max = cv::Rect(btn_max_.tl() + margin, btn_max_.br() - margin); + auto title_area = cv::Rect(0, 0, size.width - titlebar_min_width, size.height); + + auto text = cv::getTextSize(window_->get_title(), title_.face, title_.scale, title_.thickness, + &title_.baseline); + if (text.area() <= title_area.area()) { + auto origin = cv::Point(0, (size.height + text.height) / 2); + origin.x = ((title_area.width >= (size.width + text.width) / 2) ? + (size.width - text.width) / 2 : (title_area.width - text.width) / 2); + cv::putText( + buf_, window_->get_title(), + origin, title_.face, title_.scale, + CV_RGB(0xff, 0xff, 0xff), title_.thickness, CV_AA + ); + } + + buf_(cv::Rect(btn_min_.tl(), cv::Size(titlebar_min_width, size.height))) = bg_color_; + cv::line(buf_, btn_cls.tl(), btn_cls.br(), line_color_, 1, CV_AA); + cv::line(buf_, btn_cls.tl() + cv::Point(btn_cls.width, 0), btn_cls.br() - cv::Point(btn_cls.width, 0), + line_color_, 1, CV_AA); + cv::rectangle(buf_, btn_max.tl(), btn_max.br(), line_color_, 1, CV_AA); + cv::line(buf_, cv::Point(btn_min_.x + 8, btn_min_.height / 2), + cv::Point(btn_min_.x + btn_min_.width - 8, btn_min_.height / 2), line_color_, 1, CV_AA); + cv::line(buf_, cv::Point(0, 0), cv::Point(buf_.size().width, 0), border_color_, 1, CV_AA); + + write_mat_to_xrgb8888(buf_, data); + last_size_ = size; + last_title_ = window_->get_title(); + damage = cv::Rect(cv::Point(0, 0), size); + } + + return damage; +} + + +/* + * cv_wl_viewer implementation + */ +cv_wl_viewer::cv_wl_viewer(cv_wl_window *window, int flags) + : cv_wl_widget(window), flags_(flags) { +} + +void cv_wl_viewer::set_image(cv::Mat const &image) { + if (image.type() == CV_8UC1) { + cv::Mat bgr; + cv::cvtColor(image, bgr, CV_GRAY2BGR); + image_ = bgr.clone(); + } else { + image_ = image.clone(); + } + image_changed_ = true; +} + +void cv_wl_viewer::set_mouse_callback(CvMouseCallback callback, void *param) { + param_ = param; + callback_ = callback; +} + +void cv_wl_viewer::get_preferred_width(int &minimum, int &natural) const { + if (image_.size().area() == 0) { + minimum = natural = 0; + } else { + natural = image_.size().width; + minimum = (flags_ == cv::WINDOW_AUTOSIZE ? natural : 0); + } +} + +static double aspect_ratio(cv::Size const &size) { + return (double) size.height / (double) size.width; +} + +void cv_wl_viewer::get_preferred_height_for_width(int width, int &minimum, int &natural) const { + if (image_.size().area() == 0) { + minimum = natural = 0; + } else if (flags_ == cv::WINDOW_AUTOSIZE) { + CV_Assert(width == image_.size().width); + minimum = natural = image_.size().height; + } else { + natural = static_cast(width * aspect_ratio(image_.size())); + minimum = (flags_ & CV_WINDOW_FREERATIO ? 0 : natural); + } +} + +void cv_wl_viewer::on_mouse(int event, cv::Point const &p, int flag) { + // Make sure the first mouse event is delivered to clients + static int last_event = ~event, last_flag = ~flag; + static auto last_event_time = ch::steady_clock::now(); + + if (callback_) { + auto now = ch::steady_clock::now(); + auto elapsed = ch::duration_cast(now - last_event_time); + + /* Inhibit the too frequent mouse callback due to the heavy load */ + if (event != last_event || flag != last_flag || + elapsed.count() >= MOUSE_CALLBACK_MIN_INTERVAL_MILLISEC) { + last_event = event; + last_flag = flag; + last_event_time = now; + + /* Scale the coordinate to match the client's image coordinate */ + int x = static_cast((p.x - last_img_area_.x) * ((double) image_.size().width / last_img_area_.width)); + int y = static_cast((p.y - last_img_area_.y) * + ((double) image_.size().height / last_img_area_.height)); + callback_(event, x, y, flag, param_); + } + } +} + +cv::Rect cv_wl_viewer::draw(void *data, cv::Size const &size, bool force) { + if ((!force && !image_changed_ && last_size_ == size) || image_.size().area() == 0 || size.area() == 0) + return {0, 0, 0, 0}; + + last_img_area_ = cv::Rect(cv::Point(0, 0), size); + + if (flags_ == cv::WINDOW_AUTOSIZE || image_.size() == size) { + CV_Assert(image_.size() == size); + write_mat_to_xrgb8888(image_, data); + } else { + if (flags_ & CV_WINDOW_FREERATIO) { + cv::Mat resized; + cv::resize(image_, resized, size); + write_mat_to_xrgb8888(resized, data); + } else /* CV_WINDOW_KEEPRATIO */ { + auto rect = cv::Rect(cv::Point(0, 0), size); + if (aspect_ratio(size) >= aspect_ratio(image_.size())) { + rect.height = static_cast(image_.size().height * ((double) rect.width / image_.size().width)); + } else { + rect.height = size.height; + rect.width = static_cast(image_.size().width * ((double) rect.height / image_.size().height)); + } + rect.x = (size.width - rect.width) / 2; + rect.y = (size.height - rect.height) / 2; + + auto buf = cv::Mat(size, image_.type(), CV_RGB(0xa4, 0xa4, 0xa4)); + auto resized = buf(rect); + cv::resize(image_, resized, rect.size()); + write_mat_to_xrgb8888(buf, data); + + last_img_area_ = rect; + } + } + + last_size_ = size; + image_changed_ = false; + + return {{0, 0}, size}; +} + + +/* + * cv_wl_trackbar implementation + */ +cv_wl_trackbar::cv_wl_trackbar(cv_wl_window *window, std::string name, + int *value, int count, CvTrackbarCallback2 on_change, void *data) + : cv_wl_widget(window), name_(std::move(name)), count_(count) { + on_change_.value = value; + on_change_.data = data; + on_change_.callback = on_change; +} + +std::string const &cv_wl_trackbar::name() const { + return name_; +} + +int cv_wl_trackbar::get_pos() const { + return slider_.value; +} + +void cv_wl_trackbar::set_pos(int value) { + if (0 <= value && value <= count_) { + slider_.value = value; + slider_moved_ = true; + window_->show(); + } +} + +void cv_wl_trackbar::set_max(int maxval) { + count_ = maxval; + if (!(0 <= slider_.value && slider_.value <= count_)) { + slider_.value = maxval; + slider_moved_ = true; + window_->show(); + } +} + +void cv_wl_trackbar::get_preferred_width(int &minimum, int &natural) const { + minimum = natural = 320; +} + +void cv_wl_trackbar::get_preferred_height_for_width(int width, int &minimum, int &natural) const { + CV_UNUSED(width); + minimum = natural = 40; +} + +void cv_wl_trackbar::prepare_to_draw() { + bar_.text_size = cv::getTextSize( + name_ + ": " + std::to_string(count_), bar_.fontface, + bar_.fontscale, bar_.font_thickness, nullptr); + bar_.text_orig = cv::Point(2, (size_.height + bar_.text_size.height) / 2); + bar_.left = cv::Point(bar_.text_size.width + 10, size_.height / 2); + bar_.right = cv::Point(size_.width - bar_.margin - 1, size_.height / 2); + + int slider_pos_x = static_cast(((double) bar_.length() / count_ * slider_.value)); + slider_.pos = cv::Point(bar_.left.x + slider_pos_x, bar_.left.y); +} + +cv::Rect cv_wl_trackbar::draw(void *data, cv::Size const &size, bool force) { + auto damage = cv::Rect(0, 0, 0, 0); + + if (slider_moved_) { + on_change_.update(slider_.value); + on_change_.call(slider_.value); + } + + if (slider_moved_ || force) { + size_ = last_size_ = size; + + if (size_ == data_.size()) + data_ = CV_RGB(0xde, 0xde, 0xde); + else + data_ = cv::Mat(size_, CV_8UC3, CV_RGB(0xde, 0xde, 0xde)); + + this->prepare_to_draw(); + cv::putText( + data_, + (name_ + ": " + std::to_string(slider_.value)), + bar_.text_orig, bar_.fontface, bar_.fontscale, + CV_RGB(0x00, 0x00, 0x00), bar_.font_thickness, CV_AA); + + cv::line(data_, bar_.left, bar_.right, color_.bg, bar_.thickness + 3, CV_AA); + cv::line(data_, bar_.left, bar_.right, color_.fg, bar_.thickness, CV_AA); + cv::circle(data_, slider_.pos, slider_.radius, color_.fg, -1, CV_AA); + cv::circle(data_, slider_.pos, slider_.radius, color_.bg, 1, CV_AA); + + write_mat_to_xrgb8888(data_, data); + damage = cv::Rect(cv::Point(0, 0), size); + slider_moved_ = false; + } + + return damage; +} + +void cv_wl_trackbar::on_mouse(int event, cv::Point const &p, int flag) { + switch (event) { + case cv::EVENT_LBUTTONDOWN: + slider_.drag = true; + window_->update_cursor(p, true); + break; + case cv::EVENT_MOUSEMOVE: + if (!(flag & cv::EVENT_FLAG_LBUTTON)) + break; + break; + case cv::EVENT_LBUTTONUP: + if (slider_.drag && bar_.left.x <= p.x && p.x <= bar_.right.x) { + slider_.value = static_cast((double) (p.x - bar_.left.x) / bar_.length() * count_); + slider_moved_ = true; + window_->show(); + slider_.drag = (event != cv::EVENT_LBUTTONUP); + } + break; + default: + break; + } +} + + +/* + * cv_wl_window implementation + */ +cv_wl_window::cv_wl_window(shared_ptr const &display, std::string title, int flags) + : title_(std::move(title)), display_(display), + surface_(display->get_surface()), + cursor_{{}, + {display, "default", DEFAULT_CURSOR_SIZE}} { + xdg_surface_ = display->get_shell_surface(surface_); + if (!xdg_surface_) + CV_Error(StsInternal, "Failed to get xdg_surface"); + xdg_surface_add_listener(xdg_surface_, &xdgsurf_listener_, this); + + xdg_toplevel_ = xdg_surface_get_toplevel(xdg_surface_); + if (!xdg_toplevel_) + CV_Error(StsInternal, "Failed to get xdg_toplevel"); + xdg_toplevel_add_listener(xdg_toplevel_, &xdgtop_listener_, this); + xdg_toplevel_set_title(xdg_toplevel_, title_.c_str()); + + wl_surface_set_user_data(surface_, this); + + widgets_.push_back(std::make_shared(this)); + widget_geometries_.emplace_back(0, 0, 0, 0); + + viewer_ = std::make_shared(this, flags); + widget_geometries_.emplace_back(0, 0, 0, 0); + + wl_surface_commit(surface_); +} + +cv_wl_window::~cv_wl_window() { + if (frame_callback_) + wl_callback_destroy(frame_callback_); + xdg_toplevel_destroy(xdg_toplevel_); + xdg_surface_destroy(xdg_surface_); + wl_surface_destroy(surface_); +} + +cv::Size cv_wl_window::get_size() const { + return size_; +} + +std::string const &cv_wl_window::get_title() const { + return title_; +} + +void cv_wl_window::set_title(std::string const &title) { + title_ = title; + xdg_toplevel_set_title(xdg_toplevel_, title_.c_str()); +} + +cv_wl_window_state const &cv_wl_window::state() const { + return state_; +} + +cv_wl_buffer *cv_wl_window::next_buffer() { + cv_wl_buffer *buffer = nullptr; + + if (!buffers_.at(0).is_busy()) + buffer = &buffers_[0]; + else if (!buffers_.at(1).is_busy()) + buffer = &buffers_[1]; + + return buffer; +} + +void cv_wl_window::set_mouse_callback(CvMouseCallback on_mouse, void *param) { + viewer_->set_mouse_callback(on_mouse, param); +} + +void cv_wl_window::set_minimized() { + xdg_toplevel_set_minimized(xdg_toplevel_); +} + +void cv_wl_window::set_maximized(bool maximize) { + if (!maximize) + xdg_toplevel_unset_maximized(xdg_toplevel_); + else if (viewer_->get_flags() != cv::WINDOW_AUTOSIZE) + xdg_toplevel_set_maximized(xdg_toplevel_); +} + +void cv_wl_window::show_image(cv::Mat const &image) { + viewer_->set_image(image); + this->show(); +} + +void cv_wl_window::create_trackbar(std::string const &name, int *value, int count, CvTrackbarCallback2 on_change, + void *userdata) { + auto exists = this->get_trackbar(name).lock(); + if (!exists) { + auto trackbar = + std::make_shared( + this, name, value, count, on_change, userdata + ); + widgets_.emplace_back(trackbar); + widget_geometries_.emplace_back(0, 0, 0, 0); + } +} + +weak_ptr cv_wl_window::get_trackbar(std::string const &trackbar_name) const { + auto it = std::find_if(widgets_.begin(), widgets_.end(), + [&trackbar_name](shared_ptr const &widget) { + if (auto trackbar = std::dynamic_pointer_cast(widget)) + return trackbar->name() == trackbar_name; + return false; + }); + return it == widgets_.end() ? shared_ptr() + : std::static_pointer_cast(*it); +} + +static void calculate_damage(cv::Rect &surface_damage, + cv::Rect const &widget_geometry, cv::Rect const &w_damage) { + if (w_damage.area() == 0) + return; + + auto widget_damage = w_damage; + widget_damage.x += widget_geometry.x; + widget_damage.y += widget_geometry.y; + + if (surface_damage.area() == 0) { + surface_damage = widget_damage; + } else { + auto damage = cv::Rect(0, 0, 0, 0); + damage.x = std::min(surface_damage.x, widget_damage.x); + damage.y = std::min(surface_damage.y, widget_damage.y); + damage.width = + std::max(surface_damage.x + surface_damage.width, widget_damage.x + widget_damage.width) - damage.x; + damage.height = + std::max(surface_damage.y + surface_damage.height, widget_damage.y + widget_damage.height) - damage.y; + + surface_damage = damage; + } +} + +std::tuple> +cv_wl_window::manage_widget_geometry(cv::Size const &new_size) { + std::vector geometries; + + std::vector min_widths, nat_widths; + int min_width, nat_width, min_height, nat_height; + + auto store_preferred_width = [&](shared_ptr const &widget) { + widget->get_preferred_width(min_width, nat_width); + min_widths.push_back(min_width); + nat_widths.push_back(nat_width); + }; + + store_preferred_width(viewer_); + for (auto &widget: widgets_) + store_preferred_width(widget); + + int final_width = 0; + int total_height = 0; + std::function const &, int, bool)> calc_geometries; + + auto calc_autosize_geo = [&](shared_ptr const &widget, int width, bool) { + widget->get_preferred_height_for_width(width, min_height, nat_height); + geometries.emplace_back(0, total_height, width, nat_height); + total_height += nat_height; + }; + auto calc_normal_geo = [&](shared_ptr const &widget, int width, bool viewer) { + widget->get_preferred_height_for_width(width, min_height, nat_height); + int height = viewer ? (new_size.height - total_height) : nat_height; + geometries.emplace_back(0, total_height, width, height); + total_height += height; + }; + + if (viewer_->get_flags() == cv::WINDOW_AUTOSIZE) { + final_width = nat_widths[0]; + calc_geometries = calc_autosize_geo; + } else { + int total_min_height = 0; + int max_min_width = *std::max_element(min_widths.begin(), min_widths.end()); + auto calc_total_min_height = [&](shared_ptr const &widget) { + widget->get_preferred_height_for_width(max_min_width, min_height, nat_height); + total_min_height += min_height; + }; + + calc_total_min_height(viewer_); + for (auto &widget: widgets_) + calc_total_min_height(widget); + + auto min_size = cv::Size(max_min_width, total_min_height); + if (new_size.width < min_size.width || new_size.height < min_size.height) { + /* The new_size is smaller than the minimum size */ + return std::make_tuple(cv::Size(0, 0), geometries); + } else { + final_width = new_size.width; + calc_geometries = calc_normal_geo; + } + } + + for (auto &widget: widgets_) + calc_geometries(widget, final_width, false); + calc_geometries(viewer_, final_width, true); + + return std::make_tuple(cv::Size(final_width, total_height), geometries); +} + +void cv_wl_window::show(cv::Size const &size) { + if (wait_for_configure_) { + pending_.repaint_request = true; + return; + } + + auto *buffer = this->next_buffer(); + if (!next_frame_ready_ || !buffer) { + if (size.area() == 0) { + pending_.repaint_request = true; + } else { + pending_.size = size; + pending_.resize_request = true; + } + return; + } + + auto placement = + this->manage_widget_geometry(size.area() == 0 ? size_ : size); + auto new_size = std::get<0>(placement); + auto const &geometries = std::get<1>(placement); + if (new_size.area() == 0 || geometries.size() != (widgets_.size() + 1)) + return; + + bool buffer_size_changed = (buffer->size() != new_size); + if (!buffer->is_allocated() || buffer_size_changed) + buffer->create_shm(display_->shm(), new_size, WL_SHM_FORMAT_XRGB8888); + + auto surface_damage = cv::Rect(0, 0, 0, 0); + auto draw_widget = [&](shared_ptr const &widget, cv::Rect const &rect) { + auto widget_damage = widget->draw( + buffer->data() + ((new_size.width * rect.y + rect.x) * 4), + rect.size(), + buffer_size_changed + ); + calculate_damage(surface_damage, rect, widget_damage); + }; + + for (size_t i = 0; i < widgets_.size(); ++i) + draw_widget(widgets_[i], geometries[i]); + draw_widget(viewer_, geometries.back()); + + this->commit_buffer(buffer, surface_damage); + + widget_geometries_ = geometries; + size_ = new_size; +} + +void cv_wl_window::commit_buffer(cv_wl_buffer *buffer, cv::Rect const &damage) { + if (!buffer) + return; + + buffer->attach_to_surface(surface_, 0, 0); + wl_surface_damage(surface_, damage.x, damage.y, damage.width, damage.height); + + if (frame_callback_) + wl_callback_destroy(frame_callback_); + frame_callback_ = wl_surface_frame(surface_); + wl_callback_add_listener(frame_callback_, &frame_listener_, this); + + next_frame_ready_ = false; + wl_surface_commit(surface_); +} + +void cv_wl_window::handle_frame_callback(void *data, struct wl_callback *cb, uint32_t time) { + CV_UNUSED(cb); + CV_UNUSED(time); + auto *window = reinterpret_cast(data); + + window->next_frame_ready_ = true; + + if (window->pending_.resize_request) { + window->pending_.resize_request = false; + window->pending_.repaint_request = false; + window->show(window->pending_.size); + } else if (window->pending_.repaint_request) { + window->pending_.repaint_request = false; + window->show(); + } +} + +#define EDGE_AREA_MARGIN 7 + +static std::string get_cursor_name(int x, int y, cv::Size const &size, bool grab) { + std::string cursor; + + if (grab) { + cursor = "grabbing"; + } else if (0 <= y && y <= EDGE_AREA_MARGIN) { + cursor = "top_"; + if (0 <= x && x <= EDGE_AREA_MARGIN) + cursor += "left_corner"; + else if (size.width - EDGE_AREA_MARGIN <= x && x <= size.width) + cursor += "right_corner"; + else + cursor += "side"; + } else if (size.height - EDGE_AREA_MARGIN <= y && y <= size.height) { + cursor = "bottom_"; + if (0 <= x && x <= EDGE_AREA_MARGIN) + cursor += "left_corner"; + else if (size.width - EDGE_AREA_MARGIN <= x && x <= size.width) + cursor += "right_corner"; + else + cursor += "side"; + } else if (0 <= x && x <= EDGE_AREA_MARGIN) { + cursor = "left_side"; + } else if (size.width - EDGE_AREA_MARGIN <= x && x <= size.width) { + cursor = "right_side"; + } else { + cursor = "left_ptr"; + } + + return cursor; +} + +static xdg_toplevel_resize_edge cursor_name_to_enum(std::string const &cursor) { + + if (cursor == "top_left_corner") return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + else if (cursor == "top_right_corner") return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + else if (cursor == "top_side") return XDG_TOPLEVEL_RESIZE_EDGE_TOP; + else if (cursor == "bottom_left_corner") return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + else if (cursor == "bottom_right_corner") return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + else if (cursor == "bottom_side") return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + else if (cursor == "left_side") return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + else if (cursor == "right_side") return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + else return XDG_TOPLEVEL_RESIZE_EDGE_NONE; +} + +void cv_wl_window::update_cursor(cv::Point const &p, bool grab) { + auto cursor_name = get_cursor_name(p.x, p.y, size_, grab); + if (cursor_.current_name == cursor_name) + return; + + cursor_.current_name = cursor_name; + auto cursor = cursor_.theme.get_cursor(cursor_name); + cursor.lock()->set_to_mouse( + *display_->input().lock()->mouse().lock(), + mouse_enter_serial_ + ); + cursor.lock()->commit(); +} + +void cv_wl_window::interactive_move() { + xdg_toplevel_move( + xdg_toplevel_, + display_->input().lock()->seat(), + mouse_button_serial_ + ); +} + +static int get_kb_modifiers_flag(const weak_ptr &kb) { + int flag = 0; + auto modifiers = kb.lock()->get_modifiers(); + + if (modifiers & cv_wl_keyboard::MOD_CONTROL_MASK) + flag |= cv::EVENT_FLAG_CTRLKEY; + if (modifiers & cv_wl_keyboard::MOD_ALT_MASK) + flag |= cv::EVENT_FLAG_ALTKEY; + if (modifiers & cv_wl_keyboard::MOD_SHIFT_MASK) + flag |= cv::EVENT_FLAG_SHIFTKEY; + + return flag; +} + +void cv_wl_window::deliver_mouse_event(int event, cv::Point const &p, int flag) { + flag |= get_kb_modifiers_flag(display_->input().lock()->keyboard()); + + for (size_t i = 0; i < widgets_.size(); ++i) { + auto const &rect = widget_geometries_[i]; + if (rect.contains(p)) + widgets_[i]->on_mouse(event, p - rect.tl(), flag); + } + + auto const &rect = widget_geometries_.back(); + if (viewer_ && rect.contains(p)) + viewer_->on_mouse(event, p - rect.tl(), flag); +} + +void cv_wl_window::mouse_enter(cv::Point const &p, uint32_t serial) { + on_mouse_.last = p; + mouse_enter_serial_ = serial; + + this->update_cursor(p); + this->deliver_mouse_event(cv::EVENT_MOUSEMOVE, p, 0); +} + +void cv_wl_window::mouse_leave() { + on_mouse_.reset(); + cursor_.current_name.clear(); +} + +void cv_wl_window::mouse_motion(uint32_t time, cv::Point const &p) { + CV_UNUSED(time); + int flag = 0; + on_mouse_.last = p; + + if (on_mouse_.drag) { + switch (on_mouse_.button) { + case cv_wl_mouse::LBUTTON: + flag = cv::EVENT_FLAG_LBUTTON; + break; + case cv_wl_mouse::RBUTTON: + flag = cv::EVENT_FLAG_RBUTTON; + break; + case cv_wl_mouse::MBUTTON: + flag = cv::EVENT_FLAG_MBUTTON; + break; + default: + break; + } + } + + bool grabbing = + (cursor_.current_name == "grabbing" && (flag & cv::EVENT_FLAG_LBUTTON)); + this->update_cursor(p, grabbing); + this->deliver_mouse_event(cv::EVENT_MOUSEMOVE, p, flag); +} + +void cv_wl_window::mouse_button(uint32_t time, uint32_t button, wl_pointer_button_state state, uint32_t serial) { + (void) time; + int event = 0, flag = 0; + + mouse_button_serial_ = serial; + + /* Start a user-driven, interactive resize of the surface */ + if (!on_mouse_.drag && + button == cv_wl_mouse::LBUTTON && cursor_.current_name != "left_ptr" && + viewer_->get_flags() != cv::WINDOW_AUTOSIZE) { + xdg_toplevel_resize( + xdg_toplevel_, + display_->input().lock()->seat(), + serial, + cursor_name_to_enum(cursor_.current_name) + ); + return; + } + + on_mouse_.button = static_cast(button); + on_mouse_.drag = (state == WL_POINTER_BUTTON_STATE_PRESSED); + + switch (button) { + case cv_wl_mouse::LBUTTON: + event = on_mouse_.drag ? cv::EVENT_LBUTTONDOWN : cv::EVENT_LBUTTONUP; + flag = cv::EVENT_FLAG_LBUTTON; + break; + case cv_wl_mouse::RBUTTON: + event = on_mouse_.drag ? cv::EVENT_RBUTTONDOWN : cv::EVENT_RBUTTONUP; + flag = cv::EVENT_FLAG_RBUTTON; + break; + case cv_wl_mouse::MBUTTON: + event = on_mouse_.drag ? cv::EVENT_MBUTTONDOWN : cv::EVENT_MBUTTONUP; + flag = cv::EVENT_FLAG_MBUTTON; + break; + default: + break; + } + + this->update_cursor(on_mouse_.last); + this->deliver_mouse_event(event, on_mouse_.last, flag); +} + +void cv_wl_window::handle_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { + auto *window = reinterpret_cast(data); + + xdg_surface_ack_configure(surface, serial); + + if (window->wait_for_configure_) { + window->wait_for_configure_ = false; + if (window->pending_.repaint_request) + window->show(); + } +} + +void cv_wl_window::handle_toplevel_configure( + void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, struct wl_array *states) { + CV_UNUSED(toplevel); + cv::Size size = cv::Size(width, height); + auto *window = reinterpret_cast(data); + + auto old_state = window->state_; + window->state_.reset(); + + const uint32_t *state; + WL_ARRAY_FOR_EACH(state, states, const uint32_t*) { + switch (*state) { + case XDG_TOPLEVEL_STATE_MAXIMIZED: + window->state_.maximized = true; + if (!old_state.maximized) { + window->state_.prev_size_ = window->size_; + window->show(size); + } + break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: + window->state_.fullscreen = true; + break; + case XDG_TOPLEVEL_STATE_RESIZING: + window->state_.resizing = true; + if (!size.empty()) + window->show(size); + break; + case XDG_TOPLEVEL_STATE_ACTIVATED: + window->state_.focused = true; + break; + default: + /* Unknown state */ + break; + } + } + + /* When unmaximized, resize to the previous size */ + if (old_state.maximized && !window->state_.maximized) + window->show(old_state.prev_size_); + +#ifndef NDEBUG + std::cerr << "[*] DEBUG: " << __func__ + << ": maximized=" << window->state_.maximized + << " fullscreen=" << window->state_.fullscreen + << " resizing=" << window->state_.resizing + << " focused=" << window->state_.focused + << " size=" << size << std::endl; +#endif +} + +void cv_wl_window::handle_toplevel_close(void *data, struct xdg_toplevel *surface) { + CV_UNUSED(data); + CV_UNUSED(surface); + //auto *window = reinterpret_cast(data); +} + + +/* + * cv_wl_core implementation + */ +cv_wl_core::cv_wl_core() += default; + +cv_wl_core::~cv_wl_core() { + this->destroy_all_windows(); + display_.reset(); +} + +void cv_wl_core::init() { + display_ = std::make_shared(); + if (!display_) + CV_Error(StsNoMem, "Could not create display"); +} + +cv_wl_display &cv_wl_core::display() { + CV_Assert(display_); + return *display_; +} + +std::vector cv_wl_core::get_window_names() const { + std::vector names; + for (auto &&e: windows_) + names.emplace_back(e.first); + return names; +} + +shared_ptr cv_wl_core::get_window(std::string const &name) { + return windows_.count(name) >= 1 ? + windows_.at(name) : std::shared_ptr(); +} + +void *cv_wl_core::get_window_handle(std::string const &name) { + auto window = get_window(name); + return window ? get_window(name).get() : nullptr; +} + +std::string const &cv_wl_core::get_window_name(void *handle) { + return handles_[handle]; +} + +bool cv_wl_core::create_window(std::string const &name, int flags) { + auto window = std::make_shared(display_, name, flags); + auto result = windows_.insert(std::make_pair(name, window)); + handles_[window.get()] = window->get_title(); + return result.second; +} + +bool cv_wl_core::destroy_window(std::string const &name) { + return windows_.erase(name); +} + +void cv_wl_core::destroy_all_windows() { + return windows_.clear(); +} + + +/* */ +/* OpenCV highgui interfaces */ +/* */ + +/* Global wayland core object */ +class CvWlCore { +public: + CvWlCore(CvWlCore &other) = delete; + + void operator=(const CvWlCore &) = delete; + + static cv_wl_core &getInstance() { + if (!sInstance) { + sInstance = std::make_shared(); + sInstance->init(); + } + return *sInstance; + } + +protected: + static std::shared_ptr sInstance; +}; + +std::shared_ptr CvWlCore::sInstance = nullptr; + +CV_IMPL int cvStartWindowThread() { + return 0; +} + +CV_IMPL int cvNamedWindow(const char *name, int flags) { + return CvWlCore::getInstance().create_window(name, flags); +} + +CV_IMPL void cvDestroyWindow(const char *name) { + CvWlCore::getInstance().destroy_window(name); +} + +CV_IMPL void cvDestroyAllWindows() { + CvWlCore::getInstance().destroy_all_windows(); +} + +CV_IMPL void *cvGetWindowHandle(const char *name) { + return CvWlCore::getInstance().get_window_handle(name); +} + +CV_IMPL const char *cvGetWindowName(void *window_handle) { + return CvWlCore::getInstance().get_window_name(window_handle).c_str(); +} + +CV_IMPL void cvMoveWindow(const char *name, int x, int y) { + CV_UNUSED(name); + CV_UNUSED(x); + CV_UNUSED(y); + CV_LOG_ONCE_WARNING(nullptr, "Function not implemented: User cannot move window surfaces in Wayland"); +} + +CV_IMPL void cvResizeWindow(const char *name, int width, int height) { + if (auto window = CvWlCore::getInstance().get_window(name)) + window->show(cv::Size(width, height)); + else + throw_system_error("Could not get window name", errno) +} + +CV_IMPL int cvCreateTrackbar(const char *name_bar, const char *window_name, int *value, int count, + CvTrackbarCallback on_change) { + CV_UNUSED(name_bar); + CV_UNUSED(window_name); + CV_UNUSED(value); + CV_UNUSED(count); + CV_UNUSED(on_change); + CV_LOG_ONCE_WARNING(nullptr, "Not implemented, use cvCreateTrackbar2"); + + return 0; +} + +CV_IMPL int cvCreateTrackbar2(const char *trackbar_name, const char *window_name, int *val, int count, + CvTrackbarCallback2 on_notify, void *userdata) { + if (auto window = CvWlCore::getInstance().get_window(window_name)) + window->create_trackbar(trackbar_name, val, count, on_notify, userdata); + + return 0; +} + +CV_IMPL int cvGetTrackbarPos(const char *trackbar_name, const char *window_name) { + if (auto window = CvWlCore::getInstance().get_window(window_name)) { + auto trackbar_ptr = window->get_trackbar(trackbar_name); + if (auto trackbar = trackbar_ptr.lock()) + return trackbar->get_pos(); + } + + return -1; +} + +CV_IMPL void cvSetTrackbarPos(const char *trackbar_name, const char *window_name, int pos) { + if (auto window = CvWlCore::getInstance().get_window(window_name)) { + auto trackbar_ptr = window->get_trackbar(trackbar_name); + if (auto trackbar = trackbar_ptr.lock()) + trackbar->set_pos(pos); + } +} + +CV_IMPL void cvSetTrackbarMax(const char *trackbar_name, const char *window_name, int maxval) { + if (auto window = CvWlCore::getInstance().get_window(window_name)) { + auto trackbar_ptr = window->get_trackbar(trackbar_name); + if (auto trackbar = trackbar_ptr.lock()) + trackbar->set_max(maxval); + } +} + +CV_IMPL void cvSetTrackbarMin(const char *trackbar_name, const char *window_name, int minval) { + CV_UNUSED(trackbar_name); + CV_UNUSED(window_name); + CV_UNUSED(minval); +} + +CV_IMPL void cvSetMouseCallback(const char *window_name, CvMouseCallback on_mouse, void *param) { + if (auto window = CvWlCore::getInstance().get_window(window_name)) + window->set_mouse_callback(on_mouse, param); +} + +CV_IMPL void cvShowImage(const char *name, const CvArr *arr) { + auto cv_core = CvWlCore::getInstance(); + auto window = cv_core.get_window(name); + if (!window) { + cv_core.create_window(name, cv::WINDOW_AUTOSIZE); + if (!(window = cv_core.get_window(name))) + CV_Error_(StsNoMem, ("Failed to create window: %s", name)); + } + + cv::Mat mat = cv::cvarrToMat(arr, true); + window->show_image(mat); +} + +void setWindowTitle_WAYLAND(const cv::String &winname, const cv::String &title) { + if (auto window = CvWlCore::getInstance().get_window(winname)) + window->set_title(title); +} + +CV_IMPL int cvWaitKey(int delay) { + int key = -1; + auto limit = ch::duration_cast(ch::milliseconds(delay)); + auto start_time = ch::duration_cast( + ch::steady_clock::now().time_since_epoch()) + .count(); + + while (true) { + + auto res = CvWlCore::getInstance().display().run_once(); + if (res > 0) { + auto &&key_queue = + CvWlCore::getInstance().display().input().lock() + ->keyboard().lock()->get_key_queue(); + if (!key_queue.empty()) { + key = key_queue.back(); + break; + } + } + + auto end_time = ch::duration_cast( + ch::steady_clock::now().time_since_epoch()) + .count(); + + auto elapsed = end_time - start_time; + if (limit.count() > 0 && elapsed >= limit.count()) { + break; + } + + auto sleep_time = 64000 - elapsed; + if (sleep_time > 0) { + std::this_thread::sleep_for(ch::nanoseconds(sleep_time)); + } + } + + return key; +} + +#ifdef HAVE_OPENGL +CV_IMPL void cvSetOpenGlDrawCallback(const char *, CvOpenGlDrawCallback, void *) { +} + +CV_IMPL void cvSetOpenGlContext(const char *) { +} + +CV_IMPL void cvUpdateWindow(const char *) { +} +#endif // HAVE_OPENGL + +#endif // HAVE_WAYLAND +#endif // _WIN32 diff --git a/modules/highgui/test/test_gui.cpp b/modules/highgui/test/test_gui.cpp index 6bf634b500..de40e80ede 100644 --- a/modules/highgui/test/test_gui.cpp +++ b/modules/highgui/test/test_gui.cpp @@ -58,6 +58,7 @@ inline void verify_size(const std::string &nm, const cv::Mat &img) && !defined HAVE_QT \ && !defined HAVE_WIN32UI \ && !defined HAVE_COCOA \ + && !defined HAVE_WAYLAND \ ) TEST(Highgui_GUI, DISABLED_regression) #else @@ -135,6 +136,7 @@ static void Foo(int, void* counter) && !defined HAVE_GTK \ && !defined HAVE_QT \ && !defined HAVE_WIN32UI \ + && !defined HAVE_WAYLAND \ ) \ || defined(__APPLE__) // test fails on Mac (cocoa) TEST(Highgui_GUI, DISABLED_trackbar_unsafe) @@ -174,6 +176,7 @@ void testTrackbarCallback(int pos, void* param) && !defined HAVE_GTK \ && !defined HAVE_QT \ && !defined HAVE_WIN32UI \ + && !defined HAVE_WAYLAND \ ) \ || defined(__APPLE__) // test fails on Mac (cocoa) TEST(Highgui_GUI, DISABLED_trackbar)