diff options
Diffstat (limited to 'gnu/packages/patches')
15 files changed, 1733 insertions, 269 deletions
diff --git a/gnu/packages/patches/ffmpeg-jami-pipewiregrab-source-filter.patch b/gnu/packages/patches/ffmpeg-jami-pipewiregrab-source-filter.patch new file mode 100644 index 0000000000..f2da4f478f --- /dev/null +++ b/gnu/packages/patches/ffmpeg-jami-pipewiregrab-source-filter.patch @@ -0,0 +1,1451 @@ +diff --git a/configure b/configure +index 3cd3bdfb44..0756ad95ea 100755 +--- a/configure ++++ b/configure +@@ -297,6 +297,7 @@ External library support: + --enable-libxcb-shm enable X11 grabbing shm communication [autodetect] + --enable-libxcb-xfixes enable X11 grabbing mouse rendering [autodetect] + --enable-libxcb-shape enable X11 grabbing shape rendering [autodetect] ++ --enable-libpipewire enable screen grabbing using pipewire [autodetect] + --enable-libxvid enable Xvid encoding via xvidcore, + native MPEG-4/Xvid encoder exists [no] + --enable-libxml2 enable XML parsing using the C library libxml2, needed +@@ -1747,6 +1748,8 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST=" + libxcb_shm + libxcb_shape + libxcb_xfixes ++ libpipewire ++ libgio_unix + lzma + mediafoundation + metal +@@ -3709,6 +3712,7 @@ pad_opencl_filter_deps="opencl" + pan_filter_deps="swresample" + perspective_filter_deps="gpl" + phase_filter_deps="gpl" ++pipewiregrab_filter_deps="libpipewire libgio_unix pthreads" + pp7_filter_deps="gpl" + pp_filter_deps="gpl postproc" + prewitt_opencl_filter_deps="opencl" +@@ -6928,6 +6932,18 @@ if enabled libxcb; then + enabled libxcb_xfixes && check_pkg_config libxcb_xfixes xcb-xfixes xcb/xfixes.h xcb_xfixes_get_cursor_image + fi + ++# Starting with version 0.3.52, PipeWire's spa library uses the __LOCALE_C_ONLY macro to determine ++# whether the locale_t type (introduced in POSIX.1-2008) and some related functions are available (see ++# https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2390 for more information). ++# Unfortunately, this macro is specific to uclibc, which can cause build issues on systems that use a ++# different implementation of libc if POSIX 2008 support isn't enabled (which is the case for FFmpeg currently). ++# As a workaround for this problem, we add a compilation flag to ensure that __LOCALE_C_ONLY is always defined. ++add_cppflags -D__LOCALE_C_ONLY ++enabled libpipewire && check_pkg_config libpipewire "libpipewire-0.3 >= 0.3.40" pipewire/pipewire.h pw_init ++if enabled libpipewire; then ++ enabled libgio_unix && check_pkg_config libgio_unix gio-unix-2.0 gio/gio.h g_main_loop_new ++fi ++ + check_func_headers "windows.h" CreateDIBSection "$gdigrab_indev_extralibs" + + # d3d11va requires linking directly to dxgi and d3d11 if not building for +diff --git a/libavfilter/Makefile b/libavfilter/Makefile +index b3d3d981dd..abe7c3cd0d 100644 +--- a/libavfilter/Makefile ++++ b/libavfilter/Makefile +@@ -583,6 +583,7 @@ OBJS-$(CONFIG_NULLSRC_FILTER) += vsrc_testsrc.o + OBJS-$(CONFIG_OPENCLSRC_FILTER) += vf_program_opencl.o opencl.o + OBJS-$(CONFIG_PAL75BARS_FILTER) += vsrc_testsrc.o + OBJS-$(CONFIG_PAL100BARS_FILTER) += vsrc_testsrc.o ++OBJS-$(CONFIG_PIPEWIREGRAB_FILTER) += vsrc_pipewiregrab.o + OBJS-$(CONFIG_RGBTESTSRC_FILTER) += vsrc_testsrc.o + OBJS-$(CONFIG_SIERPINSKI_FILTER) += vsrc_sierpinski.o + OBJS-$(CONFIG_SMPTEBARS_FILTER) += vsrc_testsrc.o +diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c +index d7db46c2af..87204fec71 100644 +--- a/libavfilter/allfilters.c ++++ b/libavfilter/allfilters.c +@@ -548,6 +548,7 @@ extern const AVFilter ff_vsrc_nullsrc; + extern const AVFilter ff_vsrc_openclsrc; + extern const AVFilter ff_vsrc_pal75bars; + extern const AVFilter ff_vsrc_pal100bars; ++extern const AVFilter ff_vsrc_pipewiregrab; + extern const AVFilter ff_vsrc_rgbtestsrc; + extern const AVFilter ff_vsrc_sierpinski; + extern const AVFilter ff_vsrc_smptebars; +diff --git a/libavfilter/vsrc_pipewiregrab.c b/libavfilter/vsrc_pipewiregrab.c +new file mode 100644 +index 0000000000..ff9c3468ab +--- /dev/null ++++ b/libavfilter/vsrc_pipewiregrab.c +@@ -0,0 +1,1373 @@ ++/* ++ * PipeWire input grabber (ScreenCast) ++ * Copyright (C) 2024 Savoir-faire Linux, Inc. ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++/** ++ * @file ++ * PipeWireGrab video source ++ * @author Firas Ashkar <firas.ashkar at savoirfairelinux.com> ++ * @author Abhishek Ojha <abhishek.ojha at savoirfairelinux.com> ++ * @author François-Simon Fauteux-Chapleau <francois-simon.fauteux-chapleau at savoirfairelinux.com> ++ */ ++ ++#include "config.h" ++ ++#include <fcntl.h> ++#include <linux/dma-buf.h> ++#include <math.h> ++#include <pthread.h> ++#include <stdatomic.h> ++#include <stdlib.h> ++#include <string.h> ++#include <sys/mman.h> ++#include <sys/queue.h> ++ ++#include "libavutil/internal.h" ++#include "libavutil/mathematics.h" ++#include "libavutil/opt.h" ++#include "libavutil/parseutils.h" ++#include "libavutil/time.h" ++#include "libavutil/avstring.h" ++#include "libavformat/avformat.h" ++#include "libavformat/internal.h" ++#include "libavutil/avassert.h" ++#include "avfilter.h" ++#include "internal.h" ++ ++#include <pipewire/pipewire.h> ++#include <pipewire/thread-loop.h> ++#include <spa/debug/types.h> ++#include <spa/param/video/format-utils.h> ++#include <spa/param/video/raw.h> ++#include <spa/param/video/type-info.h> ++ ++#include <gio/gio.h> ++#include <gio/gunixfdlist.h> ++ ++#ifndef __USE_XOPEN2K8 ++#define F_DUPFD_CLOEXEC \ ++ 1030 /* Duplicate file descriptor with close-on-exit set. */ ++#endif ++ ++#define REQUEST_PATH "/org/freedesktop/portal/desktop/request/%s/%s" ++#define BYTES_PER_PIXEL 4 /* currently all formats assume 4 bytes per pixel */ ++#define MAX_SPA_PARAM 4 /* max number of params for spa pod */ ++ ++#define CURSOR_META_SIZE(width, height) \ ++ (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + \ ++ width * height * 4) ++ ++/** ++ * PipeWire capture types ++ */ ++typedef enum { ++ DESKTOP_CAPTURE = 1, ++ WINDOW_CAPTURE = 2, ++} pw_capture_type; ++ ++/** ++ * XDG Desktop Portal supported cursor modes ++ */ ++enum PortalCursorMode { ++ PORTAL_CURSOR_MODE_HIDDEN = 1 << 0, ++ PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1, ++}; ++ ++typedef struct PipewireGrabContext { ++ const AVClass *class; ++ ++ pthread_cond_t pipewire_initialization_cond_var; ++ pthread_mutex_t pipewire_initialization_mutex; ++ atomic_int pipewire_initialization_over; ++ ++ int pw_init_called; ++ ++ pthread_mutex_t current_frame_mutex; ++ AVFrame *current_frame; ++ ++ GDBusConnection *connection; ++ GDBusProxy *proxy; ++ GCancellable *cancellable; ++ ++ char *sender_name; ++ char *session_handle; ++ ++ uint64_t pipewire_external_node; // only needed because FFmpeg doesn't have a uint32 AVOption type ++ uint32_t pipewire_node; ++ int pipewire_fd; ++ ++ uint32_t available_cursor_modes; ++ ++ GMainLoop *glib_main_loop; ++ struct pw_thread_loop *thread_loop; ++ struct pw_context *context; ++ ++ struct pw_core *core; ++ struct spa_hook core_listener; ++ ++ struct pw_stream *stream; ++ struct spa_hook stream_listener; ++ struct spa_video_info format; ++ ++ pw_capture_type capture_type; ++ ++ int draw_mouse; ++ ++ uint32_t width, height; ++ ++ size_t frame_size; ++ uint8_t Bpp; ++ enum AVPixelFormat av_pxl_format; ++ ++ int64_t time_frame; ++ int64_t frame_duration; ++ ++ AVRational framerate; ++ ++ int portal_error; ++ int pipewire_error; ++} PipewireGrabContext; ++ ++/** ++ * DBus method/event marshalling structure ++ */ ++struct DbusCallData { ++ AVFilterContext *ctx; ++ char *request_path; ++ guint signal_id; ++ gulong cancelled_id; ++}; ++ ++ ++#define OFFSET(x) offsetof(PipewireGrabContext, x) ++#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM ++static const AVOption pipewiregrab_options[] = { ++ { "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, { .str = "ntsc" }, 0, INT_MAX, FLAGS }, ++ { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS }, ++ { "capture_type", "set the capture type (1 for screen, 2 for window)", OFFSET(capture_type), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, 2, FLAGS }, ++ { "fd", "set file descriptor to be used by PipeWire", OFFSET(pipewire_fd), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, ++ { "node", "set PipeWire node", OFFSET(pipewire_external_node), AV_OPT_TYPE_UINT64, { .i64 = 0 }, 0, 0xffffffff, FLAGS }, ++ { NULL }, ++}; ++ ++AVFILTER_DEFINE_CLASS(pipewiregrab); ++ ++/** ++ * Helper function to allow portal_init_screencast to stop and return an error ++ * code if a DBus operation/callback fails. ++ * ++ * @param ctx ++ * @param error error code ++ * @param message error message ++ */ ++static void portal_abort(AVFilterContext *ctx, int error, const char *message) ++{ ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ pw_ctx->portal_error = error; ++ av_log(ctx, AV_LOG_ERROR, "Aborting: %s\n", message); ++ ++ if (pw_ctx->glib_main_loop && ++ g_main_loop_is_running(pw_ctx->glib_main_loop)) ++ g_main_loop_quit(pw_ctx->glib_main_loop); ++} ++ ++/** ++ * Callback to handle PipeWire core info events ++ * ++ * @param user_data pointer to the filter's AVFilterContext ++ * @param info pw_core_info ++ */ ++static void on_core_info_callback(void *user_data, const struct pw_core_info *info) ++{ ++ AVFilterContext *ctx = user_data; ++ av_log(ctx, AV_LOG_DEBUG, "Server version: %s\n", info->version); ++ av_log(ctx, AV_LOG_INFO, "Library version: %s\n", pw_get_library_version()); ++ av_log(ctx, AV_LOG_DEBUG, "Header version: %s\n", pw_get_headers_version()); ++} ++ ++/** ++ * Callback to handle PipeWire core done events ++ * ++ * @param user_data pointer to the filter's AVFilterContext ++ * @param id PipeWire object id of calling ++ * @param seq PipeWire object sequence ++ */ ++static void on_core_done_callback(void *user_data, uint32_t id, int seq) ++{ ++ AVFilterContext *ctx = user_data; ++ PipewireGrabContext *pw_ctx; ++ ++ if (!ctx || !ctx->priv) ++ return; ++ ++ pw_ctx = ctx->priv; ++ ++ if (id == PW_ID_CORE) ++ pw_thread_loop_signal(pw_ctx->thread_loop, false); ++} ++ ++/** ++ * Callback to handle Pipewire core error events ++ * ++ * @param user_data pointer to the filter's AVFilterContext ++ * @param id PipeWire object id of calling ++ * @param seq PipeWire object sequence ++ * @param res error number ++ * @param message error message ++ */ ++static void on_core_error_callback(void *user_data, uint32_t id, int seq, ++ int res, const char *message) ++{ ++ AVFilterContext *ctx = user_data; ++ PipewireGrabContext *pw_ctx; ++ ++ if (!ctx) ++ return; ++ ++ av_log(ctx, AV_LOG_ERROR, ++ "PipeWire core error: %s (id=%u, seq=%d, res=%d: %s)\n", ++ message, id, seq, res, g_strerror(-res)); ++ ++ pw_ctx = ctx->priv; ++ if (!pw_ctx) { ++ av_log(ctx, AV_LOG_ERROR, ++ "Invalid private context data\n"); ++ return; ++ } ++ ++ pw_thread_loop_signal(pw_ctx->thread_loop, false); ++ ++ pw_ctx->pipewire_error = res; ++ atomic_store(&pw_ctx->pipewire_initialization_over, 1); ++ pthread_cond_signal(&pw_ctx->pipewire_initialization_cond_var); ++} ++ ++/** ++ * PipeWire core events callbacks ++ */ ++static const struct pw_core_events core_events = { ++ PW_VERSION_CORE_EVENTS, ++ .info = on_core_info_callback, ++ .done = on_core_done_callback, ++ .error = on_core_error_callback, ++}; ++ ++/** ++ * Helper function: convert spa video format to AVPixelFormat ++ * ++ * @param video_format spa video format to convert ++ * @return the corresponding AVPixelFormat ++ */ ++static enum AVPixelFormat ++spa_video_format_to_av_pixel_format(enum spa_video_format video_format) ++{ ++ switch (video_format) { ++ case SPA_VIDEO_FORMAT_RGBA: ++ case SPA_VIDEO_FORMAT_RGBx: ++ return AV_PIX_FMT_RGBA; ++ ++ case SPA_VIDEO_FORMAT_BGRA: ++ case SPA_VIDEO_FORMAT_BGRx: ++ return AV_PIX_FMT_BGRA; ++ ++ default: ++ return AV_PIX_FMT_NONE; ++ } ++} ++ ++/** ++ * Callback to free a DbusCallData object's memory and unsubscribe from the ++ * associated dbus signal. ++ * ++ * @param ptr_dbus_call_data DBus marshalling structure ++ */ ++static void dbus_call_data_free(struct DbusCallData *ptr_dbus_call_data) ++{ ++ AVFilterContext *ctx; ++ PipewireGrabContext *pw_ctx; ++ ++ if (!ptr_dbus_call_data) ++ return; ++ ++ ctx = ptr_dbus_call_data->ctx; ++ if (!ctx || !ctx->priv) ++ return; ++ ++ pw_ctx = ctx->priv; ++ ++ if (ptr_dbus_call_data->signal_id) ++ g_dbus_connection_signal_unsubscribe(pw_ctx->connection, ++ ptr_dbus_call_data->signal_id); ++ ++ if (ptr_dbus_call_data->cancelled_id > 0) ++ g_signal_handler_disconnect(pw_ctx->cancellable, ++ ptr_dbus_call_data->cancelled_id); ++ ++ g_clear_pointer(&ptr_dbus_call_data->request_path, g_free); ++ av_free(ptr_dbus_call_data); ++} ++ ++/** ++ * DBus callback of cancelled events ++ * ++ * @param cancellable (not used) ++ * @param user_data DBus marshalling structure ++ */ ++static void on_cancelled_callback(GCancellable *cancellable, gpointer user_data) ++{ ++ struct DbusCallData *ptr_dbus_call_data = user_data; ++ AVFilterContext *ctx = ptr_dbus_call_data->ctx; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ if (!pw_ctx) ++ return; ++ ++ g_dbus_connection_call(pw_ctx->connection, "org.freedesktop.portal.Desktop", ++ ptr_dbus_call_data->request_path, ++ "org.freedesktop.portal.Request", "Close", NULL, ++ NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); ++ ++ av_log(ctx, AV_LOG_WARNING, "Portal request cancelled\n"); ++ ++ pw_ctx->portal_error = ECANCELED; ++ g_main_loop_quit(pw_ctx->glib_main_loop); ++} ++ ++/** ++ * PipeWire callback of parameters changed events ++ * ++ * @param user_data DBus marshalling structure ++ * @param id contains chan param type ++ * @param param pointer to changed param structure ++ */ ++static void on_stream_param_changed_callback(void *user_data, uint32_t id, ++ const struct spa_pod *param) ++{ ++ struct spa_pod_builder pod_builder; ++ const struct spa_pod *params[MAX_SPA_PARAM]; ++ uint32_t n_params = 0; ++ uint8_t params_buffer[4096]; ++ int result; ++ PipewireGrabContext *pw_ctx; ++ AVFilterContext *ctx = user_data; ++ ++ if (!ctx || !ctx->priv || !param) ++ return; ++ ++ if (id != SPA_PARAM_Format) { ++ av_log(ctx, AV_LOG_WARNING, ++ "Ignoring non-Format param change\n"); ++ return; ++ } ++ ++ pw_ctx = ctx->priv; ++ ++ result = spa_format_parse(param, &pw_ctx->format.media_type, ++ &pw_ctx->format.media_subtype); ++ if (result < 0) { ++ av_log(ctx, AV_LOG_ERROR, "Unable to parse media type\n"); ++ pw_ctx->pipewire_error = AVERROR(EINVAL); ++ goto end; ++ } ++ ++ if (pw_ctx->format.media_type != SPA_MEDIA_TYPE_video || ++ pw_ctx->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { ++ av_log(ctx, AV_LOG_ERROR, "Unexpected media type\n"); ++ pw_ctx->pipewire_error = AVERROR(EINVAL); ++ goto end; ++ } ++ ++ spa_format_video_raw_parse(param, &pw_ctx->format.info.raw); ++ ++ av_log(ctx, AV_LOG_INFO, "Negotiated format:\n"); ++ ++ av_log(ctx, AV_LOG_INFO, "Format: %d (%s)\n", ++ pw_ctx->format.info.raw.format, ++ spa_debug_type_find_name(spa_type_video_format, ++ pw_ctx->format.info.raw.format)); ++ av_log(ctx, AV_LOG_INFO, "Size: %dx%d\n", ++ pw_ctx->format.info.raw.size.width, ++ pw_ctx->format.info.raw.size.height); ++ av_log(ctx, AV_LOG_INFO, "Framerate: %d/%d\n", ++ pw_ctx->format.info.raw.framerate.num, ++ pw_ctx->format.info.raw.framerate.denom); ++ ++ pw_ctx->width = pw_ctx->format.info.raw.size.width; ++ pw_ctx->height = pw_ctx->format.info.raw.size.height; ++ pw_ctx->Bpp = BYTES_PER_PIXEL; ++ pw_ctx->frame_size = pw_ctx->width * pw_ctx->height * pw_ctx->Bpp; ++ if (pw_ctx->frame_size + AV_INPUT_BUFFER_PADDING_SIZE > INT_MAX) { ++ av_log(ctx, AV_LOG_ERROR, "Captured area is too large\n"); ++ pw_ctx->pipewire_error = AVERROR(EINVAL); ++ goto end; ++ } ++ ++ pw_ctx->av_pxl_format = ++ spa_video_format_to_av_pixel_format(pw_ctx->format.info.raw.format); ++ if (pw_ctx->av_pxl_format == AV_PIX_FMT_NONE) { ++ av_log(ctx, AV_LOG_ERROR, ++ "Unsupported buffer format: %d\n", pw_ctx->format.info.raw.format); ++ pw_ctx->pipewire_error = AVERROR(EINVAL); ++ goto end; ++ } ++ ++ /* Video crop */ ++ pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); ++ params[n_params++] = spa_pod_builder_add_object( ++ &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, ++ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), ++ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region))); ++ ++ /* Cursor */ ++ params[n_params++] = spa_pod_builder_add_object( ++ &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, ++ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, ++ SPA_POD_CHOICE_RANGE_Int(CURSOR_META_SIZE(64, 64), ++ CURSOR_META_SIZE(1, 1), ++ CURSOR_META_SIZE(1024, 1024))); ++ ++ /* Buffer options */ ++ params[n_params++] = spa_pod_builder_add_object( ++ &pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, ++ SPA_PARAM_BUFFERS_dataType, ++ SPA_POD_Int((1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd))); ++ ++ /* Meta header */ ++ params[n_params++] = spa_pod_builder_add_object( ++ &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, ++ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), ++ SPA_PARAM_META_size, ++ SPA_POD_Int(sizeof(struct spa_meta_header))); ++ ++ pw_stream_update_params(pw_ctx->stream, params, n_params); ++ ++end: ++ // Signal pipewiregrab_init that PipeWire initialization is over (either ++ // because it was completed successfully or because there was an error, in ++ // which case pw_ctx->pipewire_error will have been set to a nonzero value). ++ atomic_store(&pw_ctx->pipewire_initialization_over, 1); ++ pthread_cond_signal(&pw_ctx->pipewire_initialization_cond_var); ++} ++ ++/** ++ * PipeWire callback of state changed events ++ * ++ * @param user_data DBus marshalling structure ++ * @param old PipeWire stream old state ++ * @param state PipeWire stream current state ++ * @param error received error information ++ */ ++static void on_stream_state_changed_callback(void *user_data, ++ enum pw_stream_state old, ++ enum pw_stream_state state, ++ const char *error) ++{ ++ AVFilterContext *ctx = user_data; ++ if (!ctx) ++ return; ++ ++ av_log(ctx, AV_LOG_INFO, "stream state: \"%s\"\n", ++ pw_stream_state_as_string(state)); ++} ++ ++/** ++ * Find most recent buffer received in a PipeWire stream ++ * ++ * @param stream stream to get buffer from ++ * @return most recent buffer in the stream ++ */ ++static struct pw_buffer *find_most_recent_buffer_and_recycle_olders(struct pw_stream *stream) ++{ ++ struct pw_buffer *pw_buf = NULL; ++ while (1) { ++ struct pw_buffer *aux = pw_stream_dequeue_buffer(stream); ++ if (!aux) ++ break; ++ if (pw_buf) ++ pw_stream_queue_buffer(stream, pw_buf); ++ pw_buf = aux; ++ } ++ return pw_buf; ++} ++ ++/** ++ * Our data processing function ++ * ++ * @param user_data DBus marshalling structure ++ */ ++static void on_stream_process_callback(void *user_data) ++{ ++ struct spa_buffer *spa_buf = NULL; ++ struct pw_buffer *pw_buf = NULL; ++ uint8_t *map = NULL; ++ void *sdata = NULL; ++ struct spa_meta_header *header = NULL; ++ struct spa_meta_region *frame_region; ++ uint32_t frame_width; ++ uint32_t frame_height; ++ AVFilterContext *ctx = user_data; ++ PipewireGrabContext *pw_ctx = NULL; ++ ++ if (!ctx || !ctx->priv) ++ return; ++ ++ pw_ctx = ctx->priv; ++ ++ // We need to wait for pw_ctx->current_frame to have been allocated before ++ // we can use it to get frames from the PipeWire thread to FFmpeg ++ pthread_mutex_lock(&pw_ctx->current_frame_mutex); ++ if (!pw_ctx->current_frame) { ++ pthread_mutex_unlock(&pw_ctx->current_frame_mutex); ++ return; ++ } ++ pthread_mutex_unlock(&pw_ctx->current_frame_mutex); ++ ++ // Get a buffer from PipeWire ++ pw_buf = find_most_recent_buffer_and_recycle_olders(pw_ctx->stream); ++ if (!pw_buf) { ++ av_log(ctx, AV_LOG_ERROR, "Out of buffers\n"); ++ return; ++ } ++ ++ // Check whether the buffer is corrupted ++ spa_buf = pw_buf->buffer; ++ header = spa_buffer_find_meta_data(spa_buf, ++ SPA_META_Header, sizeof(*header)); ++ if (header && (header->flags & SPA_META_HEADER_FLAG_CORRUPTED)) { ++ av_log(ctx, AV_LOG_ERROR, "Corrupted PipeWire buffer\n"); ++ goto end; ++ } ++ ++ // Get a pointer to the buffer's data ++ if (spa_buf->datas[0].type == SPA_DATA_MemFd ) { ++ map = mmap(NULL, spa_buf->datas[0].maxsize + spa_buf->datas[0].mapoffset, ++ PROT_READ, MAP_PRIVATE, spa_buf->datas[0].fd, 0); ++ if (map == MAP_FAILED) { ++ av_log(ctx, AV_LOG_ERROR, "mmap failed! error %s\n", g_strerror(errno)); ++ goto end; ++ } ++ sdata = SPA_PTROFF(map, spa_buf->datas[0].mapoffset, uint8_t); ++ } else if (spa_buf->datas[0].type == SPA_DATA_MemPtr) { ++ if (spa_buf->datas[0].data == NULL) { ++ av_log(ctx, AV_LOG_ERROR, "No data\n"); ++ goto end; ++ } ++ map = NULL; ++ sdata = spa_buf->datas[0].data; ++ } else { ++ av_log(ctx, AV_LOG_ERROR, "Buffer is not valid\n"); ++ goto end; ++ } ++ ++ if ((frame_region = spa_buffer_find_meta_data(spa_buf, SPA_META_VideoCrop, ++ sizeof(*frame_region))) ++ && spa_meta_region_is_valid(frame_region)) { ++ frame_width = frame_region->region.size.width; ++ frame_height = frame_region->region.size.height; ++ } else { ++ frame_width = pw_ctx->width; ++ frame_height = pw_ctx->height; ++ } ++ ++ // Update current_frame with the new data ++ pthread_mutex_lock(&pw_ctx->current_frame_mutex); ++ memcpy(pw_ctx->current_frame->data[0], sdata, spa_buf->datas[0].chunk->size); ++ pw_ctx->current_frame->width = frame_width; ++ pw_ctx->current_frame->height = frame_height; ++ pthread_mutex_unlock(&pw_ctx->current_frame_mutex); ++ ++ // Cleanup ++ if (spa_buf->datas[0].type == SPA_DATA_MemFd) ++ munmap(map, spa_buf->datas[0].maxsize + spa_buf->datas[0].mapoffset); ++end: ++ pw_stream_queue_buffer(pw_ctx->stream, pw_buf); ++ return; ++} ++ ++static const struct pw_stream_events stream_events = { ++ PW_VERSION_STREAM_EVENTS, ++ .state_changed = on_stream_state_changed_callback, ++ .param_changed = on_stream_param_changed_callback, ++ .process = on_stream_process_callback, ++}; ++ ++static struct DbusCallData *subscribe_to_signal(AVFilterContext *ctx, ++ const char *path, ++ GDBusSignalCallback callback) ++{ ++ struct DbusCallData *ptr_dbus_call_data; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ ptr_dbus_call_data = (struct DbusCallData *)av_mallocz(sizeof(struct DbusCallData)); ++ if (!ptr_dbus_call_data) ++ return NULL; ++ ++ ptr_dbus_call_data->ctx = ctx; ++ ptr_dbus_call_data->request_path = g_strdup(path); ++ ptr_dbus_call_data->cancelled_id = ++ g_signal_connect(pw_ctx->cancellable, "cancelled", ++ G_CALLBACK(on_cancelled_callback), ++ ptr_dbus_call_data /* user_data */); ++ ptr_dbus_call_data->signal_id = g_dbus_connection_signal_subscribe( ++ pw_ctx->connection, "org.freedesktop.portal.Desktop" /*sender*/, ++ "org.freedesktop.portal.Request" /*interface_name*/, ++ "Response" /*member: dbus signal name*/, ++ ptr_dbus_call_data->request_path /*object_path*/, NULL, ++ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, callback, ptr_dbus_call_data, NULL); ++ ++ return ptr_dbus_call_data; ++} ++ ++static int play_pipewire_stream(AVFilterContext *ctx) ++{ ++ int ret; ++ const struct spa_pod *ptr_spa_pod; ++ uint8_t buffer[4096]; ++ struct spa_pod_builder spa_pod_bldr = { ++ 0, ++ }; ++ ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ pw_init(NULL, NULL); ++ pw_ctx->pw_init_called = 1; ++ ++ pw_ctx->thread_loop = ++ pw_thread_loop_new("thread loop", NULL); ++ if (!pw_ctx->thread_loop) { ++ av_log(ctx, AV_LOG_ERROR, "pw_thread_loop_new failed\n"); ++ return AVERROR(ENOMEM); ++ } ++ ++ pw_ctx->context = ++ pw_context_new(pw_thread_loop_get_loop(pw_ctx->thread_loop), NULL, 0); ++ if (!pw_ctx->context) { ++ av_log(ctx, AV_LOG_ERROR, "pw_context_new failed\n"); ++ ret = AVERROR(ENOMEM); ++ goto fail; ++ } ++ ++ if (pw_thread_loop_start(pw_ctx->thread_loop) < 0) { ++ av_log(ctx, AV_LOG_ERROR, "pw_thread_loop_start failed\n"); ++ ret = AVERROR(EFAULT); ++ goto fail; ++ } ++ ++ pw_thread_loop_lock(pw_ctx->thread_loop); ++ ++ // Core ++ pw_ctx->core = ++ pw_context_connect_fd(pw_ctx->context, ++ fcntl(pw_ctx->pipewire_fd, F_DUPFD_CLOEXEC, 3), ++ NULL, 0); ++ if (!pw_ctx->core) { ++ ret = AVERROR(errno); ++ av_log(ctx, AV_LOG_ERROR, "pw_context_connect_fd failed\n"); ++ pw_thread_loop_unlock(pw_ctx->thread_loop); ++ goto fail; ++ } ++ ++ pw_core_add_listener(pw_ctx->core, &pw_ctx->core_listener, &core_events, ++ ctx /* user_data */); ++ ++ // Stream ++ pw_ctx->stream = pw_stream_new( ++ pw_ctx->core, "wayland grab", ++ pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, ++ "Capture", PW_KEY_MEDIA_ROLE, "Screen", NULL)); ++ ++ if (!pw_ctx->stream) { ++ av_log(ctx, AV_LOG_ERROR, "pw_stream_new failed\n"); ++ ret = AVERROR(ENOMEM); ++ pw_thread_loop_unlock(pw_ctx->thread_loop); ++ goto fail; ++ } ++ ++ pw_stream_add_listener(pw_ctx->stream, &pw_ctx->stream_listener, ++ &stream_events, ctx /* user_data */); ++ ++ // Stream parameters ++ spa_pod_bldr = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); ++ ptr_spa_pod = spa_pod_builder_add_object( ++ &spa_pod_bldr, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, ++ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), ++ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), ++ SPA_FORMAT_VIDEO_format, ++ SPA_POD_CHOICE_ENUM_Id(4, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx, ++ SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA), ++ SPA_FORMAT_VIDEO_size, ++ SPA_POD_CHOICE_RANGE_Rectangle(&SPA_RECTANGLE(320, 240), ++ &SPA_RECTANGLE(1, 1), ++ &SPA_RECTANGLE(4096, 4096)), ++ SPA_FORMAT_VIDEO_framerate, ++ SPA_POD_CHOICE_RANGE_Fraction( ++ &SPA_FRACTION(pw_ctx->framerate.num, ++ pw_ctx->framerate.den), ++ &SPA_FRACTION(0, 1), &SPA_FRACTION(144, 1))); ++ ++ ret = pw_stream_connect( ++ pw_ctx->stream, PW_DIRECTION_INPUT, pw_ctx->pipewire_node, ++ PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, &ptr_spa_pod, 1); ++ if (ret != 0) { ++ av_log(ctx, AV_LOG_ERROR, "pw_stream_connect failed\n"); ++ pw_thread_loop_unlock(pw_ctx->thread_loop); ++ goto fail; ++ } ++ ++ av_log(ctx, AV_LOG_INFO, "Starting screen capture ...\n"); ++ pw_thread_loop_unlock(pw_ctx->thread_loop); ++ return 0; ++ ++fail: ++ if (pw_ctx->core) { ++ pw_core_disconnect(pw_ctx->core); ++ pw_ctx->core = NULL; ++ } ++ if (pw_ctx->context) { ++ pw_context_destroy(pw_ctx->context); ++ pw_ctx->context = NULL; ++ } ++ if (pw_ctx->thread_loop) { ++ pw_thread_loop_destroy(pw_ctx->thread_loop); ++ pw_ctx->thread_loop = NULL; ++ } ++ ++ return ret; ++} ++ ++static void portal_open_pipewire_remote(AVFilterContext *ctx) ++{ ++ GUnixFDList* fd_list = NULL; ++ GVariant* result = NULL; ++ GError* error = NULL; ++ int fd_index; ++ GVariantBuilder builder; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); ++ ++ result = g_dbus_proxy_call_with_unix_fd_list_sync( ++ pw_ctx->proxy, "OpenPipeWireRemote", ++ g_variant_new("(oa{sv})", pw_ctx->session_handle, &builder), ++ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &fd_list, pw_ctx->cancellable, ++ &error); ++ if (error) ++ goto fail; ++ ++ g_variant_get(result, "(h)", &fd_index); ++ g_variant_unref(result); ++ ++ pw_ctx->pipewire_fd = g_unix_fd_list_get(fd_list, fd_index, &error); ++ g_object_unref(fd_list); ++ if (error) ++ goto fail; ++ ++ g_main_loop_quit(pw_ctx->glib_main_loop); ++ return; ++ ++fail: ++ av_log(ctx, AV_LOG_ERROR, ++ "Error retrieving PipeWire fd: %s\n", error->message); ++ g_error_free(error); ++ portal_abort(ctx, EIO, "Failed to open PipeWire remote"); ++} ++ ++static void on_start_response_received_callback( ++ GDBusConnection *connection, const char *sender_name, ++ const char *object_path, const char *interface_name, ++ const char *signal_name, GVariant *parameters, gpointer user_data) ++{ ++ GVariant* stream_properties = NULL; ++ GVariant* streams = NULL; ++ GVariant* result = NULL; ++ GVariantIter iter; ++ uint32_t response; ++ ++ struct DbusCallData *ptr_dbus_call_data = user_data; ++ AVFilterContext *ctx = ptr_dbus_call_data->ctx; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ if (!pw_ctx) { ++ portal_abort(ctx, EINVAL, "Invalid private context data"); ++ return; ++ } ++ ++ g_clear_pointer(&ptr_dbus_call_data, dbus_call_data_free); ++ ++ g_variant_get(parameters, "(u@a{sv})", &response, &result); ++ ++ if (response) { ++ g_variant_unref(result); ++ portal_abort( ++ ctx, EACCES, "Failed to start screencast, denied or cancelled by user"); ++ return; ++ } ++ ++ streams = g_variant_lookup_value(result, "streams", G_VARIANT_TYPE_ARRAY); ++ ++ g_variant_iter_init(&iter, streams); ++ av_assert0(g_variant_iter_n_children(&iter) == 1); ++ ++ g_variant_iter_loop(&iter, "(u@a{sv})", &pw_ctx->pipewire_node, ++ &stream_properties); ++ ++ av_log(ctx, AV_LOG_INFO, "Monitor selected, setting up screencast\n\n"); ++ ++ g_variant_unref(result); ++ g_variant_unref(streams); ++ g_variant_unref(stream_properties); ++ ++ portal_open_pipewire_remote(ctx); ++} ++ ++static int portal_call_dbus_method(AVFilterContext *ctx, ++ const gchar *method_name, GVariant *parameters) ++{ ++ GVariant* result; ++ GError* error = NULL; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ result = g_dbus_proxy_call_sync(pw_ctx->proxy, method_name, parameters, ++ G_DBUS_CALL_FLAGS_NONE, -1, ++ pw_ctx->cancellable, &error); ++ if (error) { ++ av_log(ctx, AV_LOG_ERROR, ++ "Call to DBus method '%s' failed: %s\n", ++ method_name, error->message); ++ g_error_free(error); ++ return EIO; ++ } ++ g_variant_unref(result); ++ return 0; ++} ++ ++static void portal_start(AVFilterContext *ctx) ++{ ++ int ret; ++ const char *request_token; ++ g_autofree char *request_path; ++ GVariantBuilder builder; ++ GVariant *parameters; ++ struct DbusCallData *ptr_dbus_call_data; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ if (!pw_ctx) { ++ portal_abort(ctx, EINVAL, "Invalid private context data"); ++ return; ++ } ++ ++ request_token = "pipewiregrabStart"; ++ request_path = g_strdup_printf(REQUEST_PATH, pw_ctx->sender_name, request_token); ++ ++ av_log(ctx, AV_LOG_INFO, "Asking for monitor…\n"); ++ ++ ptr_dbus_call_data = subscribe_to_signal(ctx, request_path, ++ on_start_response_received_callback); ++ if (!ptr_dbus_call_data) { ++ portal_abort(ctx, ENOMEM, "Failed to allocate DBus call data"); ++ return; ++ } ++ ++ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); ++ g_variant_builder_add(&builder, "{sv}", "handle_token", ++ g_variant_new_string(request_token)); ++ parameters = g_variant_new("(osa{sv})", pw_ctx->session_handle, "", &builder); ++ ++ ret = portal_call_dbus_method(ctx, "Start", parameters); ++ if (ret != 0) ++ portal_abort(ctx, ret, "Failed to start screen cast session"); ++} ++ ++static void on_select_sources_response_received_callback( ++ GDBusConnection *connection, const char *sender_name, ++ const char *object_path, const char *interface_name, ++ const char *signal_name, GVariant *parameters, gpointer user_data) ++{ ++ GVariant* ret = NULL; ++ uint32_t response; ++ struct DbusCallData *ptr_dbus_call_data = user_data; ++ AVFilterContext *ctx = ptr_dbus_call_data->ctx; ++ ++ av_log(ctx, AV_LOG_INFO, ++ "Response to select source received\n"); ++ ++ g_clear_pointer(&ptr_dbus_call_data, dbus_call_data_free); ++ ++ g_variant_get(parameters, "(u@a{sv})", &response, &ret); ++ g_variant_unref(ret); ++ if (response) { ++ portal_abort( ++ ctx, EACCES, "Failed to select screencast sources, denied or cancelled by user"); ++ return; ++ } ++ ++ portal_start(ctx); ++} ++ ++static void portal_select_sources(AVFilterContext *ctx) ++{ ++ int ret; ++ const char *request_token; ++ g_autofree char *request_path; ++ GVariantBuilder builder; ++ GVariant *parameters; ++ struct DbusCallData *ptr_dbus_call_data; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ request_token = "pipewiregrabSelectSources"; ++ request_path = g_strdup_printf(REQUEST_PATH, pw_ctx->sender_name, request_token); ++ ++ ptr_dbus_call_data = subscribe_to_signal(ctx, request_path, ++ on_select_sources_response_received_callback); ++ if (!ptr_dbus_call_data) { ++ portal_abort(ctx, ENOMEM, "Failed to allocate DBus call data"); ++ return; ++ } ++ ++ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); ++ g_variant_builder_add(&builder, "{sv}", "types", ++ g_variant_new_uint32(pw_ctx->capture_type)); ++ g_variant_builder_add(&builder, "{sv}", "multiple", ++ g_variant_new_boolean(FALSE)); ++ g_variant_builder_add(&builder, "{sv}", "handle_token", ++ g_variant_new_string(request_token)); ++ ++ if ((pw_ctx->available_cursor_modes & PORTAL_CURSOR_MODE_EMBEDDED) ++ && pw_ctx->draw_mouse) ++ g_variant_builder_add(&builder, "{sv}", "cursor_mode", ++ g_variant_new_uint32(PORTAL_CURSOR_MODE_EMBEDDED)); ++ else ++ g_variant_builder_add(&builder, "{sv}", "cursor_mode", ++ g_variant_new_uint32(PORTAL_CURSOR_MODE_HIDDEN)); ++ parameters = g_variant_new("(oa{sv})", pw_ctx->session_handle, &builder); ++ ++ ret = portal_call_dbus_method(ctx, "SelectSources", parameters); ++ if (ret != 0) ++ portal_abort(ctx, ret, "Failed to select sources for screen cast session"); ++} ++ ++static void on_create_session_response_received_callback( ++ GDBusConnection *connection, const char *sender_name, ++ const char *object_path, const char *interface_name, ++ const char *signal_name, GVariant *parameters, gpointer user_data) ++{ ++ uint32_t response; ++ GVariant* result = NULL; ++ struct DbusCallData *ptr_dbus_call_data = user_data; ++ AVFilterContext *ctx = ptr_dbus_call_data->ctx; ++ ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ if (!pw_ctx) { ++ portal_abort(ctx, EINVAL, "Invalid private context data"); ++ return; ++ } ++ ++ g_clear_pointer(&ptr_dbus_call_data, dbus_call_data_free); ++ ++ g_variant_get(parameters, "(u@a{sv})", &response, &result); ++ ++ if (response != 0) { ++ g_variant_unref(result); ++ portal_abort( ++ ctx, EACCES, "Failed to create screencast session, denied or cancelled by user"); ++ return; ++ } ++ ++ av_log(ctx, AV_LOG_DEBUG, "Screencast session created\n"); ++ ++ g_variant_lookup(result, "session_handle", "s", &pw_ctx->session_handle); ++ g_variant_unref(result); ++ ++ portal_select_sources(ctx); ++} ++ ++/** ++ * Function to create a screen cast session ++ * ++ * @param ctx ++ */ ++static void portal_create_session(AVFilterContext *ctx) ++{ ++ int ret; ++ GVariantBuilder builder; ++ GVariant *parameters; ++ const char *request_token; ++ g_autofree char *request_path; ++ struct DbusCallData *ptr_dbus_call_data; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ request_token = "pipewiregrabCreateSession"; ++ request_path = g_strdup_printf(REQUEST_PATH, pw_ctx->sender_name, request_token); ++ ++ ptr_dbus_call_data = subscribe_to_signal(ctx, request_path, ++ on_create_session_response_received_callback); ++ if (!ptr_dbus_call_data) { ++ portal_abort(ctx, ENOMEM, "Failed to allocate DBus call data"); ++ return; ++ } ++ ++ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); ++ g_variant_builder_add(&builder, "{sv}", "handle_token", ++ g_variant_new_string(request_token)); ++ g_variant_builder_add(&builder, "{sv}", "session_handle_token", ++ g_variant_new_string("pipewiregrab")); ++ parameters = g_variant_new("(a{sv})", &builder); ++ ++ ret = portal_call_dbus_method(ctx, "CreateSession", parameters); ++ if (ret != 0) ++ portal_abort(ctx, ret, "Failed to create screen cast session"); ++} ++ ++/** ++ * Helper function: get available cursor modes and update the ++ * PipewireGrabContext accordingly ++ * ++ * @param ctx ++ */ ++static void portal_update_available_cursor_modes(AVFilterContext *ctx) ++{ ++ GVariant* cached_cursor_modes = NULL; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ cached_cursor_modes = ++ g_dbus_proxy_get_cached_property(pw_ctx->proxy, "AvailableCursorModes"); ++ ++ pw_ctx->available_cursor_modes = ++ cached_cursor_modes ? g_variant_get_uint32(cached_cursor_modes) : 0; ++ ++ g_variant_unref(cached_cursor_modes); ++} ++ ++static int create_dbus_proxy(AVFilterContext *ctx) ++{ ++ GError* error = NULL; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ pw_ctx->proxy = g_dbus_proxy_new_sync( ++ pw_ctx->connection, G_DBUS_PROXY_FLAGS_NONE, NULL, ++ "org.freedesktop.portal.Desktop", ++ "/org/freedesktop/portal/desktop", ++ "org.freedesktop.portal.ScreenCast", NULL, &error); ++ if (error) { ++ av_log(ctx, AV_LOG_ERROR, ++ "Error creating proxy: %s\n", error->message); ++ g_error_free(error); ++ return EPERM; ++ } ++ return 0; ++} ++ ++/** ++ * Create DBus connection and related objects ++ * ++ * @param ctx ++ */ ++static int create_dbus_connection(AVFilterContext *ctx) ++{ ++ char *aux; ++ GError* error = NULL; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ ++ pw_ctx->cancellable = g_cancellable_new(); ++ ++ pw_ctx->connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); ++ if (error) { ++ av_log(ctx, AV_LOG_ERROR, ++ "Error getting session bus: %s\n", error->message); ++ g_error_free(error); ++ return EPERM; ++ } ++ ++ aux = g_strdup(g_dbus_connection_get_unique_name(pw_ctx->connection) + 1); ++ pw_ctx->sender_name = av_strireplace(aux, ".", "_"); ++ av_log(ctx, AV_LOG_DEBUG, "Initialized (sender name: %s)\n", ++ pw_ctx->sender_name); ++ return 0; ++} ++ ++ ++/** ++ * Use XDG Desktop Portal's ScreenCast interface to open a file descriptor that ++ * can be used by PipeWire to access the screen cast streams. ++ * (https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html) ++ * ++ * @param ctx ++ */ ++static int portal_init_screencast(AVFilterContext *ctx) ++{ ++ int ret = 0; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ GMainContext *glib_main_context; ++ ++ // Create a new GLib context and set it as the default for the current thread. ++ // This ensures that the callbacks from DBus operations started in this thread are ++ // handled by the GLib main loop defined below, even if pipewiregrab_init was ++ // called by a program which also uses GLib and already had its own main loop running. ++ glib_main_context = g_main_context_new(); ++ g_main_context_push_thread_default(glib_main_context); ++ pw_ctx->glib_main_loop = g_main_loop_new(glib_main_context, FALSE); ++ if (!pw_ctx->glib_main_loop) { ++ av_log(ctx, AV_LOG_ERROR, "g_main_loop_new failed\n"); ++ ret = ENOMEM; ++ } ++ ++ ret = create_dbus_connection(ctx); ++ if (ret != 0) ++ goto exit_glib_loop; ++ ++ ret = create_dbus_proxy(ctx); ++ if (ret != 0) ++ goto exit_glib_loop; ++ ++ portal_update_available_cursor_modes(ctx); ++ portal_create_session(ctx); ++ if (pw_ctx->portal_error) { ++ ret = pw_ctx->portal_error; ++ goto exit_glib_loop; ++ } ++ ++ g_main_loop_run(pw_ctx->glib_main_loop); ++ // The main loop will run until it's stopped by portal_open_pipewire_remote (if ++ // all DBus method calls were successfully), portal_abort (in case of error) or ++ // on_cancelled_callback (if a DBus request is cancelled). ++ // In the latter two cases, pw_ctx->portal_error gets set to a nonzero value. ++ if (pw_ctx->portal_error) ++ ret = pw_ctx->portal_error; ++ ++exit_glib_loop: ++ g_main_loop_unref(pw_ctx->glib_main_loop); ++ pw_ctx->glib_main_loop = NULL; ++ g_main_context_pop_thread_default(glib_main_context); ++ g_main_context_unref(glib_main_context); ++ ++ return AVERROR(ret); ++} ++ ++static av_cold int pipewiregrab_init(AVFilterContext *ctx) ++{ ++ int ret; ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ if (!pw_ctx) { ++ av_log(ctx, AV_LOG_ERROR, ++ "Invalid private context data!\n"); ++ return AVERROR(EINVAL); ++ } ++ ++ atomic_init(&pw_ctx->pipewire_initialization_over, 0); ++ pthread_cond_init(&pw_ctx->pipewire_initialization_cond_var, NULL); ++ pthread_mutex_init(&pw_ctx->pipewire_initialization_mutex, NULL); ++ pthread_mutex_init(&pw_ctx->current_frame_mutex, NULL); ++ ++ pw_ctx->pipewire_node = pw_ctx->pipewire_external_node; ++ if (pw_ctx->pipewire_fd == 0) { ++ ret = portal_init_screencast(ctx); ++ if (ret != 0) { ++ atomic_store(&pw_ctx->pipewire_initialization_over, 1); ++ pthread_cond_signal(&pw_ctx->pipewire_initialization_cond_var); ++ return ret; ++ } ++ } ++ ++ ret = play_pipewire_stream(ctx); ++ if (ret != 0) ++ return ret; ++ ++ // Wait until PipeWire initialization is over ++ pthread_mutex_lock(&pw_ctx->pipewire_initialization_mutex); ++ while (!atomic_load(&pw_ctx->pipewire_initialization_over)) { ++ pthread_cond_wait(&pw_ctx->pipewire_initialization_cond_var, ++ &pw_ctx->pipewire_initialization_mutex); ++ } ++ pthread_mutex_unlock(&pw_ctx->pipewire_initialization_mutex); ++ ++ if (pw_ctx->pipewire_error) ++ return pw_ctx->pipewire_error; ++ ++ return 0; ++} ++ ++static void pipewiregrab_uninit(AVFilterContext *ctx) ++{ ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ if (!pw_ctx) ++ return; ++ ++ if (pw_ctx->glib_main_loop && ++ g_main_loop_is_running(pw_ctx->glib_main_loop)) { ++ // Cancel ongoing DBus operation, if any ++ g_cancellable_cancel(pw_ctx->cancellable); ++ pthread_mutex_lock(&pw_ctx->pipewire_initialization_mutex); ++ while (!atomic_load(&pw_ctx->pipewire_initialization_over)) { ++ pthread_cond_wait(&pw_ctx->pipewire_initialization_cond_var, ++ &pw_ctx->pipewire_initialization_mutex); ++ } ++ pthread_mutex_unlock(&pw_ctx->pipewire_initialization_mutex); ++ } ++ g_clear_object(&pw_ctx->cancellable); ++ ++ // PipeWire cleanup ++ if (pw_ctx->thread_loop) { ++ pw_thread_loop_signal(pw_ctx->thread_loop, false); ++ pw_thread_loop_unlock(pw_ctx->thread_loop); ++ pw_thread_loop_stop(pw_ctx->thread_loop); ++ } ++ if (pw_ctx->stream) { ++ pw_stream_disconnect(pw_ctx->stream); ++ g_clear_pointer(&pw_ctx->stream, pw_stream_destroy); ++ pw_ctx->stream = NULL; ++ } ++ if (pw_ctx->core){ ++ pw_core_disconnect(pw_ctx->core); ++ pw_ctx->core = NULL; ++ } ++ if (pw_ctx->context) { ++ pw_context_destroy(pw_ctx->context); ++ pw_ctx->context = NULL; ++ } ++ if (pw_ctx->thread_loop) { ++ pw_thread_loop_destroy(pw_ctx->thread_loop); ++ pw_ctx->thread_loop = NULL; ++ } ++ if (pw_ctx->pw_init_called) { ++ pw_deinit(); ++ pw_ctx->pw_init_called = 0; ++ } ++ if (pw_ctx->pipewire_fd > 0) { ++ close(pw_ctx->pipewire_fd); ++ pw_ctx->pipewire_fd = 0; ++ } ++ av_frame_free(&pw_ctx->current_frame); ++ ++ // DBus cleanup ++ if (pw_ctx->session_handle) { ++ g_dbus_connection_call( ++ pw_ctx->connection, "org.freedesktop.portal.Desktop", ++ pw_ctx->session_handle, "org.freedesktop.portal.Session", "Close", ++ NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); ++ ++ g_clear_pointer(&pw_ctx->session_handle, g_free); ++ } ++ g_clear_object(&pw_ctx->connection); ++ g_clear_object(&pw_ctx->proxy); ++ g_clear_pointer(&pw_ctx->sender_name, g_free); ++} ++ ++static int pipewiregrab_config_props(AVFilterLink *outlink) ++{ ++ AVFrame *frame; ++ PipewireGrabContext *pw_ctx = outlink->src->priv; ++ ++ AVRational time_base = av_inv_q(pw_ctx->framerate); ++ pw_ctx->frame_duration = av_rescale_q(1, time_base, AV_TIME_BASE_Q); ++ pw_ctx->time_frame = av_gettime_relative(); ++ ++ outlink->w = pw_ctx->width; ++ outlink->h = pw_ctx->height; ++ outlink->time_base = AV_TIME_BASE_Q; ++ outlink->frame_rate = pw_ctx->framerate; ++ ++ frame = ff_get_video_buffer(outlink, pw_ctx->width, pw_ctx->height); ++ if (!frame) ++ return AVERROR(ENOMEM); ++ pthread_mutex_lock(&pw_ctx->current_frame_mutex); ++ pw_ctx->current_frame = frame; ++ pthread_mutex_unlock(&pw_ctx->current_frame_mutex); ++ ++ return 0; ++} ++ ++/** ++ * Helper function: calculate the wait time based ++ * on the frame duration ++ * ++ * @param pw_ctx ++ * @return current time ++ */ ++static int64_t wait_frame(PipewireGrabContext *pw_ctx) ++{ ++ int64_t curtime, delay; ++ ++ /* Calculate the time of the next frame */ ++ pw_ctx->time_frame += pw_ctx->frame_duration; ++ ++ /* wait based on the frame rate */ ++ while (1) { ++ curtime = av_gettime_relative(); ++ delay = pw_ctx->time_frame - curtime; ++ if (delay <= 0) ++ break; ++ av_usleep(delay); ++ } ++ ++ return curtime; ++} ++ ++static int pipewiregrab_request_frame(AVFilterLink *outlink) ++{ ++ int ret; ++ PipewireGrabContext *pw_ctx = outlink->src->priv; ++ AVFrame *frame = av_frame_alloc(); ++ if (!frame) ++ return AVERROR(ENOMEM); ++ ++ wait_frame(pw_ctx); ++ ++ pthread_mutex_lock(&pw_ctx->current_frame_mutex); ++ ret = av_frame_ref(frame, pw_ctx->current_frame); ++ pthread_mutex_unlock(&pw_ctx->current_frame_mutex); ++ if (ret < 0) { ++ av_frame_free(&frame); ++ return ret; ++ } ++ ++ frame->pts = av_gettime(); ++ frame->duration = pw_ctx->frame_duration; ++ frame->sample_aspect_ratio = (AVRational) {1, 1}; ++ frame->format = pw_ctx->av_pxl_format; ++ ++ return ff_filter_frame(outlink, frame); ++} ++ ++static int pipewiregrab_query_formats(AVFilterContext *ctx) ++{ ++ PipewireGrabContext *pw_ctx = ctx->priv; ++ enum AVPixelFormat pix_fmts[] = {pw_ctx->av_pxl_format, AV_PIX_FMT_NONE}; ++ ++ return ff_set_common_formats_from_list(ctx, pix_fmts); ++} ++ ++static const AVFilterPad pipewiregrab_outputs[] = { ++ { ++ .name = "default", ++ .type = AVMEDIA_TYPE_VIDEO, ++ .request_frame = pipewiregrab_request_frame, ++ .config_props = pipewiregrab_config_props, ++ }, ++}; ++ ++const AVFilter ff_vsrc_pipewiregrab= { ++ .name = "pipewiregrab", ++ .description = NULL_IF_CONFIG_SMALL("Capture screen or window using PipeWire."), ++ .priv_size = sizeof(struct PipewireGrabContext), ++ .priv_class = &pipewiregrab_class, ++ .init = pipewiregrab_init, ++ .uninit = pipewiregrab_uninit, ++ .inputs = NULL, ++ FILTER_OUTPUTS(pipewiregrab_outputs), ++ FILTER_QUERY_FUNC(pipewiregrab_query_formats), ++}; diff --git a/gnu/packages/patches/gwenview-kimageannotator.patch b/gnu/packages/patches/gwenview-kimageannotator.patch new file mode 100644 index 0000000000..92b994a0b5 --- /dev/null +++ b/gnu/packages/patches/gwenview-kimageannotator.patch @@ -0,0 +1,29 @@ +Submitted By: Douglas R. Reno <renodr at linuxfromscratch dot org> +Date: 2024-02-26 +Initial Package Version: 23.08.5 +Upstream Status: Rejected (Qt6 port is primary now) +Origin: Self +Description: Fixes building Gwenview with recent kImageAnnotator + and kColorPicker versions by adjusting their paths + and FOUND variables so that the correct libraries are + selected. + +--- gwenview-23.08.5.orig/CMakeLists.txt 2024-02-26 15:02:29.702754535 -0600 ++++ gwenview-23.08.5/CMakeLists.txt 2024-02-26 15:09:48.012866877 -0600 +@@ -166,11 +166,11 @@ if(NOT WITHOUT_X11) + endif() + + if (QT_MAJOR_VERSION STREQUAL "5") +- find_package(kImageAnnotator) +- set_package_properties(kImageAnnotator PROPERTIES URL "https://github.com/ksnip/kImageAnnotator" DESCRIPTION "The kImageAnnotator library provides tools to annotate" TYPE REQUIRED) +- if(kImageAnnotator_FOUND) +- set(KIMAGEANNOTATOR_FOUND 1) +- find_package(kColorPicker REQUIRED) ++ find_package(kImageAnnotator-Qt5) ++ set_package_properties(kImageAnnotator-Qt5 PROPERTIES URL "https://github.com/ksnip/kImageAnnotator" DESCRIPTION "The kImageAnnotator library provides tools to annotate" TYPE REQUIRED) ++ if(kImageAnnotator-Qt5_FOUND) ++ set(kImageAnnotator_FOUND 1) ++ find_package(kColorPicker-Qt5 REQUIRED) + if(NOT kImageAnnotator_VERSION VERSION_LESS 0.5.0) + set(KIMAGEANNOTATOR_CAN_LOAD_TRANSLATIONS 1) + endif() diff --git a/gnu/packages/patches/jami-qml-tests-discovery.patch b/gnu/packages/patches/jami-qml-tests-discovery.patch deleted file mode 100644 index 11fd69571c..0000000000 --- a/gnu/packages/patches/jami-qml-tests-discovery.patch +++ /dev/null @@ -1,15 +0,0 @@ -Upstream status: https://review.jami.net/c/jami-client-qt/+/25640 - -diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt -index d50908cf..587c9d15 100644 ---- a/tests/CMakeLists.txt -+++ b/tests/CMakeLists.txt -@@ -73,6 +73,8 @@ endif() - - string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE) - -+set(QUICK_TEST_SOURCE_DIR "${CMAKE_SOURCE_DIR}tests/qml/src") -+ - set(QML_TESTS_SOURCE_FILES - ${CMAKE_SOURCE_DIR}/tests/qml/main.cpp - ${TEST_QML_RESOURCES} diff --git a/gnu/packages/patches/jami-qwindowkit.patch b/gnu/packages/patches/jami-qwindowkit.patch new file mode 100644 index 0000000000..65248a6940 --- /dev/null +++ b/gnu/packages/patches/jami-qwindowkit.patch @@ -0,0 +1,37 @@ +Upstream-status: https://lists.gnu.org/archive/html/jami/2024-03/msg00008.html + +This makes it possible to use the system-provided qwindowkit library. + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 6d2dccfb..8dedff50 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -100,13 +100,13 @@ endif() + + # qwindowkit (frameless window) + add_fetch_content( +- TARGET qwindowkit ++ TARGET QWindowKit + URL https://github.com/stdware/qwindowkit.git + BRANCH 79b1f3110754f9c21af2d7dacbd07b1a9dbaf6ef + PATCHES ${QWINDOWKIT_PATCHES} + OPTIONS ${QWINDOWKIT_OPTIONS} + ) +-list(APPEND CLIENT_INCLUDE_DIRS ${QWindowKit_BINARY_DIR}/include) ++ + list(APPEND CLIENT_LIBS QWindowKit::Quick) + + set(CMAKE_AUTOMOC ON) +diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt +index b2730b71..4960899e 100644 +--- a/tests/CMakeLists.txt ++++ b/tests/CMakeLists.txt +@@ -48,7 +48,7 @@ target_include_directories(test_common_obj PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src) + target_link_directories(test_common_obj PRIVATE ${CLIENT_LINK_DIRS}) +-target_link_libraries(test_common_obj ${QML_TEST_LIBS}) ++target_link_libraries(test_common_obj ${QML_TEST_LIBS} ${CLIENT_LIBS}) + target_compile_definitions(test_common_obj PRIVATE BUILD_TESTING="ON") + + set(COMMON_TESTS_SOURCES diff --git a/gnu/packages/patches/jami-tests-qtwebengine-ifdef-to-if.patch b/gnu/packages/patches/jami-tests-qtwebengine-ifdef-to-if.patch new file mode 100644 index 0000000000..63bfde6af0 --- /dev/null +++ b/gnu/packages/patches/jami-tests-qtwebengine-ifdef-to-if.patch @@ -0,0 +1,26 @@ +Upstream-status: https://lists.gnu.org/archive/html/jami/2024-03/msg00005.html + +Fix macro value checking. + +diff --git a/tests/qml/main.cpp b/tests/qml/main.cpp +index 2fbecebe..8cb3de69 100644 +--- a/tests/qml/main.cpp ++++ b/tests/qml/main.cpp +@@ -35,7 +35,7 @@ + #include <QtQuickTest/quicktest.h> + #include <QSignalSpy> + +-#ifdef WITH_WEBENGINE ++#if WITH_WEBENGINE + #include <QtWebEngineCore> + #include <QtWebEngineQuick> + #endif +@@ -192,7 +192,7 @@ main(int argc, char** argv) + // Allow the user to enable fatal warnings for certain tests. + Utils::remove_argument(argv, argc, "--failonwarn", [&]() { qputenv("QT_FATAL_WARNINGS", "1"); }); + +-#ifdef WITH_WEBENGINE ++#if WITH_WEBENGINE + QtWebEngineQuick::initialize(); + #endif + QTEST_SET_MAIN_SOURCE_PATH diff --git a/gnu/packages/patches/jami-unbundle-dependencies.patch b/gnu/packages/patches/jami-unbundle-dependencies.patch index dab82b26cb..2732087daf 100644 --- a/gnu/packages/patches/jami-unbundle-dependencies.patch +++ b/gnu/packages/patches/jami-unbundle-dependencies.patch @@ -16,13 +16,14 @@ Change-Id: I637959fefce6a21b0ee73a793acb6c3c42dcdce0 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt -index 38e7a4e2..3f1bd599 100644 +index e802357f..6d2dccfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -545,19 +545,33 @@ add_subdirectory(3rdparty/SortFilterProxyModel) +@@ -600,20 +600,34 @@ add_subdirectory(3rdparty/SortFilterProxyModel) set(SFPM_OBJECTS $<TARGET_OBJECTS:SortFilterProxyModel>) # md4c +-set(BUILD_MD2HTML_EXECUTABLE OFF CACHE BOOL "Don't build md2html executable" FORCE) -set(BUILD_SHARED_LIBS OFF CACHE BOOL "Don't build shared md4c library" FORCE) -add_subdirectory(3rdparty/md4c EXCLUDE_FROM_ALL) -list(APPEND CLIENT_LINK_DIRS ${MD4C_BINARY_DIR}/src) @@ -34,6 +35,7 @@ index 38e7a4e2..3f1bd599 100644 + list(APPEND CLIENT_LIBS md4c::md4c-html) +else() + message("Using bundled md4c-html library") ++ set(BUILD_MD2HTML_EXECUTABLE OFF CACHE BOOL "Don't build md2html executable" FORCE) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Don't build shared md4c library" FORCE) + add_subdirectory(3rdparty/md4c EXCLUDE_FROM_ALL) + list(APPEND CLIENT_LINK_DIRS ${MD4C_BINARY_DIR}/src) diff --git a/gnu/packages/patches/libarchive-remove-potential-backdoor.patch b/gnu/packages/patches/libarchive-remove-potential-backdoor.patch new file mode 100644 index 0000000000..2b9a9e2ffe --- /dev/null +++ b/gnu/packages/patches/libarchive-remove-potential-backdoor.patch @@ -0,0 +1,47 @@ +Remove code added by 'JiaT75', the malicious actor that backdoored `xz`: + +https://github.com/libarchive/libarchive/pull/2101 + +At libarchive, they are reviewing all code contributed by this actor: + +https://github.com/libarchive/libarchive/issues/2103 + +See the original disclosure and subsequent discussion for more +information about this incident: + +https://seclists.org/oss-sec/2024/q1/268 + +Patch copied from upstream source repository: + +https://github.com/libarchive/libarchive/pull/2101/commits/e200fd8abfb4cf895a1cab4d89b67e6eefe83942 + +From 6110e9c82d8ba830c3440f36b990483ceaaea52c Mon Sep 17 00:00:00 2001 +From: Ed Maste <emaste@freebsd.org> +Date: Fri, 29 Mar 2024 18:02:06 -0400 +Subject: [PATCH] tar: make error reporting more robust and use correct errno + (#2101) + +As discussed in #1609. +--- + tar/read.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/tar/read.c b/tar/read.c +index af3d3f42..a7f14a07 100644 +--- a/tar/read.c ++++ b/tar/read.c +@@ -371,8 +371,9 @@ read_archive(struct bsdtar *bsdtar, char mode, struct archive *writer) + if (r != ARCHIVE_OK) { + if (!bsdtar->verbose) + safe_fprintf(stderr, "%s", archive_entry_pathname(entry)); +- fprintf(stderr, ": %s: ", archive_error_string(a)); +- fprintf(stderr, "%s", strerror(errno)); ++ safe_fprintf(stderr, ": %s: %s", ++ archive_error_string(a), ++ strerror(archive_errno(a))); + if (!bsdtar->verbose) + fprintf(stderr, "\n"); + bsdtar->return_value = 1; +-- +2.41.0 + diff --git a/gnu/packages/patches/opencolorio-fix-build-with-gcc11.patch b/gnu/packages/patches/opencolorio-fix-build-with-gcc11.patch deleted file mode 100644 index 06507db206..0000000000 --- a/gnu/packages/patches/opencolorio-fix-build-with-gcc11.patch +++ /dev/null @@ -1,37 +0,0 @@ -Fix build failure with GCC 11: - ------- -[...] -/tmp/guix-build-opencolorio-1.1.1.drv-0/source/src/core/ImageDesc.cpp:60:51: error: ‘this’ pointer is null [-Werror=nonnull] - 60 | os << "width=" << packedImg->getWidth() << ", "; - | ^ -/tmp/guix-build-opencolorio-1.1.1.drv-0/source/src/core/ImageDesc.cpp:274:10: note: in a call to non-static member function ‘long int OpenColorIO::v1::PackedImageDesc::getWidth() const’ - 274 | long PackedImageDesc::getWidth() const - | ^~~~~~~~~~~~~~~ -/tmp/guix-build-opencolorio-1.1.1.drv-0/source/src/core/ImageDesc.cpp:61:53: error: ‘this’ pointer is null [-Werror=nonnull] - 61 | os << "height=" << packedImg->getHeight() << ", "; - | ^ -/tmp/guix-build-opencolorio-1.1.1.drv-0/source/src/core/ImageDesc.cpp:279:10: note: in a call to non-static member function ‘long int OpenColorIO::v1::PackedImageDesc::getHeight() const’ - 279 | long PackedImageDesc::getHeight() const - | ^~~~~~~~~~~~~~~ -cc1plus: all warnings being treated as errors -[...] ------- - -Patch copied from Gentoo: - -https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=7e726d850502018b6760da78dbd4a419603016b8 - -diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt -index 1eb691b6..cff9bd83 100644 ---- a/src/core/CMakeLists.txt -+++ b/src/core/CMakeLists.txt -@@ -23,8 +23,6 @@ if(WIN32) - if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") - set(EXTERNAL_COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS} /WX") - endif() --else() -- set(EXTERNAL_COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS} -Werror") - endif() - - # SHARED diff --git a/gnu/packages/patches/openssh-gcc-13-ppc64le-fzero-call-used-regs.patch b/gnu/packages/patches/openssh-gcc-13-ppc64le-fzero-call-used-regs.patch deleted file mode 100644 index 1af9868b9a..0000000000 --- a/gnu/packages/patches/openssh-gcc-13-ppc64le-fzero-call-used-regs.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 1036d77b34a5fa15e56f516b81b9928006848cbd Mon Sep 17 00:00:00 2001 -From: Damien Miller <djm@mindrot.org> -Date: Fri, 22 Dec 2023 17:56:26 +1100 -Subject: [PATCH] better detection of broken -fzero-call-used-regs -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -gcc 13.2.0 on ppc64le refuses to compile some function, including -cipher.c:compression_alg_list() with an error: - -> sorry, unimplemented: argument ‘used’ is not supportedcw -> for ‘-fzero-call-used-regs’ on this target - -This extends the autoconf will-it-work test with a similarly- -structured function that seems to catch this. - -Spotted/tested by Colin Watson; bz3645 ---- - -Taken from upsteam, and for Guix by jackhill@jackhill.us -Thanks Marcel van der Boom for noticing: https://issues.guix.gnu.org/67948#2 - -m4/openssh.m4 | 12 +++++++++--- - 1 file changed, 9 insertions(+), 3 deletions(-) - -diff --git a/m4/openssh.m4 b/m4/openssh.m4 -index 5d4c56280..033df501c 100644 ---- a/m4/openssh.m4 -+++ b/m4/openssh.m4 -@@ -20,18 +20,24 @@ char *f2(char *s, ...) { - va_end(args); - return strdup(ret); - } -+const char *f3(int s) { -+ return s ? "good" : "gooder"; -+} - int main(int argc, char **argv) { -- (void)argv; - char b[256], *cp; -+ const char *s; - /* Some math to catch -ftrapv problems in the toolchain */ - int i = 123 * argc, j = 456 + argc, k = 789 - argc; - float l = i * 2.1; - double m = l / 0.5; - long long int n = argc * 12345LL, o = 12345LL * (long long int)argc; -+ (void)argv; - f(1); -- snprintf(b, sizeof b, "%d %d %d %f %f %lld %lld\n", i,j,k,l,m,n,o); -+ s = f3(f(2)); -+ snprintf(b, sizeof b, "%d %d %d %f %f %lld %lld %s\n", i,j,k,l,m,n,o,s); - if (write(1, b, 0) == -1) exit(0); -- cp = f2("%d %d %d %f %f %lld %lld\n", i,j,k,l,m,n,o); -+ cp = f2("%d %d %d %f %f %lld %lld %s\n", i,j,k,l,m,n,o,s); -+ if (write(1, cp, 0) == -1) exit(0); - free(cp); - /* - * Test fallthrough behaviour. clang 10's -Wimplicit-fallthrough does --- -2.41.0 - diff --git a/gnu/packages/patches/qtbase-find-tools-in-PATH.patch b/gnu/packages/patches/qtbase-find-tools-in-PATH.patch new file mode 100644 index 0000000000..d5e38b09be --- /dev/null +++ b/gnu/packages/patches/qtbase-find-tools-in-PATH.patch @@ -0,0 +1,49 @@ +Patch retrieved from NixOS + +https://github.com/NixOS/nixpkgs/blob/93ecdaa1f34354c9476062dc4fe323b442c087d5/pkgs/development/libraries/qt-6/patches/0006-qtbase-find-tools-in-PATH.patch + +From a8b9fae710a2bd5e743f5e16364eaa8c38dbd784 Mon Sep 17 00:00:00 2001 +From: rewine <luhongxu@deepin.org> +Date: Wed, 29 Mar 2023 11:51:33 +0800 +Subject: [PATCH 06/11] qtbase-find-tools-in-PATH + +1. find qt's tools in `QTTOOLSPATH` env + qt assumes that all components use the same install prefix + we can't get the real prefix for qttools when build qtbase + we will add /libexec to `QTTOOLSPATH` in qtToolsHook + find_path will also search in 'PATH' by default + see `CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH` + +2. disable tool_dependencies_enabled + We can guarantee the build order of qt components in nixpkgs + tools in qttools always build before qtdoc + qdoc_bin is not a build target now, since we find it in `QTTOOLSPATH` +--- + cmake/QtDocsHelpers.cmake | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/cmake/QtDocsHelpers.cmake b/cmake/QtDocsHelpers.cmake +index 48ed5a324bf..91d8d41fb1f 100644 +--- a/cmake/QtDocsHelpers.cmake ++++ b/cmake/QtDocsHelpers.cmake +@@ -47,9 +47,14 @@ function(qt_internal_add_docs) + set(doc_tools_libexec "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_LIBEXECDIR}") + endif() + +- set(qdoc_bin "${doc_tools_bin}/qdoc${CMAKE_EXECUTABLE_SUFFIX}") +- set(qtattributionsscanner_bin "${doc_tools_libexec}/qtattributionsscanner${CMAKE_EXECUTABLE_SUFFIX}") +- set(qhelpgenerator_bin "${doc_tools_libexec}/qhelpgenerator${CMAKE_EXECUTABLE_SUFFIX}") ++ set(tool_dependencies_enabled FALSE) ++ ++ find_path(qdoc_path name qdoc PATHS ENV QTTOOLSPATH) ++ find_path(qtattributionsscanner_path name qtattributionsscanner PATHS ENV QTTOOLSPATH) ++ find_path(qhelpgenerator_path name qhelpgenerator PATHS ENV QTTOOLSPATH) ++ set(qdoc_bin "${qdoc_path}/qdoc${CMAKE_EXECUTABLE_SUFFIX}") ++ set(qtattributionsscanner_bin "${qtattributionsscanner_path}/qtattributionsscanner${CMAKE_EXECUTABLE_SUFFIX}") ++ set(qhelpgenerator_bin "${qhelpgenerator_path}/qhelpgenerator${CMAKE_EXECUTABLE_SUFFIX}") + + get_target_property(target_type ${target} TYPE) + if (NOT target_type STREQUAL "INTERFACE_LIBRARY") +-- +2.42.0 + diff --git a/gnu/packages/patches/qtbase-qmake-fix-includedir.patch b/gnu/packages/patches/qtbase-qmake-fix-includedir.patch new file mode 100644 index 0000000000..766689c4fd --- /dev/null +++ b/gnu/packages/patches/qtbase-qmake-fix-includedir.patch @@ -0,0 +1,29 @@ +Patch retrieved from NixOS +https://github.com/NixOS/nixpkgs/blob/93ecdaa1f34354c9476062dc4fe323b442c087d5/pkgs/development/libraries/qt-6/patches/0003-qtbase-qmake-fix-includedir-in-generated-pkg-config.patch + +From 6088085d3074316dd74639fc6c1233e5862aff11 Mon Sep 17 00:00:00 2001 +From: Nick Cao <nickcao@nichi.co> +Date: Fri, 14 Apr 2023 09:34:46 +0800 +Subject: [PATCH 03/11] qtbase: qmake: fix includedir in generated pkg-config + +--- + qmake/generators/makefile.cpp | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/qmake/generators/makefile.cpp b/qmake/generators/makefile.cpp +index 11d2f0ff7df..c78ed0d3485 100644 +--- a/qmake/generators/makefile.cpp ++++ b/qmake/generators/makefile.cpp +@@ -3412,8 +3412,7 @@ MakefileGenerator::writePkgConfigFile() + << varGlue("QMAKE_PKGCONFIG_CFLAGS", "", " ", " ") + // << varGlue("DEFINES","-D"," -D"," ") + ; +- if (!project->values("QMAKE_DEFAULT_INCDIRS").contains(includeDir)) +- t << "-I${includedir}"; ++ t << "-I${includedir}"; + if (target_mode == TARG_MAC_MODE && project->isActiveConfig("lib_bundle") + && libDir != QLatin1String("/Library/Frameworks")) { + t << " -F${libdir}"; +-- +2.42.0 + diff --git a/gnu/packages/patches/qtbase-qmlimportscanner-qml-import-path.patch b/gnu/packages/patches/qtbase-qmlimportscanner-qml-import-path.patch new file mode 100644 index 0000000000..b73a1fba73 --- /dev/null +++ b/gnu/packages/patches/qtbase-qmlimportscanner-qml-import-path.patch @@ -0,0 +1,33 @@ +Retrieved from nixpkgs. +Modified to use QML_IMPORT_PATH instead of QML2_IMPORT_PATH. + +From d7a9a3b0ecdbb1b5829f25954d763d767f1c8794 Mon Sep 17 00:00:00 2001 +From: Nick Cao <nickcao@nichi.co> +Date: Tue, 10 Oct 2023 10:12:56 -0400 +Subject: [PATCH 07/11] qtbase: pass to qmlimportscanner the QML2_IMPORT_PATH + +--- + src/tools/macdeployqt/shared/shared.cpp | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/tools/macdeployqt/shared/shared.cpp b/src/tools/macdeployqt/shared/shared.cpp +index 2ae4f998944..ba10ae02bcd 100644 +--- a/src/tools/macdeployqt/shared/shared.cpp ++++ b/src/tools/macdeployqt/shared/shared.cpp +@@ -1297,6 +1297,13 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf + argumentList.append( "-importPath"); + argumentList.append(qmlImportsPath); + ++ // In a modularized installation of qt as we have in Nix, instead, we will ++ // read the paths from the environment, as they are spread in multiple ++ // locations and normally set in the environment like this ++ auto envQmlImportPaths = ::qgetenv("QML_IMPORT_PATH").split(':'); ++ for (const QString &importPath : envQmlImportPaths) ++ argumentList << "-importPath" << importPath; ++ + // run qmlimportscanner + QProcess qmlImportScanner; + qmlImportScanner.start(qmlImportScannerPath, argumentList); +-- +2.42.0 + diff --git a/gnu/packages/patches/qtbase-use-TZDIR.patch b/gnu/packages/patches/qtbase-use-TZDIR.patch deleted file mode 100644 index 98bf7493e9..0000000000 --- a/gnu/packages/patches/qtbase-use-TZDIR.patch +++ /dev/null @@ -1,141 +0,0 @@ -From 1075606f8b2f9e153c82f8e50cbd69cea9c72e87 Mon Sep 17 00:00:00 2001 -From: Edward Welbourne <edward.welbourne@qt.io> -Date: Mon, 11 Sep 2023 11:41:39 +0200 -Subject: [PATCH] Support the TZDIR environment variable - -On Linux / glibc, this overrides the default system location for the -zone info. So check for files there first. Break out a function to -manage the trying of (now three) zoneinfo directories when opening a -file by name relative to there. - -Pick-to: 6.6 6.5 -Task-number: QTBUG-116017 -Change-Id: I1f97107aabd9015c0a5543639870f1d70654ca67 ---- -* Rebased on top of v6.5.2. - - src/corelib/time/qtimezoneprivate_tz.cpp | 73 ++++++++++++++++-------- - 1 file changed, 49 insertions(+), 24 deletions(-) - -diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp -index 067191d816..a8b2fc894e 100644 ---- a/src/corelib/time/qtimezoneprivate_tz.cpp -+++ b/src/corelib/time/qtimezoneprivate_tz.cpp -@@ -51,17 +51,41 @@ typedef QHash<QByteArray, QTzTimeZone> QTzTimeZoneHash; - - static bool isTzFile(const QString &name); - -+// Open a named file under the zone info directory: -+static bool openZoneInfo(QString name, QFile *file) -+{ -+ // At least on Linux / glibc (see man 3 tzset), $TZDIR overrides the system -+ // default location for zone info: -+ const QString tzdir = qEnvironmentVariable("TZDIR"); -+ if (!tzdir.isEmpty()) { -+ file->setFileName(QDir(tzdir).filePath(name)); -+ if (file->open(QIODevice::ReadOnly)) -+ return true; -+ } -+ // Try modern system path first: -+ constexpr auto zoneShare = "/usr/share/zoneinfo/"_L1; -+ if (tzdir != zoneShare && tzdir != zoneShare.chopped(1)) { -+ file->setFileName(zoneShare + name); -+ if (file->open(QIODevice::ReadOnly)) -+ return true; -+ } -+ // Fall back to legacy system path: -+ constexpr auto zoneLib = "/usr/lib/zoneinfo/"_L1; -+ if (tzdir != zoneLib && tzdir != zoneLib.chopped(1)) { -+ file->setFileName(zoneShare + name); -+ if (file->open(QIODevice::ReadOnly)) -+ return true; -+ } -+ return false; -+} -+ - // Parse zone.tab table for territory information, read directories to ensure we - // find all installed zones (many are omitted from zone.tab; even more from - // zone1970.tab). - static QTzTimeZoneHash loadTzTimeZones() - { -- QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab"); -- if (!QFile::exists(path)) -- path = QStringLiteral("/usr/lib/zoneinfo/zone.tab"); -- -- QFile tzif(path); -- if (!tzif.open(QIODevice::ReadOnly)) -+ QFile tzif; -+ if (!openZoneInfo("zone.tab"_L1, &tzif)) - return QTzTimeZoneHash(); - - QTzTimeZoneHash zonesHash; -@@ -91,6 +115,7 @@ static QTzTimeZoneHash loadTzTimeZones() - } - } - -+ const QString path = tzif.fileName(); - const qsizetype cut = path.lastIndexOf(u'/'); - Q_ASSERT(cut > 0); - const QDir zoneDir = QDir(path.first(cut)); -@@ -761,20 +786,13 @@ QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId) - tzif.setFileName(QStringLiteral("/etc/localtime")); - if (!tzif.open(QIODevice::ReadOnly)) - return ret; -- } else { -- // Open named tz, try modern path first, if fails try legacy path -- tzif.setFileName("/usr/share/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId)); -- if (!tzif.open(QIODevice::ReadOnly)) { -- tzif.setFileName("/usr/lib/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId)); -- if (!tzif.open(QIODevice::ReadOnly)) { -- // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ -- auto check = validatePosixRule(ianaId); -- if (check.isValid) { -- ret.m_hasDst = check.hasDst; -- ret.m_posixRule = ianaId; -- } -- return ret; -- } -+ } else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) { -+ // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ -+ auto check = validatePosixRule(ianaId); -+ if (check.isValid) { -+ ret.m_hasDst = check.hasDst; -+ ret.m_posixRule = ianaId; -+ return ret; - } - } - -@@ -1317,7 +1335,8 @@ private: - { - // On most distros /etc/localtime is a symlink to a real file so extract - // name from the path -- const auto zoneinfo = "/zoneinfo/"_L1; -+ const QString tzdir = qEnvironmentVariable("TZDIR"); -+ constexpr auto zoneinfo = "/zoneinfo/"_L1; - QString path = QStringLiteral("/etc/localtime"); - long iteration = getSymloopMax(); - // Symlink may point to another symlink etc. before being under zoneinfo/ -@@ -1325,9 +1344,15 @@ private: - // symlink, like America/Montreal pointing to America/Toronto - do { - path = QFile::symLinkTarget(path); -- int index = path.indexOf(zoneinfo); -- if (index >= 0) // Found zoneinfo file; extract zone name from path: -- return QStringView{ path }.mid(index + zoneinfo.size()).toUtf8(); -+ // If it's a zoneinfo file, extract the zone name from its path: -+ int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir); -+ if (index >= 0) { -+ const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8(); -+ return tail.startsWith(u'/') ? tail.sliced(1) : tail; -+ } -+ index = path.indexOf(zoneinfo); -+ if (index >= 0) -+ return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8(); - } while (!path.isEmpty() && --iteration > 0); - - return QByteArray(); - -base-commit: af457a9f0f7eb1a2a7d11f495da508faab91a442 --- -2.41.0 - diff --git a/gnu/packages/patches/qtdeclarative-disable-qmlcache.patch b/gnu/packages/patches/qtdeclarative-disable-qmlcache.patch index 5f06ec53b4..df76fab910 100644 --- a/gnu/packages/patches/qtdeclarative-disable-qmlcache.patch +++ b/gnu/packages/patches/qtdeclarative-disable-qmlcache.patch @@ -1,16 +1,31 @@ Retrieved from https://raw.githubusercontent.com/NixOS/nixpkgs/master/pkgs/development/libraries/qt-6/patches/qtdeclarative-default-disable-qmlcache.patch +From 2d561e0a80f2d123a7348187975ee845f9dcd9e0 Mon Sep 17 00:00:00 2001 +From: Nick Cao <nickcao@nichi.co> +Date: Tue, 10 Oct 2023 11:12:27 -0400 +Subject: [PATCH] qtdeclarative: disable qml disk cache + +--- + src/qml/jsruntime/qv4engine.cpp | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp -index 852cde9e..165f1b57 100644 +index d1b4c4fff6..50f8a07420 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp -@@ -2093,7 +2093,7 @@ void ExecutionEngine::registerModule(const QString &_name, const QJSValue &modul - - bool ExecutionEngine::diskCacheEnabled() const +@@ -2232,11 +2232,7 @@ ExecutionEngine::DiskCacheOptions ExecutionEngine::diskCacheOptions() const { -- return (!disableDiskCache() && !debugger()) || forceDiskCache(); -+ return forceDiskCache(); + if (forceDiskCache()) + return DiskCache::Enabled; +- if (disableDiskCache() || debugger()) +- return DiskCache::Disabled; +- static const DiskCacheOptions options = qmlGetConfigOption< +- DiskCacheOptions, transFormDiskCache>("QML_DISK_CACHE"); +- return options; ++ return DiskCache::Disabled; } void ExecutionEngine::callInContext(QV4::Function *function, QObject *self, +-- +2.42.0 diff --git a/gnu/packages/patches/xgboost-use-system-dmlc-core.patch b/gnu/packages/patches/xgboost-use-system-dmlc-core.patch index 6b2a1651a5..cbc0feed1c 100644 --- a/gnu/packages/patches/xgboost-use-system-dmlc-core.patch +++ b/gnu/packages/patches/xgboost-use-system-dmlc-core.patch @@ -1,13 +1,13 @@ -# This patch was imported from Debian: https://sources.debian.org/src/xgboost/1.5.1-1/debian/patches/cmake-dmlc-core.patch/ +# This patch was imported from Debian: https://sources.debian.org/patches/xgboost/1.7.4-1/cmake-dmlc-core.patch/ Index: xgboost/CMakeLists.txt =================================================================== --- xgboost.orig/CMakeLists.txt +++ xgboost/CMakeLists.txt -@@ -164,7 +164,9 @@ endif (USE_NCCL) - - # dmlc-core - msvc_use_static_runtime() +@@ -205,7 +205,9 @@ msvc_use_static_runtime() + if (FORCE_SHARED_CRT) + set(DMLC_FORCE_SHARED_CRT ON) + endif () -add_subdirectory(${xgboost_SOURCE_DIR}/dmlc-core) +add_library(dmlc SHARED IMPORTED) +find_library(DMLC_LIBRARY dmlc) @@ -15,7 +15,7 @@ Index: xgboost/CMakeLists.txt if (MSVC) if (TARGET dmlc_unit_tests) -@@ -222,7 +224,7 @@ set_target_properties(runxgboost PROPERT +@@ -267,7 +269,7 @@ set_target_properties(runxgboost PROPERT #-- End CLI for xgboost # Common setup for all targets @@ -24,7 +24,7 @@ Index: xgboost/CMakeLists.txt xgboost_target_properties(${target}) xgboost_target_link_libraries(${target}) xgboost_target_defs(${target}) -@@ -273,7 +275,7 @@ install(DIRECTORY ${xgboost_SOURCE_DIR}/ +@@ -318,7 +320,7 @@ install(DIRECTORY ${xgboost_SOURCE_DIR}/ # # https://github.com/dmlc/xgboost/issues/6085 if (BUILD_STATIC_LIB) |