// 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 write_mat_to_xrgb8888(cv::Mat const &img_, void *data) { // Validate destination data. CV_CheckFalse((data == nullptr), "Destination Address must not be nullptr."); // Validate source img parameters. CV_CheckFalse(img_.empty(), "Source Mat must not be empty."); const int ncn = img_.channels(); CV_CheckType(img_.type(), ( (ncn == 1) || (ncn == 3) || (ncn == 4)), "Unsupported channels, please convert to 1, 3 or 4 channels" ); // The supported Mat depth is according to imshow() specification. const int depth = CV_MAT_DEPTH(img_.type()); CV_CheckDepth(img_.type(), ( (depth == CV_8U) || (depth == CV_8S) || (depth == CV_16U) || (depth == CV_16S) || (depth == CV_32F) || (depth == CV_64F) ), "Unsupported depth, please convert to CV_8U" ); // Convert to CV_8U cv::Mat img; const int mtype = CV_MAKE_TYPE(CV_8U, ncn); switch(CV_MAT_DEPTH(depth)) { case CV_8U: img = img_; // do nothing. break; case CV_8S: // [-128,127] -> [0,255] img_.convertTo(img, mtype, 1.0, 128); break; case CV_16U: // [0,65535] -> [0,255] img_.convertTo(img, mtype, 1.0/255. ); break; case CV_16S: // [-32768,32767] -> [0,255] img_.convertTo(img, mtype, 1.0/255. , 128); break; case CV_32F: case CV_64F: // [0, 1] -> [0,255] img_.convertTo(img, mtype, 255.); break; default: // it cannot be reachable. break; } CV_CheckDepthEQ(CV_MAT_DEPTH(img.type()), CV_8U, "img should be CV_8U"); // XRGB8888 in Little Endian(Wayland Request) = [B8:G8:R8:X8] in data array. // X is not used to show. So we can use cvtColor() with GRAY2BGRA or BGR2BGRA or copyTo(). cv::Mat dst(img.size(), CV_MAKE_TYPE(CV_8U, 4), (uint8_t*)data); if(ncn == 1) { cvtColor(img, dst, cv::COLOR_GRAY2BGRA); } else if(ncn == 3) { cvtColor(img, dst, cv::COLOR_BGR2BGRA); } else { CV_CheckTrue(ncn==4, "Unexpected channels"); img.copyTo(dst); } } 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, #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION &handle_axis_value120, #endif #ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION &handle_axis_relative_direction, #endif }; 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); } #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION static void handle_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { CV_UNUSED(data); CV_UNUSED(wl_pointer); CV_UNUSED(axis); CV_UNUSED(value120); } #endif #ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION static void handle_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) { CV_UNUSED(data); CV_UNUSED(wl_pointer); CV_UNUSED(axis); CV_UNUSED(direction); } #endif }; 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); int 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, #ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION &handle_toplevel_configure_bounds, #endif #ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION &handle_toplevel_wm_capabilities, #endif }; 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); #ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION static void handle_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { CV_UNUSED(data); CV_UNUSED(xdg_toplevel); CV_UNUSED(width); CV_UNUSED(height); } #endif #ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION static void handle_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) { CV_UNUSED(data); CV_UNUSED(xdg_toplevel); CV_UNUSED(capabilities); } #endif 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); CV_LOG_ERROR(NULL, "OpenCV Error: " << e.what()); } 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::LINE_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::LINE_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::LINE_AA); cv::rectangle(buf_, btn_max.tl(), btn_max.br(), line_color_, 1, cv::LINE_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::LINE_AA); cv::line(buf_, cv::Point(0, 0), cv::Point(buf_.size().width, 0), border_color_, 1, cv::LINE_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) { 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; // initilize slider_.value if value is not nullptr. if (value != nullptr){ set_pos(*value); } } 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(); // Update user-ptr value and call on_change() function if cv_wl_trackbar::draw() is not called. if(slider_moved_) { on_change_.update(slider_.value); on_change_.call(slider_.value); } } } 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(); // Update user-ptr and call on_change() function if cv_wl_trackbar::draw() is not called. if(slider_moved_) { on_change_.update(slider_.value); on_change_.call(slider_.value); } } } 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::LINE_AA); cv::line(data_, bar_.left, bar_.right, color_.bg, bar_.thickness + 3, cv::LINE_AA); cv::line(data_, bar_.left, bar_.right, color_.fg, bar_.thickness, cv::LINE_AA); cv::circle(data_, slider_.pos, slider_.radius, color_.fg, -1, cv::LINE_AA); cv::circle(data_, slider_.pos, slider_.radius, color_.bg, 1, cv::LINE_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(); } int cv_wl_window::create_trackbar(std::string const &name, int *value, int count, CvTrackbarCallback2 on_change, void *userdata) { int ret = 0; 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); ret = 1; } return ret; } 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) { CV_CheckTrue(display_ != nullptr, "Display is not connected."); 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) } CvRect cvGetWindowRect_WAYLAND(const char* name) { CV_UNUSED(name); CV_LOG_ONCE_WARNING(nullptr, "Function not implemented: User cannot get window rect in Wayland"); return cvRect(-1, -1, -1, -1); } 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) { int ret = 0; if (auto window = CvWlCore::getInstance().get_window(window_name)) ret = window->create_trackbar(trackbar_name, val, count, on_notify, userdata); return ret; } 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) { // see https://github.com/opencv/opencv/issues/25497 /* * To reuse the result of getInstance() repeatedly looks like better efficient implementation. * However, it defined as static shared_ptr member variable in CvWlCore. * If it reaches out of scope, cv_wl_core::~cv_wl_core() is called and all windows will be destroyed. * For workaround, avoid it. */ auto window = CvWlCore::getInstance().get_window(name); if (!window) { CvWlCore::getInstance().create_window(name, cv::WINDOW_AUTOSIZE); if (!(window = CvWlCore::getInstance().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)).count(); auto start_time = ch::duration_cast( ch::steady_clock::now().time_since_epoch()) .count(); // See https://github.com/opencv/opencv/issues/25501 // Too long sleep_for() makes no response to Wayland ping-pong mechanism // So interval is limited to 33ms (1000ms / 30fps). auto sleep_time_min = ch::duration_cast(ch::milliseconds(33)).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 > 0 && elapsed >= limit) { break; } auto sleep_time = std::min(limit - elapsed, sleep_time_min); 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