feat(ffi): expose wda and wda_bridge to C/C++ (#89)

* feat(ffi): expose wda and wda_bridge to C/C++

tested on an iPhone 13 with WebDriverAgent running:
status returns the right JSON screenshot saves a valid PNG
bridge forwards a curlrequest to the device's WDA port.

spent a while testing and writing this up do review before merging

* chore: lint
This commit is contained in:
neo
2026-04-27 16:03:32 -04:00
committed by GitHub
parent 7d41fdb211
commit 0bbd4bb95d
10 changed files with 2302 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
// Jackson Coxson
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/usbmuxd.hpp>
#include <idevice++/wda.hpp>
#include <idevice++/wda_bridge.hpp>
using namespace IdeviceFFI;
static void print_usage(const char* prog) {
std::cerr << "Usage:\n"
<< " " << prog << " status\n"
<< " " << prog << " screenshot <output.png> [bundle_id]\n"
<< " " << prog << " bridge\n";
}
static Provider build_provider(const std::string& label) {
auto mux = UsbmuxdConnection::default_new(0).expect("failed to connect to usbmuxd");
auto devices = mux.get_devices().expect("failed to list devices");
if (devices.empty()) {
std::cerr << "no devices connected\n";
std::exit(1);
}
auto& dev = devices[0];
auto udid = dev.get_udid();
if (udid.is_none()) {
std::cerr << "device has no UDID\n";
std::exit(1);
}
auto mux_id = dev.get_id();
if (mux_id.is_none()) {
std::cerr << "device has no mux id\n";
std::exit(1);
}
auto addr = UsbmuxdAddr::default_new();
const uint32_t tag = 0;
return Provider::usbmuxd_new(std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label)
.expect("failed to create provider");
}
int main(int argc, char** argv) {
idevice_init_logger(Debug, Disabled, NULL);
// Usage:
// wda status
// wda screenshot <output.png> [bundle_id]
// wda bridge
if (argc < 2) {
print_usage(argv[0]);
return 2;
}
const std::string command = argv[1];
if (command == "status") {
if (argc != 2) {
print_usage(argv[0]);
return 2;
}
auto wda = Wda::create(build_provider("wda-client")).expect("failed to create Wda client");
auto status = wda.status().expect("failed to fetch /status from WDA");
std::cout << status << "\n";
return 0;
}
if (command == "screenshot") {
if (argc < 3 || argc > 4) {
print_usage(argv[0]);
return 2;
}
const std::string out_path = argv[2];
Option<std::string> bundle_id;
if (argc == 4) {
bundle_id = Some(std::string(argv[3]));
}
auto wda = Wda::create(build_provider("wda-client")).expect("failed to create Wda client");
auto session_id = wda.start_session(bundle_id).expect("failed to start WDA session");
std::cout << "Session: " << session_id << "\n";
auto buf = wda.screenshot(None).expect("failed to capture screenshot");
wda.delete_session(session_id).expect("failed to delete WDA session");
std::ofstream out(out_path, std::ios::binary);
if (!out.is_open()) {
std::cerr << "failed to open output file: " << out_path << "\n";
return 1;
}
out.write(reinterpret_cast<const char*>(buf.data()),
static_cast<std::streamsize>(buf.size()));
out.close();
std::cout << "Screenshot saved to " << out_path << " (" << buf.size() << " bytes)\n";
return 0;
}
if (command == "bridge") {
if (argc != 2) {
print_usage(argv[0]);
return 2;
}
auto bridge = WdaBridge::start(build_provider("wda-bridge"))
.expect("failed to start WDA bridge");
auto endpoints = bridge.endpoints().expect("failed to read bridge endpoints");
if (endpoints.udid.is_some()) {
std::cout << "udid: " << endpoints.udid.unwrap() << "\n";
}
std::cout << "wda_url: " << endpoints.wda_url << "\n"
<< "mjpeg_url: " << endpoints.mjpeg_url << "\n"
<< "device_http: " << endpoints.device_http << "\n"
<< "device_mjpeg:" << endpoints.device_mjpeg << "\n";
std::cout << "\nForwarding active. Press Enter to stop.\n";
std::string line;
std::getline(std::cin, line);
return 0;
}
print_usage(argv[0]);
return 2;
}
+128
View File
@@ -0,0 +1,128 @@
// Jackson Coxson
#pragma once
#include <cstdint>
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/result.hpp>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace IdeviceFFI {
using WdaClientPtr =
std::unique_ptr<WdaClientHandle, FnDeleter<WdaClientHandle, wda_client_free>>;
/// Minimal WebDriverAgent client over a single device's direct connections.
///
/// `Wda::create` consumes the `Provider` argument; subsequent calls reuse the
/// owned provider to open per-request sockets, matching the underlying Rust
/// `WdaClient` semantics.
class Wda {
public:
static Result<Wda, FfiError> create(Provider&& provider);
// Builder-style configuration
Result<void, FfiError> set_ports(uint16_t http, uint16_t mjpeg);
Result<void, FfiError> set_timeout_ms(uint64_t ms);
Result<std::pair<uint16_t, uint16_t>, FfiError> get_ports() const;
Option<std::string> session_id() const;
// Status / session
Result<std::string, FfiError> status();
Result<std::string, FfiError> wait_until_ready(uint64_t timeout_ms);
Result<std::string, FfiError> start_session(Option<std::string> bundle_id);
Result<void, FfiError> delete_session(const std::string& session_id);
// Element finding & state
Result<std::string, FfiError> find_element(const std::string& using_,
const std::string& value,
Option<std::string> session_id);
Result<std::vector<std::string>, FfiError> find_elements(const std::string& using_,
const std::string& value,
Option<std::string> session_id);
Result<std::string, FfiError> element_attribute(const std::string& element_id,
const std::string& name,
Option<std::string> session_id);
Result<std::string, FfiError> element_text(const std::string& element_id,
Option<std::string> session_id);
Result<std::string, FfiError> element_rect(const std::string& element_id,
Option<std::string> session_id);
Result<bool, FfiError> element_displayed(const std::string& element_id,
Option<std::string> session_id);
Result<bool, FfiError> element_enabled(const std::string& element_id,
Option<std::string> session_id);
Result<bool, FfiError> element_selected(const std::string& element_id,
Option<std::string> session_id);
// Interaction
Result<void, FfiError> click(const std::string& element_id, Option<std::string> session_id);
Result<void, FfiError> send_keys(const std::string& text, Option<std::string> session_id);
Result<void, FfiError> press_button(const std::string& name, Option<std::string> session_id);
Result<void, FfiError> unlock(Option<std::string> session_id);
Result<void, FfiError> swipe(int64_t start_x,
int64_t start_y,
int64_t end_x,
int64_t end_y,
double duration,
Option<std::string> session_id);
Result<void, FfiError> tap(Option<double> x,
Option<double> y,
Option<std::string> element_id,
Option<std::string> session_id);
Result<void, FfiError> double_tap(Option<double> x,
Option<double> y,
Option<std::string> element_id,
Option<std::string> session_id);
Result<void, FfiError> touch_and_hold(double duration,
Option<double> x,
Option<double> y,
Option<std::string> element_id,
Option<std::string> session_id);
Result<void, FfiError> scroll(Option<std::string> direction,
Option<std::string> name,
Option<std::string> predicate_string,
Option<bool> to_visible,
Option<std::string> element_id,
Option<std::string> session_id);
// Output / app
Result<std::string, FfiError> source(Option<std::string> session_id);
Result<std::vector<uint8_t>, FfiError> screenshot(Option<std::string> session_id);
Result<std::string, FfiError> window_size(Option<std::string> session_id);
Result<std::string, FfiError> viewport_rect(Option<std::string> session_id);
Result<std::string, FfiError> orientation(Option<std::string> session_id);
Result<std::string, FfiError> launch_app(const std::string& bundle_id,
const std::vector<std::string>& arguments,
Option<std::string> environment_json,
Option<std::string> session_id);
Result<std::string, FfiError> activate_app(const std::string& bundle_id,
Option<std::string> session_id);
Result<bool, FfiError> terminate_app(const std::string& bundle_id,
Option<std::string> session_id);
Result<int64_t, FfiError> query_app_state(const std::string& bundle_id,
Option<std::string> session_id);
Result<std::string, FfiError> background_app(Option<double> seconds,
Option<std::string> session_id);
Result<bool, FfiError> is_locked(Option<std::string> session_id);
// RAII / moves
~Wda() noexcept = default;
Wda(Wda&&) noexcept = default;
Wda& operator=(Wda&&) noexcept = default;
Wda(const Wda&) = delete;
Wda& operator=(const Wda&) = delete;
WdaClientHandle* raw() const noexcept { return handle_.get(); }
static Wda adopt(WdaClientHandle* h) noexcept { return Wda(h); }
private:
explicit Wda(WdaClientHandle* h) noexcept : handle_(h) {}
WdaClientPtr handle_{};
};
} // namespace IdeviceFFI
+56
View File
@@ -0,0 +1,56 @@
// Jackson Coxson
#pragma once
#include <cstdint>
#include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp>
#include <idevice++/result.hpp>
#include <memory>
#include <string>
namespace IdeviceFFI {
using WdaBridgePtr =
std::unique_ptr<WdaBridgeHandle, FnDeleter<WdaBridgeHandle, wda_bridge_free>>;
/// Localhost endpoints exposed by a running WDA bridge.
struct WdaBridgeEndpoints {
Option<std::string> udid;
std::string wda_url;
std::string mjpeg_url;
uint16_t local_http = 0;
uint16_t local_mjpeg = 0;
uint16_t device_http = 0;
uint16_t device_mjpeg = 0;
};
/// Dynamic localhost bridge for a single device's WDA endpoints.
///
/// Both factories consume the `Provider` argument; dropping the bridge aborts
/// the underlying forwarder tasks.
class WdaBridge {
public:
static Result<WdaBridge, FfiError> start(Provider&& provider);
static Result<WdaBridge, FfiError> start_with_ports(Provider&& provider,
uint16_t device_http,
uint16_t device_mjpeg);
Result<WdaBridgeEndpoints, FfiError> endpoints() const;
~WdaBridge() noexcept = default;
WdaBridge(WdaBridge&&) noexcept = default;
WdaBridge& operator=(WdaBridge&&) noexcept = default;
WdaBridge(const WdaBridge&) = delete;
WdaBridge& operator=(const WdaBridge&) = delete;
WdaBridgeHandle* raw() const noexcept { return handle_.get(); }
static WdaBridge adopt(WdaBridgeHandle* h) noexcept { return WdaBridge(h); }
private:
explicit WdaBridge(WdaBridgeHandle* h) noexcept : handle_(h) {}
WdaBridgePtr handle_{};
};
} // namespace IdeviceFFI
+388
View File
@@ -0,0 +1,388 @@
// Jackson Coxson
#include <idevice++/wda.hpp>
namespace IdeviceFFI {
namespace {
inline const char* opt_cstr(const Option<std::string>& s) {
return s.is_some() ? s.expect("checked").c_str() : nullptr;
}
inline std::string adopt_cstring(char* raw) {
if (!raw) return {};
std::string out(raw);
::idevice_string_free(raw);
return out;
}
} // namespace
Result<Wda, FfiError> Wda::create(Provider&& provider) {
WdaClientHandle* out = nullptr;
FfiError e(::wda_client_new(provider.raw(), &out));
if (e) {
// The provider was consumed by the FFI regardless of success.
provider.release();
return Err(e);
}
provider.release();
return Ok(Wda::adopt(out));
}
Result<void, FfiError> Wda::set_ports(uint16_t http, uint16_t mjpeg) {
FfiError e(::wda_client_set_ports(handle_.get(), http, mjpeg));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::set_timeout_ms(uint64_t ms) {
FfiError e(::wda_client_set_timeout_ms(handle_.get(), ms));
if (e) return Err(e);
return Ok();
}
Result<std::pair<uint16_t, uint16_t>, FfiError> Wda::get_ports() const {
uint16_t http = 0;
uint16_t mjpeg = 0;
FfiError e(::wda_client_get_ports(handle_.get(), &http, &mjpeg));
if (e) return Err(e);
return Ok(std::make_pair(http, mjpeg));
}
Option<std::string> Wda::session_id() const {
char* raw = nullptr;
FfiError e(::wda_client_session_id(handle_.get(), &raw));
if (e || !raw) return None;
return Some(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::status() {
char* raw = nullptr;
FfiError e(::wda_client_status(handle_.get(), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::wait_until_ready(uint64_t timeout_ms) {
char* raw = nullptr;
FfiError e(::wda_client_wait_until_ready(handle_.get(), timeout_ms, &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::start_session(Option<std::string> bundle_id) {
char* raw = nullptr;
FfiError e(::wda_client_start_session(handle_.get(), opt_cstr(bundle_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<void, FfiError> Wda::delete_session(const std::string& session_id) {
FfiError e(::wda_client_delete_session(handle_.get(), session_id.c_str()));
if (e) return Err(e);
return Ok();
}
Result<std::string, FfiError> Wda::find_element(const std::string& using_,
const std::string& value,
Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_find_element(
handle_.get(), using_.c_str(), value.c_str(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::vector<std::string>, FfiError> Wda::find_elements(const std::string& using_,
const std::string& value,
Option<std::string> session_id) {
char** arr = nullptr;
size_t count = 0;
FfiError e(::wda_client_find_elements(handle_.get(),
using_.c_str(),
value.c_str(),
opt_cstr(session_id),
&arr,
&count));
if (e) return Err(e);
std::vector<std::string> out;
out.reserve(count);
for (size_t i = 0; i < count; ++i) {
out.emplace_back(arr[i] ? arr[i] : "");
}
::wda_client_string_array_free(arr, count);
return Ok(std::move(out));
}
Result<std::string, FfiError> Wda::element_attribute(const std::string& element_id,
const std::string& name,
Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_element_attribute(
handle_.get(), element_id.c_str(), name.c_str(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::element_text(const std::string& element_id,
Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_element_text(
handle_.get(), element_id.c_str(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::element_rect(const std::string& element_id,
Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_element_rect(
handle_.get(), element_id.c_str(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<bool, FfiError> Wda::element_displayed(const std::string& element_id,
Option<std::string> session_id) {
bool out = false;
FfiError e(::wda_client_element_displayed(
handle_.get(), element_id.c_str(), opt_cstr(session_id), &out));
if (e) return Err(e);
return Ok(out);
}
Result<bool, FfiError> Wda::element_enabled(const std::string& element_id,
Option<std::string> session_id) {
bool out = false;
FfiError e(::wda_client_element_enabled(
handle_.get(), element_id.c_str(), opt_cstr(session_id), &out));
if (e) return Err(e);
return Ok(out);
}
Result<bool, FfiError> Wda::element_selected(const std::string& element_id,
Option<std::string> session_id) {
bool out = false;
FfiError e(::wda_client_element_selected(
handle_.get(), element_id.c_str(), opt_cstr(session_id), &out));
if (e) return Err(e);
return Ok(out);
}
Result<void, FfiError> Wda::click(const std::string& element_id, Option<std::string> session_id) {
FfiError e(::wda_client_click(handle_.get(), element_id.c_str(), opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::send_keys(const std::string& text, Option<std::string> session_id) {
FfiError e(::wda_client_send_keys(handle_.get(), text.c_str(), opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::press_button(const std::string& name,
Option<std::string> session_id) {
FfiError e(::wda_client_press_button(handle_.get(), name.c_str(), opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::unlock(Option<std::string> session_id) {
FfiError e(::wda_client_unlock(handle_.get(), opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::swipe(int64_t start_x,
int64_t start_y,
int64_t end_x,
int64_t end_y,
double duration,
Option<std::string> session_id) {
FfiError e(::wda_client_swipe(
handle_.get(), start_x, start_y, end_x, end_y, duration, opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::tap(Option<double> x,
Option<double> y,
Option<std::string> element_id,
Option<std::string> session_id) {
bool has_x = x.is_some();
bool has_y = y.is_some();
double xv = has_x ? x.expect("checked") : 0.0;
double yv = has_y ? y.expect("checked") : 0.0;
FfiError e(::wda_client_tap(
handle_.get(), has_x, xv, has_y, yv, opt_cstr(element_id), opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::double_tap(Option<double> x,
Option<double> y,
Option<std::string> element_id,
Option<std::string> session_id) {
bool has_x = x.is_some();
bool has_y = y.is_some();
double xv = has_x ? x.expect("checked") : 0.0;
double yv = has_y ? y.expect("checked") : 0.0;
FfiError e(::wda_client_double_tap(
handle_.get(), has_x, xv, has_y, yv, opt_cstr(element_id), opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::touch_and_hold(double duration,
Option<double> x,
Option<double> y,
Option<std::string> element_id,
Option<std::string> session_id) {
bool has_x = x.is_some();
bool has_y = y.is_some();
double xv = has_x ? x.expect("checked") : 0.0;
double yv = has_y ? y.expect("checked") : 0.0;
FfiError e(::wda_client_touch_and_hold(handle_.get(),
duration,
has_x,
xv,
has_y,
yv,
opt_cstr(element_id),
opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<void, FfiError> Wda::scroll(Option<std::string> direction,
Option<std::string> name,
Option<std::string> predicate_string,
Option<bool> to_visible,
Option<std::string> element_id,
Option<std::string> session_id) {
bool has_to_visible = to_visible.is_some();
bool to_visible_v = has_to_visible ? to_visible.expect("checked") : false;
FfiError e(::wda_client_scroll(handle_.get(),
opt_cstr(direction),
opt_cstr(name),
opt_cstr(predicate_string),
has_to_visible,
to_visible_v,
opt_cstr(element_id),
opt_cstr(session_id)));
if (e) return Err(e);
return Ok();
}
Result<std::string, FfiError> Wda::source(Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_source(handle_.get(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::vector<uint8_t>, FfiError> Wda::screenshot(Option<std::string> session_id) {
uint8_t* bytes = nullptr;
size_t len = 0;
FfiError e(::wda_client_screenshot(handle_.get(), opt_cstr(session_id), &bytes, &len));
if (e) return Err(e);
std::vector<uint8_t> out;
if (bytes && len > 0) {
out.assign(bytes, bytes + len);
}
::idevice_data_free(bytes, len);
return Ok(std::move(out));
}
Result<std::string, FfiError> Wda::window_size(Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_window_size(handle_.get(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::viewport_rect(Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_viewport_rect(handle_.get(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::orientation(Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_orientation(handle_.get(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::launch_app(const std::string& bundle_id,
const std::vector<std::string>& arguments,
Option<std::string> environment_json,
Option<std::string> session_id) {
std::vector<const char*> c_args;
c_args.reserve(arguments.size());
for (const auto& a : arguments) c_args.push_back(a.c_str());
char* raw = nullptr;
FfiError e(::wda_client_launch_app(handle_.get(),
bundle_id.c_str(),
c_args.empty() ? nullptr : c_args.data(),
c_args.size(),
opt_cstr(environment_json),
opt_cstr(session_id),
&raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<std::string, FfiError> Wda::activate_app(const std::string& bundle_id,
Option<std::string> session_id) {
char* raw = nullptr;
FfiError e(::wda_client_activate_app(
handle_.get(), bundle_id.c_str(), opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<bool, FfiError> Wda::terminate_app(const std::string& bundle_id,
Option<std::string> session_id) {
bool out = false;
FfiError e(::wda_client_terminate_app(
handle_.get(), bundle_id.c_str(), opt_cstr(session_id), &out));
if (e) return Err(e);
return Ok(out);
}
Result<int64_t, FfiError> Wda::query_app_state(const std::string& bundle_id,
Option<std::string> session_id) {
int64_t out = 0;
FfiError e(::wda_client_query_app_state(
handle_.get(), bundle_id.c_str(), opt_cstr(session_id), &out));
if (e) return Err(e);
return Ok(out);
}
Result<std::string, FfiError> Wda::background_app(Option<double> seconds,
Option<std::string> session_id) {
bool has_seconds = seconds.is_some();
double seconds_v = has_seconds ? seconds.expect("checked") : 0.0;
char* raw = nullptr;
FfiError e(::wda_client_background_app(
handle_.get(), has_seconds, seconds_v, opt_cstr(session_id), &raw));
if (e) return Err(e);
return Ok(adopt_cstring(raw));
}
Result<bool, FfiError> Wda::is_locked(Option<std::string> session_id) {
bool out = false;
FfiError e(::wda_client_is_locked(handle_.get(), opt_cstr(session_id), &out));
if (e) return Err(e);
return Ok(out);
}
} // namespace IdeviceFFI
+46
View File
@@ -0,0 +1,46 @@
// Jackson Coxson
#include <idevice++/wda_bridge.hpp>
namespace IdeviceFFI {
Result<WdaBridge, FfiError> WdaBridge::start(Provider&& provider) {
WdaBridgeHandle* out = nullptr;
FfiError e(::wda_bridge_start(provider.raw(), &out));
// Provider is consumed by the FFI regardless of outcome.
provider.release();
if (e) return Err(e);
return Ok(WdaBridge::adopt(out));
}
Result<WdaBridge, FfiError> WdaBridge::start_with_ports(Provider&& provider,
uint16_t device_http,
uint16_t device_mjpeg) {
WdaBridgeHandle* out = nullptr;
FfiError e(::wda_bridge_start_with_ports(provider.raw(), device_http, device_mjpeg, &out));
provider.release();
if (e) return Err(e);
return Ok(WdaBridge::adopt(out));
}
Result<WdaBridgeEndpoints, FfiError> WdaBridge::endpoints() const {
WdaBridgeEndpointsC* raw = nullptr;
FfiError e(::wda_bridge_endpoints(handle_.get(), &raw));
if (e) return Err(e);
WdaBridgeEndpoints out;
if (raw->udid) {
out.udid = Some(std::string(raw->udid));
}
if (raw->wda_url) out.wda_url = raw->wda_url;
if (raw->mjpeg_url) out.mjpeg_url = raw->mjpeg_url;
out.local_http = raw->local_http;
out.local_mjpeg = raw->local_mjpeg;
out.device_http = raw->device_http;
out.device_mjpeg = raw->device_mjpeg;
::wda_bridge_endpoints_free(raw);
return Ok(std::move(out));
}
} // namespace IdeviceFFI