Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
lixk28 committed Jun 5, 2023
1 parent 912c41e commit 87cd5bb
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 125 deletions.
3 changes: 3 additions & 0 deletions Userland/Applications/Terminal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ serenity_component(

set(SOURCES
main.cpp
MainWidget.cpp
TerminalChangeListener.cpp
TerminalUtilities.cpp
)

serenity_app(Terminal ICON app-terminal)
Expand Down
29 changes: 29 additions & 0 deletions Userland/Applications/Terminal/MainWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,32 @@
*/

#include "MainWidget.h"
#include "TerminalUtilities.h"
#include <LibCore/System.h>
#include <LibConfig/Client.h>
#include <pty.h>

MainWidget::MainWidget()
{
on_tab_count_change = [this](size_t tab_count) {
set_bar_visible(tab_count > 1);
};
}

void MainWidget::keydown_event(GUI::KeyEvent& event)
{
if (event.ctrl() && event.shift() && event.key() == Key_T) {
auto new_tab = MUST(create_and_setup_terminal_widget());
MUST(add_tab(new_tab, "New Tab"_short_string));
set_active_widget(new_tab);
}

GUI::TabWidget::keydown_event(event);
}


NonnullRefPtr<VT::TerminalWidget> MainWidget::current_tab() const
{
// Note: This cast is OK because we only add VT::TerminalWidget
return *verify_cast<VT::TerminalWidget>(active_widget());
}
17 changes: 12 additions & 5 deletions Userland/Applications/Terminal/MainWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@
#pragma once

#include <LibVT/TerminalWidget.h>
#include <LibGUI/Widget.h>
#include <LibGUI/TabWidget.h>
#include <LibGUI/Shortcut.h>

class MainWidget final : public GUI::TabWidget {
C_OBJECT(MainWidget);

class MainWidget : public GUI::Widget {
public:
// ErrorOr<void> add_tab(NonnullRefPtr<VT::TerminalWidget> const& tab, String title);
// ErrorOr<void> remove_current_tab();

NonnullRefPtr<VT::TerminalWidget> create_new_tab();
void last_active_tab() {}
void remove_current_tab() {}
NonnullRefPtr<VT::TerminalWidget> current_tab() const;
// NonnullRefPtr<VT::TerminalWidget> last_active_tab() const;

virtual void keydown_event(GUI::KeyEvent& event) override;

private:
RefPtr<GUI::TabWidget> m_terminal_tabs;
MainWidget();
};
59 changes: 59 additions & 0 deletions Userland/Applications/Terminal/TerminalChangeListener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include "TerminalChangeListener.h"
#include <LibGUI/Window.h>

void TerminalChangeListener::config_bool_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, bool value)
{
VERIFY(domain == "Terminal");

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);
} else if (group == "Cursor" && key == "Blinking") {
m_parent_terminal.set_cursor_blinking(value);
}
}

void TerminalChangeListener::config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
{
VERIFY(domain == "Terminal");

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);
} 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);
m_parent_terminal.apply_size_increments_to_window(*m_parent_terminal.window());
m_parent_terminal.window()->resize(m_parent_terminal.size());
} 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);
}
}

void TerminalChangeListener::config_i32_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, i32 value)
{
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);
}
}
28 changes: 28 additions & 0 deletions Userland/Applications/Terminal/TerminalChangeListener.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <LibGfx/Font/FontDatabase.h>
#include <LibConfig/Listener.h>
#include <LibVT/TerminalWidget.h>

class TerminalChangeListener : public Config::Listener {
public:
TerminalChangeListener(VT::TerminalWidget& parent_terminal)
: m_parent_terminal(parent_terminal)
{
}

virtual void config_bool_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, bool value) override;
virtual void config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value) override;
virtual void config_i32_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, i32 value) override;

Function<void(bool)> on_confirm_close_changed;

private:
VT::TerminalWidget& m_parent_terminal;
};
95 changes: 95 additions & 0 deletions Userland/Applications/Terminal/TerminalUtilities.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include "TerminalUtilities.h"
#include <AK/Format.h>
#include <AK/FixedArray.h>
#include <AK/Try.h>
#include <AK/Vector.h>
#include <LibConfig/Client.h>
#include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h>
#include <assert.h>
#include <errno.h>
#include <pty.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>

void utmp_update(DeprecatedString const& tty, pid_t pid, bool create)
{
int utmpupdate_pid = fork();
if (utmpupdate_pid < 0) {
perror("fork");
return;
}
if (utmpupdate_pid == 0) {
// Be careful here! Because fork() only clones one thread it's
// possible that we deadlock on anything involving a mutex,
// including the heap! So resort to low-level APIs
char pid_str[32];
snprintf(pid_str, sizeof(pid_str), "%d", pid);
execl("/bin/utmpupdate", "/bin/utmpupdate", "-f", "Terminal", "-p", pid_str, (create ? "-c" : "-d"), tty.characters(), nullptr);
} else {
wait_again:
int status = 0;
if (waitpid(utmpupdate_pid, &status, 0) < 0) {
int err = errno;
if (err == EINTR)
goto wait_again;
perror("waitpid");
return;
}
if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
dbgln("Terminal: utmpupdate exited with status {}", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
dbgln("Terminal: utmpupdate exited due to unhandled signal {}", WTERMSIG(status));
}
}

ErrorOr<void> run_command(DeprecatedString command, bool keep_open)
{
DeprecatedString shell = "/bin/Shell";
auto* pw = getpwuid(getuid());
if (pw && pw->pw_shell) {
shell = pw->pw_shell;
}
endpwent();

Vector<StringView> arguments;
arguments.append(shell);
if (!command.is_empty()) {
if (keep_open)
arguments.append("--keep-open"sv);
arguments.append("-c"sv);
arguments.append(command);
}
auto env = TRY(FixedArray<StringView>::create({ "TERM=xterm"sv, "PAGER=more"sv, "PATH="sv DEFAULT_PATH_SV }));
TRY(Core::System::exec(shell, arguments, Core::System::SearchInPath::No, env.span()));
VERIFY_NOT_REACHED();
}

ErrorOr<NonnullRefPtr<VT::TerminalWidget>> create_and_setup_terminal_widget()
{
int ptm_fd;
pid_t shell_pid = forkpty(&ptm_fd, nullptr, nullptr, nullptr);
if (shell_pid == 0) {
MUST(run_command(Config::read_string("Terminal"sv, "Startup"sv, "Command"sv, ""sv), false));
VERIFY_NOT_REACHED();
}

auto ptsname = TRY(Core::System::ptsname(ptm_fd));
utmp_update(ptsname, shell_pid, true);

auto terminal = TRY(VT::TerminalWidget::try_create(ptm_fd, true));

return terminal;
}
19 changes: 19 additions & 0 deletions Userland/Applications/Terminal/TerminalUtilities.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Error.h>
#include <AK/DeprecatedString.h>
#include <AK/StringView.h>
#include <LibVT/TerminalWidget.h>
#include <sys/types.h>

void utmp_update(DeprecatedString const& tty, pid_t pid, bool create);

ErrorOr<void> run_command(DeprecatedString command, bool keep_open);

ErrorOr<NonnullRefPtr<VT::TerminalWidget>> create_and_setup_terminal_widget();
Loading

0 comments on commit 87cd5bb

Please sign in to comment.