2020-01-18 05:38:21 -03:00
/*
2021-08-25 14:39:57 -03:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2020-01-18 05:38:21 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 05:38:21 -03:00
*/
2022-03-12 17:40:32 -03:00
# include <AK/FixedArray.h>
2021-05-28 16:26:39 -03:00
# include <AK/QuickSort.h>
2023-06-27 13:03:09 -03:00
# include <AK/TypedTransfer.h>
2021-01-03 05:39:46 -03:00
# include <AK/URL.h>
2021-08-25 14:39:57 -03:00
# include <LibConfig/Client.h>
2021-11-20 21:01:21 -03:00
# include <LibConfig/Listener.h>
2023-06-27 13:03:09 -03:00
# include <LibCore/Account.h>
2020-02-06 11:04:03 -03:00
# include <LibCore/ArgsParser.h>
2023-05-25 11:37:52 -03:00
# include <LibCore/Directory.h>
2021-11-23 06:59:50 -03:00
# include <LibCore/System.h>
2021-01-03 05:39:46 -03:00
# include <LibDesktop/Launcher.h>
2023-03-24 06:56:53 -03:00
# include <LibFileSystem/FileSystem.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Action.h>
# include <LibGUI/ActionGroup.h>
# include <LibGUI/Application.h>
# include <LibGUI/BoxLayout.h>
2020-12-28 06:30:19 -03:00
# include <LibGUI/Button.h>
# include <LibGUI/CheckBox.h>
2021-05-28 16:26:39 -03:00
# include <LibGUI/ComboBox.h>
2020-12-28 06:30:19 -03:00
# include <LibGUI/Event.h>
2020-11-02 16:30:17 -03:00
# include <LibGUI/Icon.h>
2021-05-28 16:26:39 -03:00
# include <LibGUI/ItemListModel.h>
2020-02-14 21:56:30 -03:00
# include <LibGUI/Menu.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/Menubar.h>
2022-05-04 12:28:43 -03:00
# include <LibGUI/MessageBox.h>
2022-05-11 15:41:33 -03:00
# include <LibGUI/Process.h>
2020-12-28 06:30:19 -03:00
# include <LibGUI/TextBox.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Widget.h>
# include <LibGUI/Window.h>
2022-04-09 04:28:38 -03:00
# include <LibGfx/Font/FontDatabase.h>
2020-02-14 21:18:12 -03:00
# include <LibGfx/Palette.h>
2021-11-22 12:12:40 -03:00
# include <LibMain/Main.h>
2019-10-21 14:50:07 -03:00
# include <LibVT/TerminalWidget.h>
2021-04-29 22:40:38 -03:00
# include <pty.h>
2019-01-15 01:30:55 -02:00
2021-11-20 21:01:21 -03:00
class TerminalChangeListener : public Config : : Listener {
public :
TerminalChangeListener ( VT : : TerminalWidget & parent_terminal )
: m_parent_terminal ( parent_terminal )
{
}
2023-06-26 16:05:53 -03:00
virtual void config_bool_did_change ( StringView domain , StringView group , StringView key , bool value ) override
2022-01-15 01:47:43 -03:00
{
VERIFY ( domain = = " Terminal " ) ;
2022-05-04 17:36:07 -03:00
if ( group = = " Terminal " ) {
if ( key = = " ShowScrollBar " )
m_parent_terminal . set_show_scrollbar ( value ) ;
else if ( key = = " ConfirmClose " & & on_confirm_close_changed )
on_confirm_close_changed ( value ) ;
2022-05-12 18:08:51 -03:00
} else if ( group = = " Cursor " & & key = = " Blinking " ) {
m_parent_terminal . set_cursor_blinking ( value ) ;
2022-01-15 01:47:43 -03:00
}
}
2023-06-26 16:05:53 -03:00
virtual void config_string_did_change ( StringView domain , StringView group , StringView key , StringView value ) override
2021-11-20 21:01:21 -03:00
{
VERIFY ( domain = = " Terminal " ) ;
2022-12-02 13:16:58 -03:00
if ( group = = " Window " & & key = = " Bell " ) {
auto bell_mode = VT : : TerminalWidget : : BellMode : : Visible ;
if ( value = = " AudibleBeep " )
bell_mode = VT : : TerminalWidget : : BellMode : : AudibleBeep ;
if ( value = = " Visible " )
bell_mode = VT : : TerminalWidget : : BellMode : : Visible ;
if ( value = = " Disabled " )
bell_mode = VT : : TerminalWidget : : BellMode : : Disabled ;
m_parent_terminal . set_bell_mode ( bell_mode ) ;
2021-11-20 21:01:21 -03:00
} else if ( group = = " Text " & & key = = " Font " ) {
auto font = Gfx : : FontDatabase : : the ( ) . get_by_name ( value ) ;
if ( font . is_null ( ) )
font = Gfx : : FontDatabase : : default_fixed_width_font ( ) ;
m_parent_terminal . set_font_and_resize_to_fit ( * font ) ;
2022-08-14 14:22:37 -03:00
m_parent_terminal . apply_size_increments_to_window ( * m_parent_terminal . window ( ) ) ;
2021-11-20 21:01:21 -03:00
m_parent_terminal . window ( ) - > resize ( m_parent_terminal . size ( ) ) ;
2022-05-12 18:08:51 -03:00
} else if ( group = = " Cursor " & & key = = " Shape " ) {
auto cursor_shape = VT : : TerminalWidget : : parse_cursor_shape ( value ) . value_or ( VT : : CursorShape : : Block ) ;
m_parent_terminal . set_cursor_shape ( cursor_shape ) ;
2021-11-20 21:01:21 -03:00
}
}
2023-06-26 16:05:53 -03:00
virtual void config_i32_did_change ( StringView domain , StringView group , StringView key , i32 value ) override
2021-11-20 21:01:21 -03:00
{
VERIFY ( domain = = " Terminal " ) ;
if ( group = = " Terminal " & & key = = " MaxHistorySize " ) {
m_parent_terminal . set_max_history_size ( value ) ;
} else if ( group = = " Window " & & key = = " Opacity " ) {
m_parent_terminal . set_opacity ( value ) ;
}
}
2022-05-04 17:36:07 -03:00
Function < void ( bool ) > on_confirm_close_changed ;
2021-11-20 21:01:21 -03:00
private :
VT : : TerminalWidget & m_parent_terminal ;
} ;
2023-06-27 13:03:09 -03:00
static ErrorOr < void > utmp_update ( StringView tty , pid_t pid , bool create )
2020-09-06 11:14:46 -03:00
{
2023-06-27 13:03:09 -03:00
auto pid_string = TRY ( String : : number ( pid ) ) ;
Array utmp_update_command {
" -f " sv ,
" Terminal " sv ,
" -p " sv ,
pid_string . bytes_as_string_view ( ) ,
( create ? " -c " sv : " -d " sv ) ,
tty ,
} ;
auto utmpupdate_pid = TRY ( Core : : Process : : spawn ( " /bin/utmpupdate " sv , utmp_update_command , { } , Core : : Process : : KeepAsChild : : Yes ) ) ;
Core : : System : : WaitPidResult status ;
auto wait_successful = false ;
while ( ! wait_successful ) {
auto result = Core : : System : : waitpid ( utmpupdate_pid , 0 ) ;
if ( result . is_error ( ) & & result . error ( ) . code ( ) ! = EINTR ) {
return result . release_error ( ) ;
} else if ( ! result . is_error ( ) ) {
status = result . release_value ( ) ;
wait_successful = true ;
2020-11-30 01:03:42 -03:00
}
}
2023-06-27 13:03:09 -03:00
if ( WIFEXITED ( status . status ) & & WEXITSTATUS ( status . status ) ! = 0 )
dbgln ( " Terminal: utmpupdate exited with status {} " , WEXITSTATUS ( status . status ) ) ;
else if ( WIFSIGNALED ( status . status ) )
dbgln ( " Terminal: utmpupdate exited due to unhandled signal {} " , WTERMSIG ( status . status ) ) ;
return { } ;
2020-09-06 11:14:46 -03:00
}
2023-06-27 13:03:09 -03:00
static ErrorOr < void > run_command ( StringView command , bool keep_open )
2019-01-15 03:30:19 -02:00
{
2023-06-27 13:03:09 -03:00
auto shell = TRY ( String : : from_deprecated_string ( TRY ( Core : : Account : : self ( Core : : Account : : Read : : PasswdOnly ) ) . shell ( ) ) ) ;
if ( shell . is_empty ( ) )
2023-08-07 06:12:38 -03:00
shell = " /bin/Shell " _string ;
2020-05-25 09:34:57 -03:00
2022-03-12 17:40:32 -03:00
Vector < StringView > arguments ;
arguments . append ( shell ) ;
2021-04-29 22:40:38 -03:00
if ( ! command . is_empty ( ) ) {
2021-07-04 05:59:40 -03:00
if ( keep_open )
2022-07-11 14:32:29 -03:00
arguments . append ( " --keep-open " sv ) ;
arguments . append ( " -c " sv ) ;
2022-03-12 17:40:32 -03:00
arguments . append ( command ) ;
2021-04-29 22:40:38 -03:00
}
2023-01-28 17:12:17 -03:00
auto env = TRY ( FixedArray < StringView > : : create ( { " TERM=xterm " sv , " PAGER=more " sv , " PATH= " sv DEFAULT_PATH_SV } ) ) ;
2022-03-12 17:40:32 -03:00
TRY ( Core : : System : : exec ( shell , arguments , Core : : System : : SearchInPath : : No , env . span ( ) ) ) ;
2021-04-29 22:40:38 -03:00
VERIFY_NOT_REACHED ( ) ;
2019-01-15 03:30:19 -02:00
}
2019-01-15 01:30:55 -02:00
2021-11-24 19:05:25 -03:00
static ErrorOr < NonnullRefPtr < GUI : : Window > > create_find_window ( VT : : TerminalWidget & terminal )
2020-12-28 06:30:19 -03:00
{
2023-09-16 08:51:33 -03:00
auto window = GUI : : Window : : construct ( & terminal ) ;
2022-08-22 13:52:21 -03:00
window - > set_window_mode ( GUI : : WindowMode : : RenderAbove ) ;
2020-12-28 06:30:19 -03:00
window - > set_title ( " Find in Terminal " ) ;
window - > set_resizable ( false ) ;
window - > resize ( 300 , 90 ) ;
2023-09-18 21:13:48 -03:00
auto main_widget = window - > set_main_widget < GUI : : Widget > ( ) ;
2021-11-24 19:05:25 -03:00
main_widget - > set_fill_with_background_color ( true ) ;
main_widget - > set_background_role ( ColorRole : : Button ) ;
2023-08-13 09:52:36 -03:00
main_widget - > set_layout < GUI : : VerticalBoxLayout > ( 4 ) ;
2020-12-28 06:30:19 -03:00
2023-09-22 18:28:59 -03:00
auto & find = main_widget - > add < GUI : : Widget > ( ) ;
find . set_layout < GUI : : HorizontalBoxLayout > ( 4 ) ;
find . set_fixed_height ( 30 ) ;
2020-12-28 06:30:19 -03:00
2023-09-22 18:28:59 -03:00
auto & find_textbox = find . add < GUI : : TextBox > ( ) ;
find_textbox . set_fixed_width ( 230 ) ;
find_textbox . set_focus ( true ) ;
2021-09-10 20:15:44 -03:00
if ( terminal . has_selection ( ) )
2023-09-22 18:28:59 -03:00
find_textbox . set_text ( terminal . selected_text ( ) . replace ( " \n " sv , " " sv , ReplaceMode : : All ) ) ;
auto & find_backwards = find . add < GUI : : Button > ( ) ;
find_backwards . set_fixed_width ( 25 ) ;
find_backwards . set_icon ( TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/upward-triangle.png " sv ) ) ) ;
auto & find_forwards = find . add < GUI : : Button > ( ) ;
find_forwards . set_fixed_width ( 25 ) ;
find_forwards . set_icon ( TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/downward-triangle.png " sv ) ) ) ;
find_textbox . on_return_pressed = [ & find_backwards ] {
find_backwards . click ( ) ;
2020-12-28 06:30:19 -03:00
} ;
2023-09-22 18:28:59 -03:00
find_textbox . on_shift_return_pressed = [ & find_forwards ] {
find_forwards . click ( ) ;
2021-06-02 08:21:17 -03:00
} ;
2023-09-22 18:28:59 -03:00
auto & match_case = main_widget - > add < GUI : : CheckBox > ( " Case sensitive " _string ) ;
auto & wrap_around = main_widget - > add < GUI : : CheckBox > ( " Wrap around " _string ) ;
2020-12-28 06:30:19 -03:00
2023-09-22 18:28:59 -03:00
find_backwards . on_click = [ & terminal , & find_textbox , & match_case , & wrap_around ] ( auto ) {
auto needle = find_textbox . text ( ) ;
2020-12-28 06:30:19 -03:00
if ( needle . is_empty ( ) ) {
return ;
}
2023-09-22 18:28:59 -03:00
auto found_range = terminal . find_previous ( needle , terminal . normalized_selection ( ) . start ( ) , match_case . is_checked ( ) , wrap_around . is_checked ( ) ) ;
2020-12-28 06:30:19 -03:00
if ( found_range . is_valid ( ) ) {
terminal . scroll_to_row ( found_range . start ( ) . row ( ) ) ;
terminal . set_selection ( found_range ) ;
}
} ;
2023-09-22 18:28:59 -03:00
find_forwards . on_click = [ & terminal , & find_textbox , & match_case , & wrap_around ] ( auto ) {
auto needle = find_textbox . text ( ) ;
2020-12-28 06:30:19 -03:00
if ( needle . is_empty ( ) ) {
return ;
}
2023-09-22 18:28:59 -03:00
auto found_range = terminal . find_next ( needle , terminal . normalized_selection ( ) . end ( ) , match_case . is_checked ( ) , wrap_around . is_checked ( ) ) ;
2020-12-28 06:30:19 -03:00
if ( found_range . is_valid ( ) ) {
terminal . scroll_to_row ( found_range . start ( ) . row ( ) ) ;
terminal . set_selection ( found_range ) ;
}
} ;
return window ;
}
2021-11-22 12:12:40 -03:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
2019-01-16 09:57:07 -02:00
{
2021-11-27 19:26:34 -03:00
TRY ( Core : : System : : pledge ( " stdio tty rpath cpath wpath recvfd sendfd proc exec unix sigaction " ) ) ;
2020-01-11 17:07:18 -03:00
2019-12-02 12:50:10 -03:00
struct sigaction act ;
2023-06-27 13:03:09 -03:00
act . sa_mask = 0 ;
// Do not trust that both function pointers overlap.
act . sa_sigaction = nullptr ;
2019-12-02 12:50:10 -03:00
act . sa_flags = SA_NOCLDWAIT ;
act . sa_handler = SIG_IGN ;
2021-11-23 06:59:50 -03:00
TRY ( Core : : System : : sigaction ( SIGCHLD , & act , nullptr ) ) ;
2019-02-11 11:56:23 -02:00
2023-05-05 01:24:53 -03:00
auto app = TRY ( GUI : : Application : : create ( arguments ) ) ;
2021-11-22 12:12:40 -03:00
2021-11-27 19:26:34 -03:00
TRY ( Core : : System : : pledge ( " stdio tty rpath cpath wpath recvfd sendfd proc exec unix " ) ) ;
2020-01-11 17:07:18 -03:00
2022-02-11 13:33:09 -03:00
Config : : pledge_domain ( " Terminal " ) ;
2021-08-25 15:06:54 -03:00
2023-02-28 17:41:43 -03:00
StringView command_to_execute ;
2021-07-04 05:59:40 -03:00
bool keep_open = false ;
2019-09-02 02:28:52 -03:00
2020-02-02 08:34:39 -03:00
Core : : ArgsParser args_parser ;
2020-01-27 14:25:36 -03:00
args_parser . add_option ( command_to_execute , " Execute this command inside the terminal " , nullptr , ' e ' , " command " ) ;
2021-07-04 05:59:40 -03:00
args_parser . add_option ( keep_open , " Keep the terminal open after the command has finished executing " , nullptr , ' k ' ) ;
2020-03-15 13:20:19 -03:00
2021-11-22 21:03:05 -03:00
args_parser . parse ( arguments ) ;
2019-09-02 02:28:52 -03:00
2023-02-28 17:41:43 -03:00
if ( keep_open & & command_to_execute . is_empty ( ) ) {
2021-07-04 05:59:40 -03:00
warnln ( " Option -k can only be used in combination with -e. " ) ;
return 1 ;
}
2021-05-06 09:52:46 -03:00
int ptm_fd ;
pid_t shell_pid = forkpty ( & ptm_fd , nullptr , nullptr , nullptr ) ;
2023-06-27 13:03:09 -03:00
if ( shell_pid < 0 )
return Error : : from_errno ( errno ) ;
// We're the child process; run the startup command.
2021-04-29 22:40:38 -03:00
if ( shell_pid = = 0 ) {
2023-02-28 17:41:43 -03:00
if ( ! command_to_execute . is_empty ( ) )
2022-03-12 17:40:32 -03:00
TRY ( run_command ( command_to_execute , keep_open ) ) ;
2021-04-29 22:40:38 -03:00
else
2022-07-11 14:32:29 -03:00
TRY ( run_command ( Config : : read_string ( " Terminal " sv , " Startup " sv , " Command " sv , " " sv ) , false ) ) ;
2021-04-29 22:40:38 -03:00
VERIFY_NOT_REACHED ( ) ;
}
2020-09-06 11:14:46 -03:00
2021-11-24 19:25:03 -03:00
auto ptsname = TRY ( Core : : System : : ptsname ( ptm_fd ) ) ;
2023-06-27 13:03:09 -03:00
TRY ( utmp_update ( ptsname , shell_pid , true ) ) ;
2019-01-15 03:30:19 -02:00
2022-07-11 14:32:29 -03:00
auto app_icon = GUI : : Icon : : default_icon ( " app-terminal " sv ) ;
2020-11-02 16:30:17 -03:00
2023-09-16 08:51:33 -03:00
auto window = GUI : : Window : : construct ( ) ;
2019-05-16 15:13:41 -03:00
window - > set_title ( " Terminal " ) ;
2022-06-28 22:18:47 -03:00
window - > set_obey_widget_min_size ( false ) ;
2019-01-15 01:30:55 -02:00
2023-09-18 21:13:48 -03:00
auto terminal = window - > set_main_widget < VT : : TerminalWidget > ( ptm_fd , true ) ;
2021-11-24 18:54:49 -03:00
terminal - > on_command_exit = [ & ] {
2020-07-04 09:05:19 -03:00
app - > quit ( 0 ) ;
2019-10-22 16:57:53 -03:00
} ;
2021-11-24 18:54:49 -03:00
terminal - > on_title_change = [ & ] ( auto title ) {
2019-10-21 17:07:59 -03:00
window - > set_title ( title ) ;
} ;
2022-12-06 18:35:32 -03:00
terminal - > on_terminal_size_change = [ & ] ( auto size ) {
2021-02-20 12:57:31 -03:00
window - > resize ( size ) ;
} ;
2021-11-24 18:54:49 -03:00
terminal - > apply_size_increments_to_window ( * window ) ;
2020-11-02 16:30:17 -03:00
window - > set_icon ( app_icon . bitmap_for_size ( 16 ) ) ;
2020-12-19 18:47:45 -03:00
2021-11-20 21:01:21 -03:00
Config : : monitor_domain ( " Terminal " ) ;
2022-07-11 14:32:29 -03:00
auto should_confirm_close = Config : : read_bool ( " Terminal " sv , " Terminal " sv , " ConfirmClose " sv , true ) ;
2021-11-20 21:01:21 -03:00
TerminalChangeListener listener { terminal } ;
2022-07-11 14:32:29 -03:00
auto bell = Config : : read_string ( " Terminal " sv , " Window " sv , " Bell " sv , " Visible " sv ) ;
2020-12-19 18:47:45 -03:00
if ( bell = = " AudibleBeep " ) {
2021-11-24 18:54:49 -03:00
terminal - > set_bell_mode ( VT : : TerminalWidget : : BellMode : : AudibleBeep ) ;
2020-12-19 18:47:45 -03:00
} else if ( bell = = " Disabled " ) {
2021-11-24 18:54:49 -03:00
terminal - > set_bell_mode ( VT : : TerminalWidget : : BellMode : : Disabled ) ;
2020-12-19 18:47:45 -03:00
} else {
2021-11-24 18:54:49 -03:00
terminal - > set_bell_mode ( VT : : TerminalWidget : : BellMode : : Visible ) ;
2020-12-19 18:47:45 -03:00
}
2019-02-10 11:28:39 -02:00
2022-07-11 14:32:29 -03:00
auto cursor_shape = VT : : TerminalWidget : : parse_cursor_shape ( Config : : read_string ( " Terminal " sv , " Cursor " sv , " Shape " sv , " Block " sv ) ) . value_or ( VT : : CursorShape : : Block ) ;
2022-05-12 18:08:51 -03:00
terminal - > set_cursor_shape ( cursor_shape ) ;
2022-07-11 14:32:29 -03:00
auto cursor_blinking = Config : : read_bool ( " Terminal " sv , " Cursor " sv , " Blinking " sv , true ) ;
2022-05-12 18:08:51 -03:00
terminal - > set_cursor_blinking ( cursor_blinking ) ;
2021-11-24 19:05:25 -03:00
auto find_window = TRY ( create_find_window ( terminal ) ) ;
2019-05-31 16:43:58 -03:00
2022-07-11 14:32:29 -03:00
auto new_opacity = Config : : read_i32 ( " Terminal " sv , " Window " sv , " Opacity " sv , 255 ) ;
2021-11-24 18:54:49 -03:00
terminal - > set_opacity ( new_opacity ) ;
2019-08-15 10:19:54 -03:00
window - > set_has_alpha_channel ( new_opacity < 255 ) ;
2019-05-25 20:43:15 -03:00
2022-07-11 14:32:29 -03:00
auto new_scrollback_size = Config : : read_i32 ( " Terminal " sv , " Terminal " sv , " MaxHistorySize " sv , terminal - > max_history_size ( ) ) ;
2021-11-24 18:54:49 -03:00
terminal - > set_max_history_size ( new_scrollback_size ) ;
2020-11-29 11:49:45 -03:00
2022-07-11 14:32:29 -03:00
auto show_scroll_bar = Config : : read_bool ( " Terminal " sv , " Terminal " sv , " ShowScrollBar " sv , true ) ;
2022-01-15 01:47:43 -03:00
terminal - > set_show_scrollbar ( show_scroll_bar ) ;
2023-01-20 16:06:05 -03:00
auto open_settings_action = GUI : : Action : : create ( " Terminal &Settings " , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/settings.png " sv ) ) ,
2020-12-30 13:52:19 -03:00
[ & ] ( auto & ) {
2022-07-11 14:32:29 -03:00
GUI : : Process : : spawn_or_show_error ( window , " /bin/TerminalSettings " sv ) ;
2020-12-30 13:52:19 -03:00
} ) ;
2023-08-14 02:19:40 -03:00
terminal - > context_menu ( ) . add_separator ( ) ;
2023-08-14 05:14:27 -03:00
terminal - > context_menu ( ) . add_action ( open_settings_action ) ;
2020-12-30 09:42:16 -03:00
2023-08-14 05:44:42 -03:00
auto file_menu = window - > add_menu ( " &File " _string ) ;
2023-08-14 05:14:27 -03:00
file_menu - > add_action ( GUI : : Action : : create ( " Open New &Terminal " , { Mod_Ctrl | Mod_Shift , Key_N } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/app-terminal.png " sv ) ) , [ & ] ( auto & ) {
2022-07-11 14:32:29 -03:00
GUI : : Process : : spawn_or_show_error ( window , " /bin/Terminal " sv ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
2020-12-30 09:42:16 -03:00
2023-08-14 05:14:27 -03:00
file_menu - > add_action ( open_settings_action ) ;
2023-08-14 02:19:40 -03:00
file_menu - > add_separator ( ) ;
2022-05-04 12:28:43 -03:00
auto tty_has_foreground_process = [ & ] {
pid_t fg_pid = tcgetpgrp ( ptm_fd ) ;
return fg_pid ! = - 1 & & fg_pid ! = shell_pid ;
} ;
auto shell_child_process_count = [ & ] {
int background_process_count = 0 ;
2023-06-27 13:03:09 -03:00
Core : : Directory : : for_each_entry ( String : : formatted ( " /proc/{}/children " , shell_pid ) . release_value_but_fixme_should_propagate_errors ( ) , Core : : DirIterator : : Flags : : SkipParentAndBaseDir , [ & ] ( auto & , auto & ) {
2022-05-04 12:28:43 -03:00
+ + background_process_count ;
2023-05-25 11:37:52 -03:00
return IterationDecision : : Continue ;
} ) . release_value_but_fixme_should_propagate_errors ( ) ;
2022-05-04 12:28:43 -03:00
return background_process_count ;
} ;
2022-05-13 09:10:27 -03:00
auto check_terminal_quit = [ & ] ( ) - > GUI : : Dialog : : ExecResult {
2022-05-04 17:36:07 -03:00
if ( ! should_confirm_close )
2022-05-13 09:10:27 -03:00
return GUI : : MessageBox : : ExecResult : : OK ;
2023-06-27 13:03:09 -03:00
Optional < String > close_message ;
2023-05-22 14:07:09 -03:00
auto title = " Running Process " sv ;
2022-05-04 12:28:43 -03:00
if ( tty_has_foreground_process ( ) ) {
2023-08-07 06:12:38 -03:00
close_message = " Close Terminal and kill its foreground process? " _string ;
2022-05-04 12:28:43 -03:00
} else {
auto child_process_count = shell_child_process_count ( ) ;
2023-05-22 14:07:09 -03:00
if ( child_process_count > 1 ) {
title = " Running Processes " sv ;
2023-06-27 13:03:09 -03:00
close_message = String : : formatted ( " Close Terminal and kill its {} background processes? " , child_process_count ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-05-22 14:07:09 -03:00
} else if ( child_process_count = = 1 ) {
2023-08-07 06:12:38 -03:00
close_message = " Close Terminal and kill its background process? " _string ;
2023-05-22 14:07:09 -03:00
}
2022-05-04 12:28:43 -03:00
}
if ( close_message . has_value ( ) )
2023-05-22 14:07:09 -03:00
return GUI : : MessageBox : : show ( window , * close_message , title , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : OKCancel ) ;
2022-05-13 09:10:27 -03:00
return GUI : : MessageBox : : ExecResult : : OK ;
2022-05-04 12:28:43 -03:00
} ;
2023-08-14 05:14:27 -03:00
file_menu - > add_action ( GUI : : CommonActions : : make_quit_action ( [ & ] ( auto & ) {
2020-10-06 14:12:42 -03:00
dbgln ( " Terminal: Quit menu activated! " ) ;
2022-05-13 09:10:27 -03:00
if ( check_terminal_quit ( ) = = GUI : : MessageBox : : ExecResult : : OK )
2022-05-04 12:28:43 -03:00
GUI : : Application : : the ( ) - > quit ( ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
2019-02-11 12:37:12 -02:00
2023-08-14 05:44:42 -03:00
auto edit_menu = window - > add_menu ( " &Edit " _string ) ;
2023-08-14 05:14:27 -03:00
edit_menu - > add_action ( terminal - > copy_action ( ) ) ;
edit_menu - > add_action ( terminal - > paste_action ( ) ) ;
2023-08-14 02:19:40 -03:00
edit_menu - > add_separator ( ) ;
2023-08-14 05:14:27 -03:00
edit_menu - > add_action ( GUI : : Action : : create ( " &Find... " , { Mod_Ctrl | Mod_Shift , Key_F } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " sv ) ) ,
2020-12-28 06:30:19 -03:00
[ & ] ( auto & ) {
find_window - > show ( ) ;
find_window - > move_to_front ( ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
2019-11-20 17:33:23 -03:00
2023-08-14 05:44:42 -03:00
auto view_menu = window - > add_menu ( " &View " _string ) ;
2023-08-14 05:14:27 -03:00
view_menu - > add_action ( GUI : : CommonActions : : make_fullscreen_action ( [ & ] ( auto & ) {
2021-03-13 00:49:39 -03:00
window - > set_fullscreen ( ! window - > is_fullscreen ( ) ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
view_menu - > add_action ( terminal - > clear_including_history_action ( ) ) ;
2019-02-12 05:39:19 -02:00
2023-01-03 15:06:31 -03:00
auto adjust_font_size = [ & ] ( float adjustment ) {
auto & font = terminal - > font ( ) ;
auto new_size = max ( 5 , font . presentation_size ( ) + adjustment ) ;
2023-09-05 15:19:33 -03:00
if ( auto new_font = Gfx : : FontDatabase : : the ( ) . get ( font . family ( ) , new_size , font . weight ( ) , font . width ( ) , font . slope ( ) ) ) {
2023-01-03 15:06:31 -03:00
terminal - > set_font_and_resize_to_fit ( * new_font ) ;
terminal - > apply_size_increments_to_window ( * window ) ;
window - > resize ( terminal - > size ( ) ) ;
}
} ;
2023-08-14 02:19:40 -03:00
view_menu - > add_separator ( ) ;
2023-08-14 05:14:27 -03:00
view_menu - > add_action ( GUI : : CommonActions : : make_zoom_in_action ( [ & ] ( auto & ) {
2023-01-03 15:06:31 -03:00
adjust_font_size ( 1 ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
view_menu - > add_action ( GUI : : CommonActions : : make_zoom_out_action ( [ & ] ( auto & ) {
2023-01-03 15:06:31 -03:00
adjust_font_size ( - 1 ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
2023-01-03 15:06:31 -03:00
2023-08-14 05:44:42 -03:00
auto help_menu = window - > add_menu ( " &Help " _string ) ;
2023-08-14 05:14:27 -03:00
help_menu - > add_action ( GUI : : CommonActions : : make_command_palette_action ( window ) ) ;
help_menu - > add_action ( GUI : : CommonActions : : make_help_action ( [ ] ( auto & ) {
2023-04-08 06:48:22 -03:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( " /usr/share/man/man1/Applications/Terminal.md " ) , " /bin/Help " ) ;
2023-08-14 05:14:27 -03:00
} ) ) ;
2023-09-13 16:34:46 -03:00
help_menu - > add_action ( GUI : : CommonActions : : make_about_action ( " Terminal " _string , app_icon , window ) ) ;
2019-02-11 12:37:12 -02:00
2022-05-04 12:28:43 -03:00
window - > on_close_request = [ & ] ( ) - > GUI : : Window : : CloseRequestDecision {
2022-05-13 09:10:27 -03:00
if ( check_terminal_quit ( ) = = GUI : : MessageBox : : ExecResult : : OK )
2022-05-04 12:28:43 -03:00
return GUI : : Window : : CloseRequestDecision : : Close ;
return GUI : : Window : : CloseRequestDecision : : StayOpen ;
} ;
2022-11-17 11:23:09 -03:00
window - > on_input_preemption_change = [ & ] ( bool is_preempted ) {
terminal - > set_logical_focus ( ! is_preempted ) ;
} ;
2021-11-23 06:59:50 -03:00
TRY ( Core : : System : : unveil ( " /res " , " r " ) ) ;
TRY ( Core : : System : : unveil ( " /bin " , " r " ) ) ;
2022-05-04 12:28:43 -03:00
TRY ( Core : : System : : unveil ( " /proc " , " r " ) ) ;
2021-11-23 06:59:50 -03:00
TRY ( Core : : System : : unveil ( " /bin/Terminal " , " x " ) ) ;
2021-11-20 21:01:21 -03:00
TRY ( Core : : System : : unveil ( " /bin/TerminalSettings " , " x " ) ) ;
2021-11-23 06:59:50 -03:00
TRY ( Core : : System : : unveil ( " /bin/utmpupdate " , " x " ) ) ;
TRY ( Core : : System : : unveil ( " /etc/FileIconProvider.ini " , " r " ) ) ;
2022-09-06 03:04:06 -03:00
TRY ( Core : : System : : unveil ( " /tmp/session/%sid/portal/launch " , " rw " ) ) ;
TRY ( Core : : System : : unveil ( " /tmp/session/%sid/portal/config " , " rw " ) ) ;
2021-11-23 06:59:50 -03:00
TRY ( Core : : System : : unveil ( nullptr , nullptr ) ) ;
2020-01-21 08:12:15 -03:00
2023-01-11 13:20:17 -03:00
auto modified_state_check_timer = TRY ( Core : : Timer : : create_repeating ( 500 , [ & ] {
2022-05-04 12:28:43 -03:00
window - > set_modified ( tty_has_foreground_process ( ) | | shell_child_process_count ( ) > 0 ) ;
2023-01-11 13:20:17 -03:00
} ) ) ;
2022-05-04 12:28:43 -03:00
2022-05-04 17:36:07 -03:00
listener . on_confirm_close_changed = [ & ] ( bool confirm_close ) {
if ( confirm_close ) {
modified_state_check_timer - > start ( ) ;
} else {
modified_state_check_timer - > stop ( ) ;
window - > set_modified ( false ) ;
}
should_confirm_close = confirm_close ;
} ;
2021-06-12 06:59:00 -03:00
window - > show ( ) ;
2022-05-04 17:36:07 -03:00
if ( should_confirm_close )
modified_state_check_timer - > start ( ) ;
2020-09-06 11:14:46 -03:00
int result = app - > exec ( ) ;
2020-10-06 14:12:42 -03:00
dbgln ( " Exiting terminal, updating utmp " ) ;
2023-06-27 13:03:09 -03:00
TRY ( utmp_update ( ptsname , 0 , false ) ) ;
2020-09-06 11:14:46 -03:00
return result ;
2019-01-15 01:30:55 -02:00
}