mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
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:
Generated
+1
@@ -1310,6 +1310,7 @@ version = "0.5.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
"cmake",
|
||||
"cpp",
|
||||
"cpp_build",
|
||||
"cstr",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
+1
-1
Submodule lib/uxplay updated: 758fec4be6...e98981ed09
+17
-17
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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: ([
|
||||
|
||||
@@ -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
@@ -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);
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user