1111
1212#include " camera.h"
1313#include " device_enumerator.h"
14+ #include " pipewire_portal.h"
1415
1516int64_t camera_desktop_ffi_register_stream_handle (Camera* camera);
1617void camera_desktop_ffi_release_stream_handle (int64_t stream_handle);
@@ -20,6 +21,8 @@ void camera_desktop_ffi_release_handles_for_camera(Camera* camera);
2021struct PluginData {
2122 std::map<int , std::unique_ptr<Camera>> cameras;
2223 int next_camera_id = 1 ;
24+ std::unique_ptr<PipeWirePortal> portal;
25+ bool use_pipewire = false ;
2326};
2427
2528#define CAMERA_DESKTOP_PLUGIN (obj ) \
@@ -37,33 +40,48 @@ G_DEFINE_TYPE(CameraDesktopPlugin, camera_desktop_plugin, g_object_get_type())
3740
3841// --- Method handlers ---
3942
40- static void handle_available_cameras (CameraDesktopPlugin* self,
41- FlMethodCall* method_call) {
42- auto devices = DeviceEnumerator::EnumerateDevices ();
43-
43+ // Builds the Flutter response list from a vector of DeviceInfo.
44+ static void respond_with_devices (FlMethodCall* method_call,
45+ const std::vector<DeviceInfo>& devices) {
4446 g_autoptr (FlValue) result = fl_value_new_list ();
4547 for (const auto & device : devices) {
4648 g_autoptr (FlValue) entry = fl_value_new_map ();
49+ // Format: "Friendly Name (/dev/videoN)" or "Friendly Name (pw:42)"
50+ std::string display_name =
51+ device.name + " (" + device.device_path + " )" ;
4752 fl_value_set_string_take (entry, " name" ,
48- fl_value_new_string (device. name .c_str ()));
53+ fl_value_new_string (display_name .c_str ()));
4954 fl_value_set_string_take (entry, " lensDirection" ,
5055 fl_value_new_int (device.lens_direction ));
5156 fl_value_set_string_take (entry, " sensorOrientation" ,
5257 fl_value_new_int (device.sensor_orientation ));
53- // Also pass the device path so Dart can pass it back in create().
54- // The CameraDescription.name field will carry this.
55- // Override name to include both friendly name and path for disambiguation.
56- // Format: "Friendly Name (/dev/videoN)"
57- std::string display_name =
58- device.name + " (" + device.device_path + " )" ;
59- fl_value_set_string (entry, " name" ,
60- fl_value_new_string (display_name.c_str ()));
6158 fl_value_append (result, entry);
6259 }
63-
6460 fl_method_call_respond_success (method_call, result, nullptr );
6561}
6662
63+ static void handle_available_cameras (CameraDesktopPlugin* self,
64+ FlMethodCall* method_call) {
65+ if (self->data ->use_pipewire && self->data ->portal ) {
66+ // Async path: request portal permission, enumerate PipeWire nodes.
67+ g_object_ref (method_call);
68+ self->data ->portal ->EnumerateDevicesAsync (
69+ [self, method_call](std::vector<DeviceInfo> devices) {
70+ // If portal returned no devices, fall back to V4L2.
71+ if (devices.empty ()) {
72+ devices = DeviceEnumerator::EnumerateDevices ();
73+ }
74+ respond_with_devices (method_call, devices);
75+ g_object_unref (method_call);
76+ });
77+ return ;
78+ }
79+
80+ // V4L2 path (synchronous, for native Linux installs).
81+ auto devices = DeviceEnumerator::EnumerateDevices ();
82+ respond_with_devices (method_call, devices);
83+ }
84+
6785static void handle_get_platform_capabilities (FlMethodCall* method_call) {
6886 g_autoptr (FlValue) result = fl_value_new_map ();
6987 fl_value_set_string_take (result, " supportsMirrorControl" ,
@@ -130,16 +148,25 @@ static void handle_create(CameraDesktopPlugin* self,
130148 device_path = name_str;
131149 }
132150
133- if (device_path.empty () || device_path.find (" /dev/" ) != 0 ) {
151+ // Detect backend from device path prefix.
152+ CameraBackend backend = CameraBackend::kV4L2 ;
153+ if (device_path.size () > 3 && device_path.substr (0 , 3 ) == " pw:" ) {
154+ backend = CameraBackend::kPipeWire ;
155+ } else if (device_path.empty () || device_path.find (" /dev/" ) != 0 ) {
134156 g_autoptr (FlValue) details = fl_value_new_null ();
135157 fl_method_call_respond_error (method_call, " invalid_camera_name" ,
136158 " Could not extract device path from camera name" ,
137159 details, nullptr );
138160 return ;
139161 }
140162
141- // Enumerate resolutions and select the best match for the preset.
142- auto resolutions = DeviceEnumerator::EnumerateResolutions (device_path);
163+ // Enumerate resolutions based on backend.
164+ std::vector<ResolutionInfo> resolutions;
165+ if (backend == CameraBackend::kPipeWire ) {
166+ resolutions = PipeWirePortal::GetDefaultResolutions ();
167+ } else {
168+ resolutions = DeviceEnumerator::EnumerateResolutions (device_path);
169+ }
143170 auto selected = DeviceEnumerator::SelectResolution (resolutions,
144171 resolution_preset);
145172
@@ -152,6 +179,10 @@ static void handle_create(CameraDesktopPlugin* self,
152179 config.target_fps = target_fps > 0 ? target_fps : selected.max_fps ;
153180 config.target_bitrate = target_bitrate;
154181 config.audio_bitrate = audio_bitrate;
182+ config.backend = backend;
183+ if (backend == CameraBackend::kPipeWire && self->data ->portal ) {
184+ config.pw_fd = self->data ->portal ->pw_fd ();
185+ }
155186
156187 int camera_id = self->data ->next_camera_id ++;
157188 auto camera = std::make_unique<Camera>(
@@ -357,6 +388,10 @@ static void camera_desktop_plugin_class_init(CameraDesktopPluginClass* klass) {
357388
358389static void camera_desktop_plugin_init (CameraDesktopPlugin* self) {
359390 self->data = new PluginData ();
391+ self->data ->use_pipewire = PipeWirePortal::ShouldUsePipeWire ();
392+ if (self->data ->use_pipewire ) {
393+ self->data ->portal = std::make_unique<PipeWirePortal>();
394+ }
360395}
361396
362397void camera_desktop_plugin_register_with_registrar (
0 commit comments