11use serde:: Serialize ;
2+ use std:: collections:: HashMap ;
23use std:: fs;
34use std:: io:: { BufRead , BufReader , Write } ;
4- use std:: process:: { Command , Stdio , Child , ChildStdin } ;
5+ use std:: process:: { Child , ChildStdin , Command , Stdio } ;
56use std:: sync:: { Arc , Mutex } ;
6- use std:: collections:: HashMap ;
77use tauri:: { Emitter , Manager , State } ;
88use tauri_plugin_http:: reqwest;
99use tauri_plugin_shell:: ShellExt ;
@@ -40,24 +40,21 @@ struct PtyProcess {
4040
4141type ProcessMap = Arc < Mutex < HashMap < String , PtyProcess > > > ;
4242
43- // Helper function to build interactive flatpak PTY command (no auto-responses )
43+ // Helper function to build interactive flatpak PTY command with -y flag (automatic confirmation )
4444fn build_flatpak_interactive_cmd ( is_flatpak : bool , app_id : & str ) -> String {
45- let base_cmd = format ! ( "flatpak install --user flathub {}" , app_id) ;
45+ let base_cmd = format ! ( "flatpak install -y - -user flathub {}" , app_id) ;
4646 if is_flatpak {
4747 format ! (
4848 "LANG=C script -q /dev/null -c \" flatpak-spawn --host {}\" " ,
4949 base_cmd
5050 )
5151 } else {
52- format ! (
53- "LANG=C script -q /dev/null -c \" {}\" " ,
54- base_cmd
55- )
52+ format ! ( "LANG=C script -q /dev/null -c \" {}\" " , base_cmd)
5653 }
5754}
5855
59- // Helper function to build flatpak command with optional flatpak-spawn wrapper (legacy - for backward compat )
60- fn build_flatpak_install_cmd ( is_flatpak : bool , app_id : & str ) -> String {
56+ // Helper function for dependency checking ( with auto-yes responses )
57+ fn build_flatpak_dependency_check_cmd ( is_flatpak : bool , app_id : & str ) -> String {
6158 let base_cmd = format ! ( "flatpak install --user flathub {}" , app_id) ;
6259 if is_flatpak {
6360 format ! (
@@ -107,130 +104,6 @@ fn greet(name: &str) -> String {
107104 format ! ( "Hello, {}! You've been greeted from Rust!" , name)
108105}
109106
110- #[ tauri:: command]
111- async fn download_flatpakref ( app : tauri:: AppHandle , app_id : String ) -> Result < String , String > {
112- let url = format ! (
113- "https://dl.flathub.org/repo/appstream/{}.flatpakref" ,
114- app_id
115- ) ;
116-
117- app. emit (
118- "install-output" ,
119- format ! ( "Descargando referencia desde {}" , url) ,
120- )
121- . map_err ( |e| format ! ( "Failed to emit: {}" , e) ) ?;
122-
123- let client = reqwest:: Client :: new ( ) ;
124- let response = client
125- . get ( & url)
126- . send ( )
127- . await
128- . map_err ( |e| format ! ( "Error descargando flatpakref: {}" , e) ) ?;
129-
130- if !response. status ( ) . is_success ( ) {
131- return Err ( format ! ( "Error HTTP: {}" , response. status( ) ) ) ;
132- }
133-
134- let content = response
135- . text ( )
136- . await
137- . map_err ( |e| format ! ( "Error leyendo contenido: {}" , e) ) ?;
138-
139- // Obtener el directorio de datos de la app
140- let app_data_dir = app
141- . path ( )
142- . app_data_dir ( )
143- . map_err ( |e| format ! ( "Failed to get app data directory: {}" , e) ) ?;
144-
145- // Crear carpeta temp dentro del directorio de la app
146- let temp_dir = app_data_dir. join ( "temp" ) ;
147- fs:: create_dir_all ( & temp_dir) . map_err ( |e| format ! ( "Failed to create temp directory: {}" , e) ) ?;
148-
149- let flatpakref_path = temp_dir. join ( format ! ( "{}.flatpakref" , app_id) ) ;
150-
151- fs:: write ( & flatpakref_path, & content) . map_err ( |e| format ! ( "Error guardando archivo: {}" , e) ) ?;
152-
153- app. emit (
154- "install-output" ,
155- format ! ( "✓ Referencia descargada: {:?}" , flatpakref_path) ,
156- )
157- . map_err ( |e| format ! ( "Failed to emit: {}" , e) ) ?;
158-
159- Ok ( flatpakref_path. to_string_lossy ( ) . to_string ( ) )
160- }
161-
162- #[ tauri:: command]
163- async fn install_flatpak ( app : tauri:: AppHandle , app_id : String ) -> Result < ( ) , String > {
164- // Paso 1: Descargar el flatpakref
165- let flatpakref_path = download_flatpakref ( app. clone ( ) , app_id. clone ( ) ) . await ?;
166-
167- // Paso 2: Instalar desde el archivo flatpakref
168- app. emit (
169- "install-output" ,
170- "Iniciando instalación desde archivo local..." ,
171- )
172- . map_err ( |e| format ! ( "Failed to emit: {}" , e) ) ?;
173-
174- let shell = app. shell ( ) ;
175-
176- // Detectar si estamos en un flatpak
177- let is_flatpak = std:: env:: var ( "FLATPAK_ID" ) . is_ok ( ) ;
178-
179- let ( mut rx, _child) = if is_flatpak {
180- // Dentro de flatpak, usar flatpak-spawn para ejecutar en el host
181- shell
182- . command ( "flatpak-spawn" )
183- . args ( [
184- "--host" ,
185- "flatpak" ,
186- "install" ,
187- "-y" ,
188- "--user" ,
189- & flatpakref_path,
190- ] )
191- . spawn ( )
192- . map_err ( |e| format ! ( "Failed to spawn flatpak-spawn: {}" , e) ) ?
193- } else {
194- // Fuera de flatpak, usar flatpak directamente
195- shell
196- . command ( "flatpak" )
197- . args ( [ "install" , "-y" , "--user" , & flatpakref_path] )
198- . spawn ( )
199- . map_err ( |e| format ! ( "Failed to spawn flatpak: {}" , e) ) ?
200- } ;
201-
202- // Leer la salida en tiempo real
203- while let Some ( event) = rx. recv ( ) . await {
204- match event {
205- tauri_plugin_shell:: process:: CommandEvent :: Stdout ( line) => {
206- let output = String :: from_utf8_lossy ( & line) ;
207- app. emit ( "install-output" , output. to_string ( ) )
208- . map_err ( |e| format ! ( "Failed to emit event: {}" , e) ) ?;
209- }
210- tauri_plugin_shell:: process:: CommandEvent :: Stderr ( line) => {
211- let output = String :: from_utf8_lossy ( & line) ;
212- app. emit ( "install-output" , output. to_string ( ) )
213- . map_err ( |e| format ! ( "Failed to emit event: {}" , e) ) ?;
214- }
215- tauri_plugin_shell:: process:: CommandEvent :: Error ( err) => {
216- app. emit ( "install-error" , err)
217- . map_err ( |e| format ! ( "Failed to emit error: {}" , e) ) ?;
218- }
219- tauri_plugin_shell:: process:: CommandEvent :: Terminated ( payload) => {
220- // Limpiar archivo temporal
221- let _ = fs:: remove_file ( & flatpakref_path) ;
222-
223- app. emit ( "install-completed" , payload. code . unwrap_or ( -1 ) )
224- . map_err ( |e| format ! ( "Failed to emit completion: {}" , e) ) ?;
225- break ;
226- }
227- _ => { }
228- }
229- }
230-
231- Ok ( ( ) )
232- }
233-
234107#[ tauri:: command]
235108fn check_first_launch ( app : tauri:: AppHandle ) -> Result < bool , String > {
236109 // Get app data directory (compatible with Flatpak)
@@ -345,7 +218,11 @@ async fn download_and_cache_image(
345218 "svg"
346219 } else if image_url. ends_with ( ".webp" ) || image_url. contains ( ".webp?" ) {
347220 "webp"
348- } else if image_url. ends_with ( ".jpg" ) || image_url. ends_with ( ".jpeg" ) || image_url. contains ( ".jpg?" ) || image_url. contains ( ".jpeg?" ) {
221+ } else if image_url. ends_with ( ".jpg" )
222+ || image_url. ends_with ( ".jpeg" )
223+ || image_url. contains ( ".jpg?" )
224+ || image_url. contains ( ".jpeg?" )
225+ {
349226 "jpg"
350227 } else {
351228 "png" // default
@@ -441,7 +318,11 @@ fn get_cached_image_filename(cache_key: String, image_url: String) -> String {
441318}
442319
443320#[ tauri:: command]
444- fn check_cached_image_exists ( app : tauri:: AppHandle , cache_key : String , image_url : String ) -> Result < String , String > {
321+ fn check_cached_image_exists (
322+ app : tauri:: AppHandle ,
323+ cache_key : String ,
324+ image_url : String ,
325+ ) -> Result < String , String > {
445326 let app_data_dir = app
446327 . path ( )
447328 . app_data_dir ( )
@@ -465,7 +346,11 @@ fn check_cached_image_exists(app: tauri::AppHandle, cache_key: String, image_url
465346 "svg"
466347 } else if image_url. ends_with ( ".webp" ) || image_url. contains ( ".webp?" ) {
467348 "webp"
468- } else if image_url. ends_with ( ".jpg" ) || image_url. ends_with ( ".jpeg" ) || image_url. contains ( ".jpg?" ) || image_url. contains ( ".jpeg?" ) {
349+ } else if image_url. ends_with ( ".jpg" )
350+ || image_url. ends_with ( ".jpeg" )
351+ || image_url. contains ( ".jpg?" )
352+ || image_url. contains ( ".jpeg?" )
353+ {
469354 "jpg"
470355 } else {
471356 "png" // default
@@ -662,7 +547,7 @@ async fn get_install_dependencies(
662547
663548 // Second phase: If runtime is required, use controlled process with script
664549 let ( stdout, stderr) = if needs_runtime {
665- let cmd_str = build_flatpak_install_cmd ( is_flatpak, & app_id) ;
550+ let cmd_str = build_flatpak_dependency_check_cmd ( is_flatpak, & app_id) ;
666551 let ( cmd, args) = ( "sh" , vec ! [ "-c" , & cmd_str] ) ;
667552
668553 let mut child = Command :: new ( cmd)
@@ -1435,7 +1320,10 @@ async fn start_flatpak_interactive(
14351320 processes : State < ' _ , ProcessMap > ,
14361321 app_id : String ,
14371322) -> Result < ( ) , String > {
1438- eprintln ! ( "[start_flatpak_interactive] Starting for app_id: {}" , app_id) ;
1323+ eprintln ! (
1324+ "[start_flatpak_interactive] Starting for app_id: {}" ,
1325+ app_id
1326+ ) ;
14391327 let is_flatpak = std:: env:: var ( "FLATPAK_ID" ) . is_ok ( ) ;
14401328 let cmd_str = build_flatpak_interactive_cmd ( is_flatpak, & app_id) ;
14411329 eprintln ! ( "[start_flatpak_interactive] Command: {}" , cmd_str) ;
@@ -1474,10 +1362,11 @@ async fn start_flatpak_interactive(
14741362 Ok ( 0 ) => break , // EOF
14751363 Ok ( n) => {
14761364 let chunk = String :: from_utf8_lossy ( & buffer[ ..n] ) . to_string ( ) ;
1477- // Split by both \n and \r to send individual lines
1478- for line in chunk. split ( & [ '\n' , '\r' ] ) {
1365+ // Split by \n but preserve \r to allow frontend to handle line overwrites
1366+ for line in chunk. split ( '\n' ) {
14791367 if !line. is_empty ( ) {
1480- let _ = app_clone. emit ( "pty-output" , ( app_id_clone. clone ( ) , line. to_string ( ) ) ) ;
1368+ let _ = app_clone
1369+ . emit ( "pty-output" , ( app_id_clone. clone ( ) , line. to_string ( ) ) ) ;
14811370 }
14821371 }
14831372 }
@@ -1514,7 +1403,10 @@ async fn start_flatpak_interactive(
15141403 if let Some ( pty_process) = map. get_mut ( & app_id_clone3) {
15151404 match pty_process. child . try_wait ( ) {
15161405 Ok ( Some ( status) ) => {
1517- eprintln ! ( "[start_flatpak_interactive] Process terminated with status: {:?}" , status) ;
1406+ eprintln ! (
1407+ "[start_flatpak_interactive] Process terminated with status: {:?}" ,
1408+ status
1409+ ) ;
15181410 // Process has exited, emit event and remove from map
15191411 let _ = app_clone3. emit ( "pty-terminated" , app_id_clone3. clone ( ) ) ;
15201412 map. remove ( & app_id_clone3) ;
@@ -1546,21 +1438,29 @@ async fn send_to_pty(
15461438 app_id : String ,
15471439 input : String ,
15481440) -> Result < ( ) , String > {
1549- eprintln ! ( "[send_to_pty] Attempting to send '{}' to app_id: {}" , input, app_id) ;
1441+ eprintln ! (
1442+ "[send_to_pty] Attempting to send '{}' to app_id: {}" ,
1443+ input, app_id
1444+ ) ;
15501445 let mut map = processes. lock ( ) . unwrap ( ) ;
15511446
15521447 if let Some ( pty_process) = map. get_mut ( & app_id) {
15531448 eprintln ! ( "[send_to_pty] Process found, writing to stdin" ) ;
1554- pty_process. stdin
1449+ pty_process
1450+ . stdin
15551451 . write_all ( format ! ( "{}\n " , input) . as_bytes ( ) )
15561452 . map_err ( |e| format ! ( "Failed to write to stdin: {}" , e) ) ?;
1557- pty_process. stdin
1453+ pty_process
1454+ . stdin
15581455 . flush ( )
15591456 . map_err ( |e| format ! ( "Failed to flush stdin: {}" , e) ) ?;
15601457 eprintln ! ( "[send_to_pty] Successfully sent input" ) ;
15611458 Ok ( ( ) )
15621459 } else {
1563- eprintln ! ( "[send_to_pty] ERROR: No process found for app_id: {}" , app_id) ;
1460+ eprintln ! (
1461+ "[send_to_pty] ERROR: No process found for app_id: {}" ,
1462+ app_id
1463+ ) ;
15641464 Err ( format ! ( "No process found for app_id: {}" , app_id) )
15651465 }
15661466}
@@ -1622,8 +1522,6 @@ pub fn run() {
16221522 . plugin ( tauri_plugin_fs:: init ( ) )
16231523 . invoke_handler ( tauri:: generate_handler![
16241524 greet,
1625- install_flatpak,
1626- download_flatpakref,
16271525 check_first_launch,
16281526 initialize_app,
16291527 get_app_data_path,
0 commit comments