vte4/
pty.rs

1use glib::translate::*;
2use io_lifetimes::{BorrowedFd, OwnedFd};
3use std::boxed::Box as Box_;
4use std::os::unix::io::{IntoRawFd, RawFd};
5
6use std::ptr;
7
8use crate::{ffi, prelude::*, Pty};
9
10impl Pty {
11    ///
12    /// # Returns
13    ///
14    /// the file descriptor of the PTY master in @self. The
15    ///   file descriptor belongs to @self and must not be closed or have
16    ///   its flags changed
17    #[doc(alias = "vte_pty_get_fd")]
18    #[doc(alias = "get_fd")]
19    pub fn fd(&self) -> BorrowedFd<'_> {
20        unsafe {
21            let raw_fd = ffi::vte_pty_get_fd(self.to_glib_none().0);
22            BorrowedFd::borrow_raw(raw_fd)
23        }
24    }
25
26    /// Creates a new #VtePty for the PTY master @fd.
27    ///
28    /// No entry will be made in the lastlog, utmp or wtmp system files.
29    ///
30    /// Note that the newly created #VtePty will take ownership of @fd
31    /// and close it on finalize.
32    /// ## `fd`
33    /// a file descriptor to the PTY
34    /// ## `cancellable`
35    /// a #GCancellable, or [`None`]
36    ///
37    /// # Returns
38    ///
39    /// a new #VtePty for @fd, or [`None`] on error with @error filled in
40    #[doc(alias = "vte_pty_new_foreign_sync")]
41    #[doc(alias = "new_foreign_sync")]
42    pub fn foreign_sync(
43        fd: OwnedFd,
44        cancellable: Option<&impl IsA<gio::Cancellable>>,
45    ) -> Result<Pty, glib::Error> {
46        assert_initialized_main_thread!();
47        unsafe {
48            let mut error = ptr::null_mut();
49            let ret = ffi::vte_pty_new_foreign_sync(
50                fd.into_raw_fd(),
51                cancellable.map(|p| p.as_ref()).to_glib_none().0,
52                &mut error,
53            );
54            if error.is_null() {
55                Ok(from_glib_full(ret))
56            } else {
57                Err(from_glib_full(error))
58            }
59        }
60    }
61
62    /// Like vte_pty_spawn_with_fds_async(), except that this function does not
63    /// allow passing file descriptors to the child process. See vte_pty_spawn_with_fds_async()
64    /// for more information.
65    /// ## `working_directory`
66    /// the name of a directory the command should start
67    ///   in, or [`None`] to use the current working directory
68    /// ## `argv`
69    /// child's argument vector
70    /// ## `envv`
71    /// a list of environment
72    ///   variables to be added to the environment before starting the process, or [`None`]
73    /// ## `spawn_flags`
74    /// flags from #GSpawnFlags
75    /// ## `child_setup`
76    /// an extra child setup function to run in the child just before exec(), or [`None`]
77    /// ## `child_setup_data`
78    /// user data for @child_setup, or [`None`]
79    /// ## `child_setup_data_destroy`
80    /// a #GDestroyNotify for @child_setup_data, or [`None`]
81    /// ## `timeout`
82    /// a timeout value in ms, -1 for the default timeout, or G_MAXINT to wait indefinitely
83    /// ## `cancellable`
84    /// a #GCancellable, or [`None`]
85    /// ## `callback`
86    /// a #GAsyncReadyCallback, or [`None`]
87    #[doc(alias = "vte_pty_spawn_async")]
88    #[allow(clippy::too_many_arguments)]
89    pub fn spawn_async<P: FnOnce(Result<glib::Pid, glib::Error>) + 'static, Q: Fn() + 'static>(
90        &self,
91        working_directory: Option<&str>,
92        argv: &[&str],
93        envv: &[&str],
94        spawn_flags: glib::SpawnFlags,
95        child_setup: Q,
96        timeout: i32,
97        cancellable: Option<&impl IsA<gio::Cancellable>>,
98        callback: P,
99    ) {
100        assert_initialized_main_thread!();
101        let main_context = glib::MainContext::ref_thread_default();
102        let is_main_context_owner = main_context.is_owner();
103        let has_acquired_main_context = (!is_main_context_owner)
104            .then(|| main_context.acquire().ok())
105            .flatten();
106        assert!(
107            is_main_context_owner || has_acquired_main_context.is_some(),
108            "Async operations only allowed if the thread is owning the MainContext"
109        );
110        let child_setup_data: Box_<glib::thread_guard::ThreadGuard<Q>> =
111            Box_::new(glib::thread_guard::ThreadGuard::new(child_setup));
112
113        unsafe extern "C" fn child_setup_func<Q: Fn() + 'static>(user_data: glib::ffi::gpointer) {
114            let callback: Box_<glib::thread_guard::ThreadGuard<Q>> =
115                Box_::from_raw(user_data as *mut _);
116            let callback = callback.into_inner();
117            callback()
118        }
119
120        let child_setup = Some(child_setup_func::<Q> as _);
121        let callback_data: Box_<glib::thread_guard::ThreadGuard<P>> =
122            Box_::new(glib::thread_guard::ThreadGuard::new(callback));
123
124        unsafe extern "C" fn spawn_async_trampoline<
125            P: FnOnce(Result<glib::Pid, glib::Error>) + 'static,
126        >(
127            _source_object: *mut glib::gobject_ffi::GObject,
128            res: *mut gio::ffi::GAsyncResult,
129            user_data: glib::ffi::gpointer,
130        ) {
131            let mut error = ptr::null_mut();
132            let mut child_pid = 0;
133            let _ = ffi::vte_pty_spawn_finish(
134                _source_object as *mut _,
135                res,
136                &mut child_pid,
137                &mut error,
138            );
139            let result = if error.is_null() {
140                Ok(from_glib(child_pid))
141            } else {
142                Err(from_glib_full(error))
143            };
144            let callback: Box_<glib::thread_guard::ThreadGuard<P>> =
145                Box_::from_raw(user_data as *mut _);
146            let callback: P = callback.into_inner();
147            callback(result);
148        }
149
150        let callback = Some(spawn_async_trampoline::<P> as _);
151
152        unsafe extern "C" fn child_setup_data_destroy_func<Q: Fn() + 'static>(
153            data: glib::ffi::gpointer,
154        ) {
155            let _callback: Box_<Q> = Box_::from_raw(data as *mut _);
156        }
157
158        let destroy_call8 = Some(child_setup_data_destroy_func::<Q> as _);
159        let super_callback0: Box_<glib::thread_guard::ThreadGuard<Q>> = child_setup_data;
160        let super_callback1: Box_<glib::thread_guard::ThreadGuard<P>> = callback_data;
161        unsafe {
162            ffi::vte_pty_spawn_async(
163                self.to_glib_none().0,
164                working_directory.to_glib_none().0,
165                argv.to_glib_none().0,
166                envv.to_glib_none().0,
167                spawn_flags.into_glib(),
168                child_setup,
169                Box_::into_raw(super_callback0) as *mut _,
170                destroy_call8,
171                timeout,
172                cancellable.map(|p| p.as_ref()).to_glib_none().0,
173                callback,
174                Box_::into_raw(super_callback1) as *mut _,
175            );
176        }
177    }
178
179    #[doc(alias = "vte_pty_spawn_async")]
180    #[allow(clippy::too_many_arguments)]
181    pub fn spawn_future<Q: Fn() + 'static>(
182        &self,
183        working_directory: Option<&str>,
184        argv: &[&str],
185        envv: &[&str],
186        spawn_flags: glib::SpawnFlags,
187        child_setup: Q,
188        timeout: i32,
189    ) -> std::pin::Pin<
190        Box_<dyn std::future::Future<Output = Result<glib::Pid, glib::Error>> + 'static>,
191    > {
192        let working_directory = working_directory.map(ToOwned::to_owned);
193        let argv: Vec<String> = argv.iter().map(|p| p.to_string()).collect();
194        let envv: Vec<String> = envv.iter().map(|p| p.to_string()).collect();
195        Box_::pin(gio::GioFuture::new(self, move |obj, cancellable, send| {
196            let argv: Vec<&str> = argv.iter().map(|s| s.as_str()).collect();
197            let envv: Vec<&str> = envv.iter().map(|s| s.as_str()).collect();
198            obj.spawn_async(
199                working_directory.as_deref(),
200                &argv,
201                &envv,
202                spawn_flags,
203                child_setup,
204                timeout,
205                Some(cancellable),
206                move |res| {
207                    send.resolve(res);
208                },
209            );
210        }))
211    }
212
213    /// # Safety
214    ///
215    /// The map_fds have to make sense.
216    // rustdoc-stripper-ignore-next-stop
217    /// Starts the specified command under the pseudo-terminal @self.
218    /// The @argv and @envv lists should be [`None`]-terminated.
219    /// The "TERM" environment variable is automatically set to a default value,
220    /// but can be overridden from @envv.
221    /// @pty_flags controls logging the session to the specified system log files.
222    ///
223    /// Note also that [`glib::SpawnFlags::STDOUT_TO_DEV_NULL`][crate::glib::SpawnFlags::STDOUT_TO_DEV_NULL], [`glib::SpawnFlags::STDERR_TO_DEV_NULL`][crate::glib::SpawnFlags::STDERR_TO_DEV_NULL],
224    /// and [`glib::SpawnFlags::CHILD_INHERITS_STDIN`][crate::glib::SpawnFlags::CHILD_INHERITS_STDIN] are not supported in @spawn_flags, since
225    /// stdin, stdout and stderr of the child process will always be connected to
226    /// the PTY. Also [`glib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN`][crate::glib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN] is not supported; and
227    /// [`glib::SpawnFlags::DO_NOT_REAP_CHILD`][crate::glib::SpawnFlags::DO_NOT_REAP_CHILD] will always be added to @spawn_flags.
228    ///
229    /// If @fds is not [`None`], the child process will map the file descriptors from
230    /// @fds according to @map_fds; @n_map_fds must be less or equal to @n_fds.
231    /// This function will take ownership of the file descriptors in @fds;
232    /// you must not use or close them after this call. All file descriptors in @fds
233    /// must have the FD_CLOEXEC flag set on them; it will be unset in the child process
234    /// before calling man:execve(2). Note also that no file descriptor may be mapped
235    /// to stdin, stdout, or stderr (file descriptors 0, 1, or 2), since these will be
236    /// assigned to the PTY. All open file descriptors apart from those mapped as above
237    /// will be closed when execve() is called.
238    ///
239    /// Beginning with 0.60, and on linux only, and unless `VTE_SPAWN_NO_SYSTEMD_SCOPE` is
240    /// passed in @spawn_flags, the newly created child process will be moved to its own
241    /// systemd user scope; and if `VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE` is passed, and creation
242    /// of the systemd user scope fails, the whole spawn will fail.
243    /// You can override the options used for the systemd user scope by
244    /// providing a systemd override file for 'vte-spawn-.scope' unit. See man:systemd.unit(5)
245    /// for further information.
246    ///
247    /// See vte_pty_new(), and vte_terminal_watch_child() for more information.
248    /// ## `working_directory`
249    /// the name of a directory the command should start
250    ///   in, or [`None`] to use the current working directory
251    /// ## `argv`
252    /// child's argument vector
253    /// ## `envv`
254    /// a list of environment
255    ///   variables to be added to the environment before starting the process, or [`None`]
256    /// ## `fds`
257    /// an array of file descriptors, or [`None`]
258    /// ## `map_fds`
259    /// an array of integers, or [`None`]
260    /// ## `spawn_flags`
261    /// flags from #GSpawnFlags
262    /// ## `child_setup`
263    /// an extra child setup function to run in the child just before exec(), or [`None`]
264    /// ## `child_setup_data`
265    /// user data for @child_setup, or [`None`]
266    /// ## `child_setup_data_destroy`
267    /// a #GDestroyNotify for @child_setup_data, or [`None`]
268    /// ## `timeout`
269    /// a timeout value in ms, -1 for the default timeout, or G_MAXINT to wait indefinitely
270    /// ## `cancellable`
271    /// a #GCancellable, or [`None`]
272    /// ## `callback`
273    /// a #GAsyncReadyCallback, or [`None`]
274    #[allow(clippy::too_many_arguments)]
275    pub unsafe fn spawn_with_fds_async<
276        P: FnOnce(Result<glib::Pid, glib::Error>) + 'static,
277        Q: Fn() + 'static,
278    >(
279        &self,
280        working_directory: Option<&str>,
281        argv: &[&str],
282        envv: &[&str],
283        fds: Vec<OwnedFd>,
284        map_fds: &[RawFd],
285        spawn_flags: glib::SpawnFlags,
286        child_setup: Q,
287        timeout: i32,
288        cancellable: Option<&impl IsA<gio::Cancellable>>,
289        callback: P,
290    ) {
291        assert_initialized_main_thread!();
292        let main_context = glib::MainContext::ref_thread_default();
293        let is_main_context_owner = main_context.is_owner();
294        let has_acquired_main_context = (!is_main_context_owner)
295            .then(|| main_context.acquire().ok())
296            .flatten();
297        assert!(
298            is_main_context_owner || has_acquired_main_context.is_some(),
299            "Async operations only allowed if the thread is owning the MainContext"
300        );
301        let n_fds = fds.len() as _;
302        let n_map_fds = map_fds.len() as _;
303        let child_setup_data: Box_<glib::thread_guard::ThreadGuard<Q>> =
304            Box_::new(glib::thread_guard::ThreadGuard::new(child_setup));
305        unsafe extern "C" fn child_setup_func<Q: Fn() + 'static>(user_data: glib::ffi::gpointer) {
306            let callback: Box_<glib::thread_guard::ThreadGuard<Q>> =
307                Box_::from_raw(user_data as *mut _);
308            let callback = callback.into_inner();
309            callback()
310        }
311
312        let child_setup = Some(child_setup_func::<Q> as _);
313
314        let callback_data: Box_<glib::thread_guard::ThreadGuard<P>> =
315            Box_::new(glib::thread_guard::ThreadGuard::new(callback));
316
317        unsafe extern "C" fn spawn_with_fds_trampoline<
318            P: FnOnce(Result<glib::Pid, glib::Error>) + 'static,
319        >(
320            _source_object: *mut glib::gobject_ffi::GObject,
321            res: *mut gio::ffi::GAsyncResult,
322            user_data: glib::ffi::gpointer,
323        ) {
324            let mut error = ptr::null_mut();
325            let mut child_pid = 0;
326            let _ = ffi::vte_pty_spawn_finish(
327                _source_object as *mut _,
328                res,
329                &mut child_pid,
330                &mut error,
331            );
332            let result = if error.is_null() {
333                Ok(from_glib(child_pid))
334            } else {
335                Err(from_glib_full(error))
336            };
337            let callback: Box_<glib::thread_guard::ThreadGuard<P>> =
338                Box_::from_raw(user_data as *mut _);
339            let callback = callback.into_inner();
340            callback(result)
341        }
342
343        let callback = Some(spawn_with_fds_trampoline::<P> as _);
344
345        unsafe extern "C" fn child_setup_data_destroy_func<Q: Fn() + 'static>(
346            data: glib::ffi::gpointer,
347        ) {
348            let _callback: Box_<Q> = Box_::from_raw(data as *mut _);
349        }
350
351        let destroy_call12 = Some(child_setup_data_destroy_func::<Q> as _);
352        let super_callback0: Box_<glib::thread_guard::ThreadGuard<Q>> = child_setup_data;
353        let super_callback1: Box_<glib::thread_guard::ThreadGuard<P>> = callback_data;
354        let fds: Vec<RawFd> = fds.into_iter().map(|x| x.into_raw_fd()).collect();
355        unsafe {
356            ffi::vte_pty_spawn_with_fds_async(
357                self.to_glib_none().0,
358                working_directory.to_glib_none().0,
359                argv.to_glib_none().0,
360                envv.to_glib_none().0,
361                fds.to_glib_none().0,
362                n_fds,
363                map_fds.to_glib_none().0,
364                n_map_fds,
365                spawn_flags.into_glib(),
366                child_setup,
367                Box_::into_raw(super_callback0) as *mut _,
368                destroy_call12,
369                timeout,
370                cancellable.map(|p| p.as_ref()).to_glib_none().0,
371                callback,
372                Box_::into_raw(super_callback1) as *mut _,
373            );
374        }
375    }
376
377    // # Safety
378    //
379    // The map_fds have to make sense.
380    #[doc(alias = "vte_pty_spawn_async")]
381    #[allow(clippy::too_many_arguments)]
382    pub unsafe fn spawn_with_fds_future<Q: Fn() + 'static>(
383        &self,
384        working_directory: Option<&str>,
385        argv: &[&str],
386        envv: &[&str],
387        fds: Vec<OwnedFd>,
388        map_fds: &[RawFd],
389        spawn_flags: glib::SpawnFlags,
390        child_setup: Q,
391        timeout: i32,
392    ) -> std::pin::Pin<
393        Box_<dyn std::future::Future<Output = Result<glib::Pid, glib::Error>> + 'static>,
394    > {
395        let working_directory = working_directory.map(ToOwned::to_owned);
396        let argv: Vec<String> = argv.iter().map(|x| x.to_string()).collect();
397        let envv: Vec<String> = envv.iter().map(|x| x.to_string()).collect();
398        let map_fds = map_fds.to_vec();
399        Box_::pin(gio::GioFuture::new(self, move |obj, cancellable, send| {
400            let argv: Vec<&str> = argv.iter().map(|s| s.as_str()).collect();
401            let envv: Vec<&str> = envv.iter().map(|s| s.as_str()).collect();
402            obj.spawn_with_fds_async(
403                working_directory.as_deref(),
404                &argv,
405                &envv,
406                fds,
407                &map_fds,
408                spawn_flags,
409                child_setup,
410                timeout,
411                Some(cancellable),
412                move |res| {
413                    send.resolve(res);
414                },
415            );
416        }))
417    }
418}