2020-01-18 08:38:21 +00:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
2019-01-30 20:18:13 +00:00
# include <Kernel/KeyCode.h>
2019-09-02 05:28:52 +00:00
# include <LibCore/CArgsParser.h>
2019-06-07 09:48:03 +00:00
# include <LibCore/CUserInfo.h>
2019-07-28 08:18:49 +00:00
# include <LibDraw/PNGLoader.h>
2019-09-02 17:46:35 +00:00
# include <LibGUI/GAboutDialog.h>
2019-06-07 09:48:03 +00:00
# include <LibGUI/GAction.h>
2020-01-08 19:56:11 +00:00
# include <LibGUI/GActionGroup.h>
2019-02-11 13:56:23 +00:00
# include <LibGUI/GApplication.h>
2019-05-31 20:28:47 +00:00
# include <LibGUI/GBoxLayout.h>
2019-06-07 09:48:03 +00:00
# include <LibGUI/GFontDatabase.h>
2019-05-31 21:51:06 +00:00
# include <LibGUI/GGroupBox.h>
2019-02-11 14:37:12 +00:00
# include <LibGUI/GMenuBar.h>
2019-05-29 17:53:21 +00:00
# include <LibGUI/GRadioButton.h>
2019-06-07 09:48:03 +00:00
# include <LibGUI/GSlider.h>
# include <LibGUI/GWidget.h>
# include <LibGUI/GWindow.h>
2019-10-21 17:50:07 +00:00
# include <LibVT/TerminalWidget.h>
2019-06-07 09:48:03 +00:00
# include <assert.h>
# include <errno.h>
# include <fcntl.h>
# include <pwd.h>
2019-12-02 15:50:10 +00:00
# include <signal.h>
2019-06-07 09:48:03 +00:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/ioctl.h>
# include <sys/select.h>
# include <unistd.h>
2019-01-15 03:30:55 +00:00
2019-09-02 05:28:52 +00:00
static void run_command ( int ptm_fd , String command )
2019-01-15 05:30:19 +00:00
{
pid_t pid = fork ( ) ;
if ( pid = = 0 ) {
const char * tty_name = ptsname ( ptm_fd ) ;
if ( ! tty_name ) {
perror ( " ptsname " ) ;
exit ( 1 ) ;
}
close ( ptm_fd ) ;
int pts_fd = open ( tty_name , O_RDWR ) ;
2019-02-12 10:25:25 +00:00
if ( pts_fd < 0 ) {
perror ( " open " ) ;
2019-01-15 07:49:24 +00:00
exit ( 1 ) ;
}
2019-02-12 10:25:25 +00:00
// NOTE: It's okay if this fails.
2019-06-07 09:48:03 +00:00
( void ) ioctl ( 0 , TIOCNOTTY ) ;
2019-02-12 10:25:25 +00:00
2019-01-15 05:30:19 +00:00
close ( 0 ) ;
close ( 1 ) ;
close ( 2 ) ;
2019-02-12 10:25:25 +00:00
int rc = dup2 ( pts_fd , 0 ) ;
if ( rc < 0 ) {
perror ( " dup2 " ) ;
exit ( 1 ) ;
}
rc = dup2 ( pts_fd , 1 ) ;
if ( rc < 0 ) {
perror ( " dup2 " ) ;
exit ( 1 ) ;
}
rc = dup2 ( pts_fd , 2 ) ;
if ( rc < 0 ) {
perror ( " dup2 " ) ;
exit ( 1 ) ;
}
rc = close ( pts_fd ) ;
if ( rc < 0 ) {
perror ( " close " ) ;
exit ( 1 ) ;
}
2019-01-15 07:49:24 +00:00
rc = ioctl ( 0 , TIOCSCTTY ) ;
if ( rc < 0 ) {
perror ( " ioctl(TIOCSCTTY) " ) ;
exit ( 1 ) ;
}
2019-09-02 05:28:52 +00:00
const char * args [ 4 ] = { " /bin/Shell " , nullptr , nullptr , nullptr } ;
if ( ! command . is_empty ( ) ) {
args [ 1 ] = " -c " ;
args [ 2 ] = command . characters ( ) ;
}
2019-06-22 14:07:46 +00:00
const char * envs [ ] = { " TERM=xterm " , " PATH=/bin:/usr/bin:/usr/local/bin " , nullptr } ;
rc = execve ( " /bin/Shell " , const_cast < char * * > ( args ) , const_cast < char * * > ( envs ) ) ;
2019-01-15 05:30:19 +00:00
if ( rc < 0 ) {
perror ( " execve " ) ;
exit ( 1 ) ;
}
ASSERT_NOT_REACHED ( ) ;
}
}
2019-01-15 03:30:55 +00:00
2019-09-21 22:31:54 +00:00
RefPtr < GWindow > create_settings_window ( TerminalWidget & terminal , RefPtr < CConfigFile > config )
2019-05-31 20:28:47 +00:00
{
2019-09-21 16:34:06 +00:00
auto window = GWindow : : construct ( ) ;
2019-05-31 21:51:06 +00:00
window - > set_title ( " Terminal Settings " ) ;
window - > set_rect ( 50 , 50 , 200 , 140 ) ;
2019-09-21 15:05:35 +00:00
auto settings = GWidget : : construct ( ) ;
2019-05-31 21:51:06 +00:00
window - > set_main_widget ( settings ) ;
settings - > set_fill_with_background_color ( true ) ;
2019-12-24 19:57:54 +00:00
settings - > set_background_role ( ColorRole : : Button ) ;
2019-05-31 21:51:06 +00:00
settings - > set_layout ( make < GBoxLayout > ( Orientation : : Vertical ) ) ;
settings - > layout ( ) - > set_margins ( { 4 , 4 , 4 , 4 } ) ;
2019-09-21 14:13:04 +00:00
auto radio_container = GGroupBox : : construct ( " Bell Mode " , settings ) ;
2019-05-31 21:51:06 +00:00
radio_container - > set_layout ( make < GBoxLayout > ( Orientation : : Vertical ) ) ;
radio_container - > layout ( ) - > set_margins ( { 6 , 16 , 6 , 6 } ) ;
radio_container - > set_size_policy ( SizePolicy : : Fill , SizePolicy : : Fixed ) ;
2019-07-20 20:39:24 +00:00
radio_container - > set_preferred_size ( 100 , 70 ) ;
2019-05-31 21:51:06 +00:00
2019-09-21 16:58:03 +00:00
auto sysbell_radio = GRadioButton : : construct ( " Use (Audible) System Bell " , radio_container ) ;
auto visbell_radio = GRadioButton : : construct ( " Use (Visual) Terminal Bell " , radio_container ) ;
2019-05-31 21:51:06 +00:00
sysbell_radio - > set_checked ( terminal . should_beep ( ) ) ;
visbell_radio - > set_checked ( ! terminal . should_beep ( ) ) ;
2019-06-07 09:48:03 +00:00
sysbell_radio - > on_checked = [ & terminal ] ( const bool checked ) {
terminal . set_should_beep ( checked ) ;
} ;
2019-05-31 20:28:47 +00:00
2019-09-21 14:13:04 +00:00
auto slider_container = GGroupBox : : construct ( " Background Opacity " , settings ) ;
2019-05-31 21:51:06 +00:00
slider_container - > set_layout ( make < GBoxLayout > ( Orientation : : Vertical ) ) ;
slider_container - > layout ( ) - > set_margins ( { 6 , 16 , 6 , 6 } ) ;
slider_container - > set_size_policy ( SizePolicy : : Fill , SizePolicy : : Fixed ) ;
2019-07-20 20:39:24 +00:00
slider_container - > set_preferred_size ( 100 , 50 ) ;
2019-09-21 14:33:53 +00:00
auto slider = GSlider : : construct ( Orientation : : Horizontal , slider_container ) ;
2019-05-31 20:28:47 +00:00
2019-06-07 09:48:03 +00:00
slider - > on_value_changed = [ & terminal , & config ] ( int value ) {
2019-06-28 19:41:13 +00:00
terminal . set_opacity ( value ) ;
2019-06-07 09:48:03 +00:00
} ;
2019-05-31 20:28:47 +00:00
2019-06-28 19:41:13 +00:00
slider - > set_range ( 0 , 255 ) ;
slider - > set_value ( terminal . opacity ( ) ) ;
2019-05-31 20:28:47 +00:00
2019-05-31 21:51:06 +00:00
return window ;
2019-05-31 20:28:47 +00:00
}
2019-02-11 13:56:23 +00:00
int main ( int argc , char * * argv )
2019-01-16 11:57:07 +00:00
{
2020-01-17 10:12:06 +00:00
if ( pledge ( " stdio tty rpath accept cpath wpath shared_buffer proc exec unix fattr " , nullptr ) < 0 ) {
2020-01-11 20:07:18 +00:00
perror ( " pledge " ) ;
return 1 ;
}
2019-12-02 15:50:10 +00:00
struct sigaction act ;
memset ( & act , 0 , sizeof ( act ) ) ;
act . sa_flags = SA_NOCLDWAIT ;
act . sa_handler = SIG_IGN ;
int rc = sigaction ( SIGCHLD , & act , nullptr ) ;
if ( rc < 0 ) {
perror ( " sigaction " ) ;
return 1 ;
}
2019-02-11 13:56:23 +00:00
GApplication app ( argc , argv ) ;
2020-01-17 10:12:06 +00:00
if ( pledge ( " stdio tty rpath accept cpath wpath shared_buffer proc exec " , nullptr ) < 0 ) {
2020-01-11 20:07:18 +00:00
perror ( " pledge " ) ;
return 1 ;
}
2019-09-02 05:28:52 +00:00
CArgsParser args_parser ( " Terminal " ) ;
args_parser . add_arg ( " e " , " execute " , " Execute this command inside the terminal. " ) ;
CArgsParserResult args = args_parser . parse ( argc , argv ) ;
2019-08-03 06:32:07 +00:00
if ( chdir ( get_current_user_home_path ( ) . characters ( ) ) < 0 )
perror ( " chdir " ) ;
2019-05-25 23:58:22 +00:00
2019-11-13 13:04:31 +00:00
int ptm_fd = open ( " /dev/ptmx " , O_RDWR | O_CLOEXEC ) ;
2019-01-16 12:36:10 +00:00
if ( ptm_fd < 0 ) {
perror ( " open(ptmx) " ) ;
return 1 ;
}
2019-01-15 05:30:19 +00:00
2019-09-02 05:28:52 +00:00
run_command ( ptm_fd , args . get ( " e " ) ) ;
2019-01-15 05:30:19 +00:00
2019-09-21 16:34:06 +00:00
auto window = GWindow : : construct ( ) ;
2019-05-16 18:13:41 +00:00
window - > set_title ( " Terminal " ) ;
2019-04-10 12:29:47 +00:00
window - > set_background_color ( Color : : Black ) ;
2019-03-17 03:23:54 +00:00
window - > set_double_buffering_enabled ( false ) ;
2019-01-15 03:30:55 +00:00
2019-06-21 16:37:47 +00:00
RefPtr < CConfigFile > config = CConfigFile : : get_for_app ( " Terminal " ) ;
2019-10-21 18:28:30 +00:00
auto terminal = TerminalWidget : : construct ( ptm_fd , true , config ) ;
2019-10-22 19:57:53 +00:00
terminal - > on_command_exit = [ & ] {
app . quit ( 0 ) ;
} ;
2019-10-21 20:07:59 +00:00
terminal - > on_title_change = [ & ] ( auto & title ) {
window - > set_title ( title ) ;
} ;
2019-08-06 09:42:13 +00:00
window - > set_main_widget ( terminal ) ;
2019-02-11 05:09:54 +00:00
window - > move_to ( 300 , 300 ) ;
2019-08-06 09:42:13 +00:00
terminal - > apply_size_increments_to_window ( * window ) ;
2019-02-10 13:28:39 +00:00
window - > show ( ) ;
2019-07-28 08:18:49 +00:00
window - > set_icon ( load_png ( " /res/icons/16x16/app-terminal.png " ) ) ;
2019-08-06 09:42:13 +00:00
terminal - > set_should_beep ( config - > read_bool_entry ( " Window " , " AudibleBeep " , false ) ) ;
2019-02-10 13:28:39 +00:00
2019-09-21 22:31:54 +00:00
RefPtr < GWindow > settings_window ;
2019-05-31 19:43:58 +00:00
2019-05-25 23:43:15 +00:00
auto new_opacity = config - > read_num_entry ( " Window " , " Opacity " , 255 ) ;
2019-08-06 09:42:13 +00:00
terminal - > set_opacity ( new_opacity ) ;
2019-08-15 13:19:54 +00:00
window - > set_has_alpha_channel ( new_opacity < 255 ) ;
2019-05-25 23:43:15 +00:00
2019-02-11 14:37:12 +00:00
auto menubar = make < GMenuBar > ( ) ;
2019-12-09 20:05:28 +00:00
auto app_menu = GMenu : : construct ( " Terminal " ) ;
2019-11-20 20:19:17 +00:00
app_menu - > add_action ( GAction : : create ( " Open new terminal " , { Mod_Ctrl | Mod_Shift , Key_N } , GraphicsBitmap : : load_from_file ( " /res/icons/16x16/app-terminal.png " ) , [ & ] ( auto & ) {
if ( ! fork ( ) ) {
execl ( " /bin/Terminal " , " Terminal " , nullptr ) ;
exit ( 1 ) ;
}
} ) ) ;
2019-08-29 16:19:40 +00:00
app_menu - > add_action ( GAction : : create ( " Settings... " , load_png ( " /res/icons/gear16.png " ) ,
2019-09-21 16:34:06 +00:00
[ & ] ( const GAction & ) {
2019-09-21 16:58:03 +00:00
if ( ! settings_window ) {
2019-09-21 16:34:06 +00:00
settings_window = create_settings_window ( * terminal , config ) ;
2019-09-21 16:58:03 +00:00
settings_window - > on_close_request = [ & ] {
settings_window = nullptr ;
return GWindow : : CloseRequestDecision : : Close ;
} ;
}
2019-06-07 09:48:03 +00:00
settings_window - > show ( ) ;
settings_window - > move_to_front ( ) ;
} ) ) ;
2019-08-29 16:19:40 +00:00
app_menu - > add_separator ( ) ;
2019-09-14 20:10:44 +00:00
app_menu - > add_action ( GCommonActions : : make_quit_action ( [ ] ( auto & ) {
2019-02-12 13:09:48 +00:00
dbgprintf ( " Terminal: Quit menu activated! \n " ) ;
2019-02-17 08:58:35 +00:00
GApplication : : the ( ) . quit ( 0 ) ;
2019-02-12 13:09:48 +00:00
} ) ) ;
2019-02-11 14:37:12 +00:00
menubar - > add_menu ( move ( app_menu ) ) ;
2019-12-09 20:05:28 +00:00
auto edit_menu = GMenu : : construct ( " Edit " ) ;
2019-11-20 20:33:23 +00:00
edit_menu - > add_action ( terminal - > copy_action ( ) ) ;
edit_menu - > add_action ( terminal - > paste_action ( ) ) ;
menubar - > add_menu ( move ( edit_menu ) ) ;
2020-01-08 19:56:11 +00:00
GActionGroup font_action_group ;
font_action_group . set_exclusive ( true ) ;
2019-12-09 20:05:28 +00:00
auto font_menu = GMenu : : construct ( " Font " ) ;
2019-06-07 09:48:03 +00:00
GFontDatabase : : the ( ) . for_each_fixed_width_font ( [ & ] ( const StringView & font_name ) {
2020-01-08 19:56:11 +00:00
auto action = GAction : : create ( font_name , [ & ] ( GAction & action ) {
action . set_checked ( true ) ;
2019-08-06 09:42:13 +00:00
terminal - > set_font ( GFontDatabase : : the ( ) . get_by_name ( action . text ( ) ) ) ;
2019-05-25 23:43:15 +00:00
auto metadata = GFontDatabase : : the ( ) . get_metadata_by_name ( action . text ( ) ) ;
2019-07-24 08:25:43 +00:00
ASSERT ( metadata . has_value ( ) ) ;
config - > write_entry ( " Text " , " Font " , metadata . value ( ) . path ) ;
2019-05-25 23:58:22 +00:00
config - > sync ( ) ;
2019-08-06 09:42:13 +00:00
terminal - > force_repaint ( ) ;
2020-01-08 19:56:11 +00:00
} ) ;
font_action_group . add_action ( * action ) ;
action - > set_checkable ( true ) ;
if ( terminal - > font ( ) . name ( ) = = font_name )
action - > set_checked ( true ) ;
font_menu - > add_action ( * action ) ;
2019-02-12 13:35:33 +00:00
} ) ;
2019-02-12 07:39:19 +00:00
menubar - > add_menu ( move ( font_menu ) ) ;
2019-12-09 20:05:28 +00:00
auto help_menu = GMenu : : construct ( " Help " ) ;
2019-09-02 17:46:35 +00:00
help_menu - > add_action ( GAction : : create ( " About " , [ & ] ( const GAction & ) {
GAboutDialog : : show ( " Terminal " , load_png ( " /res/icons/32x32/app-terminal.png " ) , window ) ;
2019-02-12 13:09:48 +00:00
} ) ) ;
2019-02-11 23:52:19 +00:00
menubar - > add_menu ( move ( help_menu ) ) ;
2019-02-11 14:37:12 +00:00
app . set_menubar ( move ( menubar ) ) ;
2020-01-21 11:12:15 +00:00
if ( unveil ( " /res " , " r " ) < 0 ) {
perror ( " unveil " ) ;
return 1 ;
}
if ( unveil ( " /bin/Terminal " , " x " ) < 0 ) {
perror ( " unveil " ) ;
return 1 ;
}
2020-01-21 12:08:22 +00:00
if ( unveil ( config - > file_name ( ) . characters ( ) , " rwc " ) ) {
perror ( " unveil " ) ;
return 1 ;
}
2020-01-21 11:12:15 +00:00
unveil ( nullptr , nullptr ) ;
2019-05-25 23:43:15 +00:00
config - > sync ( ) ;
2019-02-11 13:56:23 +00:00
return app . exec ( ) ;
2019-01-15 03:30:55 +00:00
}