diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 4953c67e..23c58c8b 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -34,6 +34,7 @@ #include "platform.h" #include "platform/video_renderer.h" #include "rendering/slint_video_presenter.h" +#include "rendering/video_geometry.h" #include "runtime/privacy_control_state.h" #if _WIN32 #include @@ -2799,9 +2800,6 @@ void GuiApplication::ConfigureStreamVideoRenderer() { SlintVideoPresenter::SurfaceState state; state.selected_stream = focused_remote_id_; state.fullscreen = fullscreen_button_pressed_; - if (ui_) { - state.tab_count = ui_->tab_ids.size(); - } return state; }, [this] { video_frame_dirty_.store(true, std::memory_order_release); }); @@ -3250,43 +3248,21 @@ void GuiApplication::SendPointerInput(int button, int kind, float x, float y) { return; } - const auto size = (*ui_->stream)->window().size(); - const float scale = (*ui_->stream)->window().scale_factor(); - const float available_width = size.width / scale; - const float titlebar_height = - !fullscreen_button_pressed_ && (*ui_->stream)->get_custom_titlebar() - ? kWaylandTitlebarLogicalHeight - : 0.0f; - const float available_height = - size.height / scale - titlebar_height - - (fullscreen_button_pressed_ ? 0.0f - : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f)); - float render_width = available_width; - float render_height = available_height; - float offset_x = 0; - float offset_y = 0; - if (props->video_width_ > 0 && props->video_height_ > 0 && - available_width > 0 && available_height > 0) { - const float video_aspect = - static_cast(props->video_width_) / props->video_height_; - const float area_aspect = available_width / available_height; - if (video_aspect > area_aspect) { - render_height = available_width / video_aspect; - offset_y = (available_height - render_height) * 0.5f; - } else { - render_width = available_height * video_aspect; - offset_x = (available_width - render_width) * 0.5f; - } + std::optional point; + { + std::lock_guard lock(props->video_frame_mutex_); + point = MapVideoPoint(x, y, (*ui_->stream)->get_video_area_width(), + (*ui_->stream)->get_video_area_height(), + props->video_width_, props->video_height_); } - if (x < offset_x || x > offset_x + render_width || y < offset_y || - y > offset_y + render_height) { + if (!point) { return; } RemoteAction action{}; action.type = ControlType::mouse; - action.m.x = std::clamp((x - offset_x) / render_width, 0.0f, 1.0f); - action.m.y = std::clamp((y - offset_y) / render_height, 0.0f, 1.0f); + action.m.x = point->x; + action.m.y = point->y; // Slint enum order: Cancel=0, Down=1, Up=2, Move=3 and // Other=0, Left=1, Right=2, Middle=3. if (kind == 3) { @@ -3329,24 +3305,20 @@ void GuiApplication::SendScrollInput(float delta_x, float delta_y, float x, if (!props || !props->peer_ || !props->control_mouse_ || !ui_->stream) { return; } - const auto size = (*ui_->stream)->window().size(); - const float scale = (*ui_->stream)->window().scale_factor(); - const float width = size.width / scale; - const float titlebar_height = - !fullscreen_button_pressed_ && (*ui_->stream)->get_custom_titlebar() - ? kWaylandTitlebarLogicalHeight - : 0.0f; - const float height = - size.height / scale - titlebar_height - - (fullscreen_button_pressed_ ? 0.0f - : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f)); - if (width <= 0 || height <= 0) { + std::optional point; + { + std::lock_guard lock(props->video_frame_mutex_); + point = MapVideoPoint(x, y, (*ui_->stream)->get_video_area_width(), + (*ui_->stream)->get_video_area_height(), + props->video_width_, props->video_height_); + } + if (!point) { return; } RemoteAction action{}; action.type = ControlType::mouse; - action.m.x = std::clamp(x / width, 0.0f, 1.0f); - action.m.y = std::clamp(y / height, 0.0f, 1.0f); + action.m.x = point->x; + action.m.y = point->y; if (std::abs(delta_y) >= std::abs(delta_x)) { action.m.flag = MouseFlag::wheel_vertical; action.m.s = delta_y > 0 ? 1 : delta_y < 0 ? -1 : 0; diff --git a/apps/desktop/src/gui/rendering/slint_video_presenter.cpp b/apps/desktop/src/gui/rendering/slint_video_presenter.cpp index 684b14d8..def0fbd2 100644 --- a/apps/desktop/src/gui/rendering/slint_video_presenter.cpp +++ b/apps/desktop/src/gui/rendering/slint_video_presenter.cpp @@ -154,10 +154,8 @@ struct SlintVideoPresenter::Impl { const auto window_size = (*stream)->window().size(); const float scale_factor = std::max(1.0f, (*stream)->window().scale_factor()); - const int top_inset = - !surface.fullscreen && surface.tab_count > 1 - ? static_cast(std::lround(30.0f * scale_factor)) - : 0; + const int top_inset = static_cast(std::lround( + (*stream)->get_video_area_top() * scale_factor)); const bool rounded_window = (*stream)->get_custom_titlebar() && !surface.fullscreen && !(*stream)->window().is_maximized(); @@ -335,8 +333,7 @@ SlintVideoPresenter::PresentResult SlintVideoPresenter::Present( dynamic_cast(&impl_->renderer); if (metal_renderer && metal_renderer->IsActive()) { const SurfaceState surface = impl_->CurrentState(); - const double top_inset = - !surface.fullscreen && surface.tab_count > 1 ? 30.0 : 0.0; + const double top_inset = (*impl_->stream)->get_video_area_top(); const auto outcome = metal_renderer->RenderLatest( frame.remote_id, top_inset, !surface.fullscreen); if (outcome.result == VideoRenderer::RenderResult::rendered) { diff --git a/apps/desktop/src/gui/rendering/slint_video_presenter.h b/apps/desktop/src/gui/rendering/slint_video_presenter.h index a971f3bb..39a52ce0 100644 --- a/apps/desktop/src/gui/rendering/slint_video_presenter.h +++ b/apps/desktop/src/gui/rendering/slint_video_presenter.h @@ -28,7 +28,6 @@ public: struct SurfaceState { std::string selected_stream; bool fullscreen = false; - size_t tab_count = 0; }; struct Frame { @@ -78,4 +77,4 @@ private: } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/apps/desktop/src/gui/rendering/video_geometry.h b/apps/desktop/src/gui/rendering/video_geometry.h new file mode 100644 index 00000000..2c16049b --- /dev/null +++ b/apps/desktop/src/gui/rendering/video_geometry.h @@ -0,0 +1,51 @@ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-22 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _VIDEO_GEOMETRY_H_ +#define _VIDEO_GEOMETRY_H_ + +#include +#include +#include + +namespace crossdesk { + +struct VideoPoint { + float x; + float y; +}; + +// Area dimensions and pointer positions must use the same coordinate space. +// Slint supplies both in logical pixels; remote input is resolution +// independent. +inline std::optional MapVideoPoint(float x, float y, + float area_width, + float area_height, + int video_width, + int video_height) { + if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(area_width) || + !std::isfinite(area_height) || area_width <= 0 || area_height <= 0 || + video_width <= 0 || video_height <= 0) { + return std::nullopt; + } + + const double scale = + std::min(static_cast(area_width) / video_width, + static_cast(area_height) / video_height); + const double width = video_width * scale; + const double height = video_height * scale; + const double left = (area_width - width) * 0.5; + const double top = (area_height - height) * 0.5; + if (x < left || x > left + width || y < top || y > top + height) { + return std::nullopt; + } + return VideoPoint{static_cast((x - left) / width), + static_cast((y - top) / height)}; +} + +} // namespace crossdesk + +#endif \ No newline at end of file diff --git a/apps/desktop/src/gui/ui/stream_window.slint b/apps/desktop/src/gui/ui/stream_window.slint index 778fab2e..d3c067c5 100644 --- a/apps/desktop/src/gui/ui/stream_window.slint +++ b/apps/desktop/src/gui/ui/stream_window.slint @@ -142,6 +142,11 @@ export component StreamWindow inherits Window { in property custom-titlebar: false; in property native-video-enabled: false; + // Export the actual input surface in logical pixels. Native rendering and + // input mapping must follow the same layout, including title and tab bars. + out property video-area-top: content-layer.y + video.y; + out property video-area-width: video.width; + out property video-area-height: video.height; in property window-active: true; in property window-maximized: false; in property <[StreamTab]> tabs;