@@ -279,13 +279,13 @@ namespace pipewire {
279279 * @param mem_type Mem type.
280280 * @param width Frame or display width in pixels.
281281 * @param height Frame or display height in pixels.
282- * @param refresh_rate Refresh rate .
282+ * @param target_framerate Target framerate expressed as AVRational .
283283 * @param dmabuf_infos Dmabuf infos.
284284 * @param n_dmabuf_infos N dmabuf infos.
285285 * @param display_is_nvidia Display is nvidia.
286286 * @return 0 when the PipeWire stream is configured; nonzero on negotiation failure.
287287 */
288- int ensure_stream (const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate , const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
288+ int ensure_stream (const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const AVRational target_framerate , const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
289289 pw_thread_loop_lock (loop);
290290 int result = 0 ;
291291 if (!stream_data.stream ) {
@@ -316,15 +316,15 @@ namespace pipewire {
316316 (mem_type == platf::mem_type_e::cuda && display_is_nvidia));
317317 if (use_dmabuf) {
318318 for (int i = 0 ; i < n_dmabuf_infos; i++) {
319- auto format_param = build_format_parameter (&pod_builder, width, height, refresh_rate , dmabuf_infos[i].format , dmabuf_infos[i].modifiers , dmabuf_infos[i].n_modifiers );
319+ auto format_param = build_format_parameter (&pod_builder, width, height, target_framerate , dmabuf_infos[i].format , dmabuf_infos[i].modifiers , dmabuf_infos[i].n_modifiers );
320320 params[n_params] = format_param;
321321 n_params++;
322322 }
323323 }
324324
325325 // Add fallback for memptr
326326 for (const auto &fmt : format_map) {
327- auto format_param = build_format_parameter (&pod_builder, width, height, refresh_rate , fmt.pw_format , nullptr , 0 );
327+ auto format_param = build_format_parameter (&pod_builder, width, height, target_framerate , fmt.pw_format , nullptr , 0 );
328328 params[n_params] = format_param;
329329 n_params++;
330330 }
@@ -473,7 +473,7 @@ namespace pipewire {
473473 uint64_t object_serial;
474474 bool negotiate_maxframerate_ = true ;
475475
476- struct spa_pod *build_format_parameter (struct spa_pod_builder *b, uint32_t width, uint32_t height, uint32_t refresh_rate , int32_t format, uint64_t *modifiers, int n_modifiers) {
476+ struct spa_pod *build_format_parameter (struct spa_pod_builder *b, uint32_t width, uint32_t height, AVRational target_framerate , int32_t format, uint64_t *modifiers, int n_modifiers) {
477477 struct spa_pod_frame object_frame;
478478 struct spa_pod_frame modifier_frame;
479479 std::array<struct spa_rectangle , 3 > sizes;
@@ -483,18 +483,22 @@ namespace pipewire {
483483 sizes[1 ] = SPA_RECTANGLE (1 , 1 );
484484 sizes[2 ] = SPA_RECTANGLE (8192 , 4096 );
485485
486- framerates[0 ] = SPA_FRACTION (0 , 1 ) ; // default; we only want variable rate, thus bypassing compositor pacing
486+ framerates[0 ] = SPA_FRACTION (uint32_t (target_framerate. num ), uint32_t (target_framerate. den )) ; // default/preferred
487487 framerates[1 ] = SPA_FRACTION (0 , 1 ); // min
488- framerates[2 ] = SPA_FRACTION (0 , 1 ); // max
488+ framerates[2 ] = SPA_FRACTION (1000 , 1 ); // max
489489
490490 spa_pod_builder_push_object (b, &object_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
491491 spa_pod_builder_add (b, SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_video), 0 );
492492 spa_pod_builder_add (b, SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw), 0 );
493493 spa_pod_builder_add (b, SPA_FORMAT_VIDEO_format, SPA_POD_Id (format), 0 );
494494 spa_pod_builder_add (b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle (&sizes[0 ], &sizes[1 ], &sizes[2 ]), 0 );
495- spa_pod_builder_add (b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction (&framerates[0 ]), 0 );
496495 if (negotiate_maxframerate_) {
496+ // Always request variable rate (0, 1) for framerate when populating maxFramerate with default,min,max values
497+ spa_pod_builder_add (b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction (&framerates[1 ]), 0 );
497498 spa_pod_builder_add (b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction (&framerates[0 ], &framerates[1 ], &framerates[2 ]), 0 );
499+ } else {
500+ // Request target framerate (target_framerate) for framerate in fallback case
501+ spa_pod_builder_add (b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction (&framerates[0 ]), 0 );
498502 }
499503
500504 if (format == SPA_VIDEO_FORMAT_xBGR_210LE) {
@@ -809,14 +813,22 @@ namespace pipewire {
809813 */
810814 int init (platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
811815 // calculate frame interval we should capture at
812- framerate = config.framerate ;
813816 delay = ::video::capture_frame_interval (config);
814- const AVRational fps = ::video::framerate_to_rational (config);
817+
818+ // WORKAROUND: request variable rate (0, 1) capture only if the active compositor is KWin 5.x-6.7.x.
819+ // Ref: https://bugs.kde.org/show_bug.cgi?id=524129
820+ const static std::vector<int > kwin_version = get_running_kwin_version ();
821+ const static bool negotiate_variable_rate = !kwin_version.empty () && (kwin_version[0 ] == 5 || (kwin_version[0 ] == 6 && kwin_version[1 ] < 8 ));
822+
823+ const AVRational fps = (negotiate_variable_rate ? AVRational {0 , 1 } : ::video::framerate_to_rational (config));
815824 if (fps.den != 1 ) {
816825 BOOST_LOG (info) << " [pipewire] Requested frame rate [" << fps.num << " /" << fps.den << " , approx. " << av_q2d (fps) << " fps]" ;
826+ } else if (fps.num == 0 && fps.den == 1 ) {
827+ BOOST_LOG (info) << " [pipewire] Requested variable rate capture [host pacing: " << std::chrono::duration<double , std::milli>(delay).count () << " ms]" ;
817828 } else {
818829 BOOST_LOG (info) << " [pipewire] Requested frame rate [" << fps.num << " fps]" ;
819830 }
831+ this ->target_framerate = fps;
820832 mem_type = hwdevice_type;
821833
822834 if (get_dmabuf_modifiers () < 0 ) {
@@ -836,8 +848,6 @@ namespace pipewire {
836848 // Verify or update display parameters for streaming to ensure absolute touch inputs work as expected
837849 verify_and_update_display_parameters ();
838850
839- framerate = config.framerate ;
840-
841851 if (!shared_state) {
842852 shared_state = std::make_shared<shared_state_t >();
843853 } else {
@@ -854,7 +864,7 @@ namespace pipewire {
854864 }
855865
856866 // Start PipeWire now so format negotiation can proceed before capture start
857- if (pipewire.ensure_stream (mem_type, width, height, framerate , dmabuf_infos.data (), n_dmabuf_infos, display_is_nvidia) < 0 ) {
867+ if (pipewire.ensure_stream (mem_type, width, height, target_framerate , dmabuf_infos.data (), n_dmabuf_infos, display_is_nvidia) < 0 ) {
858868 BOOST_LOG (error) << " [pipewire] Failed to ensure pipewire stream. pipewire_t::init() failed." ;
859869 return -1 ;
860870 }
@@ -966,7 +976,7 @@ namespace pipewire {
966976 platf::capture_e capture (const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
967977 auto next_frame = std::chrono::steady_clock::now ();
968978
969- if (pipewire.ensure_stream (mem_type, width, height, framerate , dmabuf_infos.data (), n_dmabuf_infos, display_is_nvidia) < 0 ) {
979+ if (pipewire.ensure_stream (mem_type, width, height, target_framerate , dmabuf_infos.data (), n_dmabuf_infos, display_is_nvidia) < 0 ) {
970980 BOOST_LOG (error) << " [pipewire] Failed to ensure pipewire stream. capture() failed with error." ;
971981 return platf::capture_e::error;
972982 }
@@ -1141,6 +1151,89 @@ namespace pipewire {
11411151 return false ;
11421152 }
11431153
1154+ /* *
1155+ * Fetch the currently running KWin version (if available from its DBUS support information method)
1156+ *
1157+ * @return A vector with 3 elements containing KWin's major.minor.micro version or an empty vector if KWin's version could not be determined
1158+ */
1159+ static std::vector<int > get_running_kwin_version () {
1160+ #if !GLIB_CHECK_VERSION(2, 74, 0)
1161+ // Compatibility for Ubuntu 22.04 (Glib 2.72)
1162+ constexpr auto G_REGEX_DEFAULT = static_cast <GRegexCompileFlags>(0 );
1163+ constexpr auto G_REGEX_MATCH_DEFAULT = static_cast <GRegexMatchFlags>(0 );
1164+ #endif
1165+ auto conn = g_bus_get_sync (G_BUS_TYPE_SESSION , nullptr , nullptr );
1166+ std::vector<int > result;
1167+
1168+ if (!conn) {
1169+ return result;
1170+ }
1171+
1172+ auto reply = g_dbus_connection_call_sync (
1173+ conn,
1174+ " org.kde.KWin" ,
1175+ " /KWin" ,
1176+ " org.kde.KWin" ,
1177+ " supportInformation" ,
1178+ nullptr ,
1179+ G_VARIANT_TYPE (" (s)" ),
1180+ G_DBUS_CALL_FLAGS_NONE ,
1181+ -1 ,
1182+ nullptr ,
1183+ nullptr
1184+ );
1185+
1186+ if (!reply) {
1187+ g_clear_object (&conn);
1188+ return result;
1189+ }
1190+
1191+ g_autofree gchar *support_info = nullptr ;
1192+ g_variant_get (reply, " (s)" , &support_info);
1193+
1194+ if (!support_info) {
1195+ g_variant_unref (reply);
1196+ g_clear_object (&conn);
1197+ return result;
1198+ }
1199+
1200+ auto *regex = g_regex_new (
1201+ " KWin version: ([0-9]+)\\ .([0-9]+)\\ .([0-9]+)" ,
1202+ G_REGEX_DEFAULT ,
1203+ G_REGEX_MATCH_DEFAULT ,
1204+ nullptr
1205+ );
1206+
1207+ if (!regex) {
1208+ g_variant_unref (reply);
1209+ g_clear_object (&conn);
1210+ return result;
1211+ }
1212+
1213+ GMatchInfo *match_info = nullptr ;
1214+ g_regex_match (regex, support_info, G_REGEX_MATCH_DEFAULT , &match_info);
1215+
1216+ if (g_match_info_matches (match_info)) {
1217+ g_autofree const gchar *major =
1218+ g_match_info_fetch (match_info, 1 );
1219+ g_autofree const gchar *minor =
1220+ g_match_info_fetch (match_info, 2 );
1221+ g_autofree const gchar *micro =
1222+ g_match_info_fetch (match_info, 3 );
1223+
1224+ result.emplace_back (std::atoi (major));
1225+ result.emplace_back (std::atoi (minor));
1226+ result.emplace_back (std::atoi (micro));
1227+ }
1228+
1229+ g_match_info_free (match_info);
1230+ g_regex_unref (regex);
1231+ g_variant_unref (reply);
1232+ g_clear_object (&conn);
1233+
1234+ return result;
1235+ }
1236+
11441237 private:
11451238 bool is_buffer_redundant (const egl::img_descriptor_t *img) {
11461239 // Check for corrupted frame
@@ -1280,7 +1373,7 @@ namespace pipewire {
12801373 std::optional<std::uint64_t > last_pts {};
12811374 std::optional<std::uint64_t > last_seq {};
12821375 std::uint64_t sequence {};
1283- uint32_t framerate ;
1376+ AVRational target_framerate ;
12841377
12851378 protected:
12861379 // Allow subclasses to access for pipewire requirements setup and stream dead checks
0 commit comments