use std::collections::HashMap;
use std::fs::File;
use std::io::{ Seek, SeekFrom, Write };
use std::os::fd::{ FromRawFd, OwnedFd };
use std::sync::atomic::{ AtomicU32, Ordering };
use zbus::blocking::{ Connection, Proxy };
use zbus::zvariant::{ Fd, OwnedObjectPath, OwnedValue, Value };
use super::gtk::{ self, PageValue };
use super::PrintOptions;
const DESTINATION: &str = "org.freedesktop.portal.Desktop";
const PATH: &str = "/org/freedesktop/portal/desktop";
const INTERFACE: &str = "org.freedesktop.portal.Print";
const REQUEST: &str = "org.freedesktop.portal.Request";
static NEXT_TOKEN: AtomicU32 = AtomicU32::new( 0 );
pub fn sandboxed() -> bool
{
std::path::Path::new( "/.flatpak-info" ).exists()
}
pub fn prepare( title: &str, options: &PrintOptions ) -> Result<Option<( PrintOptions, u32 )>, String>
{
let connection = Connection::session().map_err( |e| e.to_string() )?;
let settings: HashMap<&str, Value> = gtk::settings( options, None ).into_iter().map( |( k, v )| ( k, Value::from( v ) ) ).collect();
let page: HashMap<&str, Value> = gtk::page_setup( options ).into_iter().map( |( k, v )| ( k, page_value( v ) ) ).collect();
let ( response, mut results ) = request( &connection, |token|
{
let extra = HashMap::from( [ ( "handle_token", Value::from( token ) ), ( "modal", Value::from( true ) ) ] );
portal( &connection )?.call::<_, _, OwnedObjectPath>( "PreparePrint", &( "", title, settings, page, extra ) )
} )?;
match response
{
0 => {}
1 => return Ok( None ),
_ => return Err( rust_i18n::t!( "print.failed" ).to_string() ),
}
let token = results.remove( "token" ).and_then( |v| u32::try_from( v ).ok() ).ok_or( "the portal gave no token" )?;
let settings = results.remove( "settings" ).map( strings ).unwrap_or_default();
let page = results.remove( "page-setup" ).map( page_values ).unwrap_or_default();
Ok( Some( ( gtk::options( &settings, &page, options ), token ) ) )
}
pub fn print( token: u32, title: &str, pdf: &[ u8 ] ) -> Result<(), String>
{
let connection = Connection::session().map_err( |e| e.to_string() )?;
let fd = memfd( pdf ).map_err( |e| e.to_string() )?;
let ( response, _ ) = request( &connection, |handle|
{
let extra = HashMap::from( [ ( "handle_token", Value::from( handle ) ), ( "token", Value::from( token ) ), ( "modal", Value::from( true ) ) ] );
portal( &connection )?.call::<_, _, OwnedObjectPath>( "Print", &( "", title, Fd::from( &fd ), extra ) )
} )?;
if response == 0 { Ok( () ) } else { Err( rust_i18n::t!( "print.failed" ).to_string() ) }
}
fn portal( connection: &Connection ) -> zbus::Result<Proxy<'static>>
{
Proxy::new( connection, DESTINATION, PATH, INTERFACE )
}
fn request( connection: &Connection, call: impl FnOnce( &str ) -> zbus::Result<OwnedObjectPath> ) -> Result<( u32, HashMap<String, OwnedValue> ), String>
{
let unique = connection.unique_name().ok_or( "no name on the bus" )?.as_str().trim_start_matches( ':' ).replace( '.', "_" );
let token = format!( "ltk{}_{}", std::process::id(), NEXT_TOKEN.fetch_add( 1, Ordering::Relaxed ) );
let path = format!( "{PATH}/request/{unique}/{token}" );
let proxy = Proxy::new( connection, DESTINATION, path.as_str(), REQUEST ).map_err( |e| e.to_string() )?;
let mut answers = proxy.receive_signal( "Response" ).map_err( |e| e.to_string() )?;
call( &token ).map_err( |e| e.to_string() )?;
let answer = answers.next().ok_or( "the portal did not answer" )?;
answer.body().deserialize::<( u32, HashMap<String, OwnedValue> )>().map_err( |e| e.to_string() )
}
fn page_value( value: PageValue ) -> Value<'static>
{
match value
{
PageValue::Text( text ) => Value::from( text ),
PageValue::Number( number ) => Value::from( number ),
}
}
fn dict( value: OwnedValue ) -> HashMap<String, OwnedValue>
{
HashMap::<String, OwnedValue>::try_from( value ).unwrap_or_default()
}
fn strings( value: OwnedValue ) -> HashMap<String, String>
{
dict( value ).into_iter().filter_map( |( k, v )| String::try_from( v ).ok().map( |v| ( k, v ) ) ).collect()
}
fn page_values( value: OwnedValue ) -> HashMap<String, PageValue>
{
dict( value ).into_iter()
.filter_map( |( k, v )|
{
let value = match &*v
{
Value::Str( s ) => PageValue::Text( s.to_string() ),
Value::F64( n ) => PageValue::Number( *n ),
_ => return None,
};
Some( ( k, value ) )
} )
.collect()
}
fn memfd( data: &[ u8 ] ) -> std::io::Result<OwnedFd>
{
let raw = unsafe { libc::memfd_create( c"ltk-print".as_ptr(), libc::MFD_CLOEXEC ) };
if raw < 0 { return Err( std::io::Error::last_os_error() ); }
let mut file = unsafe { File::from_raw_fd( raw ) };
file.write_all( data )?;
file.seek( SeekFrom::Start( 0 ) )?;
Ok( OwnedFd::from( file ) )
}