mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 14:17:36 +00:00
Kernel: Move TTY-related code to a new subdirectory under Devices
The TTY subsystem is represented with unix devices, so it should be under the Devices directory like the Audio, Storage, GPU and HID subsystems.
This commit is contained in:
parent
c99c065a40
commit
b55199c227
31 changed files with 42 additions and 42 deletions
96
Kernel/Devices/TTY/ConsoleManagement.cpp
Normal file
96
Kernel/Devices/TTY/ConsoleManagement.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Singleton.h>
|
||||
#include <Kernel/Boot/CommandLine.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Devices/DeviceManagement.h>
|
||||
#include <Kernel/Devices/GPU/Management.h>
|
||||
#include <Kernel/Devices/TTY/ConsoleManagement.h>
|
||||
#include <Kernel/Library/Panic.h>
|
||||
#include <Kernel/Sections.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static Singleton<ConsoleManagement> s_the;
|
||||
|
||||
void ConsoleManagement::resolution_was_changed()
|
||||
{
|
||||
for (auto& console : m_consoles) {
|
||||
console->refresh_after_resolution_change();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConsoleManagement::is_initialized()
|
||||
{
|
||||
if (!s_the.is_initialized())
|
||||
return false;
|
||||
if (s_the->m_consoles.is_empty())
|
||||
return false;
|
||||
if (!s_the->m_active_console)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ConsoleManagement& ConsoleManagement::the()
|
||||
{
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT ConsoleManagement::ConsoleManagement()
|
||||
{
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT void ConsoleManagement::initialize()
|
||||
{
|
||||
for (size_t index = 0; index < s_max_virtual_consoles; index++) {
|
||||
// FIXME: Better determine the debug TTY we chose...
|
||||
if (index == 1) {
|
||||
VERIFY(DeviceManagement::the().is_console_device_attached());
|
||||
m_consoles.append(VirtualConsole::create_with_preset_log(index, DeviceManagement::the().console_device().logbuffer()));
|
||||
continue;
|
||||
}
|
||||
m_consoles.append(VirtualConsole::create(index));
|
||||
}
|
||||
// Note: By default the active console is the first one.
|
||||
auto tty_number = kernel_command_line().switch_to_tty();
|
||||
if (tty_number > m_consoles.size()) {
|
||||
PANIC("Switch to tty value is invalid: {} ", tty_number);
|
||||
}
|
||||
m_active_console = m_consoles[tty_number];
|
||||
SpinlockLocker lock(m_lock);
|
||||
m_active_console->set_active(true);
|
||||
if (!m_active_console->is_graphical())
|
||||
m_active_console->clear();
|
||||
}
|
||||
|
||||
void ConsoleManagement::switch_to(unsigned index)
|
||||
{
|
||||
SpinlockLocker lock(m_lock);
|
||||
VERIFY(m_active_console);
|
||||
VERIFY(index < m_consoles.size());
|
||||
if (m_active_console->index() == index)
|
||||
return;
|
||||
|
||||
bool was_graphical = m_active_console->is_graphical();
|
||||
m_active_console->set_active(false);
|
||||
m_active_console = m_consoles[index];
|
||||
dbgln_if(VIRTUAL_CONSOLE_DEBUG, "Console: Switch to {}", index);
|
||||
|
||||
// Before setting current console to be "active", switch between graphical mode to "textual" mode
|
||||
// if needed. This will ensure we clear the screen and also that WindowServer won't print anything
|
||||
// in between.
|
||||
if (m_active_console->is_graphical() && !was_graphical) {
|
||||
m_active_console->set_active(true);
|
||||
GraphicsManagement::the().activate_graphical_mode();
|
||||
return;
|
||||
} else if (!m_active_console->is_graphical() && was_graphical) {
|
||||
GraphicsManagement::the().deactivate_graphical_mode();
|
||||
}
|
||||
m_active_console->set_active(true);
|
||||
}
|
||||
|
||||
}
|
45
Kernel/Devices/TTY/ConsoleManagement.h
Normal file
45
Kernel/Devices/TTY/ConsoleManagement.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Devices/TTY/VirtualConsole.h>
|
||||
#include <Kernel/Library/NonnullLockRefPtr.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ConsoleManagement {
|
||||
friend class VirtualConsole;
|
||||
|
||||
public:
|
||||
ConsoleManagement();
|
||||
|
||||
static constexpr size_t s_max_virtual_consoles = 6;
|
||||
|
||||
static bool is_initialized();
|
||||
static ConsoleManagement& the();
|
||||
|
||||
void switch_to(unsigned);
|
||||
void initialize();
|
||||
|
||||
void resolution_was_changed();
|
||||
|
||||
void switch_to_debug() { switch_to(1); }
|
||||
|
||||
NonnullLockRefPtr<VirtualConsole> first_tty() const { return m_consoles[0]; }
|
||||
NonnullLockRefPtr<VirtualConsole> debug_tty() const { return m_consoles[1]; }
|
||||
|
||||
RecursiveSpinlock<LockRank::None>& tty_write_lock() { return m_tty_write_lock; }
|
||||
|
||||
private:
|
||||
Vector<NonnullLockRefPtr<VirtualConsole>, s_max_virtual_consoles> m_consoles;
|
||||
VirtualConsole* m_active_console { nullptr };
|
||||
Spinlock<LockRank::None> m_lock {};
|
||||
RecursiveSpinlock<LockRank::None> m_tty_write_lock {};
|
||||
};
|
||||
|
||||
};
|
134
Kernel/Devices/TTY/MasterPTY.cpp
Normal file
134
Kernel/Devices/TTY/MasterPTY.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/API/Ioctl.h>
|
||||
#include <Kernel/API/POSIX/errno.h>
|
||||
#include <Kernel/API/POSIX/signal_numbers.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Devices/TTY/MasterPTY.h>
|
||||
#include <Kernel/Devices/TTY/PTYMultiplexer.h>
|
||||
#include <Kernel/Devices/TTY/SlavePTY.h>
|
||||
#include <Kernel/Interrupts/InterruptDisabler.h>
|
||||
#include <Kernel/Tasks/Process.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
ErrorOr<NonnullLockRefPtr<MasterPTY>> MasterPTY::try_create(unsigned int index)
|
||||
{
|
||||
auto buffer = TRY(DoubleBuffer::try_create("MasterPTY: Buffer"sv));
|
||||
auto master_pty = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) MasterPTY(index, move(buffer))));
|
||||
auto credentials = Process::current().credentials();
|
||||
auto slave_pty = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) SlavePTY(*master_pty, credentials->uid(), credentials->gid(), index)));
|
||||
master_pty->m_slave = slave_pty;
|
||||
TRY(master_pty->after_inserting());
|
||||
TRY(slave_pty->after_inserting());
|
||||
return master_pty;
|
||||
}
|
||||
|
||||
MasterPTY::MasterPTY(unsigned index, NonnullOwnPtr<DoubleBuffer> buffer)
|
||||
: CharacterDevice(200, index)
|
||||
, m_index(index)
|
||||
, m_buffer(move(buffer))
|
||||
{
|
||||
m_buffer->set_unblock_callback([this]() {
|
||||
if (m_slave)
|
||||
evaluate_block_conditions();
|
||||
});
|
||||
}
|
||||
|
||||
MasterPTY::~MasterPTY()
|
||||
{
|
||||
dbgln_if(MASTERPTY_DEBUG, "~MasterPTY({})", m_index);
|
||||
PTYMultiplexer::the().notify_master_destroyed({}, m_index);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> MasterPTY::read(OpenFileDescription&, u64, UserOrKernelBuffer& buffer, size_t size)
|
||||
{
|
||||
if (!m_slave && m_buffer->is_empty())
|
||||
return 0;
|
||||
return m_buffer->read(buffer, size);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> MasterPTY::write(OpenFileDescription&, u64, UserOrKernelBuffer const& buffer, size_t size)
|
||||
{
|
||||
if (!m_slave)
|
||||
return EIO;
|
||||
m_slave->on_master_write(buffer, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool MasterPTY::can_read(OpenFileDescription const&, u64) const
|
||||
{
|
||||
if (!m_slave)
|
||||
return true;
|
||||
return !m_buffer->is_empty();
|
||||
}
|
||||
|
||||
bool MasterPTY::can_write(OpenFileDescription const&, u64) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void MasterPTY::notify_slave_closed(Badge<SlavePTY>)
|
||||
{
|
||||
dbgln_if(MASTERPTY_DEBUG, "MasterPTY({}): slave closed, my retains: {}, slave retains: {}", m_index, ref_count(), m_slave->ref_count());
|
||||
// +1 ref for my MasterPTY::m_slave
|
||||
// +1 ref for OpenFileDescription::m_device
|
||||
if (m_slave->ref_count() == 2)
|
||||
m_slave = nullptr;
|
||||
}
|
||||
|
||||
ErrorOr<size_t> MasterPTY::on_slave_write(UserOrKernelBuffer const& data, size_t size)
|
||||
{
|
||||
if (m_closed)
|
||||
return EIO;
|
||||
return m_buffer->write(data, size);
|
||||
}
|
||||
|
||||
bool MasterPTY::can_write_from_slave() const
|
||||
{
|
||||
if (m_closed)
|
||||
return true;
|
||||
return m_buffer->space_for_writing() >= 2;
|
||||
}
|
||||
|
||||
ErrorOr<void> MasterPTY::close()
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
// After the closing OpenFileDescription dies, slave is the only thing keeping me alive.
|
||||
// From this point, let's consider ourselves closed.
|
||||
m_closed = true;
|
||||
|
||||
if (m_slave)
|
||||
m_slave->hang_up();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> MasterPTY::ioctl(OpenFileDescription& description, unsigned request, Userspace<void*> arg)
|
||||
{
|
||||
TRY(Process::current().require_promise(Pledge::tty));
|
||||
if (!m_slave)
|
||||
return EIO;
|
||||
switch (request) {
|
||||
case TIOCGPTN: {
|
||||
int master_pty_index = index();
|
||||
return copy_to_user(static_ptr_cast<int*>(arg), &master_pty_index);
|
||||
}
|
||||
case TIOCSWINSZ:
|
||||
case TIOCGPGRP:
|
||||
return m_slave->ioctl(description, request, arg);
|
||||
default:
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<KString>> MasterPTY::pseudo_path(OpenFileDescription const&) const
|
||||
{
|
||||
return KString::formatted("ptm:{}", m_index);
|
||||
}
|
||||
|
||||
}
|
52
Kernel/Devices/TTY/MasterPTY.h
Normal file
52
Kernel/Devices/TTY/MasterPTY.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <Kernel/Devices/CharacterDevice.h>
|
||||
#include <Kernel/Library/DoubleBuffer.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class SlavePTY;
|
||||
|
||||
class MasterPTY final : public CharacterDevice {
|
||||
public:
|
||||
static ErrorOr<NonnullLockRefPtr<MasterPTY>> try_create(unsigned index);
|
||||
virtual ~MasterPTY() override;
|
||||
|
||||
unsigned index() const { return m_index; }
|
||||
ErrorOr<size_t> on_slave_write(UserOrKernelBuffer const&, size_t);
|
||||
bool can_write_from_slave() const;
|
||||
void notify_slave_closed(Badge<SlavePTY>);
|
||||
bool is_closed() const { return m_closed; }
|
||||
|
||||
virtual ErrorOr<NonnullOwnPtr<KString>> pseudo_path(OpenFileDescription const&) const override;
|
||||
|
||||
private:
|
||||
explicit MasterPTY(unsigned index, NonnullOwnPtr<DoubleBuffer> buffer);
|
||||
|
||||
// ^Device
|
||||
virtual bool is_openable_by_jailed_processes() const override { return true; }
|
||||
|
||||
// ^CharacterDevice
|
||||
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override;
|
||||
virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override;
|
||||
virtual bool can_read(OpenFileDescription const&, u64) const override;
|
||||
virtual bool can_write(OpenFileDescription const&, u64) const override;
|
||||
virtual ErrorOr<void> close() override;
|
||||
virtual bool is_master_pty() const override { return true; }
|
||||
virtual ErrorOr<void> ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) override;
|
||||
virtual StringView class_name() const override { return "MasterPTY"sv; }
|
||||
|
||||
LockRefPtr<SlavePTY> m_slave;
|
||||
unsigned m_index;
|
||||
bool m_closed { false };
|
||||
NonnullOwnPtr<DoubleBuffer> m_buffer;
|
||||
};
|
||||
|
||||
}
|
65
Kernel/Devices/TTY/PTYMultiplexer.cpp
Normal file
65
Kernel/Devices/TTY/PTYMultiplexer.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Singleton.h>
|
||||
#include <Kernel/API/POSIX/errno.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Devices/TTY/MasterPTY.h>
|
||||
#include <Kernel/Devices/TTY/PTYMultiplexer.h>
|
||||
#include <Kernel/FileSystem/OpenFileDescription.h>
|
||||
#include <Kernel/Sections.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static Singleton<PTYMultiplexer> s_the;
|
||||
|
||||
PTYMultiplexer& PTYMultiplexer::the()
|
||||
{
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT PTYMultiplexer::PTYMultiplexer()
|
||||
: CharacterDevice(5, 2)
|
||||
{
|
||||
m_freelist.with([&](auto& freelist) {
|
||||
freelist.ensure_capacity(max_pty_pairs);
|
||||
for (int i = max_pty_pairs; i > 0; --i)
|
||||
freelist.unchecked_append(i - 1);
|
||||
});
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT PTYMultiplexer::~PTYMultiplexer() = default;
|
||||
|
||||
UNMAP_AFTER_INIT void PTYMultiplexer::initialize()
|
||||
{
|
||||
MUST(the().after_inserting());
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<OpenFileDescription>> PTYMultiplexer::open(int options)
|
||||
{
|
||||
return m_freelist.with([&](auto& freelist) -> ErrorOr<NonnullRefPtr<OpenFileDescription>> {
|
||||
if (freelist.is_empty())
|
||||
return EBUSY;
|
||||
|
||||
auto master_index = freelist.take_last();
|
||||
auto master = TRY(MasterPTY::try_create(master_index));
|
||||
dbgln_if(PTMX_DEBUG, "PTYMultiplexer::open: Vending master {}", master->index());
|
||||
auto description = TRY(OpenFileDescription::try_create(*master));
|
||||
description->set_rw_mode(options);
|
||||
description->set_file_flags(options);
|
||||
return description;
|
||||
});
|
||||
}
|
||||
|
||||
void PTYMultiplexer::notify_master_destroyed(Badge<MasterPTY>, unsigned index)
|
||||
{
|
||||
m_freelist.with([&](auto& freelist) {
|
||||
freelist.append(index);
|
||||
dbgln_if(PTMX_DEBUG, "PTYMultiplexer: {} added to freelist", index);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
41
Kernel/Devices/TTY/PTYMultiplexer.h
Normal file
41
Kernel/Devices/TTY/PTYMultiplexer.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <Kernel/Devices/CharacterDevice.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class MasterPTY;
|
||||
|
||||
class PTYMultiplexer final : public CharacterDevice {
|
||||
public:
|
||||
PTYMultiplexer();
|
||||
virtual ~PTYMultiplexer() override;
|
||||
|
||||
static void initialize();
|
||||
static PTYMultiplexer& the();
|
||||
|
||||
// ^CharacterDevice
|
||||
virtual ErrorOr<NonnullRefPtr<OpenFileDescription>> open(int options) override;
|
||||
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override { return 0; }
|
||||
virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override { return 0; }
|
||||
virtual bool can_read(OpenFileDescription const&, u64) const override { return true; }
|
||||
virtual bool can_write(OpenFileDescription const&, u64) const override { return true; }
|
||||
|
||||
void notify_master_destroyed(Badge<MasterPTY>, unsigned index);
|
||||
|
||||
private:
|
||||
// ^CharacterDevice
|
||||
virtual StringView class_name() const override { return "PTYMultiplexer"sv; }
|
||||
|
||||
static constexpr size_t max_pty_pairs = 64;
|
||||
SpinlockProtected<Vector<unsigned, max_pty_pairs>, LockRank::None> m_freelist {};
|
||||
};
|
||||
|
||||
}
|
114
Kernel/Devices/TTY/SlavePTY.cpp
Normal file
114
Kernel/Devices/TTY/SlavePTY.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Singleton.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Devices/TTY/MasterPTY.h>
|
||||
#include <Kernel/Devices/TTY/SlavePTY.h>
|
||||
#include <Kernel/Tasks/Process.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static Singleton<SpinlockProtected<SlavePTY::List, LockRank::None>> s_all_instances;
|
||||
|
||||
SpinlockProtected<SlavePTY::List, LockRank::None>& SlavePTY::all_instances()
|
||||
{
|
||||
return s_all_instances;
|
||||
}
|
||||
|
||||
bool SlavePTY::unref() const
|
||||
{
|
||||
bool did_hit_zero = SlavePTY::all_instances().with([&](auto&) {
|
||||
if (deref_base())
|
||||
return false;
|
||||
m_list_node.remove();
|
||||
const_cast<SlavePTY&>(*this).revoke_weak_ptrs();
|
||||
return true;
|
||||
});
|
||||
if (did_hit_zero) {
|
||||
const_cast<SlavePTY&>(*this).will_be_destroyed();
|
||||
delete this;
|
||||
}
|
||||
return did_hit_zero;
|
||||
}
|
||||
|
||||
SlavePTY::SlavePTY(NonnullRefPtr<MasterPTY> master, UserID uid, GroupID gid, unsigned index)
|
||||
: TTY(201, index)
|
||||
, m_master(move(master))
|
||||
, m_index(index)
|
||||
, m_uid(uid)
|
||||
, m_gid(gid)
|
||||
{
|
||||
set_size(80, 25);
|
||||
SlavePTY::all_instances().with([&](auto& list) { list.append(*this); });
|
||||
}
|
||||
|
||||
SlavePTY::~SlavePTY()
|
||||
{
|
||||
dbgln_if(SLAVEPTY_DEBUG, "~SlavePTY({})", m_index);
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<KString>> SlavePTY::pseudo_name() const
|
||||
{
|
||||
return KString::formatted("pts:{}", m_index);
|
||||
}
|
||||
|
||||
void SlavePTY::echo(u8 ch)
|
||||
{
|
||||
if (should_echo_input()) {
|
||||
auto buffer = UserOrKernelBuffer::for_kernel_buffer(&ch);
|
||||
[[maybe_unused]] auto result = m_master->on_slave_write(buffer, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void SlavePTY::on_master_write(UserOrKernelBuffer const& buffer, size_t size)
|
||||
{
|
||||
auto result = buffer.read_buffered<128>(size, [&](ReadonlyBytes data) {
|
||||
for (const auto& byte : data)
|
||||
emit(byte, false);
|
||||
return data.size();
|
||||
});
|
||||
if (!result.is_error())
|
||||
evaluate_block_conditions();
|
||||
}
|
||||
|
||||
ErrorOr<size_t> SlavePTY::on_tty_write(UserOrKernelBuffer const& data, size_t size)
|
||||
{
|
||||
m_time_of_last_write = kgettimeofday();
|
||||
return m_master->on_slave_write(data, size);
|
||||
}
|
||||
|
||||
bool SlavePTY::can_write(OpenFileDescription const&, u64) const
|
||||
{
|
||||
return m_master->can_write_from_slave();
|
||||
}
|
||||
|
||||
bool SlavePTY::can_read(OpenFileDescription const& description, u64 offset) const
|
||||
{
|
||||
if (m_master->is_closed())
|
||||
return true;
|
||||
return TTY::can_read(description, offset);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> SlavePTY::read(OpenFileDescription& description, u64 offset, UserOrKernelBuffer& buffer, size_t size)
|
||||
{
|
||||
if (m_master->is_closed())
|
||||
return 0;
|
||||
return TTY::read(description, offset, buffer, size);
|
||||
}
|
||||
|
||||
ErrorOr<void> SlavePTY::close()
|
||||
{
|
||||
m_master->notify_slave_closed({});
|
||||
return {};
|
||||
}
|
||||
|
||||
FileBlockerSet& SlavePTY::blocker_set()
|
||||
{
|
||||
return m_master->blocker_set();
|
||||
}
|
||||
|
||||
}
|
64
Kernel/Devices/TTY/SlavePTY.h
Normal file
64
Kernel/Devices/TTY/SlavePTY.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/Devices/TTY/TTY.h>
|
||||
#include <Kernel/FileSystem/InodeIdentifier.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class MasterPTY;
|
||||
|
||||
class SlavePTY final : public TTY {
|
||||
public:
|
||||
virtual bool unref() const override;
|
||||
virtual ~SlavePTY() override;
|
||||
|
||||
void on_master_write(UserOrKernelBuffer const&, size_t);
|
||||
unsigned index() const { return m_index; }
|
||||
|
||||
UnixDateTime time_of_last_write() const { return m_time_of_last_write; }
|
||||
|
||||
virtual FileBlockerSet& blocker_set() override;
|
||||
|
||||
UserID uid() const { return m_uid; }
|
||||
GroupID gid() const { return m_gid; }
|
||||
|
||||
private:
|
||||
// ^Device
|
||||
virtual bool is_openable_by_jailed_processes() const override { return true; }
|
||||
|
||||
// ^TTY
|
||||
virtual ErrorOr<NonnullOwnPtr<KString>> pseudo_name() const override;
|
||||
virtual ErrorOr<size_t> on_tty_write(UserOrKernelBuffer const&, size_t) override;
|
||||
virtual void echo(u8) override;
|
||||
|
||||
// ^CharacterDevice
|
||||
virtual bool can_read(OpenFileDescription const&, u64) const override;
|
||||
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override;
|
||||
virtual bool can_write(OpenFileDescription const&, u64) const override;
|
||||
virtual StringView class_name() const override { return "SlavePTY"sv; }
|
||||
virtual ErrorOr<void> close() override;
|
||||
|
||||
friend class MasterPTY;
|
||||
SlavePTY(NonnullRefPtr<MasterPTY>, UserID, GroupID, unsigned index);
|
||||
|
||||
NonnullRefPtr<MasterPTY> const m_master;
|
||||
UnixDateTime m_time_of_last_write {};
|
||||
unsigned m_index { 0 };
|
||||
|
||||
UserID const m_uid { 0 };
|
||||
GroupID const m_gid { 0 };
|
||||
|
||||
mutable IntrusiveListNode<SlavePTY> m_list_node;
|
||||
|
||||
public:
|
||||
using List = IntrusiveList<&SlavePTY::m_list_node>;
|
||||
static SpinlockProtected<SlavePTY::List, LockRank::None>& all_instances();
|
||||
};
|
||||
|
||||
}
|
579
Kernel/Devices/TTY/TTY.cpp
Normal file
579
Kernel/Devices/TTY/TTY.cpp
Normal file
|
@ -0,0 +1,579 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <Kernel/API/Ioctl.h>
|
||||
#include <Kernel/API/POSIX/errno.h>
|
||||
#include <Kernel/API/POSIX/signal_numbers.h>
|
||||
#include <Kernel/API/ttydefaults.h>
|
||||
#include <Kernel/API/ttydefaultschars.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Devices/TTY/TTY.h>
|
||||
#include <Kernel/Interrupts/InterruptDisabler.h>
|
||||
#include <Kernel/UnixTypes.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
TTY::TTY(MajorNumber major, MinorNumber minor)
|
||||
: CharacterDevice(major, minor)
|
||||
{
|
||||
set_default_termios();
|
||||
}
|
||||
|
||||
TTY::~TTY() = default;
|
||||
|
||||
void TTY::set_default_termios()
|
||||
{
|
||||
memset(&m_termios, 0, sizeof(m_termios));
|
||||
m_termios.c_iflag = TTYDEF_IFLAG;
|
||||
m_termios.c_oflag = TTYDEF_OFLAG;
|
||||
m_termios.c_cflag = TTYDEF_CFLAG;
|
||||
m_termios.c_lflag = TTYDEF_LFLAG;
|
||||
m_termios.c_ispeed = TTYDEF_SPEED;
|
||||
m_termios.c_ospeed = TTYDEF_SPEED;
|
||||
memcpy(m_termios.c_cc, ttydefchars, sizeof(ttydefchars));
|
||||
}
|
||||
|
||||
ErrorOr<size_t> TTY::read(OpenFileDescription&, u64, UserOrKernelBuffer& buffer, size_t size)
|
||||
{
|
||||
if (Process::current().pgid() != pgid()) {
|
||||
// FIXME: Should we propagate this error path somehow?
|
||||
[[maybe_unused]] auto rc = Process::current().send_signal(SIGTTIN, nullptr);
|
||||
return EINTR;
|
||||
}
|
||||
if (m_input_buffer.size() < static_cast<size_t>(size))
|
||||
size = m_input_buffer.size();
|
||||
|
||||
bool need_evaluate_block_conditions = false;
|
||||
auto result = buffer.write_buffered<512>(size, [&](Bytes data) {
|
||||
size_t bytes_written = 0;
|
||||
for (; bytes_written < data.size(); ++bytes_written) {
|
||||
auto bit_index = m_input_buffer.head_index();
|
||||
bool is_special_character = m_special_character_bitmask[bit_index / 8] & (1 << (bit_index % 8));
|
||||
if (in_canonical_mode() && is_special_character) {
|
||||
u8 ch = m_input_buffer.dequeue();
|
||||
if (ch == '\0') {
|
||||
// EOF
|
||||
m_available_lines--;
|
||||
need_evaluate_block_conditions = true;
|
||||
break;
|
||||
} else {
|
||||
// '\n' or EOL
|
||||
data[bytes_written++] = ch;
|
||||
m_available_lines--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
data[bytes_written] = m_input_buffer.dequeue();
|
||||
}
|
||||
return bytes_written;
|
||||
});
|
||||
if ((!result.is_error() && result.value() > 0) || need_evaluate_block_conditions)
|
||||
evaluate_block_conditions();
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorOr<size_t> TTY::write(OpenFileDescription&, u64, UserOrKernelBuffer const& buffer, size_t size)
|
||||
{
|
||||
if (m_termios.c_lflag & TOSTOP && Process::current().pgid() != pgid()) {
|
||||
[[maybe_unused]] auto rc = Process::current().send_signal(SIGTTOU, nullptr);
|
||||
return EINTR;
|
||||
}
|
||||
|
||||
constexpr size_t num_chars = 256;
|
||||
return buffer.read_buffered<num_chars>(size, [&](ReadonlyBytes bytes) -> ErrorOr<size_t> {
|
||||
u8 modified_data[num_chars * 2];
|
||||
size_t modified_data_size = 0;
|
||||
for (const auto& byte : bytes) {
|
||||
process_output(byte, [&modified_data, &modified_data_size](u8 out_ch) {
|
||||
modified_data[modified_data_size++] = out_ch;
|
||||
});
|
||||
}
|
||||
auto bytes_written_or_error = on_tty_write(UserOrKernelBuffer::for_kernel_buffer(modified_data), modified_data_size);
|
||||
if (bytes_written_or_error.is_error() || !(m_termios.c_oflag & OPOST) || !(m_termios.c_oflag & ONLCR))
|
||||
return bytes_written_or_error;
|
||||
auto bytes_written = bytes_written_or_error.value();
|
||||
if (bytes_written == modified_data_size)
|
||||
return bytes.size();
|
||||
|
||||
// Degenerate case where we converted some newlines and encountered a partial write
|
||||
|
||||
// Calculate where in the input buffer the last character would have been
|
||||
size_t pos_data = 0;
|
||||
for (size_t pos_modified_data = 0; pos_modified_data < bytes_written; ++pos_data) {
|
||||
if (bytes[pos_data] == '\n')
|
||||
pos_modified_data += 2;
|
||||
else
|
||||
pos_modified_data += 1;
|
||||
|
||||
// Handle case where the '\r' got written but not the '\n'
|
||||
// FIXME: Our strategy is to retry writing both. We should really be queuing a write for the corresponding '\n'
|
||||
if (pos_modified_data > bytes_written)
|
||||
--pos_data;
|
||||
}
|
||||
return pos_data;
|
||||
});
|
||||
}
|
||||
|
||||
void TTY::echo_with_processing(u8 ch)
|
||||
{
|
||||
process_output(ch, [this](u8 out_ch) { echo(out_ch); });
|
||||
}
|
||||
|
||||
template<typename Functor>
|
||||
void TTY::process_output(u8 ch, Functor put_char)
|
||||
{
|
||||
if (m_termios.c_oflag & OPOST) {
|
||||
if (ch == '\n' && (m_termios.c_oflag & ONLCR))
|
||||
put_char('\r');
|
||||
put_char(ch);
|
||||
} else {
|
||||
put_char(ch);
|
||||
}
|
||||
}
|
||||
|
||||
bool TTY::can_read(OpenFileDescription const&, u64) const
|
||||
{
|
||||
if (in_canonical_mode()) {
|
||||
return m_available_lines > 0;
|
||||
}
|
||||
return !m_input_buffer.is_empty();
|
||||
}
|
||||
|
||||
bool TTY::can_write(OpenFileDescription const&, u64) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TTY::is_eol(u8 ch) const
|
||||
{
|
||||
return ch == m_termios.c_cc[VEOL];
|
||||
}
|
||||
|
||||
bool TTY::is_eof(u8 ch) const
|
||||
{
|
||||
return ch == m_termios.c_cc[VEOF];
|
||||
}
|
||||
|
||||
bool TTY::is_kill(u8 ch) const
|
||||
{
|
||||
return ch == m_termios.c_cc[VKILL];
|
||||
}
|
||||
|
||||
bool TTY::is_erase(u8 ch) const
|
||||
{
|
||||
return ch == m_termios.c_cc[VERASE];
|
||||
}
|
||||
|
||||
bool TTY::is_werase(u8 ch) const
|
||||
{
|
||||
return ch == m_termios.c_cc[VWERASE];
|
||||
}
|
||||
|
||||
void TTY::emit(u8 ch, bool do_evaluate_block_conditions)
|
||||
{
|
||||
if (m_termios.c_iflag & ISTRIP)
|
||||
ch &= 0x7F;
|
||||
|
||||
if (should_generate_signals()) {
|
||||
if (ch == m_termios.c_cc[VINFO]) {
|
||||
generate_signal(SIGINFO);
|
||||
return;
|
||||
}
|
||||
if (ch == m_termios.c_cc[VINTR]) {
|
||||
generate_signal(SIGINT);
|
||||
return;
|
||||
}
|
||||
if (ch == m_termios.c_cc[VQUIT]) {
|
||||
generate_signal(SIGQUIT);
|
||||
return;
|
||||
}
|
||||
if (ch == m_termios.c_cc[VSUSP]) {
|
||||
generate_signal(SIGTSTP);
|
||||
if (auto original_process_parent = m_original_process_parent.strong_ref()) {
|
||||
[[maybe_unused]] auto rc = original_process_parent->send_signal(SIGCHLD, nullptr);
|
||||
}
|
||||
// TODO: Else send it to the session leader maybe?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ScopeGuard guard([&]() {
|
||||
if (do_evaluate_block_conditions)
|
||||
evaluate_block_conditions();
|
||||
});
|
||||
|
||||
if (ch == '\r' && (m_termios.c_iflag & ICRNL))
|
||||
ch = '\n';
|
||||
else if (ch == '\n' && (m_termios.c_iflag & INLCR))
|
||||
ch = '\r';
|
||||
|
||||
auto current_char_head_index = (m_input_buffer.head_index() + m_input_buffer.size()) % TTY_BUFFER_SIZE;
|
||||
m_special_character_bitmask[current_char_head_index / 8] &= ~(1u << (current_char_head_index % 8));
|
||||
|
||||
auto set_special_bit = [&] {
|
||||
m_special_character_bitmask[current_char_head_index / 8] |= (1u << (current_char_head_index % 8));
|
||||
};
|
||||
|
||||
if (in_canonical_mode()) {
|
||||
if (is_eof(ch)) {
|
||||
// Since EOF might change between when the data came in and when it is read,
|
||||
// we use '\0' along with the bitmask to signal EOF. Any non-zero byte with
|
||||
// the special bit set signals an end-of-line.
|
||||
set_special_bit();
|
||||
m_available_lines++;
|
||||
m_input_buffer.enqueue('\0');
|
||||
return;
|
||||
}
|
||||
if (is_kill(ch) && m_termios.c_lflag & ECHOK) {
|
||||
kill_line();
|
||||
return;
|
||||
}
|
||||
if (is_erase(ch) && m_termios.c_lflag & ECHOE) {
|
||||
do_backspace();
|
||||
return;
|
||||
}
|
||||
if (is_werase(ch)) {
|
||||
erase_word();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ch == '\n') {
|
||||
if (m_termios.c_lflag & ECHO || m_termios.c_lflag & ECHONL)
|
||||
echo_with_processing('\n');
|
||||
|
||||
set_special_bit();
|
||||
m_input_buffer.enqueue('\n');
|
||||
m_available_lines++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_eol(ch)) {
|
||||
set_special_bit();
|
||||
m_available_lines++;
|
||||
}
|
||||
}
|
||||
|
||||
m_input_buffer.enqueue(ch);
|
||||
if (m_termios.c_lflag & ECHO)
|
||||
echo_with_processing(ch);
|
||||
}
|
||||
|
||||
bool TTY::can_do_backspace() const
|
||||
{
|
||||
// can't do back space if we're empty. Plus, we don't want to
|
||||
// remove any lines "committed" by newlines or ^D.
|
||||
if (!m_input_buffer.is_empty() && !is_eol(m_input_buffer.last()) && m_input_buffer.last() != '\0') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t length_with_tabs(CircularDeque<u8, TTY_BUFFER_SIZE> const& line)
|
||||
{
|
||||
size_t length = 0;
|
||||
for (auto& ch : line) {
|
||||
length += (ch == '\t') ? 8 - (length % 8) : 1;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
void TTY::do_backspace()
|
||||
{
|
||||
if (can_do_backspace()) {
|
||||
auto ch = m_input_buffer.dequeue_end();
|
||||
size_t to_delete = 1;
|
||||
|
||||
if (ch == '\t') {
|
||||
auto length = length_with_tabs(m_input_buffer);
|
||||
to_delete = 8 - (length % 8);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < to_delete; ++i) {
|
||||
// We deliberately don't process the output here.
|
||||
echo('\b');
|
||||
echo(' ');
|
||||
echo('\b');
|
||||
}
|
||||
|
||||
evaluate_block_conditions();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Currently, both erase_word() and kill_line work by sending
|
||||
// a lot of VERASE characters; this is done because Terminal.cpp
|
||||
// doesn't currently support VWERASE and VKILL. When these are
|
||||
// implemented we could just send a VKILL or VWERASE.
|
||||
|
||||
void TTY::erase_word()
|
||||
{
|
||||
// Note: if we have leading whitespace before the word
|
||||
// we want to delete we have to also delete that.
|
||||
bool first_char = false;
|
||||
bool did_dequeue = false;
|
||||
while (can_do_backspace()) {
|
||||
u8 ch = m_input_buffer.last();
|
||||
if (ch == ' ' && first_char)
|
||||
break;
|
||||
if (ch != ' ')
|
||||
first_char = true;
|
||||
m_input_buffer.dequeue_end();
|
||||
did_dequeue = true;
|
||||
erase_character();
|
||||
}
|
||||
if (did_dequeue)
|
||||
evaluate_block_conditions();
|
||||
}
|
||||
|
||||
void TTY::kill_line()
|
||||
{
|
||||
bool did_dequeue = false;
|
||||
while (can_do_backspace()) {
|
||||
m_input_buffer.dequeue_end();
|
||||
did_dequeue = true;
|
||||
erase_character();
|
||||
}
|
||||
if (did_dequeue)
|
||||
evaluate_block_conditions();
|
||||
}
|
||||
|
||||
void TTY::erase_character()
|
||||
{
|
||||
// We deliberately don't process the output here.
|
||||
echo(m_termios.c_cc[VERASE]);
|
||||
echo(' ');
|
||||
echo(m_termios.c_cc[VERASE]);
|
||||
}
|
||||
|
||||
void TTY::generate_signal(int signal)
|
||||
{
|
||||
if (!pgid())
|
||||
return;
|
||||
if (should_flush_on_signal())
|
||||
flush_input();
|
||||
dbgln_if(TTY_DEBUG, "Send signal {} to everyone in pgrp {}", signal, pgid().value());
|
||||
InterruptDisabler disabler; // FIXME: Iterate over a set of process handles instead?
|
||||
MUST(Process::current().for_each_in_pgrp_in_same_jail(pgid(), [&](auto& process) -> ErrorOr<void> {
|
||||
dbgln_if(TTY_DEBUG, "Send signal {} to {}", signal, process);
|
||||
// FIXME: Should this error be propagated somehow?
|
||||
[[maybe_unused]] auto rc = process.send_signal(signal, nullptr);
|
||||
return {};
|
||||
}));
|
||||
}
|
||||
|
||||
void TTY::flush_input()
|
||||
{
|
||||
m_available_lines = 0;
|
||||
m_input_buffer.clear();
|
||||
evaluate_block_conditions();
|
||||
}
|
||||
|
||||
ErrorOr<void> TTY::set_termios(termios const& t)
|
||||
{
|
||||
ErrorOr<void> rc;
|
||||
m_termios = t;
|
||||
|
||||
dbgln_if(TTY_DEBUG, "set_termios: ECHO={}, ISIG={}, ICANON={}, ECHOE={}, ECHOK={}, ECHONL={}, ISTRIP={}, ICRNL={}, INLCR={}, IGNCR={}, OPOST={}, ONLCR={}",
|
||||
should_echo_input(),
|
||||
should_generate_signals(),
|
||||
in_canonical_mode(),
|
||||
((m_termios.c_lflag & ECHOE) != 0),
|
||||
((m_termios.c_lflag & ECHOK) != 0),
|
||||
((m_termios.c_lflag & ECHONL) != 0),
|
||||
((m_termios.c_iflag & ISTRIP) != 0),
|
||||
((m_termios.c_iflag & ICRNL) != 0),
|
||||
((m_termios.c_iflag & INLCR) != 0),
|
||||
((m_termios.c_iflag & IGNCR) != 0),
|
||||
((m_termios.c_oflag & OPOST) != 0),
|
||||
((m_termios.c_oflag & ONLCR) != 0));
|
||||
|
||||
struct FlagDescription {
|
||||
tcflag_t value;
|
||||
StringView name;
|
||||
};
|
||||
|
||||
constexpr FlagDescription unimplemented_iflags[] = {
|
||||
{ IGNBRK, "IGNBRK"sv },
|
||||
{ BRKINT, "BRKINT"sv },
|
||||
{ IGNPAR, "IGNPAR"sv },
|
||||
{ PARMRK, "PARMRK"sv },
|
||||
{ INPCK, "INPCK"sv },
|
||||
{ IGNCR, "IGNCR"sv },
|
||||
{ IUCLC, "IUCLC"sv },
|
||||
{ IXON, "IXON"sv },
|
||||
{ IXANY, "IXANY"sv },
|
||||
{ IXOFF, "IXOFF"sv },
|
||||
{ IMAXBEL, "IMAXBEL"sv },
|
||||
{ IUTF8, "IUTF8"sv }
|
||||
};
|
||||
for (auto flag : unimplemented_iflags) {
|
||||
if (m_termios.c_iflag & flag.value) {
|
||||
dbgln("FIXME: iflag {} unimplemented", flag.name);
|
||||
rc = ENOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr FlagDescription unimplemented_oflags[] = {
|
||||
{ OLCUC, "OLCUC"sv },
|
||||
{ ONOCR, "ONOCR"sv },
|
||||
{ ONLRET, "ONLRET"sv },
|
||||
{ OFILL, "OFILL"sv },
|
||||
{ OFDEL, "OFDEL"sv }
|
||||
};
|
||||
for (auto flag : unimplemented_oflags) {
|
||||
if (m_termios.c_oflag & flag.value) {
|
||||
dbgln("FIXME: oflag {} unimplemented", flag.name);
|
||||
rc = ENOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_termios.c_cflag & CSIZE) != CS8) {
|
||||
dbgln("FIXME: Character sizes other than 8 bits are not supported");
|
||||
rc = ENOTIMPL;
|
||||
}
|
||||
|
||||
constexpr FlagDescription unimplemented_cflags[] = {
|
||||
{ CSTOPB, "CSTOPB"sv },
|
||||
{ CREAD, "CREAD"sv },
|
||||
{ PARENB, "PARENB"sv },
|
||||
{ PARODD, "PARODD"sv },
|
||||
{ HUPCL, "HUPCL"sv },
|
||||
{ CLOCAL, "CLOCAL"sv }
|
||||
};
|
||||
for (auto flag : unimplemented_cflags) {
|
||||
if (m_termios.c_cflag & flag.value) {
|
||||
dbgln("FIXME: cflag {} unimplemented", flag.name);
|
||||
rc = ENOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr FlagDescription unimplemented_lflags[] = {
|
||||
{ TOSTOP, "TOSTOP"sv },
|
||||
{ IEXTEN, "IEXTEN"sv }
|
||||
};
|
||||
for (auto flag : unimplemented_lflags) {
|
||||
if (m_termios.c_lflag & flag.value) {
|
||||
dbgln("FIXME: lflag {} unimplemented", flag.name);
|
||||
rc = ENOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
ErrorOr<void> TTY::ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg)
|
||||
{
|
||||
auto& current_process = Process::current();
|
||||
TRY(current_process.require_promise(Pledge::tty));
|
||||
#if 0
|
||||
// FIXME: When should we block things?
|
||||
// How do we make this work together with MasterPTY forwarding to us?
|
||||
if (current_process.tty() && current_process.tty() != this) {
|
||||
return ENOTTY;
|
||||
}
|
||||
#endif
|
||||
switch (request) {
|
||||
case TIOCGPGRP: {
|
||||
auto user_pgid = static_ptr_cast<pid_t*>(arg);
|
||||
auto pgid = this->pgid().value();
|
||||
return copy_to_user(user_pgid, &pgid);
|
||||
}
|
||||
case TIOCSPGRP: {
|
||||
ProcessGroupID pgid = static_cast<pid_t>(arg.ptr());
|
||||
if (pgid <= 0)
|
||||
return EINVAL;
|
||||
InterruptDisabler disabler;
|
||||
auto process_group = ProcessGroup::from_pgid(pgid);
|
||||
// Disallow setting a nonexistent PGID.
|
||||
if (!process_group)
|
||||
return EINVAL;
|
||||
|
||||
auto process = Process::from_pid_in_same_jail(ProcessID(pgid.value()));
|
||||
SessionID new_sid = process ? process->sid() : Process::get_sid_from_pgid(pgid);
|
||||
if (!new_sid || new_sid != current_process.sid())
|
||||
return EPERM;
|
||||
if (process && pgid != process->pgid())
|
||||
return EPERM;
|
||||
m_pg = TRY(process_group->try_make_weak_ptr());
|
||||
|
||||
if (process) {
|
||||
if (auto parent = Process::from_pid_ignoring_jails(process->ppid())) {
|
||||
m_original_process_parent = *parent;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
m_original_process_parent = nullptr;
|
||||
return {};
|
||||
}
|
||||
case TCGETS: {
|
||||
auto user_termios = static_ptr_cast<termios*>(arg);
|
||||
return copy_to_user(user_termios, &m_termios);
|
||||
}
|
||||
case TCSETS:
|
||||
case TCSETSF:
|
||||
case TCSETSW: {
|
||||
auto user_termios = static_ptr_cast<termios const*>(arg);
|
||||
auto termios = TRY(copy_typed_from_user(user_termios));
|
||||
auto rc = set_termios(termios);
|
||||
if (request == TCSETSF)
|
||||
flush_input();
|
||||
return rc;
|
||||
}
|
||||
case TCFLSH: {
|
||||
// Serenity's TTY implementation does not use an output buffer, so ignore TCOFLUSH.
|
||||
auto operation = static_cast<u8>(arg.ptr());
|
||||
if (operation == TCIFLUSH || operation == TCIOFLUSH) {
|
||||
flush_input();
|
||||
} else if (operation != TCOFLUSH) {
|
||||
return EINVAL;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
case TIOCGWINSZ: {
|
||||
auto user_winsize = static_ptr_cast<winsize*>(arg);
|
||||
winsize ws {};
|
||||
ws.ws_row = m_rows;
|
||||
ws.ws_col = m_columns;
|
||||
ws.ws_xpixel = 0;
|
||||
ws.ws_ypixel = 0;
|
||||
return copy_to_user(user_winsize, &ws);
|
||||
}
|
||||
case TIOCSWINSZ: {
|
||||
auto user_winsize = static_ptr_cast<winsize const*>(arg);
|
||||
auto ws = TRY(copy_typed_from_user(user_winsize));
|
||||
if (ws.ws_col == m_columns && ws.ws_row == m_rows)
|
||||
return {};
|
||||
m_rows = ws.ws_row;
|
||||
m_columns = ws.ws_col;
|
||||
generate_signal(SIGWINCH);
|
||||
return {};
|
||||
}
|
||||
case TIOCSCTTY:
|
||||
current_process.set_tty(this);
|
||||
return {};
|
||||
case TIOCSTI:
|
||||
return EIO;
|
||||
case TIOCNOTTY:
|
||||
current_process.set_tty(nullptr);
|
||||
return {};
|
||||
}
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
void TTY::set_size(unsigned short columns, unsigned short rows)
|
||||
{
|
||||
m_rows = rows;
|
||||
m_columns = columns;
|
||||
}
|
||||
|
||||
void TTY::hang_up()
|
||||
{
|
||||
generate_signal(SIGHUP);
|
||||
}
|
||||
}
|
96
Kernel/Devices/TTY/TTY.h
Normal file
96
Kernel/Devices/TTY/TTY.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/CircularDeque.h>
|
||||
#include <Kernel/Devices/CharacterDevice.h>
|
||||
#include <Kernel/Library/DoubleBuffer.h>
|
||||
#include <Kernel/Library/LockWeakPtr.h>
|
||||
#include <Kernel/Tasks/ProcessGroup.h>
|
||||
#include <Kernel/UnixTypes.h>
|
||||
|
||||
#define TTY_BUFFER_SIZE 1024
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class TTY : public CharacterDevice {
|
||||
public:
|
||||
virtual ~TTY() override;
|
||||
|
||||
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override;
|
||||
virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override;
|
||||
virtual bool can_read(OpenFileDescription const&, u64) const override;
|
||||
virtual bool can_write(OpenFileDescription const&, u64) const override;
|
||||
virtual ErrorOr<void> ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) override;
|
||||
|
||||
unsigned short rows() const { return m_rows; }
|
||||
unsigned short columns() const { return m_columns; }
|
||||
|
||||
ProcessGroupID pgid() const
|
||||
{
|
||||
if (auto pg = m_pg.strong_ref())
|
||||
return pg->pgid();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ErrorOr<void> set_termios(termios const&);
|
||||
bool should_generate_signals() const { return (m_termios.c_lflag & ISIG) == ISIG; }
|
||||
bool should_flush_on_signal() const { return (m_termios.c_lflag & NOFLSH) != NOFLSH; }
|
||||
bool should_echo_input() const { return (m_termios.c_lflag & ECHO) == ECHO; }
|
||||
bool in_canonical_mode() const { return (m_termios.c_lflag & ICANON) == ICANON; }
|
||||
|
||||
void set_default_termios();
|
||||
void hang_up();
|
||||
|
||||
virtual ErrorOr<NonnullOwnPtr<KString>> pseudo_name() const = 0;
|
||||
|
||||
protected:
|
||||
virtual ErrorOr<size_t> on_tty_write(UserOrKernelBuffer const&, size_t) = 0;
|
||||
void set_size(unsigned short columns, unsigned short rows);
|
||||
|
||||
TTY(MajorNumber major, MinorNumber minor);
|
||||
void emit(u8, bool do_evaluate_block_conditions = false);
|
||||
void echo_with_processing(u8);
|
||||
|
||||
bool can_do_backspace() const;
|
||||
void do_backspace();
|
||||
void erase_word();
|
||||
void erase_character();
|
||||
void kill_line();
|
||||
void flush_input();
|
||||
|
||||
bool is_eol(u8) const;
|
||||
bool is_eof(u8) const;
|
||||
bool is_kill(u8) const;
|
||||
bool is_erase(u8) const;
|
||||
bool is_werase(u8) const;
|
||||
|
||||
void generate_signal(int signal);
|
||||
|
||||
int m_available_lines { 0 };
|
||||
|
||||
private:
|
||||
// ^CharacterDevice
|
||||
virtual bool is_tty() const final override { return true; }
|
||||
|
||||
virtual void echo(u8) = 0;
|
||||
|
||||
template<typename Functor>
|
||||
void process_output(u8, Functor put_char);
|
||||
|
||||
CircularDeque<u8, TTY_BUFFER_SIZE> m_input_buffer;
|
||||
// FIXME: use something like AK::Bitmap but which takes a size template parameter
|
||||
u8 m_special_character_bitmask[TTY_BUFFER_SIZE / 8];
|
||||
|
||||
LockWeakPtr<Process> m_original_process_parent;
|
||||
LockWeakPtr<ProcessGroup> m_pg;
|
||||
termios m_termios;
|
||||
unsigned short m_rows { 0 };
|
||||
unsigned short m_columns { 0 };
|
||||
};
|
||||
|
||||
}
|
507
Kernel/Devices/TTY/VirtualConsole.cpp
Normal file
507
Kernel/Devices/TTY/VirtualConsole.cpp
Normal file
|
@ -0,0 +1,507 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <Kernel/Arch/Delay.h>
|
||||
#if ARCH(X86_64)
|
||||
# include <Kernel/Arch/x86_64/PCSpeaker.h>
|
||||
#endif
|
||||
#include <Kernel/Boot/CommandLine.h>
|
||||
#include <Kernel/Devices/DeviceManagement.h>
|
||||
#include <Kernel/Devices/GPU/Management.h>
|
||||
#include <Kernel/Devices/HID/Management.h>
|
||||
#include <Kernel/Devices/TTY/ConsoleManagement.h>
|
||||
#include <Kernel/Devices/TTY/VirtualConsole.h>
|
||||
#include <Kernel/Heap/kmalloc.h>
|
||||
#include <Kernel/Library/StdLib.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <LibVT/Color.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
ConsoleImpl::ConsoleImpl(VirtualConsole& client)
|
||||
: Terminal(client)
|
||||
{
|
||||
}
|
||||
|
||||
void ConsoleImpl::invalidate_cursor()
|
||||
{
|
||||
}
|
||||
void ConsoleImpl::clear()
|
||||
{
|
||||
m_client.clear();
|
||||
}
|
||||
void ConsoleImpl::clear_history()
|
||||
{
|
||||
}
|
||||
|
||||
void ConsoleImpl::set_size(u16 determined_columns, u16 determined_rows)
|
||||
{
|
||||
VERIFY(determined_columns);
|
||||
VERIFY(determined_rows);
|
||||
|
||||
if (determined_columns == columns() && determined_rows == rows())
|
||||
return;
|
||||
|
||||
m_columns = determined_columns;
|
||||
m_rows = determined_rows;
|
||||
|
||||
m_scroll_region_top = 0;
|
||||
m_scroll_region_bottom = determined_rows - 1;
|
||||
|
||||
m_current_state.cursor.clamp(rows() - 1, columns() - 1);
|
||||
m_normal_saved_state.cursor.clamp(rows() - 1, columns() - 1);
|
||||
m_alternate_saved_state.cursor.clamp(rows() - 1, columns() - 1);
|
||||
m_saved_cursor_position.clamp(rows() - 1, columns() - 1);
|
||||
m_horizontal_tabs.resize(determined_columns);
|
||||
for (unsigned i = 0; i < determined_columns; ++i)
|
||||
m_horizontal_tabs[i] = (i % 8) == 0;
|
||||
// Rightmost column is always last tab on line.
|
||||
m_horizontal_tabs[determined_columns - 1] = 1;
|
||||
m_client.terminal_did_resize(m_columns, m_rows);
|
||||
}
|
||||
void ConsoleImpl::scroll_up(u16 region_top, u16 region_bottom, size_t count)
|
||||
{
|
||||
// NOTE: We have to invalidate the cursor first.
|
||||
m_client.invalidate_cursor(cursor_row());
|
||||
m_client.scroll_up(region_top, region_bottom, count);
|
||||
}
|
||||
|
||||
void ConsoleImpl::scroll_down(u16 region_top, u16 region_bottom, size_t count)
|
||||
{
|
||||
m_client.invalidate_cursor(cursor_row());
|
||||
m_client.scroll_down(region_top, region_bottom, count);
|
||||
}
|
||||
|
||||
void ConsoleImpl::put_character_at(unsigned row, unsigned column, u32 ch)
|
||||
{
|
||||
m_client.put_character_at(row, column, ch, m_current_state.attribute);
|
||||
m_last_code_point = ch;
|
||||
}
|
||||
|
||||
void ConsoleImpl::clear_in_line(u16 row, u16 first_column, u16 last_column)
|
||||
{
|
||||
m_client.clear_in_line(row, first_column, last_column);
|
||||
}
|
||||
|
||||
void ConsoleImpl::scroll_left(u16 row, u16 column, size_t count)
|
||||
{
|
||||
m_client.scroll_left(row, column, count);
|
||||
}
|
||||
|
||||
void ConsoleImpl::scroll_right(u16 row, u16 column, size_t count)
|
||||
{
|
||||
m_client.scroll_right(row, column, count);
|
||||
}
|
||||
|
||||
void VirtualConsole::set_graphical(bool graphical)
|
||||
{
|
||||
m_graphical = graphical;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<KString>> VirtualConsole::pseudo_name() const
|
||||
{
|
||||
return KString::formatted("tty:{}", m_index);
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT NonnullLockRefPtr<VirtualConsole> VirtualConsole::create(size_t index)
|
||||
{
|
||||
auto virtual_console_or_error = DeviceManagement::try_create_device<VirtualConsole>(index);
|
||||
// FIXME: Find a way to propagate errors
|
||||
VERIFY(!virtual_console_or_error.is_error());
|
||||
return virtual_console_or_error.release_value();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT NonnullLockRefPtr<VirtualConsole> VirtualConsole::create_with_preset_log(size_t index, CircularQueue<char, 16384> const& log)
|
||||
{
|
||||
auto virtual_console = VirtualConsole::create(index);
|
||||
// HACK: We have to go through the TTY layer for correct newline handling.
|
||||
// It would be nice to not have to make all these calls, but we can't get the underlying data pointer
|
||||
// and head index. If we did that, we could reduce this to at most 2 calls.
|
||||
for (auto ch : log) {
|
||||
virtual_console->emit_char(ch);
|
||||
}
|
||||
return virtual_console;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT void VirtualConsole::initialize()
|
||||
{
|
||||
VERIFY(GraphicsManagement::the().console());
|
||||
set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row());
|
||||
m_console_impl.set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row());
|
||||
|
||||
// Allocate twice of the max row * max column * sizeof(Cell) to ensure we can have some sort of history mechanism...
|
||||
auto size = GraphicsManagement::the().console()->max_column() * GraphicsManagement::the().console()->max_row() * sizeof(Cell) * 2;
|
||||
m_cells = MM.allocate_kernel_region(Memory::page_round_up(size).release_value_but_fixme_should_propagate_errors(), "Virtual Console Cells"sv, Memory::Region::Access::ReadWrite, AllocationStrategy::AllocateNow).release_value();
|
||||
|
||||
// Add the lines, so we also ensure they will be flushed now
|
||||
for (size_t row = 0; row < rows(); row++) {
|
||||
m_lines.append({ true, 0 });
|
||||
}
|
||||
VERIFY(m_cells);
|
||||
}
|
||||
|
||||
void VirtualConsole::refresh_after_resolution_change()
|
||||
{
|
||||
auto old_rows_count = rows();
|
||||
auto old_columns_count = columns();
|
||||
set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row());
|
||||
m_console_impl.set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row());
|
||||
|
||||
// Note: From now on, columns() and rows() are updated with the new settings.
|
||||
|
||||
auto size = GraphicsManagement::the().console()->max_column() * GraphicsManagement::the().console()->max_row() * sizeof(Cell) * 2;
|
||||
auto new_cells = MM.allocate_kernel_region(Memory::page_round_up(size).release_value_but_fixme_should_propagate_errors(), "Virtual Console Cells"sv, Memory::Region::Access::ReadWrite, AllocationStrategy::AllocateNow).release_value();
|
||||
|
||||
if (rows() < old_rows_count) {
|
||||
m_lines.shrink(rows());
|
||||
} else {
|
||||
for (size_t row = 0; row < (size_t)(rows() - old_rows_count); row++) {
|
||||
m_lines.append({ true, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
// Note: A potential loss of displayed data occur when resolution width shrinks.
|
||||
auto common_rows_count = min(old_rows_count, rows());
|
||||
auto common_columns_count = min(old_columns_count, columns());
|
||||
for (size_t row = 0; row < common_rows_count; row++) {
|
||||
auto& line = m_lines[row];
|
||||
memcpy(new_cells->vaddr().offset(row * columns() * sizeof(Cell)).as_ptr(), m_cells->vaddr().offset(row * old_columns_count * sizeof(Cell)).as_ptr(), common_columns_count * sizeof(Cell));
|
||||
line.dirty = true;
|
||||
}
|
||||
|
||||
// Update the new cells Region
|
||||
m_cells = move(new_cells);
|
||||
m_console_impl.m_need_full_flush = true;
|
||||
flush_dirty_lines();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT VirtualConsole::VirtualConsole(unsigned const index)
|
||||
: TTY(4, index)
|
||||
, m_index(index)
|
||||
, m_console_impl(*this)
|
||||
{
|
||||
initialize();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT VirtualConsole::~VirtualConsole()
|
||||
{
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
ErrorOr<void> VirtualConsole::ioctl(OpenFileDescription& description, unsigned request, Userspace<void*> arg)
|
||||
{
|
||||
TRY(Process::current().require_promise(Pledge::tty));
|
||||
switch (request) {
|
||||
case KDSETMODE: {
|
||||
auto mode = static_cast<unsigned int>(arg.ptr());
|
||||
if (mode != KD_TEXT && mode != KD_GRAPHICS)
|
||||
return EINVAL;
|
||||
|
||||
set_graphical(mode == KD_GRAPHICS);
|
||||
return {};
|
||||
}
|
||||
case KDGETMODE: {
|
||||
auto mode_ptr = static_ptr_cast<int*>(arg);
|
||||
int mode = (is_graphical()) ? KD_GRAPHICS : KD_TEXT;
|
||||
return copy_to_user(mode_ptr, &mode);
|
||||
}
|
||||
}
|
||||
return TTY::ioctl(description, request, arg);
|
||||
}
|
||||
|
||||
static inline Graphics::Console::Color ansi_color_to_standard_vga_color(VT::Color::ANSIColor color)
|
||||
{
|
||||
switch (color) {
|
||||
case VT::Color::ANSIColor::DefaultBackground:
|
||||
case VT::Color::ANSIColor::Black:
|
||||
return Graphics::Console::Color::Black;
|
||||
case VT::Color::ANSIColor::Red:
|
||||
return Graphics::Console::Color::Red;
|
||||
case VT::Color::ANSIColor::Green:
|
||||
return Graphics::Console::Color::Green;
|
||||
case VT::Color::ANSIColor::Yellow:
|
||||
// VGA only has bright yellow, and treats normal yellow as a brownish orange color.
|
||||
return Graphics::Console::Color::Brown;
|
||||
case VT::Color::ANSIColor::Blue:
|
||||
return Graphics::Console::Color::Blue;
|
||||
case VT::Color::ANSIColor::Magenta:
|
||||
return Graphics::Console::Color::Magenta;
|
||||
case VT::Color::ANSIColor::Cyan:
|
||||
return Graphics::Console::Color::Cyan;
|
||||
case VT::Color::ANSIColor::DefaultForeground:
|
||||
case VT::Color::ANSIColor::White:
|
||||
return Graphics::Console::Color::LightGray;
|
||||
case VT::Color::ANSIColor::BrightBlack:
|
||||
return Graphics::Console::Color::DarkGray;
|
||||
case VT::Color::ANSIColor::BrightRed:
|
||||
return Graphics::Console::Color::BrightRed;
|
||||
case VT::Color::ANSIColor::BrightGreen:
|
||||
return Graphics::Console::Color::BrightGreen;
|
||||
case VT::Color::ANSIColor::BrightYellow:
|
||||
return Graphics::Console::Color::Yellow;
|
||||
case VT::Color::ANSIColor::BrightBlue:
|
||||
return Graphics::Console::Color::BrightBlue;
|
||||
case VT::Color::ANSIColor::BrightMagenta:
|
||||
return Graphics::Console::Color::BrightMagenta;
|
||||
case VT::Color::ANSIColor::BrightCyan:
|
||||
return Graphics::Console::Color::BrightCyan;
|
||||
case VT::Color::ANSIColor::BrightWhite:
|
||||
return Graphics::Console::Color::White;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static inline Graphics::Console::Color terminal_to_standard_color(VT::Color color)
|
||||
{
|
||||
switch (color.kind()) {
|
||||
case VT::Color::Kind::Named:
|
||||
return ansi_color_to_standard_vga_color(color.as_named());
|
||||
default:
|
||||
return Graphics::Console::Color::LightGray;
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualConsole::on_key_pressed(KeyEvent event)
|
||||
{
|
||||
// Ignore keyboard in graphical mode.
|
||||
if (m_graphical)
|
||||
return;
|
||||
|
||||
if (!event.is_press())
|
||||
return;
|
||||
|
||||
Processor::deferred_call_queue([this, event]() {
|
||||
m_console_impl.handle_key_press(event.key, event.code_point, event.flags);
|
||||
});
|
||||
}
|
||||
|
||||
ErrorOr<size_t> VirtualConsole::on_tty_write(UserOrKernelBuffer const& data, size_t size)
|
||||
{
|
||||
SpinlockLocker global_lock(ConsoleManagement::the().tty_write_lock());
|
||||
auto result = data.read_buffered<512>(size, [&](ReadonlyBytes buffer) {
|
||||
for (const auto& byte : buffer)
|
||||
m_console_impl.on_input(byte);
|
||||
return buffer.size();
|
||||
});
|
||||
if (m_active)
|
||||
flush_dirty_lines();
|
||||
return result;
|
||||
}
|
||||
|
||||
void VirtualConsole::set_active(bool active)
|
||||
{
|
||||
VERIFY(ConsoleManagement::the().m_lock.is_locked());
|
||||
VERIFY(m_active != active);
|
||||
m_active = active;
|
||||
|
||||
if (active) {
|
||||
HIDManagement::the().set_client(this);
|
||||
|
||||
m_console_impl.m_need_full_flush = true;
|
||||
flush_dirty_lines();
|
||||
} else {
|
||||
HIDManagement::the().set_client(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualConsole::emit_char(char ch)
|
||||
{
|
||||
// Since we are standards-compliant by not moving to column 1 on '\n', we have to add an extra carriage return to
|
||||
// do newlines properly. The `TTY` layer handles adding it.
|
||||
echo_with_processing(static_cast<u8>(ch));
|
||||
}
|
||||
|
||||
void VirtualConsole::flush_dirty_lines()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
VERIFY(GraphicsManagement::is_initialized());
|
||||
VERIFY(GraphicsManagement::the().console());
|
||||
for (u16 visual_row = 0; visual_row < rows(); ++visual_row) {
|
||||
auto& line = m_lines[visual_row];
|
||||
if (!line.dirty && !m_console_impl.m_need_full_flush)
|
||||
continue;
|
||||
for (size_t column = 0; column < columns(); ++column) {
|
||||
auto& cell = cell_at(column, visual_row);
|
||||
|
||||
auto foreground_color = terminal_to_standard_color(cell.attribute.effective_foreground_color());
|
||||
if (has_flag(cell.attribute.flags, VT::Attribute::Flags::Bold))
|
||||
foreground_color = (Graphics::Console::Color)((u8)foreground_color | 0x08);
|
||||
GraphicsManagement::the().console()->write(column,
|
||||
visual_row,
|
||||
((u8)cell.ch < 128 ? cell.ch : '?'),
|
||||
terminal_to_standard_color(cell.attribute.effective_background_color()),
|
||||
foreground_color);
|
||||
}
|
||||
line.dirty = false;
|
||||
}
|
||||
GraphicsManagement::the().console()->set_cursor(m_console_impl.cursor_column(), m_console_impl.cursor_row());
|
||||
m_console_impl.m_need_full_flush = false;
|
||||
}
|
||||
|
||||
void VirtualConsole::beep()
|
||||
{
|
||||
if (!kernel_command_line().is_pc_speaker_enabled())
|
||||
return;
|
||||
#if ARCH(X86_64)
|
||||
PCSpeaker::tone_on(440);
|
||||
microseconds_delay(10000);
|
||||
PCSpeaker::tone_off();
|
||||
#endif
|
||||
}
|
||||
|
||||
void VirtualConsole::set_window_title(StringView)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
void VirtualConsole::set_window_progress(int, int)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
void VirtualConsole::terminal_did_resize(u16 columns, u16 rows)
|
||||
{
|
||||
// FIXME: Allocate more Region(s) or deallocate them if needed...
|
||||
dbgln("VC {}: Resized to {} x {}", index(), columns, rows);
|
||||
}
|
||||
|
||||
void VirtualConsole::terminal_history_changed(int)
|
||||
{
|
||||
// Do nothing, I guess?
|
||||
}
|
||||
|
||||
void VirtualConsole::emit(u8 const* data, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
TTY::emit(data[i], true);
|
||||
}
|
||||
|
||||
void VirtualConsole::set_cursor_shape(VT::CursorShape)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void VirtualConsole::set_cursor_blinking(bool)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void VirtualConsole::echo(u8 ch)
|
||||
{
|
||||
m_console_impl.on_input(ch);
|
||||
if (m_active)
|
||||
flush_dirty_lines();
|
||||
}
|
||||
|
||||
VirtualConsole::Cell& VirtualConsole::cell_at(size_t x, size_t y)
|
||||
{
|
||||
auto* ptr = (VirtualConsole::Cell*)(m_cells->vaddr().as_ptr());
|
||||
ptr += (y * columns()) + x;
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
void VirtualConsole::clear()
|
||||
{
|
||||
auto* cell = (Cell*)m_cells->vaddr().as_ptr();
|
||||
for (size_t y = 0; y < rows(); y++) {
|
||||
m_lines[y].dirty = true;
|
||||
for (size_t x = 0; x < columns(); x++) {
|
||||
cell[x].clear();
|
||||
}
|
||||
cell += columns();
|
||||
}
|
||||
m_console_impl.set_cursor(0, 0);
|
||||
}
|
||||
|
||||
void VirtualConsole::scroll_up(u16 region_top, u16 region_bottom, size_t count)
|
||||
{
|
||||
VERIFY(region_top <= region_bottom);
|
||||
size_t region_size = region_bottom - region_top + 1;
|
||||
count = min(count, region_size);
|
||||
size_t line_bytes = (columns() * sizeof(Cell));
|
||||
memmove(m_cells->vaddr().offset(line_bytes * region_top).as_ptr(), m_cells->vaddr().offset(line_bytes * (region_top + count)).as_ptr(), line_bytes * (region_size - count));
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
clear_line(region_bottom - i);
|
||||
for (u16 row = region_top; row <= region_bottom; ++row)
|
||||
m_lines[row].dirty = true;
|
||||
}
|
||||
|
||||
void VirtualConsole::scroll_down(u16 region_top, u16 region_bottom, size_t count)
|
||||
{
|
||||
VERIFY(region_top <= region_bottom);
|
||||
size_t region_size = region_bottom - region_top + 1;
|
||||
count = min(count, region_size);
|
||||
size_t line_bytes = (columns() * sizeof(Cell));
|
||||
memmove(m_cells->vaddr().offset(line_bytes * (region_top + count)).as_ptr(), m_cells->vaddr().offset(line_bytes * region_top).as_ptr(), line_bytes * (region_size - count));
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
clear_line(region_top + i);
|
||||
for (u16 row = region_top; row <= region_bottom; ++row)
|
||||
m_lines[row].dirty = true;
|
||||
}
|
||||
|
||||
void VirtualConsole::scroll_left(u16 row, u16 column, size_t count)
|
||||
{
|
||||
VERIFY(row < rows());
|
||||
VERIFY(column < columns());
|
||||
count = min<size_t>(count, columns() - column);
|
||||
memmove(&cell_at(column, row), &cell_at(column + count, row), sizeof(Cell) * (columns() - column - count));
|
||||
for (size_t i = column + count; i < columns(); ++i)
|
||||
cell_at(i, row).clear();
|
||||
m_lines[row].dirty = true;
|
||||
}
|
||||
|
||||
void VirtualConsole::scroll_right(u16 row, u16 column, size_t count)
|
||||
{
|
||||
VERIFY(row < rows());
|
||||
VERIFY(column < columns());
|
||||
count = min<size_t>(count, columns() - column);
|
||||
memmove(&cell_at(column + count, row), &cell_at(column, row), sizeof(Cell) * (columns() - column - count));
|
||||
for (size_t i = column; i < column + count; ++i)
|
||||
cell_at(i, row).clear();
|
||||
m_lines[row].dirty = true;
|
||||
}
|
||||
|
||||
void VirtualConsole::clear_in_line(u16 row, u16 first_column, u16 last_column)
|
||||
{
|
||||
VERIFY(row < rows());
|
||||
VERIFY(first_column <= last_column);
|
||||
VERIFY(last_column < columns());
|
||||
m_lines[row].dirty = true;
|
||||
for (size_t x = first_column; x <= last_column; x++)
|
||||
cell_at(x, row).clear();
|
||||
}
|
||||
|
||||
void VirtualConsole::put_character_at(unsigned row, unsigned column, u32 code_point, const VT::Attribute& attribute)
|
||||
{
|
||||
VERIFY(row < rows());
|
||||
VERIFY(column < columns());
|
||||
auto& line = m_lines[row];
|
||||
auto& cell = cell_at(column, row);
|
||||
cell.attribute.foreground_color = attribute.foreground_color;
|
||||
cell.attribute.background_color = attribute.background_color;
|
||||
cell.attribute.flags = attribute.flags;
|
||||
if (code_point > 128)
|
||||
cell.ch = ' ';
|
||||
else
|
||||
cell.ch = code_point;
|
||||
cell.attribute.flags |= VT::Attribute::Flags::Touched;
|
||||
line.dirty = true;
|
||||
// FIXME: Maybe we should consider to change length after printing a special char in a column
|
||||
if (code_point <= 20)
|
||||
return;
|
||||
line.length = max<size_t>(line.length, column);
|
||||
}
|
||||
|
||||
void VirtualConsole::invalidate_cursor(size_t row)
|
||||
{
|
||||
m_lines[row].dirty = true;
|
||||
}
|
||||
|
||||
}
|
143
Kernel/Devices/TTY/VirtualConsole.h
Normal file
143
Kernel/Devices/TTY/VirtualConsole.h
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <Kernel/API/KeyCode.h>
|
||||
#include <Kernel/Devices/GPU/Console/Console.h>
|
||||
#include <Kernel/Devices/Generic/ConsoleDevice.h>
|
||||
#include <Kernel/Devices/HID/Management.h>
|
||||
#include <Kernel/Devices/TTY/TTY.h>
|
||||
#include <LibVT/Attribute.h>
|
||||
#include <LibVT/Color.h>
|
||||
#include <LibVT/Position.h>
|
||||
#include <LibVT/Terminal.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ConsoleManagement;
|
||||
class VirtualConsole;
|
||||
// FIXME: This implementation has no knowledge about keeping terminal history...
|
||||
class ConsoleImpl final : public VT::Terminal {
|
||||
public:
|
||||
explicit ConsoleImpl(VirtualConsole&);
|
||||
|
||||
virtual void set_size(u16 columns, u16 rows) override;
|
||||
|
||||
private:
|
||||
virtual void invalidate_cursor() override;
|
||||
virtual void clear() override;
|
||||
virtual void clear_history() override;
|
||||
|
||||
virtual void scroll_up(u16 region_top, u16 region_bottom, size_t count) override;
|
||||
virtual void scroll_down(u16 region_top, u16 region_bottom, size_t count) override;
|
||||
virtual void scroll_left(u16 row, u16 column, size_t count) override;
|
||||
virtual void scroll_right(u16 row, u16 column, size_t count) override;
|
||||
virtual void put_character_at(unsigned row, unsigned column, u32 ch) override;
|
||||
virtual void clear_in_line(u16 row, u16 first_column, u16 last_column) override;
|
||||
};
|
||||
|
||||
class VirtualConsole final : public TTY
|
||||
, public KeyboardClient
|
||||
, public VT::TerminalClient {
|
||||
friend class ConsoleManagement;
|
||||
friend class DeviceManagement;
|
||||
friend class ConsoleImpl;
|
||||
friend class VT::Terminal;
|
||||
|
||||
public:
|
||||
struct Line {
|
||||
bool dirty;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
struct Cell {
|
||||
void clear()
|
||||
{
|
||||
ch = ' ';
|
||||
attribute.reset();
|
||||
}
|
||||
char ch;
|
||||
VT::Attribute attribute;
|
||||
};
|
||||
|
||||
public:
|
||||
static NonnullLockRefPtr<VirtualConsole> create(size_t index);
|
||||
static NonnullLockRefPtr<VirtualConsole> create_with_preset_log(size_t index, CircularQueue<char, 16384> const&);
|
||||
|
||||
virtual ~VirtualConsole() override;
|
||||
|
||||
size_t index() const { return m_index; }
|
||||
|
||||
void refresh_after_resolution_change();
|
||||
|
||||
bool is_graphical() const { return m_graphical; }
|
||||
void set_graphical(bool graphical);
|
||||
|
||||
void emit_char(char);
|
||||
|
||||
private:
|
||||
explicit VirtualConsole(unsigned const index);
|
||||
// ^KeyboardClient
|
||||
virtual void on_key_pressed(KeyEvent) override;
|
||||
|
||||
// ^TTY
|
||||
virtual ErrorOr<NonnullOwnPtr<KString>> pseudo_name() const override;
|
||||
virtual ErrorOr<size_t> on_tty_write(UserOrKernelBuffer const&, size_t) override;
|
||||
virtual ErrorOr<void> ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) override;
|
||||
virtual void echo(u8) override;
|
||||
|
||||
// ^TerminalClient
|
||||
virtual void beep() override;
|
||||
virtual void set_window_title(StringView) override;
|
||||
virtual void set_window_progress(int, int) override;
|
||||
virtual void terminal_did_resize(u16 columns, u16 rows) override;
|
||||
virtual void terminal_history_changed(int) override;
|
||||
virtual void emit(u8 const*, size_t) override;
|
||||
virtual void set_cursor_shape(VT::CursorShape) override;
|
||||
virtual void set_cursor_blinking(bool) override;
|
||||
|
||||
// ^CharacterDevice
|
||||
virtual StringView class_name() const override { return "VirtualConsole"sv; }
|
||||
|
||||
void set_active(bool);
|
||||
void flush_dirty_lines();
|
||||
|
||||
unsigned m_index;
|
||||
bool m_active { false };
|
||||
bool m_graphical { false };
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
void invalidate_cursor(size_t row);
|
||||
|
||||
void clear();
|
||||
|
||||
Cell& cell_at(size_t column, size_t row);
|
||||
|
||||
using ParamVector = Vector<unsigned, 4>;
|
||||
|
||||
void scroll_down(u16 region_top, u16 region_bottom, size_t count);
|
||||
void scroll_up(u16 region_top, u16 region_bottom, size_t count);
|
||||
void scroll_left(u16 row, u16 column, size_t count);
|
||||
void scroll_right(u16 row, u16 column, size_t count);
|
||||
void clear_line(size_t index)
|
||||
{
|
||||
clear_in_line(index, 0, m_console_impl.columns() - 1);
|
||||
}
|
||||
void clear_in_line(u16 row, u16 first_column, u16 last_column);
|
||||
void put_character_at(unsigned row, unsigned column, u32 ch, const VT::Attribute&);
|
||||
|
||||
OwnPtr<Memory::Region> m_cells;
|
||||
Vector<VirtualConsole::Line> m_lines;
|
||||
ConsoleImpl m_console_impl;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue