diff --git a/src/builtins/fg.rs b/src/builtins/fg.rs index 59b645bd3..1e6854ddd 100644 --- a/src/builtins/fg.rs +++ b/src/builtins/fg.rs @@ -139,12 +139,13 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built // Note if tty transfer fails, we still try running the job. parser.job_promote_at(job_pos); + let mut handoff = TtyHandoff::new(); let _ = make_fd_blocking(STDIN_FILENO); { let job_group = job.group(); job_group.set_is_foreground(true); if job.entitled_to_terminal() { - crate::input_common::terminal_protocols_disable_ifn(); + handoff.disable_tty_protocols(); } let tmodes = job_group.tmodes.borrow(); if job_group.wants_terminal() && tmodes.is_some() { @@ -155,7 +156,6 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built } } } - let mut handoff = TtyHandoff::new(); handoff.to_job_group(job.group.as_ref().unwrap()); let resumed = job.resume(); if resumed { diff --git a/src/builtins/fish_key_reader.rs b/src/builtins/fish_key_reader.rs index c7867db26..d70d2a554 100644 --- a/src/builtins/fish_key_reader.rs +++ b/src/builtins/fish_key_reader.rs @@ -7,7 +7,7 @@ //! //! Type "exit" or "quit" to terminate the program. -use std::{cell::RefCell, ops::ControlFlow, os::unix::prelude::OsStrExt, sync::atomic::Ordering}; +use std::{cell::RefCell, ops::ControlFlow, os::unix::prelude::OsStrExt}; use libc::{STDIN_FILENO, TCSANOW, VEOF, VINTR}; use once_cell::unsync::OnceCell; @@ -30,10 +30,13 @@ proc::set_interactive_session, reader::{check_exit_loop_maybe_warning, initial_query, reader_init}, signal::signal_set_handlers, - terminal::{Capability, KITTY_KEYBOARD_SUPPORTED}, + terminal::Capability, threads, topic_monitor::topic_monitor_init, - tty_handoff::{initialize_tty_metadata, TtyHandoff}, + tty_handoff::{ + get_kitty_keyboard_capability, initialize_tty_metadata, set_kitty_keyboard_capability, + TtyHandoff, + }, wchar::prelude::*, wgetopt::{wopt, ArgType, WGetopter, WOption}, }; @@ -96,9 +99,8 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool) CharEvent::Key(kevt) => kevt, CharEvent::Readline(_) | CharEvent::Command(_) | CharEvent::Implicit(_) => continue, CharEvent::QueryResponse(QueryResponseEvent::PrimaryDeviceAttribute) => { - if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed) == Capability::Unknown as _ { - KITTY_KEYBOARD_SUPPORTED - .store(Capability::NotSupported as _, Ordering::Release); + if get_kitty_keyboard_capability() == Capability::Unknown { + set_kitty_keyboard_capability(Capability::NotSupported); } continue; } diff --git a/src/builtins/read.rs b/src/builtins/read.rs index cfeec69b8..80fea9eae 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -12,7 +12,6 @@ use crate::env::READ_BYTE_LIMIT; use crate::env::{EnvVar, EnvVarFlags}; use crate::input_common::decode_input_byte; -use crate::input_common::terminal_protocols_disable_ifn; use crate::input_common::DecodeState; use crate::input_common::InvalidPolicy; use crate::nix::isatty; @@ -22,6 +21,7 @@ use crate::tokenizer::Tokenizer; use crate::tokenizer::TOK_ACCEPT_UNFINISHED; use crate::tokenizer::TOK_ARGUMENT_LIST; +use crate::tty_handoff::TtyHandoff; use crate::wcstringutil::split_about; use crate::wcstringutil::split_string_tok; use crate::wutil; @@ -244,9 +244,10 @@ fn read_interactive( let mline = { let _interactive = parser.push_scope(|s| s.is_interactive = true); + let mut scoped_handoff = TtyHandoff::new(); + scoped_handoff.enable_tty_protocols(); reader_readline(parser, NonZeroUsize::try_from(nchars).ok()) }; - terminal_protocols_disable_ifn(); if let Some(line) = mline { *buff = line; if nchars > 0 && nchars < buff.len() { diff --git a/src/input_common.rs b/src/input_common.rs index b54183749..280652715 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -9,13 +9,10 @@ self, alt, canonicalize_control_char, canonicalize_keyed_control_char, char_to_symbol, ctrl, function_key, shift, Key, Modifiers, ViewportPosition, }; -use crate::reader::reader_current_data; use crate::reader::reader_test_and_clear_interrupted; -use crate::terminal::{ - Capability, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE, -}; +use crate::terminal::{Capability, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE}; use crate::threads::iothread_port; -use crate::tty_handoff::set_tty_protocols_active; +use crate::tty_handoff::set_kitty_keyboard_capability; use crate::universal_notifier::default_notifier; use crate::wchar::{encode_byte_to_char, prelude::*}; use crate::wutil::encoding::{mbrtowc, mbstate_t, zero_mbstate}; @@ -648,22 +645,6 @@ fn parse_mask(mask: u32) -> (Modifiers, bool) { (modifiers, caps_lock) } -// Trampolines to enable or disable terminal protocols. -pub fn terminal_protocols_enable_ifn() { - if set_tty_protocols_active(true) { - // Our tty has been modified, so save the current timestamps so that - // the reader doesn't believe it has to repaint the prompt. - reader_current_data().map(|data| data.save_screen_state()); - } -} -pub fn terminal_protocols_disable_ifn() { - if set_tty_protocols_active(false) { - // Our tty has been modified, so save the current timestamps so that - // the reader doesn't believe it has to repaint the prompt. - reader_current_data().map(|data| data.save_screen_state()); - } -} - // A data type used by the input machinery. pub struct InputData { // The file descriptor from which we read input, often stdin. @@ -1187,7 +1168,7 @@ fn parse_csi(&mut self, buffer: &mut Vec) -> Option { reader, "Received kitty progressive enhancement flags, marking as supported" ); - KITTY_KEYBOARD_SUPPORTED.store(Capability::Supported as _, Ordering::Release); + set_kitty_keyboard_capability(Capability::Supported); return None; } @@ -1421,7 +1402,6 @@ fn readch_timed(&mut self, wait_time_ms: usize) -> Option { if let Some(evt) = self.try_pop() { return Some(evt); } - terminal_protocols_enable_ifn(); // We are not prepared to handle a signal immediately; we only want to know if we get input on // our fd before the timeout. Use pselect to block all signals; we will handle signals diff --git a/src/parser.rs b/src/parser.rs index 6f5215b32..dd4ef9f7d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -14,7 +14,7 @@ }; use crate::fds::{open_dir, BEST_O_SEARCH}; use crate::global_safety::RelaxedAtomicBool; -use crate::input_common::{terminal_protocols_disable_ifn, TerminalQuery}; +use crate::input_common::TerminalQuery; use crate::io::IoChain; use crate::job_group::MaybeJobId; use crate::operation_context::{OperationContext, EXPANSION_LIMIT_DEFAULT}; @@ -681,8 +681,6 @@ pub fn eval_node( // Create a new execution context. let mut execution_context = ExecutionContext::new(ps, block_io.clone(), &self.line_counter); - terminal_protocols_disable_ifn(); - // Check the exec count so we know if anything got executed. let prev_exec_count = self.libdata().exec_count; let prev_status_count = self.libdata().status_count; diff --git a/src/reader.rs b/src/reader.rs index edd0da71c..86e77967c 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -11,6 +11,11 @@ //! When the user searches forward, i.e. presses Alt-down, the list is consulted for previous search //! result, and subsequent backwards searches are also handled by consulting the list up until the //! end of the list is reached, at which point regular searching will commence. +//! +//! In general interactive reads work with the tty protocols (CSI-U, etc) enabled; these are disabled +//! before calling out to fish script, wildcards, or completions. Note CSI-U protocol prevents +//! control-C from generating SIGINT, so failing to disable these would prevent cancellation of wildcard +//! expansion, etc. use libc::{ c_char, ECHO, EINTR, EIO, EISDIR, ENOTTY, EPERM, ESRCH, ICANON, ICRNL, IEXTEN, INLCR, IXOFF, @@ -81,14 +86,9 @@ SearchType, }; use crate::input::init_input; -use crate::input_common::stop_query; -use crate::input_common::terminal_protocols_disable_ifn; -use crate::input_common::CursorPositionQuery; -use crate::input_common::ImplicitEvent; -use crate::input_common::QueryResponseEvent; -use crate::input_common::TerminalQuery; use crate::input_common::{ - terminal_protocols_enable_ifn, CharEvent, CharInputStyle, InputData, ReadlineCmd, + stop_query, CharEvent, CharInputStyle, CursorPositionQuery, ImplicitEvent, InputData, + QueryResponseEvent, ReadlineCmd, TerminalQuery, }; use crate::io::IoChain; use crate::key::ViewportPosition; @@ -131,9 +131,7 @@ QueryCursorPosition, QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute, QueryXtgettcap, QueryXtversion, }; -use crate::terminal::{ - Capability, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE, -}; +use crate::terminal::{Capability, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE}; use crate::termsize::{termsize_invalidate_tty, termsize_last, termsize_update}; use crate::text_face::parse_text_face; use crate::text_face::TextFace; @@ -147,7 +145,10 @@ tok_command, MoveWordStateMachine, MoveWordStyle, TokenType, Tokenizer, TOK_ACCEPT_UNFINISHED, TOK_SHOW_COMMENTS, }; -use crate::tty_handoff::{initialize_tty_metadata, tty_metadata}; +use crate::tty_handoff::{ + get_kitty_keyboard_capability, get_tty_protocols_active, initialize_tty_metadata, + safe_deactivate_tty_protocols, set_kitty_keyboard_capability, tty_metadata, TtyHandoff, +}; use crate::wchar::prelude::*; use crate::wcstringutil::string_prefixes_string_maybe_case_insensitive; use crate::wcstringutil::{ @@ -712,6 +713,11 @@ fn read_i(parser: &Parser) { let mut data = reader_push(parser, &history_session_id(parser.vars()), conf); data.import_history_if_necessary(); + // Set up tty protocols. These should be enabled while we're reading interactively, + // and disabled before we run fish script, wildcards, or completions. This is scoped. + // Note this may be disabled within the loop, e.g. when running fish script bound to keys. + let mut tty = TtyHandoff::new(); + while !check_exit_loop_maybe_warning(Some(&mut data)) { RUN_COUNT.fetch_add(1, Ordering::Relaxed); @@ -723,6 +729,8 @@ fn read_i(parser: &Parser) { continue; } + // Got a command. Disable tty protocols while we execute it. + tty.disable_tty_protocols(); data.clear(EditableLineTag::Commandline); data.update_buff_pos(EditableLineTag::Commandline, None); BufferedOutputter::new(Outputter::stdoutput()).write_command(Osc133CommandStart(&command)); @@ -758,7 +766,8 @@ fn read_i(parser: &Parser) { } reader_pop(); - // If we got SIGHUP, ensure the tty is redirected. + // If we got SIGHUP, ensure the tty is redirected and release tty handoff without + // trying to muck with protocols. if reader_received_sighup() { // If we are the top-level reader, then we translate SIGHUP into exit_forced. redirect_tty_after_sighup(); @@ -892,11 +901,9 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) { } } -// TODO(pca): this is run in our "AT_EXIT" handler from a SIGTERM handler. -// It must be made async-signal-safe (or not invoked). pub fn reader_deinit(restore_foreground_pgroup: bool) { safe_restore_term_mode(); - crate::input_common::terminal_protocols_disable_ifn(); + safe_deactivate_tty_protocols(); if restore_foreground_pgroup { restore_term_foreground_process_group_for_exit(); } @@ -2175,6 +2182,8 @@ impl<'a> Reader<'a> { /// Read a command to execute, respecting input bindings. /// Return the command, or none if we were asked to cancel (e.g. SIGHUP). fn readline(&mut self, nchars: Option) -> Option { + let mut tty = TtyHandoff::new(); + self.rls = Some(ReadlineLoopState::new()); // Suppress fish_trace during executing key bindings. @@ -2242,11 +2251,18 @@ fn readline(&mut self, nchars: Option) -> Option { self.force_exec_prompt_and_repaint = true; while !self.rls().finished && !check_exit_loop_maybe_warning(Some(self)) { + // Enable tty protocols while we read input. + tty.enable_tty_protocols(); if self.handle_char_event(None).is_break() { break; } } + // Disable tty protocols now that we're going to execute a command. + if tty.disable_tty_protocols() { + self.save_screen_state(); + } + if self.conf.transient_prompt { self.exec_prompt(true, true); } @@ -2306,10 +2322,16 @@ fn readline(&mut self, nchars: Option) -> Option { fn eval_bind_cmd(&mut self, cmd: &wstr) { let last_statuses = self.parser.vars().get_last_statuses(); let prev_exec_external_count = self.parser.libdata().exec_external_count; + // Disable TTY protocols while we run a bind command, because it may call out. + let mut scoped_tty = TtyHandoff::new(); + let mut modified_tty = scoped_tty.disable_tty_protocols(); + self.parser.eval(cmd, &IoChain::new()); self.parser.set_last_statuses(last_statuses); - if self.parser.libdata().exec_external_count != prev_exec_external_count - && self.data.left_prompt_buff.contains('\n') + modified_tty |= scoped_tty.reclaim(); + if modified_tty + || (self.parser.libdata().exec_external_count != prev_exec_external_count + && self.data.left_prompt_buff.contains('\n')) { self.save_screen_state(); } @@ -2351,7 +2373,6 @@ fn read_normal_chars(&mut self) -> Option { let mut accumulated_chars = WString::new(); while accumulated_chars.len() < limit { - terminal_protocols_enable_ifn(); let evt = self.read_char(); let CharEvent::Key(kevt) = &evt else { event_needing_handling = Some(evt); @@ -2545,11 +2566,11 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo // Rogue reply. return ControlFlow::Continue(()); } - if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed) - == Capability::Unknown as _ - { - KITTY_KEYBOARD_SUPPORTED - .store(Capability::NotSupported as _, Ordering::Release); + if get_kitty_keyboard_capability() == Capability::Unknown { + set_kitty_keyboard_capability(Capability::NotSupported); + // We may have written to the tty, so save the screen state + // so we don't repaint. + self.screen.save_status(); } } QueryResponseEvent::CursorPositionReport(cursor_pos) => { @@ -2796,7 +2817,12 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { } } else { // Either the user hit tab only once, or we had no visible completion list. + // Disable tty protocols while we compute completions, so that control-C + // triggers SIGINT (suppressed by CSI-U). + let mut tty = TtyHandoff::new(); + tty.disable_tty_protocols(); self.compute_and_apply_completions(c); + tty.reclaim(); } } rl::PagerToggleSearch => { @@ -4587,6 +4613,10 @@ fn exec_prompt(&mut self, full_prompt: bool, final_prompt: bool) { // Prompts must be run non-interactively. let _noninteractive = self.parser.push_scope(|s| s.is_interactive = false); + // Suppress TTY protocols in a scoped way so that e.g. control-C can cancel the prompt. + let mut scoped_tty = TtyHandoff::new(); + scoped_tty.disable_tty_protocols(); + // Update the termsize now. // This allows prompts to react to $COLUMNS. self.update_termsize(); @@ -5680,6 +5710,10 @@ fn check_for_orphaned_process(loop_count: usize, shell_pgid: libc::pid_t) -> boo /// Run the specified command with the correct terminal modes, and while taking care to perform job /// notification, set the title, etc. fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes { + assert!( + !get_tty_protocols_active(), + "TTY protocols should not be active" + ); let ft = tok_command(cmd); // Provide values for `status current-command` and `status current-commandline` @@ -6232,6 +6266,10 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) { c, ReadlineCmd::Complete | ReadlineCmd::CompleteAndSearch )); + assert!( + !get_tty_protocols_active(), + "should not be called with TTY protocols active" + ); // Remove a trailing backslash. This may trigger an extra repaint, but this is // rare. @@ -6261,9 +6299,6 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) { token_range.start += cmdsub_range.start; token_range.end += cmdsub_range.start; - // Wildcard expansion and completion below check for cancellation. - terminal_protocols_disable_ifn(); - // Check if we have a wildcard within this string; if so we first attempt to expand the // wildcard; if that succeeds we don't then apply user completions (#8593). let mut wc_expanded = WString::new(); diff --git a/src/signal.rs b/src/signal.rs index 9aca3d42f..3c4e2dd5a 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -7,6 +7,7 @@ use crate::reader::{reader_handle_sigint, reader_sighup, safe_restore_term_mode}; use crate::termsize::TermsizeContainer; use crate::topic_monitor::{topic_monitor_principal, Generation, GenerationsList, Topic}; +use crate::tty_handoff::{safe_deactivate_tty_protocols, safe_mark_tty_invalid}; use crate::wchar::prelude::*; use crate::wutil::{fish_wcstoi, perror}; use errno::{errno, set_errno}; @@ -83,13 +84,15 @@ extern "C" fn fish_signal_handler( // Exit unless the signal was trapped. if !observed { reader_sighup(); + safe_mark_tty_invalid(); } topic_monitor_principal().post(Topic::sighupint); } libc::SIGTERM => { - // Handle sigterm. The only thing we do is restore the front process ID, then die. + // Handle sigterm. The only thing we do is restore the front process ID and disable protocols, then die. if !observed { safe_restore_term_mode(); + safe_deactivate_tty_protocols(); // Safety: signal() and raise() are async-signal-safe. unsafe { libc::signal(libc::SIGTERM, libc::SIG_DFL); diff --git a/src/terminal.rs b/src/terminal.rs index 4a597c0bb..dbc7d1dbe 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -216,15 +216,14 @@ fn maybe_terminfo( true } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(u8)] -pub(crate) enum Capability { +pub enum Capability { Unknown, Supported, NotSupported, } -pub(crate) static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _); - pub(crate) static SCROLL_FORWARD_SUPPORTED: RelaxedAtomicBool = RelaxedAtomicBool::new(false); pub(crate) static SCROLL_FORWARD_TERMINFO_CODE: &str = "indn"; diff --git a/src/tty_handoff.rs b/src/tty_handoff.rs index 7a81dea76..ff2baa57e 100644 --- a/src/tty_handoff.rs +++ b/src/tty_handoff.rs @@ -12,14 +12,14 @@ KittyKeyboardProgressiveEnhancementsDisable, KittyKeyboardProgressiveEnhancementsEnable, ModifyOtherKeysDisable, ModifyOtherKeysEnable, }; -use crate::terminal::{Capability, Output, Outputter, KITTY_KEYBOARD_SUPPORTED}; +use crate::terminal::{Capability, Output, Outputter}; use crate::threads::assert_is_main_thread; use crate::wchar_ext::ToWString; use crate::wutil::perror; use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG}; use std::mem::MaybeUninit; use std::os::fd::BorrowedFd; -use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering}; // Facts about our environment, which inform how we handle the tty. #[derive(Debug, Copy, Clone)] @@ -60,6 +60,35 @@ fn detect() -> Self { } } +// Whether CSI-U ("Kitty") support is present in the TTY. +static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _); + +// Get the support capability for CSI-U ("Kitty") protocols. +pub fn get_kitty_keyboard_capability() -> Capability { + let cap = KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed); + match cap { + x if x == Capability::Supported as _ => Capability::Supported, + x if x == Capability::NotSupported as _ => Capability::NotSupported, + _ => Capability::Unknown, + } +} + +// Set CSI-U ("Kitty") support capability. +// This correctly handles the case where we think protocols are already enabled. +pub fn set_kitty_keyboard_capability(cap: Capability) { + assert_is_main_thread(); + // Disable and renable protocols around capabilities. + let mut tty = TtyHandoff::new(); + tty.disable_tty_protocols(); + KITTY_KEYBOARD_SUPPORTED.store(cap as _, Ordering::Relaxed); + FLOG!( + term_protocols, + "Set Kitty keyboard capability to", + format!("{:?}", cap) + ); + tty.reclaim(); +} + // Helper to determine which keyboard protocols to enable. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ProtocolKind { @@ -215,10 +244,13 @@ pub fn initialize_tty_metadata() { // A marker of the current state of the tty protocols. static TTY_PROTOCOLS_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false); +// A marker that the tty has been closed (SIGHUP, etc) and so we should not try to write to it. +static TTY_INVALID: RelaxedAtomicBool = RelaxedAtomicBool::new(false); + // Enable or disable TTY protocols by writing the appropriate commands to the tty. // Return true if we emitted any bytes to the tty. // Note this does NOT intialize the TTY protocls if not already initialized. -pub fn set_tty_protocols_active(enable: bool) -> bool { +fn set_tty_protocols_active(enable: bool) -> bool { assert_is_main_thread(); // Have protocols at all? We require someone else to have initialized them. let Some(protocols) = tty_protocols() else { @@ -226,11 +258,17 @@ pub fn set_tty_protocols_active(enable: bool) -> bool { }; // Already set? // Note we don't need atomic swaps as this is only called on the main thread. + // Also note we (logically) set and clear this even if we got SIGHUP. if TTY_PROTOCOLS_ACTIVE.load() == enable { return false; } TTY_PROTOCOLS_ACTIVE.store(enable); + // Did we get SIGHUP? + if TTY_INVALID.load() { + return false; + } + // Write the commands to the tty, ignoring errors. let commands = protocols.safe_get_commands(enable); let _ = common::write_loop(&libc::STDOUT_FILENO, commands); @@ -259,10 +297,15 @@ pub fn safe_deactivate_tty_protocols() { // No protocols set, nothing to do. return; }; - if !TTY_PROTOCOLS_ACTIVE.load() { return; } + + // Did we get SIGHUP? + if TTY_INVALID.load() { + return; + } + TTY_PROTOCOLS_ACTIVE.store(false); let commands = protocols.safe_get_commands(false); // Safety: just writing data to stdout. @@ -270,6 +313,12 @@ pub fn safe_deactivate_tty_protocols() { let _ = nix::unistd::write(stdout_fd, commands); } +// Called from a signal handler to mark the tty as invalid (e.g. SIGHUP). +// This suppresses any further attempts to write protocols to the tty, +pub fn safe_mark_tty_invalid() { + TTY_INVALID.store(true); +} + // Allows transferring the tty to a job group, while it runs, in a scoped fashion. // This has several responsibilities: // - Invoking tcsetpgrp() to transfer the tty to the job group.