add device context management and use qml6glsink for airplay

- Introduced a new module `device_ctx` to manage device-related services and state.
- Replaced direct access to `APP_DEVICE_STATE` with functions from `device_ctx` for better encapsulation.
- Added constants for recent and favorites album queries in `constants.rs`.
- Updated SQL queries in `query_sqlite.rs` to use constants from `constants.rs`.
- Removed the `hause_arrest.rs` file and integrated its functionality into the new structure.
- Refactored `ServiceFactory` to create AFC clients using the new device context.
- Improved error handling and logging in various modules.
- Airplay now uses qml6glsink.
- Cleaned up unused imports and commented-out code across multiple files.
This commit is contained in:
uncor3
2026-05-12 21:59:47 +00:00
parent 9858bc14fd
commit a51958b7a0
20 changed files with 678 additions and 535 deletions
Generated
+1
View File
@@ -1310,6 +1310,7 @@ version = "0.5.0"
dependencies = [
"anyhow",
"cc",
"cmake",
"cpp",
"cpp_build",
"cstr",
+1
View File
@@ -35,3 +35,4 @@ cpp_build = "0.5.11"
walkdir = "2.5.0"
winres = "0.1.12"
pkg-config = "0.3.33"
cmake = "0.1.58"
+129 -1
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright © 2021-2022 Adrian <adrian.eddy at gmail>
use cmake::build;
use std::env;
use std::fmt::Write;
use std::path::Path;
@@ -14,6 +15,7 @@ fn compile_qml(dir: &str, qt_include_path: &str, qt_library_path: &str) {
config.include(&format!("{}/QtQml", qt_include_path));
println!("cargo:rerun-if-changed=src/live_reload.cpp");
println!("cargo:rerun-if-changed=src/utils.rs");
if cfg!(target_os = "macos") {
config.include(format!("{}/QtCore.framework/Headers/", qt_library_path));
@@ -116,21 +118,79 @@ fn compile_qml(dir: &str, qt_include_path: &str, qt_library_path: &str) {
println!("cargo:rustc-link-lib=static:+whole-archive=qmlcache");
}
// TODO: we may need to do moc
// fn find_moc(qt_library_path: &str) -> String {
// let qt_path = Path::new(qt_library_path).parent().unwrap();
// let candidates = [
// qt_path.join("libexec/moc"),
// qt_path.join("../macos/libexec/moc"),
// qt_path.join("../msvc2019_64/bin/moc"),
// ];
// for c in candidates {
// if c.exists() {
// return c.to_string_lossy().to_string();
// }
// }
// "moc".to_string()
// }
// fn run_moc(moc: &str, header: &str, out_dir: &Path) -> String {
// let base = Path::new(header)
// .file_stem()
// .unwrap()
// .to_string_lossy()
// .to_string();
// let moc_cpp = out_dir.join(format!("moc_{}.cpp", base));
// assert!(
// Command::new(moc)
// .args([header, "-o", moc_cpp.to_str().unwrap()])
// .status()
// .unwrap()
// .success()
// );
// moc_cpp.to_string_lossy().to_string()
// }
fn compile_bridge(qt_include_path: &str) {
println!("compile_bridge");
println!("cargo:rerun-if-changed=src/bridge.cpp");
println!("cargo:rerun-if-changed=src/include/bridge.h");
println!("cargo:rerun-if-changed=src/networkdeviceprovider.h");
println!("cargo:rerun-if-changed=src/core/services/avahi/avahi_service.h");
println!("cargo:rerun-if-changed=src/core/services/avahi/avahi_service.cpp");
let dir_var = env::var("OUT_DIR").unwrap();
// let out_dir = Path::new(&dir_var);
// let moc = find_moc(&env::var("DEP_QT_LIBRARY_PATH").unwrap());
let mut cc_build = cc::Build::new();
cc_build
.cpp(true)
.file("src/bridge.cpp")
// .file("src/core/services/avahi/avahi_service.cpp")
// .include("src")
.include(qt_include_path)
.include(format!("{}/QtCore", qt_include_path))
.include(format!("{}/QtGui", qt_include_path))
.flag_if_supported("-std=c++17")
.flag_if_supported("-Wno-deprecated-declarations");
// for f in env::var("DEP_QT_COMPILE_FLAGS")
// .unwrap()
// .split_terminator(';')
// {
// cc_build.flag(f);
// }
// for header in [
// "src/networkdeviceprovider.h",
// "src/core/services/avahi/avahi_service.h",
// ] {
// if Path::new(header).exists() {
// let moc_cpp = run_moc(&moc, header, out_dir);
// cc_build.file(moc_cpp);
// }
// }
if let Ok(ffmpeg_dir) = std::env::var("FFMPEG_DIR") {
cc_build.include(format!("{}/include", ffmpeg_dir));
println!("cargo:rustc-link-search={}/lib", ffmpeg_dir);
@@ -148,15 +208,64 @@ fn compile_bridge(qt_include_path: &str) {
let _ = pkg_config::Config::new().probe("libavcodec");
let _ = pkg_config::Config::new().probe("libavutil");
let _ = pkg_config::Config::new().probe("libswscale");
let _ = pkg_config::Config::new().probe("avahi-client");
}
cc_build.compile("bridge");
for lib in ["avformat", "avcodec", "avutil", "swscale"] {
for lib in ["avformat", "avcodec", "avutil", "swscale", "avahi-client"] {
println!("cargo:rustc-link-lib={}", lib);
}
}
fn compile_uxplay() {
let uxplay = cmake::Config::new("lib/uxplay")
.build_target("uxplay")
//no need for x11
.define("NO_X11_DEPS", "ON")
.build();
let build = uxplay.display();
println!("cargo:rustc-link-search=native={build}/build");
println!("cargo:rustc-link-search=native={build}/build/lib");
println!("cargo:rustc-link-search=native={build}/build/renderers");
println!("cargo:rustc-link-search=native={build}/build/lib/llhttp");
println!("cargo:rustc-link-search=native={build}/build/lib/playfair");
println!("cargo:rustc-link-lib=static=uxplay");
println!("cargo:rustc-link-lib=static=renderers");
println!("cargo:rustc-link-lib=static=airplay");
println!("cargo:rustc-link-lib=static=llhttp");
println!("cargo:rustc-link-lib=static=playfair");
// TODO: don't depend on Qt6Core
pkg_config::Config::new().probe("Qt6Core").unwrap();
// gst stuff
for pkg in &[
"gstreamer-1.0",
"gstreamer-app-1.0",
"gstreamer-video-1.0",
"gstreamer-audio-1.0",
] {
pkg_config::Config::new().probe(pkg).unwrap();
}
pkg_config::Config::new().probe("openssl").unwrap();
// Linux uses avahi
pkg_config::Config::new().probe("avahi-compat-libdns_sd").unwrap();
// FIXME: macOS and Windows
// println!("cargo:rustc-link-lib=dns_sd");
// glib
pkg_config::Config::new().probe("glib-2.0").unwrap();
pkg_config::Config::new().probe("gobject-2.0").unwrap();
pkg_config::Config::new().probe("libplist-2.0").unwrap();
}
fn main() {
println!("cargo:rerun-if-changed=src/main.rs");
@@ -187,6 +296,24 @@ fn main() {
config.flag(f);
}
let mut add_pkg_includes = |pkg: &str| {
let lib = pkg_config::Config::new()
.cargo_metadata(false)
.probe(pkg)
.unwrap_or_else(|e| panic!("pkg-config probe failed for {pkg}: {e}"));
for p in lib.include_paths {
config.include(p);
}
};
add_pkg_includes("gstreamer-1.0");
add_pkg_includes("gstreamer-app-1.0");
add_pkg_includes("gstreamer-video-1.0");
add_pkg_includes("gstreamer-audio-1.0");
add_pkg_includes("glib-2.0");
add_pkg_includes("gobject-2.0");
let mut public_include = |name| {
if cfg!(target_os = "macos") {
config.include(format!("{}/{}.framework/Headers/", qt_library_path, name));
@@ -328,4 +455,5 @@ fn main() {
}
compile_bridge(&qt_include_path);
compile_uxplay();
}
+17 -17
View File
@@ -1,9 +1,10 @@
use crate::qt_threading::{QtThread, QtThreading};
use crate::{APP_DEVICE_STATE, RUNTIME, run_sync};
use crate::device_ctx;
use crate::qt_threading::QtThreading;
use crate::{RUNTIME, run_sync};
use idevice::{
IdeviceService,
afc::{AfcClient, opcode::AfcFopenMode},
};
use macros::QtThreading;
use once_cell::sync::Lazy;
use qmetaobject::prelude::*;
use qttypes::{QStringList, QVariantMap};
@@ -16,7 +17,7 @@ use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufWriter};
use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio::sync::oneshot;
#[derive(QObject)]
#[derive(QObject, QtThreading)]
pub struct AfcServices {
base: qt_base_class!(trait QObject),
afc: Arc<Mutex<AfcClient>>,
@@ -31,20 +32,18 @@ pub struct AfcServices {
),
start_video_stream: qt_method!(fn(&self, file_path: QString) -> QString),
delete_path: qt_method!(fn(&self, path: QString) -> bool),
}
impl QtThreading for AfcServices {
fn qt_thread(&self) -> crate::qt_threading::QtThread<Self>
where
Self: Sized,
{
QtThread::new(self)
}
//only required for hause_arrest afc
bundle_id: qt_property!(QString),
}
impl AfcServices {
/* udid is for debugging purposes */
pub fn from_afc_client(afc_client: Arc<Mutex<AfcClient>>, udid: String) -> Self {
pub fn from_afc_client(
afc_client: Arc<Mutex<AfcClient>>,
/* udid is for debugging purposes */
udid: String,
//only required for hause_arrest afc
bundle_id: Option<String>,
) -> Self {
Self {
afc: afc_client,
udid: udid,
@@ -57,6 +56,7 @@ impl AfcServices {
check_is_dir_and_list_finished: Default::default(),
start_video_stream: Default::default(),
delete_path: Default::default(),
bundle_id: bundle_id.map_or_else(QString::default, QString::from),
}
}
@@ -325,7 +325,7 @@ impl AfcServices {
let udid_for_insert = udid.clone();
// FIXME: should we do it here ?
let inserted = run_sync(async move {
let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_for_insert).cloned();
let maybe_device = device_ctx::get_device_opt(udid_for_insert).await;
let device = match maybe_device {
Some(d) => d,
None => return false,
@@ -387,7 +387,7 @@ impl AfcServices {
run_sync(async move {
let afc_arc = {
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let maybe_device = device_ctx::get_device_opt(&udid).await;
let device = match maybe_device {
Some(d) => d,
+78
View File
@@ -0,0 +1,78 @@
use crate::{RUNTIME, qt_threading::QtThreading, qvariantmap_insert};
use idevice::utils;
use ipatool::Account;
use ipatool::IpaTool;
use macros::QtThreading;
use qmetaobject::prelude::*;
use qttypes::{QStringList, QVariantMap};
use std::ffi::CString;
use std::os::raw::c_void;
use std::os::raw::{c_char, c_int};
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use cpp::*;
static VIDEO_ITEM_PTR : AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" {
fn init_uxplay(argc: c_int, argv: *mut *mut c_char) -> c_int;
fn set_uxplay_gl_callbacks(
connection_cb: extern "C" fn(bool),
get_video_item_cb: extern "C" fn() -> *mut c_void,
);
}
extern "C" fn rust_uxplay_get_video_item() -> *mut c_void {
VIDEO_ITEM_PTR.load(Ordering::Acquire) as *mut c_void
}
extern "C" fn rust_uxplay_connection_cb(connected : bool) {
}
#[derive(QObject, Default, QtThreading)]
pub struct Airplay {
base: qt_base_class!(trait QObject),
init : qt_method!(fn(&self, video_item : QVariant) -> bool),
load_gst_gl : qt_method!(fn(&self) -> bool),
connection_change : qt_signal!(connected: bool)
}
impl Airplay {
fn load_gst_gl(&self) -> bool {
crate::utils::force_load_gst_gl()
}
fn init(&self, video_item: QVariant ) -> bool {
let ptr = crate::utils::qvariant_to_ptr(video_item);
VIDEO_ITEM_PTR.store(ptr as usize, Ordering::Release);
unsafe {
set_uxplay_gl_callbacks(
rust_uxplay_connection_cb,
rust_uxplay_get_video_item
);
}
std::thread::spawn(||{
let arg0 = CString::new("uxplay").unwrap();
let arg1 = CString::new("-p").unwrap();
let arg2 =CString::new("-fps").unwrap();
let arg3 =CString::new("60").unwrap();
let mut c_args = vec![arg0.as_ptr() as *mut c_char, arg1.as_ptr() as *mut c_char, arg2.as_ptr() as *mut c_char,arg3.as_ptr() as *mut c_char, std::ptr::null_mut()];
unsafe {
init_uxplay((c_args.len() - 1) as i32, c_args.as_mut_ptr());
}
});
true
}
}
+50
View File
@@ -0,0 +1,50 @@
pub const RECENTS_ALBUM_ID: i32 = -1;
pub const FAVS_ALBUM_ID: i32 = -2;
pub static IOS_15_ALBUM_QUERY_STATEMENT: &str = "SELECT
ZGENERICALBUM.Z_PK,
ZGENERICALBUM.ZTITLE,
ZGENERICALBUM.ZCACHEDCOUNT,
ZASSET.ZDIRECTORY,
ZASSET.ZFILENAME
FROM ZGENERICALBUM
LEFT JOIN ZASSET ON ZGENERICALBUM.ZKEYASSET = ZASSET.Z_PK
WHERE ZGENERICALBUM.ZKEYASSET IS NOT NULL
";
pub static RECENTS_ALBUM_QUERY: &str = "
SELECT
ZASSET.ZFILENAME,
ZASSET.ZDIRECTORY,
(SELECT COUNT(*) FROM ZASSET)
FROM ZASSET
ORDER BY ZASSET.Z_PK DESC
LIMIT 1
";
pub static RECENTS_QUERY: &str = "
SELECT
ZASSET.ZFILENAME,
ZASSET.ZDIRECTORY
FROM ZASSET ORDER BY ZASSET.Z_PK DESC
";
pub static FAVS_ALBUM_QUERY: &str = "
SELECT
ZASSET.ZFILENAME,
ZASSET.ZDIRECTORY,
(SELECT COUNT(*) FROM ZASSET WHERE ZASSET.ZFAVORITE = 1)
FROM ZASSET
WHERE ZASSET.ZFAVORITE = 1
ORDER BY ZASSET.Z_PK DESC
LIMIT 1
";
pub static FAVS_QUERY: &str = "
SELECT
ZASSET.ZFILENAME,
ZASSET.ZDIRECTORY
FROM ZASSET
WHERE ZASSET.ZFAVORITE = 1
ORDER BY ZASSET.Z_PK DESC
";
+4 -2
View File
@@ -30,8 +30,9 @@ use once_cell::sync::Lazy;
use plist::{Dictionary, Value};
use qmetaobject::prelude::*;
use crate::device_ctx::{APP_DEVICE_STATE,DeviceServices};
use crate::{
APP_DEVICE_STATE, APP_LABEL, DeviceServices, EV_CONNECTED, EV_DISCONNECTED, EV_FAIL,
APP_LABEL, EV_CONNECTED, EV_DISCONNECTED, EV_FAIL,
EV_PAIRING_PENDING, POSSIBLE_ROOT, RUNTIME,
qt_threading::{QtThread, QtThreading},
utils,
@@ -45,7 +46,7 @@ pub struct Core {
init_wireless_device:
qt_method!(fn(&mut self, ip: QString, pairing_file: QString, mac_address: QString)),
// get_pairing_files : qt_method(fn remove_device(mut &self, udid: &QString)),
get_pairing_files : qt_method!(fn (&mut self) -> QVariantMap),
// remove_device : qt_method!(fn (&mut self, udid: QString)),
device_event: qt_signal!(event_type : u32, udid : QString , info : QVariantMap),
init_failed: qt_signal!(mac_address : QString),
@@ -569,6 +570,7 @@ async fn init_idescriptor_device<
lockdown: Arc::new(Mutex::new(lc)),
};
// FIXME: use device_ctx
{
let mut state = APP_DEVICE_STATE.lock().await;
if let Some(mut old) = state.insert(udid.to_string(), device_services) {
+39
View File
@@ -0,0 +1,39 @@
use idevice::{
afc::AfcClient, diagnostics_relay::DiagnosticsRelayClient, lockdown::LockdownClient,
};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::oneshot;
use tokio::task::JoinHandle;
#[derive(Clone)]
pub struct DeviceServices {
pub afc: Arc<Mutex<AfcClient>>,
pub afc2: Option<Arc<Mutex<AfcClient>>>,
pub diag: Arc<Mutex<DiagnosticsRelayClient>>,
pub heartbeat_task: Option<Arc<JoinHandle<()>>>,
pub video_streams: Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>,
pub provider: Arc<Mutex<Box<dyn idevice::provider::IdeviceProvider>>>,
pub lockdown: Arc<Mutex<LockdownClient>>,
}
// FIXME: shouldn't be public
pub static APP_DEVICE_STATE: Lazy<Mutex<HashMap<String, DeviceServices>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub async fn get_device(udid: impl Into<String>) -> anyhow::Result<DeviceServices> {
let udid_str = udid.into();
let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_str).cloned();
match maybe_device {
Some(d) => Ok(d),
None => anyhow::bail!(format!("Device with udid {} does not exist", udid_str)),
}
}
pub async fn get_device_opt(udid: impl Into<String>) -> Option<DeviceServices> {
let udid_str = udid.into();
APP_DEVICE_STATE.lock().await.get(&udid_str).cloned()
}
-374
View File
@@ -1,374 +0,0 @@
use crate::{APP_DEVICE_STATE, RUNTIME, afc, run_sync, utils};
use cxx_qt::{CxxQtType, Threading};
use cxx_qt_lib::{QByteArray, QMap, QMapPair_QString_QVariant, QString};
use idevice::afc::{AfcClient, opcode::AfcFopenMode};
use std::pin::Pin;
use std::sync::Arc;
use tokio::{
io::AsyncReadExt,
net::TcpListener,
sync::{Mutex, oneshot},
};
#[cxx_qt::bridge(namespace = "CXX")]
mod qobject {
#[namespace = ""]
unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h");
include!("cxx-qt-lib/qlist.h");
include!("cxx-qt-lib/qbytearray.h");
include!("cxx-qt-lib/qmap.h");
include!("cxx-qt-lib/qvariant.h");
type QString = cxx_qt_lib::QString;
type QMap_QString_QVariant = cxx_qt_lib::QMap<cxx_qt_lib::QMapPair_QString_QVariant>;
type QByteArray = cxx_qt_lib::QByteArray;
}
extern "RustQt" {
#[qobject]
type HauseArrest = super::RHauseArrestBackend;
#[qinvokable]
fn get_bundle_id(self: &HauseArrest) -> &QString;
#[qinvokable]
fn init_session(self: Pin<&mut HauseArrest>);
#[qsignal]
fn init_session_finished(self: Pin<&mut HauseArrest>, success: bool);
#[qinvokable]
fn check_is_dir_and_list(self: &HauseArrest, path: &QString);
#[qsignal]
fn check_is_dir_and_list_finished(
self: Pin<&mut HauseArrest>,
success: bool,
entries: &QMap_QString_QVariant,
);
#[qinvokable]
fn file_to_buffer(self: &HauseArrest, file_path: &QString) -> QByteArray;
#[qinvokable]
fn start_video_stream(self: &HauseArrest, file_path: &QString) -> QString;
#[qinvokable]
fn delete_path(self: &HauseArrest, path: &QString) -> bool;
}
impl cxx_qt::Threading for HauseArrest {}
impl cxx_qt::Constructor<(QString, QString), NewArguments = (QString, QString)> for HauseArrest {}
}
#[derive(Default)]
pub struct RHauseArrestBackend {
udid: QString,
bundle_id: QString,
afc_handle: Option<Arc<Mutex<AfcClient>>>,
}
impl cxx_qt::Constructor<(QString, QString)> for qobject::HauseArrest {
type BaseArguments = ();
type InitializeArguments = ();
type NewArguments = (QString, QString);
fn route_arguments(
args: (QString, QString),
) -> (
Self::NewArguments,
Self::BaseArguments,
Self::InitializeArguments,
) {
(args, (), ())
}
fn new(args: (QString, QString)) -> RHauseArrestBackend {
RHauseArrestBackend {
udid: args.0,
bundle_id: args.1,
afc_handle: None,
}
}
}
impl qobject::HauseArrest {
fn get_bundle_id(&self) -> &QString {
use cxx_qt::CxxQtType;
&self.rust().bundle_id
}
fn init_session(self: Pin<&mut Self>) {
let udid_str = self.udid.to_string();
let bundle_id_str = self.bundle_id.to_string();
let qt_thread = self.qt_thread();
RUNTIME.spawn(async move {
let afc_client = {
let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_str).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("HouseArrest: Device {} not found", udid_str);
qt_thread
.queue(move |q| {
q.init_session_finished(false);
})
.ok();
return;
}
};
let provider_guard = device.provider.lock().await;
match utils::vend_app_documents(provider_guard.as_ref(), &bundle_id_str).await {
Ok(afc_client) => afc_client,
Err(e) => {
eprintln!(
"Failed to initialize HouseArrest session for {}: {}",
bundle_id_str, e
);
qt_thread
.queue(move |q| {
q.init_session_finished(false);
})
.ok();
return;
}
}
};
qt_thread
.queue(move |mut qobject| {
qobject.as_mut().rust_mut().afc_handle = Some(Arc::new(Mutex::new(afc_client)));
})
.ok();
qt_thread
.queue(move |q| {
q.init_session_finished(true);
})
.ok();
});
}
fn check_is_dir_and_list(self: &Self, path: &QString) {
let qt_t = self.qt_thread();
let path_str = path.to_string();
let afc_opt = self.rust().afc_handle.clone();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let Some(afc_handle) = afc_opt else {
eprintln!("HouseArrest: AfcClient not initialized");
qt_thread
.queue(move |q| {
q.check_is_dir_and_list_finished(
false,
&QMap::<QMapPair_QString_QVariant>::default(),
);
})
.ok();
return;
};
let mut afc_client = afc_handle.lock().await;
let map_result = afc::check_is_dir_and_list(&mut *afc_client, path_str).await;
qt_thread
.queue(move |q| {
q.check_is_dir_and_list_finished(true, &map_result);
})
.ok();
});
}
fn file_to_buffer(&self, album_path: &QString) -> QByteArray {
let album_path_string = album_path.to_string();
let afc_opt = self.rust().afc_handle.clone();
let data: Vec<u8> = run_sync(async move {
let Some(afc_handle) = afc_opt else {
eprintln!("HouseArrest: AfcClient not initialized");
return Vec::new();
};
let mut afc_client = afc_handle.lock().await;
let mut fd = match afc_client
.open(album_path_string.clone(), AfcFopenMode::RdOnly)
.await
{
Ok(f) => f,
Err(e) => {
eprintln!("file_to_buffer: failed to open {album_path_string}: {e}");
return Vec::new();
}
};
let mut buf = Vec::new();
let mut chunk = vec![0u8; 8192];
loop {
let n = match fd.read(&mut chunk).await {
Ok(n) => n,
Err(e) => {
eprintln!("file_to_buffer: failed to read {album_path_string}: {e}");
buf.clear();
break;
}
};
if n == 0 {
break;
}
buf.extend_from_slice(&chunk[..n]);
}
fd.close().await.ok();
buf
});
if data.is_empty() {
QByteArray::default()
} else {
QByteArray::from(&data[..])
}
}
fn start_video_stream(&self, file_path: &QString) -> QString {
let afc_opt = self.rust().afc_handle.clone();
let Some(afc) = afc_opt else {
eprintln!("HouseArrest: AfcClient not initialized");
return QString::default();
};
let udid_str = self.udid.to_string();
let path_str = file_path.to_string();
let cloned_path = path_str.clone();
eprintln!(
"start_video_stream: request udid={} path={}",
udid_str, cloned_path
);
// bind ephemeral port on localhost
let listener = match std::net::TcpListener::bind("127.0.0.1:0") {
Ok(l) => l,
Err(e) => {
eprintln!("start_video_stream: bind failed: {e}");
return QString::default();
}
};
let local_addr = match listener.local_addr() {
Ok(a) => a,
Err(e) => {
eprintln!("start_video_stream: local_addr failed: {e}");
return QString::default();
}
};
listener.set_nonblocking(true).ok();
// create Tokio TcpListener inside runtime
let std_listener = {
let _guard = RUNTIME.handle().enter();
match TcpListener::from_std(listener) {
Ok(l) => l,
Err(e) => {
eprintln!("start_video_stream: from_std failed: {e}");
return QString::default();
}
}
};
let port = local_addr.port();
let encoded = urlencoding::encode(&cloned_path);
let url = format!("http://127.0.0.1:{}/{}", port, encoded);
let url_clone = url.clone();
let url_clone_for_log = url.clone();
let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>();
let udid_for_insert = udid_str.clone();
let url_for_insert = url.clone();
let inserted = run_sync(async move {
let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_for_insert).cloned();
let device = match maybe_device {
Some(d) => d,
None => return false,
};
let mut video_streams = device.video_streams.lock().await;
video_streams.insert(url_for_insert, shutdown_tx);
true
});
if !inserted {
eprintln!(
"start_video_stream: failed to insert video stream for udid={} path={}",
udid_str, cloned_path
);
return QString::default();
}
eprintln!(
"start_video_stream: serving {} for udid={} path={}",
url_clone, udid_str, cloned_path
);
// accept-loop task
RUNTIME.spawn(async move {
loop {
tokio::select! {
_ = &mut shutdown_rx => {
// shutdown requested
eprintln!("start_video_stream: shutdown requested for {}", url_clone);
break;
}
accept_res = std_listener.accept() => {
let (socket, peer) = match accept_res {
Ok(s) => s,
Err(e) => {
eprintln!("start_video_stream: accept error: {e} on {}", url_clone);
break;
}
};
eprintln!("start_video_stream: accepted connection from {} on {}", peer, url_clone);
let path_clone = path_str.clone();
let afc_for_conn = afc.clone();
tokio::spawn(async move {
let mut afc = afc_for_conn.lock().await;
afc::handle_http_connection(&mut afc, path_clone, socket).await;
});
}
}
}
eprintln!("start_video_stream: accept-loop exiting for {}", url_clone);
});
QString::from(url_clone_for_log)
}
fn delete_path(&self, path: &QString) -> bool {
let path_str = path.to_string();
let afc_opt = self.rust().afc_handle.clone();
run_sync(async move {
let Some(afc_handle) = afc_opt else {
eprintln!("HouseArrest: AfcClient not initialized");
return false;
};
let mut afc_client = afc_handle.lock().await;
match afc_client.remove(&path_str).await {
Ok(_) => true,
Err(e) => {
eprintln!("delete_path: failed to delete {}: {}", path_str, e);
false
}
}
})
}
}
+5 -23
View File
@@ -1,3 +1,7 @@
use crate::RUNTIME;
use crate::device_ctx;
use crate::qt_threading::{QtThread, QtThreading};
use crate::utils::{AfcReader, create_image_from_buffer, generate_thumbnail, is_video_file};
use idevice::afc::AfcClient;
use idevice::afc::opcode::AfcFopenMode;
use once_cell::sync::Lazy;
@@ -15,10 +19,6 @@ use tokio::{
sync::{Notify, Semaphore},
};
use crate::qt_threading::{QtThread, QtThreading};
use crate::utils::{AfcReader, create_image_from_buffer, generate_thumbnail, is_video_file};
use crate::{APP_DEVICE_STATE, RUNTIME};
#[derive(Default, QObject)]
pub struct ImageLoader {
base: qt_base_class!(trait QObject),
@@ -123,25 +123,7 @@ fn ensure_worker_started() {
let _permit = permit;
let res: anyhow::Result<()> = async {
let afc_arc = {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(key.udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
// eprintln!(
// "image_loader::read_file_via_afc: device {udid} not found"
// );
anyhow::bail!("No device");
}
};
device.afc.clone()
};
let afc_arc = device_ctx::get_device(key.udid.as_str()).await?.afc;
let mut img = QImage::default();
if is_video_file(&key.path) {
-5
View File
@@ -82,11 +82,6 @@ impl QQuickImageProvider for ImageProvider {
);
}
println!("index ={}", index);
// self.loader
// .request_thumbnail(QString::from(udid), QString::from(path), index);
self.loader.pinned().clone().borrow_mut().request_thumbnail(
QString::from(udid),
QString::from(path),
+28 -16
View File
@@ -23,7 +23,9 @@ use crate::qquickimageprovider_imp::AddImageProvider;
pub mod afc_services;
pub mod apps;
pub mod constants;
pub mod core;
pub mod device_ctx;
pub mod image_cache;
pub mod image_loader;
pub mod image_provider;
@@ -33,6 +35,7 @@ pub mod qt_threading;
pub mod query_sqlite;
pub mod service_factory;
pub mod service_manager;
pub mod airplay;
pub mod utils;
pub const POSSIBLE_ROOT: &str = "../../../../";
@@ -55,22 +58,9 @@ cpp! {{
#include <QIcon>
#include "src/live_reload.cpp"
// #include "src/networkdeviceprovider.h"
}}
#[derive(Clone)]
pub struct DeviceServices {
pub afc: Arc<Mutex<AfcClient>>,
pub afc2: Option<Arc<Mutex<AfcClient>>>,
pub diag: Arc<Mutex<DiagnosticsRelayClient>>,
pub heartbeat_task: Option<Arc<JoinHandle<()>>>,
pub video_streams: Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>,
pub provider: Arc<Mutex<Box<dyn idevice::provider::IdeviceProvider>>>,
pub lockdown: Arc<Mutex<LockdownClient>>,
}
pub static APP_DEVICE_STATE: Lazy<Mutex<HashMap<String, DeviceServices>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
@@ -78,6 +68,7 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
.unwrap()
});
// TODO: handle timeout here
pub fn run_sync<F, R>(fut: F) -> R
where
F: Future<Output = R> + Send + 'static,
@@ -128,7 +119,8 @@ fn main() {
// FIXME: fluentui example app was forcing OpenGL
// but do we need this ?
// #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
// QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
// QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
// QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
// #endif
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
@@ -198,7 +190,7 @@ fn main() {
// #[cfg(not(compiled_qml))]
// crate::resources_qml::rsrc_qml();
qml_register_type::<core::Core>(cstr::cstr!("iDescriptor"), 1, 0, cstr::cstr!("Core"));
// qml_register_type::<core::Core>(cstr::cstr!("iDescriptor"), 1, 0, cstr::cstr!("Core"));
qml_register_type::<query_sqlite::Query>(
cstr::cstr!("iDescriptor"),
1,
@@ -206,9 +198,20 @@ fn main() {
cstr::cstr!("Query"),
);
// qml_register_type::<airplay::Airplay>(
// cstr::cstr!("iDescriptor"),
// 1,
// 0,
// cstr::cstr!("Airplay"),
// );
let mut engine = QmlEngine::new();
// let mut dpi = cpp!(unsafe[] -> f64 as "double" { return QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0; });
let core_obj = QObjectBox::new(core::Core::default());
engine.set_object_property("core".into(), core_obj.pinned());
let obj = QObjectBox::new(image_loader::ImageLoader::default());
engine.set_object_property("imageLoader".into(), obj.pinned());
@@ -218,8 +221,17 @@ fn main() {
let provider_ref_cell = QObjectBox::new(image_provider::ImageProvider::default(obj));
engine.add_image_provider("thumb", provider_ref_cell);
let airplay = QObjectBox::new(airplay::Airplay::default());
engine.set_object_property("AirplayImp".into(), airplay.pinned());
let engine_ptr = engine.cpp_ptr();
// cpp!(unsafe [engine_ptr as "QQmlApplicationEngine *"] {
// engine_ptr->rootContext()->setContextProperty("NetworkDeviceProvider", NetworkDeviceProvider::sharedInstance());
// });
let service_factory = QObjectBox::new(crate::service_factory::ServiceFactory::new(engine_ptr));
engine.set_object_property("serviceFactory".into(), service_factory.pinned());
+123 -77
View File
@@ -1,19 +1,15 @@
use qmetaobject::prelude::*;
use qttypes::{QStringList, QVariantMap};
use crate::qt_threading::{QtThread, QtThreading};
use crate::{APP_DEVICE_STATE, RUNTIME, run_sync};
use idevice::afc::{AfcClient, opcode::AfcFopenMode};
use idevice::{
IdeviceError, IdeviceService, diagnostics_relay::DiagnosticsRelayClient,
house_arrest::HouseArrestClient, installation_proxy::InstallationProxyClient,
provider::IdeviceProvider,
use crate::constants::{
FAVS_ALBUM_ID, FAVS_ALBUM_QUERY, FAVS_QUERY, IOS_15_ALBUM_QUERY_STATEMENT, RECENTS_ALBUM_ID,
RECENTS_ALBUM_QUERY, RECENTS_QUERY,
};
use once_cell::sync::Lazy;
use plist::Dictionary as PlistDictionary;
use plist_macro::plist;
use regex::Regex;
use crate::device_ctx;
use crate::qt_threading::{QtThread, QtThreading};
use crate::utils::create_album_info;
use crate::{RUNTIME, run_sync};
use idevice::afc::{AfcClient, opcode::AfcFopenMode};
use rusqlite::{Connection, Rows};
use serde_json::json;
use std::default;
@@ -53,7 +49,6 @@ impl QtThreading for Query {
}
impl Default for Query {
// qml_register_type calls ::default
fn default() -> Self {
let mut state = QVariantMap::default();
state.insert(QString::from("init"), QVariant::from(false));
@@ -83,18 +78,7 @@ impl Query {
RUNTIME.spawn(async move {
let res: Result<(), Box<dyn std::error::Error + Send + Sync>> = (async {
let mut gallery_db_bytes = {
let afc_arc = {
let device = APP_DEVICE_STATE
.lock()
.await
.get(&udid_clone.to_string())
.cloned();
match device {
Some(d) => d.afc.clone(),
None => return Err("Device not found".into()),
}
};
let afc_arc = device_ctx::get_device(udid_clone).await?.afc;
let mut afc = afc_arc.lock().await;
let mut fd = afc
.open("/PhotoData/Photos.sqlite", AfcFopenMode::RdOnly)
@@ -170,7 +154,7 @@ impl Query {
let con_arc = match &self.connection {
Some(c) => c.clone(),
None => {
println!("WTF NO CONN");
println!("NO CONN");
return;
}
};
@@ -181,17 +165,7 @@ impl Query {
let res: Result<(), Box<dyn std::error::Error + Send + Sync>> = (async {
let conn = con_arc.lock().await;
//recents album
let mut recents_stmt = conn.prepare(
"
SELECT
ZASSET.ZFILENAME as 'FNAME',
ZASSET.ZDIRECTORY as 'DIR',
(SELECT COUNT(*) FROM ZASSET) as 'COUNT'
FROM ZASSET
ORDER BY ZASSET.Z_PK DESC
LIMIT 1
",
)?;
let mut recents_stmt = conn.prepare(RECENTS_ALBUM_QUERY)?;
let (fname, fdir, count) = recents_stmt.query_row([], |r| {
let fname: String = r.get(0)?;
@@ -200,9 +174,7 @@ impl Query {
Ok((fname, fdir, count))
})?;
let recents_album_data =
json!({ "item_count" : count, "file_path" : format!("{}/{}",fdir,fname)})
.to_string();
let recents_album_data = create_album_info(RECENTS_ALBUM_ID, count, fdir, fname);
albums.insert(
QString::from("Recents"),
@@ -210,18 +182,7 @@ impl Query {
);
//favs
let mut favs_stmt = conn.prepare(
"
SELECT
ZASSET.ZFILENAME,
ZASSET.ZDIRECTORY,
(SELECT COUNT(*) FROM ZASSET WHERE ZASSET.ZFAVORITE = 1)
FROM ZASSET
WHERE ZASSET.ZFAVORITE = 1
ORDER BY ZASSET.Z_PK DESC
LIMIT 1
",
)?;
let mut favs_stmt = conn.prepare(FAVS_ALBUM_QUERY)?;
let (fname, fdir, count) = favs_stmt.query_row([], |r| {
let fname: String = r.get(0)?;
@@ -230,9 +191,7 @@ impl Query {
Ok((fname, fdir, count))
})?;
let favs_album_data =
json!({"item_count" : count, "file_path" : format!("{}/{}",fdir,fname) })
.to_string();
let favs_album_data = create_album_info(FAVS_ALBUM_ID, count, fdir, fname);
albums.insert(
QString::from("Favorites"),
@@ -255,18 +214,7 @@ impl Query {
// ",
// )?;
let mut stmt = conn.prepare(
"SELECT
ZGENERICALBUM.Z_PK,
ZGENERICALBUM.ZTITLE,
ZGENERICALBUM.ZCACHEDCOUNT,
ZASSET.ZDIRECTORY,
ZASSET.ZFILENAME
FROM ZGENERICALBUM
LEFT JOIN ZASSET ON ZGENERICALBUM.ZKEYASSET = ZASSET.Z_PK
WHERE ZGENERICALBUM.ZKEYASSET IS NOT NULL
",
)?;
let mut stmt = conn.prepare(IOS_15_ALBUM_QUERY_STATEMENT)?;
let rows_iter = stmt.query_map([], |row| {
let album_id: i32 = row.get(0)?;
@@ -280,12 +228,8 @@ impl Query {
for row_res in rows_iter {
let (album_id, title, item_count, asset_dir, asset_file_name) = row_res?;
let album_data = crate::utils::create_album_info(
album_id,
item_count,
asset_dir,
asset_file_name,
);
let album_data =
create_album_info(album_id, item_count, asset_dir, asset_file_name);
albums.insert(
QString::from(title),
@@ -323,6 +267,18 @@ impl Query {
Some(c) => c.clone(),
None => return,
};
match id {
FAVS_ALBUM_ID => {
return self.query_favs();
}
RECENTS_ALBUM_ID => {
return self.query_recents();
}
/* continue if something else */
_ => {}
};
let q_thread = self.qt_thread();
RUNTIME.spawn(async move {
@@ -361,16 +317,106 @@ impl Query {
q.album_queried(id, list);
});
}
Err(_) => {
println!("Error querying album")
Err(e) => {
println!("Error querying album {}", e.to_string())
}
}
});
}
fn query_favs(self: Pin<&mut Self>) {}
fn query_favs(&mut self) {
let q_thread = self.qt_thread();
let con_arc = match &self.connection {
Some(c) => c.clone(),
None => {
println!("NO CONN");
return;
}
};
fn query_recents(self: Pin<&mut Self>) {}
RUNTIME.spawn(async move {
let res: anyhow::Result<QStringList> = async {
let con = con_arc.lock().await;
let mut list: QStringList = QStringList::default();
//favs album
let mut favs_stmt = con.prepare(FAVS_QUERY)?;
let favs_iter = favs_stmt.query_map([], |r| {
let fname: String = r.get(0)?;
let fdir: String = r.get(1)?;
Ok((fname, fdir))
})?;
for fav_item in favs_iter {
let (fname, fdir) = fav_item?;
list.push(QString::from(format!("{}/{}", fdir, fname)));
}
Ok(list)
}
.await;
match res {
Ok(list) => {
println!("Album loaded has length :{}", list.len());
q_thread.queue(move |q| {
q.album_queried(FAVS_ALBUM_ID, list);
});
}
Err(e) => {
println!("Error querying album {}", e.to_string())
}
}
});
}
fn query_recents(&mut self) {
let q_thread = self.qt_thread();
let con_arc = match &self.connection {
Some(c) => c.clone(),
None => {
println!("NO CONN");
return;
}
};
RUNTIME.spawn(async move {
let res: anyhow::Result<QStringList> = async {
let con = con_arc.lock().await;
let mut list: QStringList = QStringList::default();
//recents album
let mut recents_stmt = con.prepare(RECENTS_QUERY)?;
let recents_iter = recents_stmt.query_map([], |r| {
let fname: String = r.get(0)?;
let fdir: String = r.get(1)?;
Ok((fname, fdir))
})?;
for recent_item in recents_iter {
let (fname, fdir) = recent_item?;
list.push(QString::from(format!("{}/{}", fdir, fname)));
}
Ok(list)
}
.await;
match res {
Ok(list) => {
println!("Album loaded has length :{}", list.len());
q_thread.queue(move |q| {
q.album_queried(RECENTS_ALBUM_ID, list);
});
}
Err(e) => {
println!("Error querying album {}", e.to_string())
}
}
});
}
}
// fn get_album_query_sql(ios_ver : i32, id : i32) {
+57 -9
View File
@@ -1,7 +1,12 @@
use crate::utils::{empty_qjsvalue, engine_ptr_new_object};
use crate::{APP_DEVICE_STATE, afc_services::AfcServices, run_sync};
use crate::device_ctx;
use crate::utils::{empty_qjsvalue, engine_ptr_new_object, vend_app_documents};
use crate::{afc_services::AfcServices, run_sync};
use idevice::afc::AfcClient;
use qmetaobject::{QJSValue, prelude::*};
use std::ffi::c_void;
use std::sync::Arc;
use tokio::sync::Mutex;
/*
we need this because qml side
@@ -14,6 +19,8 @@ pub struct ServiceFactory {
/* SAFETY: check if engine_ptr.is_null() */
engine_ptr: *mut c_void,
create_afc_client: qt_method!(fn(&self, udid: QString, afc2: bool) -> QJSValue),
create_hause_arrest_afc_client:
qt_method!(fn(&self, udid: QString, bundle_id: QString) -> QJSValue),
}
impl ServiceFactory {
@@ -22,6 +29,7 @@ impl ServiceFactory {
base: Default::default(),
engine_ptr,
create_afc_client: Default::default(),
create_hause_arrest_afc_client: Default::default(),
}
}
@@ -37,11 +45,7 @@ impl ServiceFactory {
let afc_arc = run_sync({
let udid_str = udid_str.clone();
async move {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid_str.as_str())
.cloned();
let maybe_device = device_ctx::get_device_opt(&udid_str).await;
let device = match maybe_device {
Some(d) => d,
None => {
@@ -59,13 +63,57 @@ impl ServiceFactory {
});
let Some(afc) = afc_arc else {
eprintln!("ServiceFactory: no AFC client available for {udid_str} (afc2={afc2})");
println!(
"ServiceFactory: no AFC client available for {} (afc2={})",
&udid_str, afc2
);
return empty_qjsvalue();
};
let obj = AfcServices::from_afc_client(afc, udid_str);
let obj = AfcServices::from_afc_client(afc, udid_str, None);
let obj_ptr = qmetaobject::into_leaked_cpp_ptr(obj);
engine_ptr_new_object(engine_ptr, obj_ptr)
}
fn create_hause_arrest_afc_client(&self, udid: QString, bundle_id: QString) -> QJSValue {
let engine_ptr: *mut c_void = self.engine_ptr;
let udid_clone = udid.clone();
let bundle_id_clone = bundle_id.clone();
if engine_ptr.is_null() {
eprintln!("ServiceFactory: engine_ptr is null");
return empty_qjsvalue();
}
let afc_res: anyhow::Result<AfcClient> = run_sync(async move {
let device = device_ctx::get_device(udid).await?;
let provider_guard = device.provider.lock().await;
Ok(vend_app_documents(provider_guard.as_ref(), &bundle_id.to_string()).await?)
});
match afc_res {
Ok(afc) => {
let obj = AfcServices::from_afc_client(
Arc::new(Mutex::new(afc)),
udid_clone.to_string(),
Some(bundle_id_clone.to_string()),
);
let obj_ptr = qmetaobject::into_leaked_cpp_ptr(obj);
engine_ptr_new_object(engine_ptr, obj_ptr)
}
Err(e) => {
println!(
"Couldn't create hause_arrest_afc for bundle id {} for device {} : Error : {}",
bundle_id_clone,
udid_clone,
e.to_string()
);
return empty_qjsvalue();
}
}
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
use qmetaobject::prelude::*;
use qttypes::{QVariantMap,QStringList};
use crate::{APP_DEVICE_STATE, RUNTIME, run_sync, utils,qt_threading::{QtThread,QtThreading},DeviceServices};
use crate::device_ctx::{DeviceServices,get_device_opt};
use crate::{RUNTIME, run_sync, utils,qt_threading::{QtThread,QtThreading}};
use macros::QtThreading;
use idevice::{afc::opcode::AfcFopenMode, provider};
use idevice::services::core_device_proxy::CoreDeviceProxy;
@@ -87,7 +87,7 @@ impl ServiceManager {
loop {
interval.tick().await;
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let maybe_device = get_device_opt(udid.as_str()).await;
let device = match maybe_device {
Some(d) => d,
+7 -4
View File
@@ -4,24 +4,27 @@ import QtQml.Models 2.15
import QtQuick 2.15
import iDescriptor 1.0
/*
core is a global obj set from rust side
engine.set_object_property("core".into(), core_obj.pinned());
*/
QtObject {
id: root
property ListModel devices: ListModel {}
property string currentDeviceUdid : ""
// default info section
// default to info section
property int currentSection : 0
property bool showWelcomePage : true
readonly property Core core: Core {}
function init() {
root.core.init()
core.init()
}
// workaround to use connections inside a QtObject
property var _connections : Connections {
target: root.core
target: core
function onDevice_event(eventType, udid, info) {
console.log("Device event:", eventType, udid, JSON.stringify(info))
+40 -1
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Dialogs
import "." as App
// FIXME: handle window creation logic
@@ -9,6 +10,11 @@ Item {
id: root
anchors.fill: parent
MessageDialog {
id: errorDialog
title: "Error"
text: ""
}
property string currentDeviceUdid: ""
readonly property bool hasDevice: App.DeviceContext.devices && App.DeviceContext.devices.count > 0
@@ -16,7 +22,40 @@ Item {
// 0 Airplayer, 1 VirtualLocation, 2 LiveScreen, 3 QueryMobileGestalt, 4 DeveloperDiskImages,
// 5 WirelessGalleryImport, 6 iFuse, 7 CableInfo, 8 NetworkDevices, 9 MountDevImage,
// 10 Restart, 11 Shutdown, 12 RecoveryMode, 13 EnableWifiConnections
signal toolClicked(int toolId, bool requiresDevice)
// signal toolClicked(int toolId, bool requiresDevice)
function toolClicked(toolId, requiresDevice) {
switch (toolId) {
case 0:
const gl_plugin_loaded = AirplayImp.load_gst_gl()
if (!gl_plugin_loaded) {
errorDialog.text = "Failed to load gst gl plugin, make sure you have your GPU drivers installed"
errorDialog.open()
return;
}
const comp = Qt.createComponent("./tools/Airplay.qml")
if (comp.status === Component.Ready) {
const win = comp.createObject(null,{
})
if (win !== null) {
win.show()
} else {
console.error("createObject failed:", comp.errorString())
}
} else if (comp.status === Component.Error) {
console.error("Component failed to load:", comp.errorString())
}
break;
default:
console.log(`No tool for id ${toolId}`)
}
}
signal deviceSelectionChanged(string udid)
readonly property var mainToolsModel: ([
+58
View File
@@ -0,0 +1,58 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Window
import iDescriptor 1.0
import org.freedesktop.gstreamer.Qt6GLVideoItem 1.0
Window {
id: win
width: 900
height: 600
visible: true
Component.onCompleted: {
Qt.callLater(() => {
AirplayImp.init(video)
})
}
GstGLQt6VideoItem {
id: video
anchors.fill: parent
objectName: "videoItem"
z: 99
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.7)
border.width: 1
border.color: "white"
anchors.bottom: video.bottom
anchors.bottomMargin: 15
anchors.horizontalCenter: parent.horizontalCenter
width : parent.width - 30
height: parent.height - 30
radius: 8
MouseArea {
id: mousearea
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.opacity = 1.0
hidetimer.start()
}
}
Timer {
id: hidetimer
interval: 5000
onTriggered: {
parent.opacity = 0.0
stop()
}
}
}
}
+37 -2
View File
@@ -1,5 +1,4 @@
use crate::POSSIBLE_ROOT;
use crate::run_sync;
use crate::{POSSIBLE_ROOT, run_sync};
use cpp::*;
use idevice::{
IdeviceError, IdeviceService,
@@ -38,6 +37,7 @@ cpp! {{
#include <QClipboard>
#include <QEvent>
#include "src/include/bridge.h"
#include <gst/gst.h>
QCoreApplication *globalApp = nullptr;
}}
@@ -610,3 +610,38 @@ pub fn engine_ptr_new_object(engine_ptr: *mut c_void, obj_ptr: *mut c_void) -> Q
}
//TODO: implement
// pub fn heic_to_qimage()
pub fn force_load_gst_gl() -> bool {
/*
need to load qml6glsink in order
to make Qt6GLVideoItem available on qml side
*/
cpp!(unsafe [] -> bool as "bool"{
gst_init(nullptr,nullptr);
// GstElement *pipeline = gst_pipeline_new (NULL);
// GstElement *src = gst_element_factory_make ("videotestsrc", NULL);
// GstElement *glupload = gst_element_factory_make ("glupload", NULL);
GstElement *sink = gst_element_factory_make ("qml6glsink", NULL);
if (!sink) {
return false;
}
gst_object_unref(sink);
return true;
})
}
pub fn qvariant_to_ptr(item : QVariant) -> usize {
cpp::cpp!(unsafe [item as "QVariant"] -> usize as "uintptr_t" {
QObject* o = item.value<QObject *>();
if (!o) return 0;
// better do this
QQuickItem* q = qobject_cast<QQuickItem*>(o);
if (!q) return 0;
return reinterpret_cast<uintptr_t>(q);
})
}