mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-05-17 20:20:34 +00:00
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:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user