From 9df88bac3edef11a087a68a1bf3a0c8313ba5b1a Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 12:19:45 +0200 Subject: [PATCH 001/103] libui: Add a GUI and graphics library --- CMakeLists.txt | 1 + libui/CMakeLists.txt | 15 ++++++++++++++ libui/include/ui/Canvas.h | 30 +++++++++++++++++++++++++++ libui/include/ui/Color.h | 34 +++++++++++++++++++++++++++++++ libui/include/ui/Point.h | 13 ++++++++++++ libui/include/ui/Rect.h | 18 ++++++++++++++++ libui/src/Canvas.cpp | 43 +++++++++++++++++++++++++++++++++++++++ libui/src/Color.cpp | 0 libui/src/Rect.cpp | 18 ++++++++++++++++ tools/install-headers.sh | 2 ++ tools/sources.sh | 2 +- 11 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 libui/CMakeLists.txt create mode 100644 libui/include/ui/Canvas.h create mode 100644 libui/include/ui/Color.h create mode 100644 libui/include/ui/Point.h create mode 100644 libui/include/ui/Rect.h create mode 100644 libui/src/Canvas.cpp create mode 100644 libui/src/Color.cpp create mode 100644 libui/src/Rect.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index acacbcf6..8ffd2f6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ endif() add_subdirectory(libluna) add_subdirectory(libos) +add_subdirectory(libui) add_subdirectory(libc) add_subdirectory(kernel) add_subdirectory(apps) diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt new file mode 100644 index 00000000..b9bf2e94 --- /dev/null +++ b/libui/CMakeLists.txt @@ -0,0 +1,15 @@ +# The UI and graphics library for Luna. + +file(GLOB HEADERS include/ui/*.h) + +set(SOURCES + ${HEADERS} + src/Canvas.cpp + src/Color.cpp + src/Rect.cpp +) + +add_library(ui ${SOURCES}) +target_compile_options(ui PRIVATE ${COMMON_FLAGS}) +target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/) +target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include) diff --git a/libui/include/ui/Canvas.h b/libui/include/ui/Canvas.h new file mode 100644 index 00000000..c90f0ae7 --- /dev/null +++ b/libui/include/ui/Canvas.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace ui +{ + /** + * @brief A drawable surface. + */ + struct Canvas + { + int width; + int height; + int stride; + u8* ptr; + + static Canvas create(u8* ptr, int width, int height); + Result subcanvas(Point begin, int width, int height, bool clamp); + + Rect rect() + { + return Rect { .begin = { 0, 0 }, .width = width, .height = height }; + } + + void fill(Color color); + }; +}; diff --git a/libui/include/ui/Color.h b/libui/include/ui/Color.h new file mode 100644 index 00000000..95a8d974 --- /dev/null +++ b/libui/include/ui/Color.h @@ -0,0 +1,34 @@ +#pragma once +#include + +namespace ui +{ + /** + * @brief A 32-bit RGBA color. + */ + struct Color + { + union { + u32 raw; + u8 colors[4]; + }; + + static constexpr Color from_u32(u32 raw) + { + return Color { .raw = raw }; + } + + static constexpr Color from_rgba(u8 red, u8 green, u8 blue, u8 alpha) + { + return Color { .colors = { blue, green, red, alpha } }; + } + + static constexpr Color from_rgb(u8 red, u8 green, u8 blue) + { + return from_rgba(red, green, blue, 0xff); + } + }; + + static constexpr Color WHITE = Color::from_rgb(0xff, 0xff, 0xff); + static constexpr Color BLACK = Color::from_rgb(0x00, 0x00, 0x00); +}; diff --git a/libui/include/ui/Point.h b/libui/include/ui/Point.h new file mode 100644 index 00000000..146ea0b8 --- /dev/null +++ b/libui/include/ui/Point.h @@ -0,0 +1,13 @@ +#pragma once + +namespace ui +{ + /** + * @brief A point in 2D space. + */ + struct Point + { + int x { 0 }; + int y { 0 }; + }; +} diff --git a/libui/include/ui/Rect.h b/libui/include/ui/Rect.h new file mode 100644 index 00000000..6d2bdec6 --- /dev/null +++ b/libui/include/ui/Rect.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace ui +{ + /** + * @brief A simple rectangle. + */ + struct Rect + { + Point begin; + int width; + int height; + + bool contains(Point point); + Point normalize(Point point); + }; +} diff --git a/libui/src/Canvas.cpp b/libui/src/Canvas.cpp new file mode 100644 index 00000000..812610c0 --- /dev/null +++ b/libui/src/Canvas.cpp @@ -0,0 +1,43 @@ +#include +#include + +namespace ui +{ + Canvas Canvas::create(u8* ptr, int width, int height) + { + return Canvas { .width = width, .height = height, .stride = width, .ptr = ptr }; + } + + Result Canvas::subcanvas(Point begin, int w, int h, bool clamp) + { + if (!clamp) + { + if (begin.x + w > width) return err(ERANGE); + if (begin.y + h > height) return err(ERANGE); + } + else + { + if (begin.x + w > width) w = width - begin.x; + if (begin.y + h > height) h = height - begin.y; + } + + u8* p = ptr + begin.x * sizeof(Color) + (begin.y * sizeof(Color) * stride); + + return Canvas { .width = w, .height = h, .stride = stride, .ptr = p }; + } + + void Canvas::fill(Color color) + { + u8* p = ptr; + for (int i = 0; i < height; i++) + { + u32* colorp = (u32*)p; + for (int j = 0; j < width; j++) + { + *colorp = color.raw; + colorp++; + } + p += stride * sizeof(Color); + } + } +} diff --git a/libui/src/Color.cpp b/libui/src/Color.cpp new file mode 100644 index 00000000..e69de29b diff --git a/libui/src/Rect.cpp b/libui/src/Rect.cpp new file mode 100644 index 00000000..5d180eab --- /dev/null +++ b/libui/src/Rect.cpp @@ -0,0 +1,18 @@ +#include + +namespace ui +{ + bool Rect::contains(Point point) + { + return point.x >= begin.x && point.y >= begin.y && point.x <= begin.x + width && point.y <= begin.y + height; + } + + Point Rect::normalize(Point point) + { + if (point.x < begin.x) point.x = begin.x; + if (point.y < begin.y) point.y = begin.y; + if (point.x > begin.x + width) point.x = begin.x + width; + if (point.y > begin.y + height) point.y = begin.y + height; + return point; + } +}; diff --git a/tools/install-headers.sh b/tools/install-headers.sh index e7f919dd..999936d2 100755 --- a/tools/install-headers.sh +++ b/tools/install-headers.sh @@ -8,10 +8,12 @@ cd $LUNA_ROOT mkdir -p $LUNA_BASE mkdir -p $LUNA_BASE/usr/include mkdir -p $LUNA_BASE/usr/include/luna +mkdir -p $LUNA_BASE/usr/include/ui mkdir -p $LUNA_BASE/usr/include/os mkdir -p $LUNA_BASE/usr/include/moon cp --preserve=timestamps -RT libc/include/ $LUNA_BASE/usr/include cp --preserve=timestamps -RT libluna/include/luna/ $LUNA_BASE/usr/include/luna +cp --preserve=timestamps -RT libui/include/ui/ $LUNA_BASE/usr/include/ui cp --preserve=timestamps -RT libos/include/os/ $LUNA_BASE/usr/include/os cp --preserve=timestamps -RT kernel/src/api/ $LUNA_BASE/usr/include/moon diff --git a/tools/sources.sh b/tools/sources.sh index fcbd49e1..752d1883 100755 --- a/tools/sources.sh +++ b/tools/sources.sh @@ -4,7 +4,7 @@ source $(dirname $0)/env.sh cd $LUNA_ROOT -FOLDERS=(kernel libc libos libluna apps shell tests) +FOLDERS=(kernel libc libos libui libluna apps shell tests) SOURCES=($(find ${FOLDERS[@]} -type f -name "*.cpp")) SOURCES+=($(find ${FOLDERS[@]} -type f -name "*.h")) -- 2.34.1 From 36cc84c50dd0f54862b708c59b0e2e0ca3f07a5b Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 12:20:59 +0200 Subject: [PATCH 002/103] wind: Add a simple display server skeleton using libui No client functionality yet, but it's a start. --- CMakeLists.txt | 1 + wind/CMakeLists.txt | 14 +++++++ wind/Mouse.cpp | 21 ++++++++++ wind/Mouse.h | 18 +++++++++ wind/Screen.cpp | 34 ++++++++++++++++ wind/Screen.h | 27 +++++++++++++ wind/main.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 wind/CMakeLists.txt create mode 100644 wind/Mouse.cpp create mode 100644 wind/Mouse.h create mode 100644 wind/Screen.cpp create mode 100644 wind/Screen.h create mode 100644 wind/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ffd2f6d..dd08c362 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,3 +51,4 @@ add_subdirectory(kernel) add_subdirectory(apps) add_subdirectory(tests) add_subdirectory(shell) +add_subdirectory(wind) diff --git a/wind/CMakeLists.txt b/wind/CMakeLists.txt new file mode 100644 index 00000000..a7bb5bc9 --- /dev/null +++ b/wind/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SOURCES + main.cpp + Screen.h + Screen.cpp + Mouse.h + Mouse.cpp +) + +add_executable(wind ${SOURCES}) +target_compile_options(wind PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings) +add_dependencies(wind libc) +target_include_directories(wind PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(wind PRIVATE os ui) +install(TARGETS wind DESTINATION ${LUNA_BASE}/usr/bin) diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp new file mode 100644 index 00000000..ed1f171f --- /dev/null +++ b/wind/Mouse.cpp @@ -0,0 +1,21 @@ +#include "Mouse.h" + +Mouse::Mouse(ui::Canvas& screen) +{ + m_position.x = screen.width / 2; + m_position.y = screen.height / 2; + m_screen_rect = screen.rect(); +} + +void Mouse::draw(ui::Canvas& screen) +{ + auto canvas = screen.subcanvas(m_position, 10, 10, true).release_value(); + canvas.fill(ui::WHITE); +} + +void Mouse::move(const moon::MousePacket& packet) +{ + m_position.x += packet.xdelta; + m_position.y -= packet.ydelta; + m_position = m_screen_rect.normalize(m_position); +} diff --git a/wind/Mouse.h b/wind/Mouse.h new file mode 100644 index 00000000..c188ceea --- /dev/null +++ b/wind/Mouse.h @@ -0,0 +1,18 @@ +#pragma once +#include "Screen.h" +#include +#include + +class Mouse +{ + public: + Mouse(ui::Canvas& screen); + + void move(const moon::MousePacket& packet); + + void draw(ui::Canvas& screen); + + private: + ui::Point m_position; + ui::Rect m_screen_rect; +}; diff --git a/wind/Screen.cpp b/wind/Screen.cpp new file mode 100644 index 00000000..b44874ba --- /dev/null +++ b/wind/Screen.cpp @@ -0,0 +1,34 @@ +#include "Screen.h" +#include +#include +#include +#include +#include + +Result Screen::open() +{ + int fd = ::open("/dev/fb0", O_RDWR | O_CLOEXEC); + if (fd < 0) return err(errno); + + int width = ioctl(fd, FB_GET_WIDTH); + int height = ioctl(fd, FB_GET_HEIGHT); + + void* p = mmap(nullptr, width * height * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + close(fd); + return err(errno); + } + + Screen screen; + + screen.m_canvas = ui::Canvas::create((u8*)p, width, height); + screen.m_size = width * height * BYTES_PER_PIXEL; + + return screen; +} + +void Screen::sync() +{ + msync(m_canvas.ptr, size(), MS_SYNC); +} diff --git a/wind/Screen.h b/wind/Screen.h new file mode 100644 index 00000000..a98295c6 --- /dev/null +++ b/wind/Screen.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include + +constexpr int BYTES_PER_PIXEL = 4; + +class Screen +{ + public: + static Result open(); + + ui::Canvas& canvas() + { + return m_canvas; + } + + int size() const + { + return m_size; + } + + void sync(); + + private: + ui::Canvas m_canvas; + int m_size; +}; diff --git a/wind/main.cpp b/wind/main.cpp new file mode 100644 index 00000000..318da9b9 --- /dev/null +++ b/wind/main.cpp @@ -0,0 +1,98 @@ +#include "Mouse.h" +#include "Screen.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Result luna_main(int argc, char** argv) +{ + StringView socket_path = "/tmp/wind.sock"; + StringView user; + + os::ArgumentParser parser; + parser.add_description("The display server for Luna's graphical user interface."_sv); + parser.add_system_program_info("wind"_sv); + parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv); + parser.add_value_argument(user, 'u', "user"_sv, "the user to run as"_sv); + parser.parse(argc, argv); + + if (geteuid() != 0) + { + os::eprintln("error: wind must be run as root to initialize resources, run with --user= to drop " + "privileges afterwards"); + return 1; + } + + auto mouse = TRY(os::File::open("/dev/mouse", os::File::ReadOnly)); + mouse->set_buffer(os::File::NotBuffered); + mouse->set_close_on_exec(); + + auto keyboard = TRY(os::File::open("/dev/kbd", os::File::ReadOnly)); + keyboard->set_buffer(os::File::NotBuffered); + keyboard->set_close_on_exec(); + + auto screen = TRY(Screen::open()); + + Mouse mouse_pointer { screen.canvas() }; + + ioctl(STDIN_FILENO, TTYSETGFX, 1); + + setpgid(0, 0); + + int fd = open("/dev/null", O_RDONLY); + if (fd >= 0) + { + dup2(fd, STDIN_FILENO); + close(fd); + } + + clearenv(); + + if (!user.is_empty()) + { + auto* pwd = getpwnam(user.chars()); + if (pwd) + { + setgid(pwd->pw_gid); + setuid(pwd->pw_uid); + } + } + + ui::Color background = ui::BLACK; + + while (1) + { + struct pollfd fds[] = { + { .fd = mouse->fd(), .events = POLLIN, .revents = 0 }, + { .fd = keyboard->fd(), .events = POLLIN, .revents = 0 }, + }; + + int rc = poll(fds, 2, 1000); + if (!rc) continue; + if (rc < 0) { os::println("poll: error: %s", strerror(errno)); } + + if (fds[0].revents & POLLIN) + { + moon::MousePacket packet; + TRY(mouse->read_typed(packet)); + mouse_pointer.move(packet); + } + if (fds[1].revents & POLLIN) + { + moon::KeyboardPacket packet; + TRY(keyboard->read_typed(packet)); + background = ui::Color::from_rgb(0x00, 0x00, packet.key * 2); + } + + screen.canvas().fill(background); + mouse_pointer.draw(screen.canvas()); + screen.sync(); + } +} -- 2.34.1 From 5b89fccb6a695f627914bcadf0ba5017fc304985 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 12:21:16 +0200 Subject: [PATCH 003/103] base: Start wind on startup instead of the shell --- base/etc/init/99-login | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/etc/init/99-login b/base/etc/init/99-login index 2a3b5f95..29cdbcaa 100644 --- a/base/etc/init/99-login +++ b/base/etc/init/99-login @@ -1,4 +1,4 @@ Name=login -Description=Start the command-line login program. -Command=/usr/bin/login +Description=Start the display server. +Command=/usr/bin/wind --user=selene Restart=true -- 2.34.1 From 15192837c0f1eb0a50f328a861394eb4d7539969 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 16:28:14 +0200 Subject: [PATCH 004/103] kernel: Support listening sockets in poll() --- kernel/src/net/Socket.h | 4 ++++ kernel/src/net/UnixSocket.h | 10 ++++++++++ kernel/src/sys/poll.cpp | 22 +++++++++++++++++++--- libluna/include/luna/CircularQueue.h | 4 ++-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/kernel/src/net/Socket.h b/kernel/src/net/Socket.h index 1ac16c92..5b980958 100644 --- a/kernel/src/net/Socket.h +++ b/kernel/src/net/Socket.h @@ -65,6 +65,10 @@ class Socket : public VFS::FileInode m_metadata.nlinks--; } + virtual bool can_accept_connections() const = 0; + + virtual bool can_read_data() const = 0; + virtual ~Socket() = default; protected: diff --git a/kernel/src/net/UnixSocket.h b/kernel/src/net/UnixSocket.h index 69b23cfa..9ec88000 100644 --- a/kernel/src/net/UnixSocket.h +++ b/kernel/src/net/UnixSocket.h @@ -17,6 +17,16 @@ class UnixSocket : public Socket return (m_state == Connected || m_state == Reset) && !m_data.size(); } + bool can_read_data() const override + { + return (m_state == Connected || m_state == Reset) && m_data.size(); + } + + bool can_accept_connections() const override + { + return !m_listen_queue.is_empty(); + } + Result send(const u8*, usize, int) override; Result recv(u8*, usize, int) const override; diff --git a/kernel/src/sys/poll.cpp b/kernel/src/sys/poll.cpp index f268060b..30ac2f24 100644 --- a/kernel/src/sys/poll.cpp +++ b/kernel/src/sys/poll.cpp @@ -2,6 +2,7 @@ #include "Pledge.h" #include "fs/VFS.h" #include "memory/MemoryManager.h" +#include "net/Socket.h" #include "sys/Syscall.h" #include "thread/Scheduler.h" #include @@ -47,10 +48,25 @@ Result sys_poll(Registers*, SyscallArgs args) auto& inode = inodes[i]; if (!inode) continue; - if (kfds[i].events & POLLIN && !inode->will_block_if_read()) + if (kfds[i].events & POLLIN) { - fds_with_events++; - kfds[i].revents |= POLLIN; + if (inode->type() == VFS::InodeType::Socket) + { + auto socket = (Socket*)inode.ptr(); + if (socket->can_read_data() || socket->can_accept_connections()) + { + fds_with_events++; + kfds[i].revents |= POLLIN; + } + } + else + { + if (!inode->will_block_if_read()) + { + fds_with_events++; + kfds[i].revents |= POLLIN; + } + } } } diff --git a/libluna/include/luna/CircularQueue.h b/libluna/include/luna/CircularQueue.h index a9e087f2..922fda0e 100644 --- a/libluna/include/luna/CircularQueue.h +++ b/libluna/include/luna/CircularQueue.h @@ -37,7 +37,7 @@ template class CircularQueue * @return true The queue is empty. * @return false The queue is not empty. */ - bool is_empty() + bool is_empty() const { return m_tail.load() == m_head.load(); } @@ -124,7 +124,7 @@ template class DynamicCircularQueue * @return true The queue is empty. * @return false The queue is not empty. */ - bool is_empty() + bool is_empty() const { return m_tail.load() == m_head.load(); } -- 2.34.1 From b6fe96e3644bd9d4f636ef2bcf69484bd174b2bc Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 16:39:30 +0200 Subject: [PATCH 005/103] libos: Add a new LocalServer class for local domain sockets --- libos/CMakeLists.txt | 1 + libos/include/os/LocalServer.h | 132 +++++++++++++++++++++++++++++++++ libos/src/LocalServer.cpp | 87 ++++++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 libos/include/os/LocalServer.h create mode 100644 libos/src/LocalServer.cpp diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index df35f48a..1dfa58b6 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES src/Mode.cpp src/Prompt.cpp src/Security.cpp + src/LocalServer.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/LocalServer.h b/libos/include/os/LocalServer.h new file mode 100644 index 00000000..9a1c9a7e --- /dev/null +++ b/libos/include/os/LocalServer.h @@ -0,0 +1,132 @@ +#pragma once +#include +#include +#include + +namespace os +{ + /** + * @brief A local domain server, used to communicate between processes on the same machine. + */ + class LocalServer : public Shareable + { + public: + /** + * @brief Create a new server object and bind it to a local address. + * + * @param path The path to use for the server socket. + * @param blocking Whether the server should block if no connections are available when calling accept(). + * @return Result> An error, or a new server object. + */ + static Result> create(StringView path, bool blocking); + + /** + * @brief Activate the server and start listening for connections. + * + * @param backlog The number of unaccepted connections to keep. + * @return Result Whether the operation succeded. + */ + Result listen(int backlog); + + /** + * @brief Return the underlying socket file descriptor used by this object. + * + * @return int The file descriptor. + */ + int fd() const + { + return m_fd; + } + + /** + * @brief An interface to communicate with clients connected to a local server. + */ + class Client : public Shareable + { + public: + /** + * @brief Read arbitrary data from the client. The call will block if there is no data and the parent server + * object has not been created as non-blocking. + * + * @param buf The buffer to read data into. + * @param length The maximum amount of bytes to read. + * @return Result An error, or the number of bytes read. + */ + Result recv(u8* buf, usize length); + + /** + * @brief Read an object from the client. The call will block if there is no data and the parent server + * object has not been created as non-blocking. + * + * @tparam T The type of the object. + * @param out A reference to the object to read data into. + * @return Result Whether the operation succeded. + */ + template Result recv_typed(T& out) + { + TRY(recv((u8*)&out, sizeof(T))); + return {}; + } + + /** + * @brief Send arbitrary data to the client. + * + * @param buf The buffer to send data from. + * @param length The amount of bytes to send. + * @return Result An error, or the number of bytes actually sent. + */ + Result send(const u8* buf, usize length); + + /** + * @brief Send an object to the client. + * + * @tparam T The type of the object. + * @param out A reference to the object to send data from. + * @return Result Whether the operation succeded. + */ + template Result send_typed(const T& out) + { + TRY(send((const u8*)&out, sizeof(T))); + return {}; + } + + /** + * @brief Disconnect from the attached client. + * + * This will make any further reads on the client return ECONNRESET, and will make this object invalid. + */ + void disconnect(); + + /** + * @brief Return the underlying socket file descriptor used by this object. + * + * @return int The file descriptor. + */ + int fd() const + { + return m_fd; + } + + Client(int fd); + ~Client(); + + private: + int m_fd; + }; + + /** + * @brief Accept a new incoming connection and return a handle to it. If there are no incoming connections, + * accept() either blocks until there is one (if the object was created with blocking=true), or returns EAGAIN + * (if the object was created with blocking=false). + * + * @return Result> An error, or a handle to the new connection. + */ + Result> accept(); + + ~LocalServer(); + + private: + int m_fd; + bool m_blocking; + }; +} diff --git a/libos/src/LocalServer.cpp b/libos/src/LocalServer.cpp new file mode 100644 index 00000000..fba62c57 --- /dev/null +++ b/libos/src/LocalServer.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace os +{ + Result> LocalServer::create(StringView path, bool blocking) + { + auto server = TRY(make_shared()); + + (void)os::FileSystem::remove(path); // We explicitly ignore any error here, either it doesn't exist (which is + // fine), or it cannot be removed, which will make bind() fail later. + + int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) return err(errno); + + struct sockaddr_un un; + un.sun_family = AF_UNIX; + strncpy(un.sun_path, path.chars(), sizeof(un.sun_path)); + + if (bind(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0) + { + close(sockfd); + return err(errno); + } + + if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); } + server->m_blocking = blocking; + + fcntl(sockfd, F_SETFD, FD_CLOEXEC); + + server->m_fd = sockfd; + return server; + } + + Result LocalServer::listen(int backlog) + { + if (::listen(m_fd, backlog) < 0) return err(errno); + return {}; + } + + Result> LocalServer::accept() + { + int fd = ::accept(m_fd, nullptr, nullptr); + if (fd < 0) return err(errno); + if (!m_blocking) fcntl(fd, F_SETFL, O_NONBLOCK); + return make_shared(fd); + } + + LocalServer::~LocalServer() + { + close(m_fd); + } + + LocalServer::Client::Client(int fd) : m_fd(fd) + { + } + + LocalServer::Client::~Client() + { + if (m_fd >= 0) close(m_fd); + } + + Result LocalServer::Client::recv(u8* buf, usize length) + { + ssize_t nread = read(m_fd, buf, length); + if (nread < 0) return err(errno); + return nread; + } + + Result LocalServer::Client::send(const u8* buf, usize length) + { + ssize_t nwrite = write(m_fd, buf, length); + if (nwrite < 0) return err(errno); + return nwrite; + } + + void LocalServer::Client::disconnect() + { + close(m_fd); + m_fd = -1; + } +} -- 2.34.1 From 335911c2872e761514926f851af99d2749612358 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 16:40:17 +0200 Subject: [PATCH 006/103] wind: Create a local server object --- wind/Screen.cpp | 10 ++++------ wind/main.cpp | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/wind/Screen.cpp b/wind/Screen.cpp index b44874ba..8d0e224c 100644 --- a/wind/Screen.cpp +++ b/wind/Screen.cpp @@ -7,18 +7,16 @@ Result Screen::open() { - int fd = ::open("/dev/fb0", O_RDWR | O_CLOEXEC); + int fd = ::open("/dev/fb0", O_RDWR); if (fd < 0) return err(errno); int width = ioctl(fd, FB_GET_WIDTH); int height = ioctl(fd, FB_GET_HEIGHT); void* p = mmap(nullptr, width * height * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) - { - close(fd); - return err(errno); - } + close(fd); + + if (p == MAP_FAILED) { return err(errno); } Screen screen; diff --git a/wind/main.cpp b/wind/main.cpp index 318da9b9..bda0fa86 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,9 @@ Result luna_main(int argc, char** argv) auto screen = TRY(Screen::open()); + auto server = TRY(os::LocalServer::create(socket_path, false)); + TRY(server->listen(20)); + Mouse mouse_pointer { screen.canvas() }; ioctl(STDIN_FILENO, TTYSETGFX, 1); @@ -67,14 +71,17 @@ Result luna_main(int argc, char** argv) ui::Color background = ui::BLACK; + Vector> clients; + while (1) { struct pollfd fds[] = { { .fd = mouse->fd(), .events = POLLIN, .revents = 0 }, { .fd = keyboard->fd(), .events = POLLIN, .revents = 0 }, + { .fd = server->fd(), .events = POLLIN, .revents = 0 }, }; - int rc = poll(fds, 2, 1000); + int rc = poll(fds, 3, 1000); if (!rc) continue; if (rc < 0) { os::println("poll: error: %s", strerror(errno)); } @@ -90,6 +97,12 @@ Result luna_main(int argc, char** argv) TRY(keyboard->read_typed(packet)); background = ui::Color::from_rgb(0x00, 0x00, packet.key * 2); } + if (fds[2].revents & POLLIN) + { + auto client = TRY(server->accept()); + os::println("wind: New client connected!"); + TRY(clients.try_append(client)); + } screen.canvas().fill(background); mouse_pointer.draw(screen.canvas()); -- 2.34.1 From 8859fc3d6af24b991708668b0f5e32550d796f97 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 17:38:49 +0200 Subject: [PATCH 007/103] libui+wind: (Draggable) windows --- base/etc/init/99-login | 2 ++ libui/include/ui/Canvas.h | 30 ++++++++++++++++++++++++++++-- libui/include/ui/Color.h | 31 ++++++++++++++++++++++++++++++- libui/include/ui/Rect.h | 23 ++++++++++++++++++++++- libui/src/Canvas.cpp | 20 +++++++++++--------- libui/src/Rect.cpp | 16 +++++++++++----- wind/CMakeLists.txt | 2 ++ wind/Mouse.cpp | 36 ++++++++++++++++++++++++++++++++++-- wind/Mouse.h | 6 +++++- wind/Window.cpp | 20 ++++++++++++++++++++ wind/Window.h | 19 +++++++++++++++++++ wind/main.cpp | 16 +++++++++++----- 12 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 wind/Window.cpp create mode 100644 wind/Window.h diff --git a/base/etc/init/99-login b/base/etc/init/99-login index 29cdbcaa..bb7e7ac9 100644 --- a/base/etc/init/99-login +++ b/base/etc/init/99-login @@ -1,4 +1,6 @@ Name=login Description=Start the display server. Command=/usr/bin/wind --user=selene +StandardOutput=/dev/uart0 +StandardError=/dev/uart0 Restart=true diff --git a/libui/include/ui/Canvas.h b/libui/include/ui/Canvas.h index c90f0ae7..02a662ba 100644 --- a/libui/include/ui/Canvas.h +++ b/libui/include/ui/Canvas.h @@ -17,14 +17,40 @@ namespace ui int stride; u8* ptr; + /** + * @brief Create a new Canvas object. + * + * @param ptr The memory to use for the canvas. It must be of at least width * height * 4 bytes of length. + * @param width The width of the canvas. + * @param height The height of the canvas. + * @return Canvas The new Canvas object. + */ static Canvas create(u8* ptr, int width, int height); - Result subcanvas(Point begin, int width, int height, bool clamp); + /** + * @brief Return a new Canvas that represents a subsection of the current one. + * + * @param rect The dimensions of the new canvas. If these exceed the bounds of the current canvas, they will be + * clamped. + * @return Canvas The new Canvas object. + */ + Canvas subcanvas(Rect rect); + + /** + * @brief Return the dimensions of the current canvas. + * + * @return Rect This canvas's dimensions, as a Rect object. + */ Rect rect() { - return Rect { .begin = { 0, 0 }, .width = width, .height = height }; + return Rect { .pos = { 0, 0 }, .width = width, .height = height }; } + /** + * @brief Fill the entire canvas with one color. + * + * @param color The color to use. + */ void fill(Color color); }; }; diff --git a/libui/include/ui/Color.h b/libui/include/ui/Color.h index 95a8d974..39d8db91 100644 --- a/libui/include/ui/Color.h +++ b/libui/include/ui/Color.h @@ -4,7 +4,7 @@ namespace ui { /** - * @brief A 32-bit RGBA color. + * @brief A 32-bit ARGB color. */ struct Color { @@ -13,16 +13,39 @@ namespace ui u8 colors[4]; }; + /** + * @brief Construct a new color from a 32-bit ARGB integer. + * + * @param raw The integer representing the color. + * @return constexpr Color The new color. + */ static constexpr Color from_u32(u32 raw) { return Color { .raw = raw }; } + /** + * @brief Construct a new color from its separate RGBA values (from 0 to 255). + * + * @param red The red value. + * @param green The green value. + * @param blue The blue value. + * @param alpha The alpha value. + * @return constexpr Color The new color. + */ static constexpr Color from_rgba(u8 red, u8 green, u8 blue, u8 alpha) { return Color { .colors = { blue, green, red, alpha } }; } + /** + * @brief Construct a new color from its separate RGB values (from 0 to 255). + * + * @param red The red value. + * @param green The green value. + * @param blue The blue value. + * @return constexpr Color The new color. + */ static constexpr Color from_rgb(u8 red, u8 green, u8 blue) { return from_rgba(red, green, blue, 0xff); @@ -31,4 +54,10 @@ namespace ui static constexpr Color WHITE = Color::from_rgb(0xff, 0xff, 0xff); static constexpr Color BLACK = Color::from_rgb(0x00, 0x00, 0x00); + + static constexpr Color BLUE = Color::from_rgb(0x00, 0x00, 0xff); + static constexpr Color GREEN = Color::from_rgb(0x00, 0xff, 0x00); + static constexpr Color RED = Color::from_rgb(0xff, 0x00, 0x00); + + static constexpr Color CYAN = Color::from_rgb(0x00, 0xff, 0xff); }; diff --git a/libui/include/ui/Rect.h b/libui/include/ui/Rect.h index 6d2bdec6..774013ad 100644 --- a/libui/include/ui/Rect.h +++ b/libui/include/ui/Rect.h @@ -8,11 +8,32 @@ namespace ui */ struct Rect { - Point begin; + Point pos; int width; int height; + /** + * @brief Check if a point is contained in this rectangle. + * + * @param point The point to check. + * @return true The point is contained inside the rectangle. + * @return false The point is not contained inside the rectangle. + */ bool contains(Point point); + + /** + * @brief Normalize a point to fit inside this rectangle. + * + * @param point The original point. + * @return Point The normalized point. + */ Point normalize(Point point); + + /** + * @brief Return a copy of this rectangle with no negative values (normalized to 0). + * + * @return Rect The new rectangle. + */ + Rect absolute(); }; } diff --git a/libui/src/Canvas.cpp b/libui/src/Canvas.cpp index 812610c0..d0e1b41e 100644 --- a/libui/src/Canvas.cpp +++ b/libui/src/Canvas.cpp @@ -8,22 +8,24 @@ namespace ui return Canvas { .width = width, .height = height, .stride = width, .ptr = ptr }; } - Result Canvas::subcanvas(Point begin, int w, int h, bool clamp) + Canvas Canvas::subcanvas(Rect rect) { - if (!clamp) + if (rect.pos.x < 0) { - if (begin.x + w > width) return err(ERANGE); - if (begin.y + h > height) return err(ERANGE); + rect.pos.x = 0; + rect.width = rect.width + rect.pos.x; } - else + if (rect.pos.y < 0) { - if (begin.x + w > width) w = width - begin.x; - if (begin.y + h > height) h = height - begin.y; + rect.pos.y = 0; + rect.height = rect.height + rect.pos.y; } + if (rect.pos.x + rect.width > width) rect.width = width - rect.pos.x; + if (rect.pos.y + rect.height > height) rect.height = height - rect.pos.y; - u8* p = ptr + begin.x * sizeof(Color) + (begin.y * sizeof(Color) * stride); + u8* p = ptr + rect.pos.x * sizeof(Color) + (rect.pos.y * sizeof(Color) * stride); - return Canvas { .width = w, .height = h, .stride = stride, .ptr = p }; + return Canvas { .width = rect.width, .height = rect.height, .stride = stride, .ptr = p }; } void Canvas::fill(Color color) diff --git a/libui/src/Rect.cpp b/libui/src/Rect.cpp index 5d180eab..444157a0 100644 --- a/libui/src/Rect.cpp +++ b/libui/src/Rect.cpp @@ -4,15 +4,21 @@ namespace ui { bool Rect::contains(Point point) { - return point.x >= begin.x && point.y >= begin.y && point.x <= begin.x + width && point.y <= begin.y + height; + return (point.x >= pos.x) && (point.y >= pos.y) && (point.x <= (pos.x + width)) && + (point.y <= (pos.y + height)); } Point Rect::normalize(Point point) { - if (point.x < begin.x) point.x = begin.x; - if (point.y < begin.y) point.y = begin.y; - if (point.x > begin.x + width) point.x = begin.x + width; - if (point.y > begin.y + height) point.y = begin.y + height; + if (point.x < pos.x) point.x = pos.x; + if (point.y < pos.y) point.y = pos.y; + if (point.x > pos.x + width) point.x = pos.x + width; + if (point.y > pos.y + height) point.y = pos.y + height; return point; } + + Rect Rect::absolute() + { + return Rect { ui::Point { pos.x < 0 ? 0 : pos.x, pos.y < 0 ? 0 : pos.y }, width, height }; + } }; diff --git a/wind/CMakeLists.txt b/wind/CMakeLists.txt index a7bb5bc9..8f5db052 100644 --- a/wind/CMakeLists.txt +++ b/wind/CMakeLists.txt @@ -4,6 +4,8 @@ set(SOURCES Screen.cpp Mouse.h Mouse.cpp + Window.h + Window.cpp ) add_executable(wind ${SOURCES}) diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index ed1f171f..b807e5c1 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -1,4 +1,5 @@ #include "Mouse.h" +#include Mouse::Mouse(ui::Canvas& screen) { @@ -9,13 +10,44 @@ Mouse::Mouse(ui::Canvas& screen) void Mouse::draw(ui::Canvas& screen) { - auto canvas = screen.subcanvas(m_position, 10, 10, true).release_value(); + auto canvas = screen.subcanvas(ui::Rect { m_position, 10, 10 }); canvas.fill(ui::WHITE); } -void Mouse::move(const moon::MousePacket& packet) +void Mouse::update(const moon::MousePacket& packet) { m_position.x += packet.xdelta; m_position.y -= packet.ydelta; m_position = m_screen_rect.normalize(m_position); + + if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window) + { + g_windows.for_each_reversed([this](Window* window) { + if (!this->m_dragging_window && window->surface.contains(this->m_position)) + { + this->m_dragging_window = window; + this->m_initial_drag_position = ui::Point { this->m_position.x - window->surface.pos.x, + this->m_position.y - window->surface.pos.y }; + os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, + window->surface.pos.y, window->surface.width, window->surface.height, + this->m_initial_drag_position.x, this->m_initial_drag_position.y); + } + }); + } + else if (m_dragging_window && !(packet.buttons & moon::MouseButton::Left)) + { + os::println("Stopped drag: window at (%d,%d,%d,%d) with offset (%d,%d)", m_dragging_window->surface.pos.x, + m_dragging_window->surface.pos.y, m_dragging_window->surface.width, + m_dragging_window->surface.height, this->m_initial_drag_position.x, + this->m_initial_drag_position.y); + m_dragging_window = nullptr; + } + + if (m_dragging_window) + { + m_dragging_window->surface.pos = + ui::Point { m_position.x - m_initial_drag_position.x, m_position.y - m_initial_drag_position.y }; + m_dragging_window->surface = m_dragging_window->surface.absolute(); + m_dragging_window->focus(); + } } diff --git a/wind/Mouse.h b/wind/Mouse.h index c188ceea..17d79c6d 100644 --- a/wind/Mouse.h +++ b/wind/Mouse.h @@ -1,5 +1,6 @@ #pragma once #include "Screen.h" +#include "Window.h" #include #include @@ -8,11 +9,14 @@ class Mouse public: Mouse(ui::Canvas& screen); - void move(const moon::MousePacket& packet); + void update(const moon::MousePacket& packet); void draw(ui::Canvas& screen); private: ui::Point m_position; ui::Rect m_screen_rect; + + Window* m_dragging_window = nullptr; + ui::Point m_initial_drag_position; }; diff --git a/wind/Window.cpp b/wind/Window.cpp new file mode 100644 index 00000000..fc2918f2 --- /dev/null +++ b/wind/Window.cpp @@ -0,0 +1,20 @@ +#include "Window.h" + +LinkedList g_windows; + +void Window::draw(ui::Canvas& screen) +{ + screen.subcanvas(surface).fill(color); +} + +void Window::focus() +{ + // Bring the window to the front of the list. + g_windows.remove(this); + g_windows.append(this); +} + +Window::Window(ui::Rect r, ui::Color c) : surface(r), color(c) +{ + g_windows.append(this); +} diff --git a/wind/Window.h b/wind/Window.h new file mode 100644 index 00000000..45f1655b --- /dev/null +++ b/wind/Window.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include +#include + +struct Window : public LinkedListNode +{ + ui::Rect surface; + ui::Color color; + + Window(ui::Rect, ui::Color); + + void focus(); + + void draw(ui::Canvas& screen); +}; + +extern LinkedList g_windows; diff --git a/wind/main.cpp b/wind/main.cpp index bda0fa86..ae8c2bcd 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -1,5 +1,6 @@ #include "Mouse.h" #include "Screen.h" +#include "Window.h" #include #include #include @@ -71,10 +72,19 @@ Result luna_main(int argc, char** argv) ui::Color background = ui::BLACK; + TRY(make(ui::Rect { 200, 200, 600, 400 }, ui::GREEN)); + TRY(make(ui::Rect { 100, 100, 300, 200 }, ui::RED)); + TRY(make(ui::Rect { 600, 130, 350, 250 }, ui::CYAN)); + Vector> clients; while (1) { + screen.canvas().fill(background); + for (auto* window : g_windows) window->draw(screen.canvas()); + mouse_pointer.draw(screen.canvas()); + screen.sync(); + struct pollfd fds[] = { { .fd = mouse->fd(), .events = POLLIN, .revents = 0 }, { .fd = keyboard->fd(), .events = POLLIN, .revents = 0 }, @@ -89,7 +99,7 @@ Result luna_main(int argc, char** argv) { moon::MousePacket packet; TRY(mouse->read_typed(packet)); - mouse_pointer.move(packet); + mouse_pointer.update(packet); } if (fds[1].revents & POLLIN) { @@ -103,9 +113,5 @@ Result luna_main(int argc, char** argv) os::println("wind: New client connected!"); TRY(clients.try_append(client)); } - - screen.canvas().fill(background); - mouse_pointer.draw(screen.canvas()); - screen.sync(); } } -- 2.34.1 From b6c85595bee8830cf0504a8491cb9087e72f35b7 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 17:39:49 +0200 Subject: [PATCH 008/103] base: Remove startup items not necessary for GUI startup --- base/etc/init/01-motd | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 base/etc/init/01-motd diff --git a/base/etc/init/01-motd b/base/etc/init/01-motd deleted file mode 100644 index 3c6d5aac..00000000 --- a/base/etc/init/01-motd +++ /dev/null @@ -1,4 +0,0 @@ -Name=motd -Description=Show the message of the day to the user. -Command=/usr/bin/cat /etc/motd -Wait=true -- 2.34.1 From 01da7be57cad323c5a7a5220b144f9a36232a7d5 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 17:50:07 +0200 Subject: [PATCH 009/103] libui: Remove unnecessary stuff --- libui/CMakeLists.txt | 1 - libui/src/Canvas.cpp | 1 - libui/src/Color.cpp | 0 3 files changed, 2 deletions(-) delete mode 100644 libui/src/Color.cpp diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index b9bf2e94..b33466c7 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -5,7 +5,6 @@ file(GLOB HEADERS include/ui/*.h) set(SOURCES ${HEADERS} src/Canvas.cpp - src/Color.cpp src/Rect.cpp ) diff --git a/libui/src/Canvas.cpp b/libui/src/Canvas.cpp index d0e1b41e..8ea75987 100644 --- a/libui/src/Canvas.cpp +++ b/libui/src/Canvas.cpp @@ -1,4 +1,3 @@ -#include #include namespace ui diff --git a/libui/src/Color.cpp b/libui/src/Color.cpp deleted file mode 100644 index e69de29b..00000000 -- 2.34.1 From 07dc7064f83f2c127588f4280ddb2996a49ebef3 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 17:52:19 +0200 Subject: [PATCH 010/103] libui: Add getters for separate color values --- libui/include/ui/Color.h | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/libui/include/ui/Color.h b/libui/include/ui/Color.h index 39d8db91..4fbf29c2 100644 --- a/libui/include/ui/Color.h +++ b/libui/include/ui/Color.h @@ -13,6 +13,46 @@ namespace ui u8 colors[4]; }; + /** + * @brief Return the blue value of this color. + * + * @return constexpr u8 The blue value. + */ + constexpr u8 red() const + { + return colors[2]; + } + + /** + * @brief Return the green value of this color. + * + * @return constexpr u8 The green value. + */ + constexpr u8 green() const + { + return colors[1]; + } + + /** + * @brief Return the blue value of this color. + * + * @return constexpr u8 The blue value. + */ + constexpr u8 blue() const + { + return colors[0]; + } + + /** + * @brief Return the alpha value of this color. + * + * @return constexpr u8 The alpha value. + */ + constexpr u8 alpha() const + { + return colors[3]; + } + /** * @brief Construct a new color from a 32-bit ARGB integer. * -- 2.34.1 From 7441e396b3296324174b91e9cf0c4e865f49b88e Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 3 Aug 2023 18:32:58 +0200 Subject: [PATCH 011/103] libui: Remove redundant statement --- libui/src/Canvas.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libui/src/Canvas.cpp b/libui/src/Canvas.cpp index 8ea75987..a0c1c647 100644 --- a/libui/src/Canvas.cpp +++ b/libui/src/Canvas.cpp @@ -9,16 +9,8 @@ namespace ui Canvas Canvas::subcanvas(Rect rect) { - if (rect.pos.x < 0) - { - rect.pos.x = 0; - rect.width = rect.width + rect.pos.x; - } - if (rect.pos.y < 0) - { - rect.pos.y = 0; - rect.height = rect.height + rect.pos.y; - } + if (rect.pos.x < 0) rect.pos.x = 0; + if (rect.pos.y < 0) rect.pos.y = 0; if (rect.pos.x + rect.width > width) rect.width = width - rect.pos.x; if (rect.pos.y + rect.height > height) rect.height = height - rect.pos.y; -- 2.34.1 From 2b3e9b778af397c4807c8c69c9cb745bae285a0f Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 13:08:02 +0200 Subject: [PATCH 012/103] libui: Add Rect::relative --- libui/include/ui/Rect.h | 8 ++++++++ libui/src/Rect.cpp | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/libui/include/ui/Rect.h b/libui/include/ui/Rect.h index 774013ad..6503a6ed 100644 --- a/libui/include/ui/Rect.h +++ b/libui/include/ui/Rect.h @@ -29,6 +29,14 @@ namespace ui */ Point normalize(Point point); + /** + * @brief Transform an absolute position to a position relative to this rectangle. + * + * @param pos The original absolute position. + * @return Point The position relative to this rectangle. + */ + Point relative(Point pos); + /** * @brief Return a copy of this rectangle with no negative values (normalized to 0). * diff --git a/libui/src/Rect.cpp b/libui/src/Rect.cpp index 444157a0..448655ad 100644 --- a/libui/src/Rect.cpp +++ b/libui/src/Rect.cpp @@ -17,6 +17,14 @@ namespace ui return point; } + Point Rect::relative(Point point) + { + point = normalize(point); + point.x -= pos.x; + point.y -= pos.y; + return point; + } + Rect Rect::absolute() { return Rect { ui::Point { pos.x < 0 ? 0 : pos.x, pos.y < 0 ? 0 : pos.y }, width, height }; -- 2.34.1 From 277953065ab591c83f17bcfa5c19c20e57964330 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 13:08:48 +0200 Subject: [PATCH 013/103] wind: Reorder drag sequence --- wind/Mouse.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index b807e5c1..bc1a3461 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -20,21 +20,7 @@ void Mouse::update(const moon::MousePacket& packet) m_position.y -= packet.ydelta; m_position = m_screen_rect.normalize(m_position); - if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window) - { - g_windows.for_each_reversed([this](Window* window) { - if (!this->m_dragging_window && window->surface.contains(this->m_position)) - { - this->m_dragging_window = window; - this->m_initial_drag_position = ui::Point { this->m_position.x - window->surface.pos.x, - this->m_position.y - window->surface.pos.y }; - os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, - window->surface.pos.y, window->surface.width, window->surface.height, - this->m_initial_drag_position.x, this->m_initial_drag_position.y); - } - }); - } - else if (m_dragging_window && !(packet.buttons & moon::MouseButton::Left)) + if (m_dragging_window && !(packet.buttons & moon::MouseButton::Left)) { os::println("Stopped drag: window at (%d,%d,%d,%d) with offset (%d,%d)", m_dragging_window->surface.pos.x, m_dragging_window->surface.pos.y, m_dragging_window->surface.width, @@ -48,6 +34,21 @@ void Mouse::update(const moon::MousePacket& packet) m_dragging_window->surface.pos = ui::Point { m_position.x - m_initial_drag_position.x, m_position.y - m_initial_drag_position.y }; m_dragging_window->surface = m_dragging_window->surface.absolute(); - m_dragging_window->focus(); + } + + else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window) + { + g_windows.for_each_reversed([this](Window* window) { + if (!this->m_dragging_window && window->surface.contains(this->m_position)) + { + this->m_dragging_window = window; + this->m_initial_drag_position = ui::Point { this->m_position.x - window->surface.pos.x, + this->m_position.y - window->surface.pos.y }; + os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, + window->surface.pos.y, window->surface.width, window->surface.height, + this->m_initial_drag_position.x, this->m_initial_drag_position.y); + window->focus(); + } + }); } } -- 2.34.1 From 7ab0c6b72b509b5d77094e0af1906084a57c0083 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 15:10:33 +0200 Subject: [PATCH 014/103] libluna: Add assignment operators to Buffer --- libluna/include/luna/Buffer.h | 3 +++ libluna/src/Buffer.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/libluna/include/luna/Buffer.h b/libluna/include/luna/Buffer.h index cd711c0a..3a561eaf 100644 --- a/libluna/include/luna/Buffer.h +++ b/libluna/include/luna/Buffer.h @@ -23,6 +23,9 @@ class Buffer Buffer(Buffer&& other); Buffer(const Buffer& other) = delete; // For now. + Buffer& operator=(Buffer&&); + Buffer& operator=(const Buffer&) = delete; + /** * @brief Create a Buffer object, allocating a specific amount of memory for it. * diff --git a/libluna/src/Buffer.cpp b/libluna/src/Buffer.cpp index b0a0b29b..188ec368 100644 --- a/libluna/src/Buffer.cpp +++ b/libluna/src/Buffer.cpp @@ -24,6 +24,16 @@ Buffer::Buffer(Buffer&& other) : m_data(other.data()), m_size(other.size()) other.m_data = nullptr; } +Buffer& Buffer::operator=(Buffer&& other) +{ + if (&other == this) return *this; + if (m_data) free_impl(m_data); + m_data = other.m_data; + m_size = other.m_size; + other.m_data = nullptr; + return *this; +} + Buffer::~Buffer() { if (m_data) free_impl(m_data); -- 2.34.1 From 16fa55899ecb57a862fd7660fe3f660a2a819f8c Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 16:03:25 +0200 Subject: [PATCH 015/103] libui: Rename Rect::absolute to normalized and add a new absolute function --- libui/include/ui/Rect.h | 18 +++++++++++++++++- libui/src/Rect.cpp | 14 +++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/libui/include/ui/Rect.h b/libui/include/ui/Rect.h index 6503a6ed..da6a2603 100644 --- a/libui/include/ui/Rect.h +++ b/libui/include/ui/Rect.h @@ -37,11 +37,27 @@ namespace ui */ Point relative(Point pos); + /** + * @brief Transform a position relative to this rectangle to an absolute position. + * + * @param pos The original relative position. + * @return Point The absolute position. + */ + Point absolute(Point pos); + + /** + * @brief Transform another rectangle relative to this one to an absolute rectangle. + * + * @param rect The original relative rectangle. + * @return Point The absolute rectangle. + */ + Rect absolute(Rect rect); + /** * @brief Return a copy of this rectangle with no negative values (normalized to 0). * * @return Rect The new rectangle. */ - Rect absolute(); + Rect normalized(); }; } diff --git a/libui/src/Rect.cpp b/libui/src/Rect.cpp index 448655ad..e96eb7e9 100644 --- a/libui/src/Rect.cpp +++ b/libui/src/Rect.cpp @@ -25,7 +25,19 @@ namespace ui return point; } - Rect Rect::absolute() + Point Rect::absolute(Point point) + { + point.x += pos.x; + point.y += pos.y; + return point; + } + + Rect Rect::absolute(Rect rect) + { + return Rect { absolute(rect.pos), rect.width, rect.height }; + } + + Rect Rect::normalized() { return Rect { ui::Point { pos.x < 0 ? 0 : pos.x, pos.y < 0 ? 0 : pos.y }, width, height }; } -- 2.34.1 From c6c32f34f2cb7c955ecf7796d435b54448e88b2a Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 16:05:42 +0200 Subject: [PATCH 016/103] libui: Add Color::GRAY --- libui/include/ui/Color.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libui/include/ui/Color.h b/libui/include/ui/Color.h index 4fbf29c2..f549abb5 100644 --- a/libui/include/ui/Color.h +++ b/libui/include/ui/Color.h @@ -94,6 +94,7 @@ namespace ui static constexpr Color WHITE = Color::from_rgb(0xff, 0xff, 0xff); static constexpr Color BLACK = Color::from_rgb(0x00, 0x00, 0x00); + static constexpr Color GRAY = Color::from_rgb(0x80, 0x80, 0x80); static constexpr Color BLUE = Color::from_rgb(0x00, 0x00, 0xff); static constexpr Color GREEN = Color::from_rgb(0x00, 0xff, 0x00); -- 2.34.1 From 23f7210a87b852fc4ccd7cf8eef9c9142ff04292 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 16:07:44 +0200 Subject: [PATCH 017/103] libui: Add PSF font loading and rendering --- .gitignore | 6 +- base/usr/share/fonts/Tamsyn-Bold.psf | Bin 0 -> 4679 bytes base/usr/share/fonts/Tamsyn-Regular.psf | Bin 0 -> 4677 bytes libui/CMakeLists.txt | 2 + libui/include/ui/Font.h | 55 +++++++++++ libui/src/Font.cpp | 117 ++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 base/usr/share/fonts/Tamsyn-Bold.psf create mode 100644 base/usr/share/fonts/Tamsyn-Regular.psf create mode 100644 libui/include/ui/Font.h create mode 100644 libui/src/Font.cpp diff --git a/.gitignore b/.gitignore index 20ec66d8..845a9e47 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,11 @@ build/ initrd/boot/moon env-local.sh initrd/bin/** -base/usr/** +base/usr/bin/** +base/usr/include/** +base/usr/lib/** +base/usr/share/pkgdb/** +!base/usr/share/fonts/* base/etc/skel/LICENSE .fakeroot kernel/config.cmake diff --git a/base/usr/share/fonts/Tamsyn-Bold.psf b/base/usr/share/fonts/Tamsyn-Bold.psf new file mode 100644 index 0000000000000000000000000000000000000000..4eb9107d3c38b3d5f62599a1be264719a00103d7 GIT binary patch literal 4679 zcmeHJSCbS~6mDy)x_p>gee5cx7R8(gR@+6|Q4E+sMa3LON87N&&MLaB?l_B>6%!(& zqLM@<7(r1`tLFR(-mKym_{iJmcW&sg%PPe)ck15h@8omt>D%|2y*IuVaGb(<#pMP@~_N2CX}RUnjDiRl_EP1PtiZ@=#it(_SpT;_VNzH^F954 z{_#KjjP3n-`|khn6g<^l7S9wppHF807#^x|t=6g4YDe7P>Gq-|Nh12y${X(O!Er=UMH%{U2I)>P zu1;3tT!|L6UOB-$RjZXsMms{T)v8tK>kC6l(rKe%C`mdpl8%jKJsAy+(|Qzn8%auq zsMhef8&;}e7-sTe|3I8hl49)#-A1ER66TNFss*0{sWztQ3J5qR7v>ZfV!^;KEjd4F9~{k;CHB}ZPrCS8rvY17%$ zV03tY&6ShGO&yNkTrtspyns!8>CT@)CO0+U=QFS4!=L1uRUv3viL%a zM@Gr=Op@xR0en4tegjsRt1<8E=W*iqGYHrL!)$}Rtt4oA5wW@Wam%6sm(uT z)f3h{_=R}XJfCFNQMEWY(VnpZlz)62ufUWF=+P6IdjJ`HNZI^6qg(?|yH7OhakClM z^Ybi;y8QWm*H$!kGD^vvfA2G5b=Z^o%k`$EZ|>j!uxbvX~V(TH$GOx)D538jmDA{)5u@p4pVJem#`XWs%g* zyzb%>Cbc9_QFE4gd8*7#TCH}c)f#beWcLTnm;U*w$nOjEU&892gzP>+eUaByO9Fg86fcXCnSFAN$l?c1+~>yeOOBR!lsjJ(=+1cS9c zMNEGsd0PWhBl{SHd^z8c5XA~rt9W=;Hk@L=2d&+ZSl-))7tsddX&|AqU5D{#)Y z70ogA$IXtqcb@u1tolW)@fPvC>UhO|?@c{TgLiC6i}8?R`GJiu!!EuI=MCWGZM+$u zM)~@6b@788pbcR5-*g;>Ir4fGvHB}w&8LJJpTkq`SMGk3%PSu^w0D(2ucoQ^YaCeFgyI0xtAJe-dUa3L;2 z4RbLMgSZ%%AjYK_!hBqY1xRo?>bL?|Vj-@=)wl-NViB&x^|%2y;wCJ{65Nbia4VML zHr$Rounc$NF5HcKa4+t|{dfQm;vqEfFdjh@kK!?;cpOjQNj!yNw6Gi_Xk!#l;~A{L zvv>|;Sc!2wj~6h34qimpWbvO%Zxec((c7ZA_O_w-6?)s5_&4Z%%fFqRyR{IeguOj# zs<%&<{pRs6$STN7kkycvA+JDQg}erN9r6a`O~_l2w;^jF??B##ya#z7@&RNmBfH1>{S}Cdg*U7RXk}Hpo|y?U1h_-$1^F?11cq?1Jou qd%4T-YdG@`(`|h62ilaSiQ*0soIQ7CI z%ZKWL!fcI5w}wIf*uUTZ`P2(Soz-f!^MWKP6qt5;l>BKs??RgBuzgN+{1(H}-TZl; zI1fE0J9|#Qea8M-JX&Au&nP(;Pptm}Uh;)7JROEB){jj#s~amsl}IX8Wb*}!#Rlit z&2$j7g<(FOE(O-vj(X3m;EHMW@rK?}tJOMY)VsUqYOJ(-`Z7UaOa?(l5yEWNBq5s( zEn_j)$R(U0`E7PfbdOw)iX1pgE$4EEm`-Q3$uO)`^h_!0?pZVtMI|d71<>IhMSK0Gncm(tBp)Xdrz50r81nP=V@q|JIv{CSO=W5B}Gfn)q9yHNA`$z z-T6-o7Tb80ZvS)mztIk`M&>*7&Ff@ikiFkce`9jx%zP`T2y_N84&5}vcZ=H?4_nw~@lcfvQB)nWnDJMJ*_{UtR}%8wQB-j9N?0F3mR{D`4}!AA%%w_a zoWH1ATexgtt?K+!&Zy?>buI6&*SpxPd^E7AhxX!lRDM-+>!D*)3M_w2&!qK?rqi6W zS7z+FOgcSm`Bg3IX0g#eqF>`;F87>>`B?yi@%kHP zN{rZF$@H?t_Kg2-{j`7d*R6j_S2*LNW+M{&rxTh=P45U*i}jUt0l0j)_$I`3osL94 zh!g9dO6dZK*PqxFz-W1sAI`D@xWyZq&UCWq&fnQ1Q~fyC3K^ zqkRw4zK3bg!`UF^-4`hDVaj`$_2yyvR4TN$uWD~E=)w{Bvye}yG39RiW zu(p@PTp|Cy;A184(dlmzw~W7X+*Om~^-Zbxy!!(6(;qf_Uoif~*!mY^ogc;S_*H(A znEOSaPw{;)l`iQ&Af<{?N_vhlfY_&HaI?UOK?|29}!p%?Q7u z1E)4`glf&hYCN@R3;PE4_3WG2Pw3gzdfZC9i~R`uLH0FD1kIg5h;|e(8AV)!YcT~= zF%8$@dfb2;F&#Id12ZraH)9rV!EDSyC+6Z-+=dcvM;UjZf-Xe36II-WZp=dsb@bqF z%ttTo!2&Eq9~NOT?nOWD!~Ix-2k;;s!cshpNAM_?;V}$gIUdIocoI+HX*`2x@f@DV z3wRMPVGu841zy3ccnz;(C5EsHZ{SS~<1I9F6z3e8+tA#O<_;BWa~GPs(cHtp_oBIv zj|1$7R3dZ=m7?Y`H5}KkCn2jL??Bc--i5pec^|SC@&V*S$VZTMkdGnjA)i1#g?t9t z0QnrU5%L9O6XZ+CX2@5NEs(Dv-$1^FY=wLW`5y8EWE*5VWCvs?WEW&NWDn#=$X>`k y$bQHH$U(><$WM@;A%`KqKz@b%1~~#b3i%!K2jm#!Psm@8 +#include +#include +#include + +#define PSF_FONT_MAGIC 0x864ab572 + +namespace ui +{ + class Font : public Shareable + { + public: + enum FontWeight + { + Regular, + Bold, + }; + + static Result> load(const os::Path& path); + static Result> load_builtin(StringView name, FontWeight weight); + + static SharedPtr default_font(); + static SharedPtr default_bold_font(); + + void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas); + void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas); + + int width() const + { + return m_psf_header.width; + } + + int height() const + { + return m_psf_header.height; + } + + private: + struct PSFHeader + { + u32 magic; + u32 version; // zero + u32 headersize; + u32 flags; // 0 if there's no unicode table + u32 numglyph; + u32 bytesperglyph; + int height; + int width; + }; + + PSFHeader m_psf_header; + Buffer m_font_data; + }; +}; diff --git a/libui/src/Font.cpp b/libui/src/Font.cpp new file mode 100644 index 00000000..c1291757 --- /dev/null +++ b/libui/src/Font.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +SharedPtr s_default_font = {}; +SharedPtr s_default_bold_font = {}; + +constexpr static int BYTES_PER_PIXEL = (int)sizeof(ui::Color); + +namespace ui +{ + Result> Font::load(const os::Path& path) + { + auto font = TRY(make_shared()); + + auto file = TRY(os::File::open(path, os::File::ReadOnly)); + + TRY(file->read_typed(font->m_psf_header)); + + if (font->m_psf_header.magic != PSF_FONT_MAGIC) + { + os::eprintln("ui::Font::load(%s) failed: font magic does not match PSF2 magic", path.name().chars()); + return err(ENOTSUP); + } + + if (font->m_psf_header.version != 0) + { + os::eprintln("ui::Font::load(%s) failed: font version is unsupported", path.name().chars()); + return err(ENOTSUP); + } + + if (font->m_psf_header.flags) + { + os::eprintln("ui::Font::load(%s) warning: font has a unicode table, which we're ignoring", + path.name().chars()); + // todo(); // Font has a unicode table, oh no! + } + + font->m_font_data = TRY(file->read_all()); // Read the rest of the file into the font data buffer. + + return font; + } + + Result> Font::load_builtin(StringView name, FontWeight weight) + { + auto path = TRY(String::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(), + weight == FontWeight::Bold ? "Bold" : "Regular")); + + return load(path.view()); + } + + SharedPtr Font::default_font() + { + if (s_default_font) return s_default_font; + s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value(); + return s_default_font; + } + + SharedPtr Font::default_bold_font() + { + if (s_default_bold_font) return s_default_bold_font; + s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value(); + return s_default_bold_font; + } + + void Font::render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas) + { + check(canvas.width == m_psf_header.width && canvas.height == m_psf_header.height); + + const wchar_t str[] = { codepoint, 0 }; + render(str, color, canvas); + } + + void Font::render(const wchar_t* text, ui::Color color, ui::Canvas& canvas) + { + usize len = wcslen(text); + + int height = m_psf_header.height; + int width = m_psf_header.width; + int last_char_width = width; + + if (canvas.width < (m_psf_header.width * static_cast(len))) + { + len = (canvas.width / width) + 1; + last_char_width = canvas.width % width; + } + + if (canvas.height < height) height = canvas.height; + + const int bytes_per_line = (m_psf_header.width + 7) / 8; + + for (usize i = 0; i < len; i++) + { + if (i + 1 == len) width = last_char_width; + wchar_t codepoint = text[i]; + + u8* glyph = + m_font_data.data() + (codepoint > 0 && codepoint < (wchar_t)m_psf_header.numglyph ? codepoint : 0) * + m_psf_header.bytesperglyph; + + u32 offset = (u32)i * (m_psf_header.width + 1) * BYTES_PER_PIXEL; + for (int y = 0; y < height; y++) + { + u32 line = offset; + int mask = 1 << (m_psf_header.width - 1); + for (int x = 0; x < m_psf_header.width; x++) + { + if (*((u32*)glyph) & mask) *(u32*)(canvas.ptr + line) = color.raw; + mask >>= 1; + line += BYTES_PER_PIXEL; + } + glyph += bytes_per_line; + offset += canvas.stride * BYTES_PER_PIXEL; + } + } + } +} -- 2.34.1 From f6ef79e7592c4d712aee7dd45720a9df33880db6 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 16:08:58 +0200 Subject: [PATCH 018/103] wind: Add window titlebars using ui::Font --- wind/Mouse.cpp | 23 +++++++++++++++-------- wind/Window.cpp | 23 +++++++++++++++++++++-- wind/Window.h | 6 +++++- wind/main.cpp | 6 +++--- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index bc1a3461..546e7765 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -33,22 +33,29 @@ void Mouse::update(const moon::MousePacket& packet) { m_dragging_window->surface.pos = ui::Point { m_position.x - m_initial_drag_position.x, m_position.y - m_initial_drag_position.y }; - m_dragging_window->surface = m_dragging_window->surface.absolute(); + m_dragging_window->surface = m_dragging_window->surface.normalized(); } else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window) { - g_windows.for_each_reversed([this](Window* window) { - if (!this->m_dragging_window && window->surface.contains(this->m_position)) + // Iterate from the end of the list, since windows at the beginning are stacked at the bottom and windows at the + // top are at the end. + for (Window* window = g_windows.last().value_or(nullptr); window; + window = g_windows.previous(window).value_or(nullptr)) + { + if (window->surface.absolute(window->titlebar).contains(m_position)) { - this->m_dragging_window = window; - this->m_initial_drag_position = ui::Point { this->m_position.x - window->surface.pos.x, - this->m_position.y - window->surface.pos.y }; + m_dragging_window = window; + m_initial_drag_position = + ui::Point { m_position.x - window->surface.pos.x, m_position.y - window->surface.pos.y }; os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, window->surface.pos.y, window->surface.width, window->surface.height, - this->m_initial_drag_position.x, this->m_initial_drag_position.y); + m_initial_drag_position.x, m_initial_drag_position.y); window->focus(); } - }); + else if (window->surface.absolute(window->contents).contains(m_position)) + break; // We don't want to continue iterating, otherwise this would take into account windows whose + // titlebar is underneath another window's contents! + } } } diff --git a/wind/Window.cpp b/wind/Window.cpp index fc2918f2..66638f2b 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -1,10 +1,26 @@ #include "Window.h" +#include +#include +#include LinkedList g_windows; void Window::draw(ui::Canvas& screen) { - screen.subcanvas(surface).fill(color); + auto window = screen.subcanvas(surface); + window.subcanvas(contents).fill(color); + + wchar_t buffer[4096]; + Utf8StringDecoder decoder(name.chars()); + decoder.decode(buffer, sizeof(buffer)).release_value(); + + auto font = ui::Font::default_font(); + + auto titlebar_canvas = window.subcanvas(titlebar); + titlebar_canvas.fill(ui::GRAY); + + auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height }); + font->render(buffer, ui::BLACK, textarea); } void Window::focus() @@ -14,7 +30,10 @@ void Window::focus() g_windows.append(this); } -Window::Window(ui::Rect r, ui::Color c) : surface(r), color(c) +Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), name(n) { + auto font = ui::Font::default_font(); + titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 }; + contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }; g_windows.append(this); } diff --git a/wind/Window.h b/wind/Window.h index 45f1655b..7b529413 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -7,9 +8,12 @@ struct Window : public LinkedListNode { ui::Rect surface; + ui::Rect titlebar; + ui::Rect contents; ui::Color color; + StringView name; - Window(ui::Rect, ui::Color); + Window(ui::Rect, ui::Color, StringView); void focus(); diff --git a/wind/main.cpp b/wind/main.cpp index ae8c2bcd..c095332d 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -72,9 +72,9 @@ Result luna_main(int argc, char** argv) ui::Color background = ui::BLACK; - TRY(make(ui::Rect { 200, 200, 600, 400 }, ui::GREEN)); - TRY(make(ui::Rect { 100, 100, 300, 200 }, ui::RED)); - TRY(make(ui::Rect { 600, 130, 350, 250 }, ui::CYAN)); + TRY(make(ui::Rect { 200, 200, 600, 400 }, ui::GREEN, "Calculator"_sv)); + TRY(make(ui::Rect { 100, 100, 300, 200 }, ui::RED, "Settings"_sv)); + TRY(make(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv)); Vector> clients; -- 2.34.1 From 916b19825dc847c6d8d64f0aa44770818b11e3d7 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 21:14:43 +0200 Subject: [PATCH 019/103] libui: Add an interface to fill a Canvas with an array of pixels --- libui/include/ui/Canvas.h | 8 ++++++++ libui/src/Canvas.cpp | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/libui/include/ui/Canvas.h b/libui/include/ui/Canvas.h index 02a662ba..74a34217 100644 --- a/libui/include/ui/Canvas.h +++ b/libui/include/ui/Canvas.h @@ -52,5 +52,13 @@ namespace ui * @param color The color to use. */ void fill(Color color); + + /** + * @brief Fill the canvas with pixels. + * + * @param pixels The array of pixels (must be at least width*height). + * @param stride The number of pixels to skip to go to the next line. + */ + void fill(u32* pixels, int stride); }; }; diff --git a/libui/src/Canvas.cpp b/libui/src/Canvas.cpp index a0c1c647..d14bc768 100644 --- a/libui/src/Canvas.cpp +++ b/libui/src/Canvas.cpp @@ -33,4 +33,21 @@ namespace ui p += stride * sizeof(Color); } } + + void Canvas::fill(u32* pixels, int _stride) + { + u8* p = ptr; + for (int i = 0; i < height; i++) + { + u32* colorp = (u32*)p; + for (int j = 0; j < width; j++) + { + u32 pix = pixels[j]; + if (Color::from_u32(pix).alpha() == 0xff) *colorp = pix; + colorp++; + } + pixels += _stride; + p += stride * sizeof(Color); + } + } } -- 2.34.1 From dc91d047de40a36c72daa7cbe227ccc55fe7f525 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 21:16:31 +0200 Subject: [PATCH 020/103] libui: Add support for TGA image loading --- libui/CMakeLists.txt | 1 + libui/include/ui/Image.h | 71 ++++++++++++++++++++++++++++++++++++++++ libui/src/Image.cpp | 24 ++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 libui/include/ui/Image.h create mode 100644 libui/src/Image.cpp diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index c28eb3e2..1a0fa74b 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES src/Canvas.cpp src/Rect.cpp src/Font.cpp + src/Image.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/Image.h b/libui/include/ui/Image.h new file mode 100644 index 00000000..fa1cdb39 --- /dev/null +++ b/libui/include/ui/Image.h @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include + +namespace ui +{ + /** + * @brief An image in the TGA file format. + */ + class Image : public Shareable + { + public: + /** + * @brief Load a new TGA image from a file. + * + * @param path The path to open. + * @return Result> An error, or a new Image object. + */ + static Result> load(const os::Path& path); + + /** + * @brief Return the array of pixels contained in the image. + * + * @return u32* The array of pixels. + */ + u32* pixels() + { + return (u32*)m_image_data.data(); + } + + /** + * @brief Return the width of the image. + * + * @return u16 The width. + */ + u16 width() + { + return m_tga_header.w; + } + + /** + * @brief Return the height of the image. + * + * @return u16 The height. + */ + u16 height() + { + return m_tga_header.h; + } + + private: + struct [[gnu::packed]] TGAHeader + { + u8 idlen; + u8 colormap; + u8 encoding; + u16 cmaporig, cmaplen; + u8 cmapent; + u16 x; + u16 y; + u16 w; + u16 h; + u8 bpp; + u8 pixeltype; + }; + + TGAHeader m_tga_header; + Buffer m_image_data; + }; +} diff --git a/libui/src/Image.cpp b/libui/src/Image.cpp new file mode 100644 index 00000000..de6051ad --- /dev/null +++ b/libui/src/Image.cpp @@ -0,0 +1,24 @@ +#include +#include + +namespace ui +{ + Result> Image::load(const os::Path& path) + { + auto image = TRY(make_shared()); + auto file = TRY(os::File::open(path, os::File::ReadOnly)); + + TRY(file->read_typed(image->m_tga_header)); + + if (image->m_tga_header.encoding != 2) todo(); + if (image->m_tga_header.bpp != 32) todo(); + + Buffer image_id; + TRY(file->read(image_id, image->m_tga_header.idlen)); + + TRY(file->read(image->m_image_data, + image->m_tga_header.w * image->m_tga_header.h * (image->m_tga_header.bpp / 8))); + + return image; + } +} -- 2.34.1 From 6d78fc64f9c5e02bd21a125139750a4981fc02fb Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 21:17:50 +0200 Subject: [PATCH 021/103] wind: Add a close button to windows using a TGA icon --- base/usr/share/icons/16x16/app-close.tga | Bin 0 -> 1068 bytes wind/Mouse.cpp | 16 +++++++++++++--- wind/Window.cpp | 12 ++++++++++++ wind/Window.h | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 base/usr/share/icons/16x16/app-close.tga diff --git a/base/usr/share/icons/16x16/app-close.tga b/base/usr/share/icons/16x16/app-close.tga new file mode 100644 index 0000000000000000000000000000000000000000..9ced18384d2da769148217b591a9e5879e42b62f GIT binary patch literal 1068 zcmbtOTMB?M3@e^sNAS}hy~TuqDEPwrUqszUAe6EPD@d9&$rR}Xrbm9>1_lgjEFbF0JBIHPy=^R9g<&bhePPD@^A!At%D-iR1W!;LZJ pE4}f%+#PrBU?1x`qq*g^G@S#l@gkGObHDr@jsurface.absolute(window->titlebar).contains(m_position)) + if (window->surface.absolute(window->close_button).contains(m_position)) + { + // Close button pressed + g_windows.remove(window); + delete window; + break; + } + else if (window->surface.absolute(window->titlebar).contains(m_position)) { m_dragging_window = window; - m_initial_drag_position = - ui::Point { m_position.x - window->surface.pos.x, m_position.y - window->surface.pos.y }; + m_initial_drag_position = window->surface.relative(m_position); os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, window->surface.pos.y, window->surface.width, window->surface.height, m_initial_drag_position.x, m_initial_drag_position.y); window->focus(); + break; } else if (window->surface.absolute(window->contents).contains(m_position)) + { + window->focus(); break; // We don't want to continue iterating, otherwise this would take into account windows whose // titlebar is underneath another window's contents! + } } } } diff --git a/wind/Window.cpp b/wind/Window.cpp index 66638f2b..09306bef 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -2,9 +2,12 @@ #include #include #include +#include LinkedList g_windows; +static SharedPtr g_close_icon; + void Window::draw(ui::Canvas& screen) { auto window = screen.subcanvas(surface); @@ -21,6 +24,12 @@ void Window::draw(ui::Canvas& screen) auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height }); font->render(buffer, ui::BLACK, textarea); + + if (g_close_icon) + { + auto close_area = window.subcanvas(close_button); + close_area.fill(g_close_icon->pixels(), g_close_icon->width()); + } } void Window::focus() @@ -34,6 +43,9 @@ Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), na { auto font = ui::Font::default_font(); titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 }; + close_button = ui::Rect { surface.width - 26, 10, 16, 16 }; contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }; g_windows.append(this); + + if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").value_or({}); } diff --git a/wind/Window.h b/wind/Window.h index 7b529413..86f638d2 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -9,6 +9,7 @@ struct Window : public LinkedListNode { ui::Rect surface; ui::Rect titlebar; + ui::Rect close_button; ui::Rect contents; ui::Color color; StringView name; -- 2.34.1 From 25ad2b17aa6ae6475a030cfcfd4d512ee894fea2 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 21:18:09 +0200 Subject: [PATCH 022/103] wind: Render an actual TGA mouse cursor --- base/usr/share/cursors/default.tga | Bin 0 -> 1004 bytes wind/Mouse.cpp | 10 ++++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 base/usr/share/cursors/default.tga diff --git a/base/usr/share/cursors/default.tga b/base/usr/share/cursors/default.tga new file mode 100644 index 0000000000000000000000000000000000000000..ef3bb7a0b14996e69219c755d329a7e8e230de91 GIT binary patch literal 1004 zcmZQzU}As)9tIHx1q~qYKNQ0x2+=V0AetEP|6yJ +#include + +static SharedPtr g_mouse_cursor; Mouse::Mouse(ui::Canvas& screen) { m_position.x = screen.width / 2; m_position.y = screen.height / 2; m_screen_rect = screen.rect(); + + g_mouse_cursor = ui::Image::load("/usr/share/cursors/default.tga").value_or({}); } void Mouse::draw(ui::Canvas& screen) { - auto canvas = screen.subcanvas(ui::Rect { m_position, 10, 10 }); - canvas.fill(ui::WHITE); + if (!g_mouse_cursor) return; + auto canvas = screen.subcanvas(ui::Rect { m_position, g_mouse_cursor->width(), g_mouse_cursor->height() }); + canvas.fill(g_mouse_cursor->pixels(), g_mouse_cursor->width()); } void Mouse::update(const moon::MousePacket& packet) -- 2.34.1 From 35d2bd6931b39f79b196eb5e2e134704d85ee59e Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 6 Aug 2023 12:07:43 +0200 Subject: [PATCH 023/103] libui: Render font characters properly with no spacing, matching the width calculations --- libui/src/Font.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libui/src/Font.cpp b/libui/src/Font.cpp index c1291757..36cc3e0c 100644 --- a/libui/src/Font.cpp +++ b/libui/src/Font.cpp @@ -65,8 +65,6 @@ namespace ui void Font::render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas) { - check(canvas.width == m_psf_header.width && canvas.height == m_psf_header.height); - const wchar_t str[] = { codepoint, 0 }; render(str, color, canvas); } @@ -98,7 +96,7 @@ namespace ui m_font_data.data() + (codepoint > 0 && codepoint < (wchar_t)m_psf_header.numglyph ? codepoint : 0) * m_psf_header.bytesperglyph; - u32 offset = (u32)i * (m_psf_header.width + 1) * BYTES_PER_PIXEL; + u32 offset = (u32)i * m_psf_header.width * BYTES_PER_PIXEL; for (int y = 0; y < height; y++) { u32 line = offset; -- 2.34.1 From c0ada40e2c2a832688b358ac3244690dce7adeed Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 6 Aug 2023 12:44:16 +0200 Subject: [PATCH 024/103] libui: Add Rect::contains(Rect) --- libui/include/ui/Rect.h | 9 +++++++++ libui/src/Rect.cpp | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/libui/include/ui/Rect.h b/libui/include/ui/Rect.h index da6a2603..5c99f042 100644 --- a/libui/include/ui/Rect.h +++ b/libui/include/ui/Rect.h @@ -21,6 +21,15 @@ namespace ui */ bool contains(Point point); + /** + * @brief Check if another rectangle is contained in this one. + * + * @param point The rectangle to check. + * @return true The other rectangle is contained inside this one. + * @return false The other rectangle is not contained inside this one. + */ + bool contains(Rect rect); + /** * @brief Normalize a point to fit inside this rectangle. * diff --git a/libui/src/Rect.cpp b/libui/src/Rect.cpp index e96eb7e9..36f7f225 100644 --- a/libui/src/Rect.cpp +++ b/libui/src/Rect.cpp @@ -8,6 +8,15 @@ namespace ui (point.y <= (pos.y + height)); } + bool Rect::contains(Rect rect) + { + if (!contains(rect.pos)) return false; + Point rel = relative(rect.pos); + if ((rel.x + rect.width) > width) return false; + if ((rel.y + rect.height) > height) return false; + return true; + } + Point Rect::normalize(Point point) { if (point.x < pos.x) point.x = pos.x; -- 2.34.1 From 98aaf1f7ffbd809f43dc3f684057a4e2c96ea3fc Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 6 Aug 2023 12:44:35 +0200 Subject: [PATCH 025/103] libui: Properly cut off the last drawn character if necessary --- libui/src/Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libui/src/Font.cpp b/libui/src/Font.cpp index 36cc3e0c..1c99965c 100644 --- a/libui/src/Font.cpp +++ b/libui/src/Font.cpp @@ -101,7 +101,7 @@ namespace ui { u32 line = offset; int mask = 1 << (m_psf_header.width - 1); - for (int x = 0; x < m_psf_header.width; x++) + for (int x = 0; x < width; x++) { if (*((u32*)glyph) & mask) *(u32*)(canvas.ptr + line) = color.raw; mask >>= 1; -- 2.34.1 From 11e0025a5bba204c4550d3e9cd9f8c314665489e Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 6 Aug 2023 12:44:56 +0200 Subject: [PATCH 026/103] wind: Make sure windows have a minimum size to fit the titlebar --- wind/Window.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wind/Window.cpp b/wind/Window.cpp index 09306bef..45110ccf 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -42,6 +42,8 @@ void Window::focus() Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), name(n) { auto font = ui::Font::default_font(); + if (surface.width < 36) surface.width = 36; + if (surface.height < (font->height() + 20)) surface.height = font->height() + 20; titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 }; close_button = ui::Rect { surface.width - 26, 10, 16, 16 }; contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }; -- 2.34.1 From 3fefb74710108dfe4ddfb733350d4de57ed618bb Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 6 Aug 2023 12:45:13 +0200 Subject: [PATCH 027/103] wind: Generate random windows on keypresses --- wind/main.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/wind/main.cpp b/wind/main.cpp index c095332d..cecb4a22 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -11,10 +11,13 @@ #include #include #include +#include #include Result luna_main(int argc, char** argv) { + srand((unsigned)time(NULL)); + StringView socket_path = "/tmp/wind.sock"; StringView user; @@ -105,7 +108,14 @@ Result luna_main(int argc, char** argv) { moon::KeyboardPacket packet; TRY(keyboard->read_typed(packet)); - background = ui::Color::from_rgb(0x00, 0x00, packet.key * 2); + if (!packet.released) + { + TRY(make(ui::Rect { rand() % screen.canvas().width, rand() % screen.canvas().height, + rand() % screen.canvas().width, rand() % screen.canvas().height }, + ui::Color::from_rgb(static_cast(rand() % 256), static_cast(rand() % 256), + static_cast(rand() % 256)), + strerror(packet.key))); + } } if (fds[2].revents & POLLIN) { -- 2.34.1 From fbb66a9fc38c2616189acfca4b5245006e0f1ee4 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 7 Aug 2023 19:10:27 +0200 Subject: [PATCH 028/103] libui+wind: Move some static variables inside functions --- libui/CMakeLists.txt | 2 +- libui/src/Font.cpp | 11 ++++------- wind/CMakeLists.txt | 2 +- wind/Window.cpp | 15 ++++++--------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 1a0fa74b..79c0deae 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -11,7 +11,7 @@ set(SOURCES ) add_library(ui ${SOURCES}) -target_compile_options(ui PRIVATE ${COMMON_FLAGS}) +target_compile_options(ui PRIVATE ${COMMON_FLAGS} -fno-threadsafe-statics) target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/) target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include) target_link_libraries(ui PUBLIC os) diff --git a/libui/src/Font.cpp b/libui/src/Font.cpp index 1c99965c..2e99d921 100644 --- a/libui/src/Font.cpp +++ b/libui/src/Font.cpp @@ -2,9 +2,6 @@ #include #include -SharedPtr s_default_font = {}; -SharedPtr s_default_bold_font = {}; - constexpr static int BYTES_PER_PIXEL = (int)sizeof(ui::Color); namespace ui @@ -51,15 +48,15 @@ namespace ui SharedPtr Font::default_font() { - if (s_default_font) return s_default_font; - s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value(); + static SharedPtr s_default_font = {}; + if (!s_default_font) s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value(); return s_default_font; } SharedPtr Font::default_bold_font() { - if (s_default_bold_font) return s_default_bold_font; - s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value(); + static SharedPtr s_default_bold_font = {}; + if (!s_default_bold_font) s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value(); return s_default_bold_font; } diff --git a/wind/CMakeLists.txt b/wind/CMakeLists.txt index 8f5db052..0ddb710f 100644 --- a/wind/CMakeLists.txt +++ b/wind/CMakeLists.txt @@ -9,7 +9,7 @@ set(SOURCES ) add_executable(wind ${SOURCES}) -target_compile_options(wind PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings) +target_compile_options(wind PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings -fno-threadsafe-statics) add_dependencies(wind libc) target_include_directories(wind PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(wind PRIVATE os ui) diff --git a/wind/Window.cpp b/wind/Window.cpp index 45110ccf..43141247 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -6,8 +6,6 @@ LinkedList g_windows; -static SharedPtr g_close_icon; - void Window::draw(ui::Canvas& screen) { auto window = screen.subcanvas(surface); @@ -25,11 +23,12 @@ void Window::draw(ui::Canvas& screen) auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height }); font->render(buffer, ui::BLACK, textarea); - if (g_close_icon) - { - auto close_area = window.subcanvas(close_button); - close_area.fill(g_close_icon->pixels(), g_close_icon->width()); - } + static SharedPtr g_close_icon; + + if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").release_value(); + + auto close_area = window.subcanvas(close_button); + close_area.fill(g_close_icon->pixels(), g_close_icon->width()); } void Window::focus() @@ -48,6 +47,4 @@ Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), na close_button = ui::Rect { surface.width - 26, 10, 16, 16 }; contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }; g_windows.append(this); - - if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").value_or({}); } -- 2.34.1 From 0bb96985bfd7c29150e1befc672c7eb8f6b8383e Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 7 Aug 2023 19:16:43 +0200 Subject: [PATCH 029/103] libui: Document ui::Font --- libui/include/ui/Font.h | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/libui/include/ui/Font.h b/libui/include/ui/Font.h index 2f2962cb..a2614638 100644 --- a/libui/include/ui/Font.h +++ b/libui/include/ui/Font.h @@ -8,29 +8,85 @@ namespace ui { + /** + * @brief A class holding PSF font data, used for direct rendering of glyphs into a canvas. + */ class Font : public Shareable { public: + /** + * @brief An enum used to select a font weight when loading a font. + */ enum FontWeight { Regular, Bold, }; + /** + * @brief Load a Font object from a font file. + * + * @param path The full path to the font file. + * @return Result> An error, or the loaded Font object. + */ static Result> load(const os::Path& path); + + /** + * @brief Load a system font by name. + * + * @param name The name of the font to load (the default system font is "Tamsyn"). + * @param weight The weight of the font (regular or bold). + * @return Result> An error, or the loaded Font object. + */ static Result> load_builtin(StringView name, FontWeight weight); + /** + * @brief Return a pointer to the system's default font. + * + * @return SharedPtr The default font. + */ static SharedPtr default_font(); + + /** + * @brief Return a pointer to the system's default bold font. + * + * @return SharedPtr The default bold font. + */ static SharedPtr default_bold_font(); + /** + * @brief Render a single Unicode code point into a canvas, using this font's glyphs. + * + * @param codepoint The code point to render. + * @param color The color to draw the code point in. + * @param canvas The canvas to use. + */ void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas); + + /** + * @brief Render a Unicode text string into a canvas, using this font's glyphs. + * + * @param text The string to render (must be null-terminated). + * @param color The color to draw the code point in. + * @param canvas The canvas to use. + */ void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas); + /** + * @brief Return the width of this font's glyphs. + * + * @return int The width. + */ int width() const { return m_psf_header.width; } + /** + * @brief Return the height of this font's glyphs. + * + * @return int The height. + */ int height() const { return m_psf_header.height; -- 2.34.1 From 639eb30c7b3eaa6baaeb23eac28b9459452e6fd4 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 7 Aug 2023 19:17:22 +0200 Subject: [PATCH 030/103] libui: Change 'into' to 'onto' --- libui/include/ui/Font.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libui/include/ui/Font.h b/libui/include/ui/Font.h index a2614638..44b13163 100644 --- a/libui/include/ui/Font.h +++ b/libui/include/ui/Font.h @@ -55,7 +55,7 @@ namespace ui static SharedPtr default_bold_font(); /** - * @brief Render a single Unicode code point into a canvas, using this font's glyphs. + * @brief Render a single Unicode code point onto a canvas, using this font's glyphs. * * @param codepoint The code point to render. * @param color The color to draw the code point in. @@ -64,7 +64,7 @@ namespace ui void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas); /** - * @brief Render a Unicode text string into a canvas, using this font's glyphs. + * @brief Render a Unicode text string onto a canvas, using this font's glyphs. * * @param text The string to render (must be null-terminated). * @param color The color to draw the code point in. -- 2.34.1 From 03096680aef7eb72a9ed0106c83abbcb46faceb3 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 7 Aug 2023 22:44:41 +0200 Subject: [PATCH 031/103] libos: Add os::LocalClient --- libos/CMakeLists.txt | 1 + libos/include/os/LocalClient.h | 90 ++++++++++++++++++++++++++++++++++ libos/src/LocalClient.cpp | 59 ++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 libos/include/os/LocalClient.h create mode 100644 libos/src/LocalClient.cpp diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index 1dfa58b6..4a2a10fd 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES src/Prompt.cpp src/Security.cpp src/LocalServer.cpp + src/LocalClient.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/LocalClient.h b/libos/include/os/LocalClient.h new file mode 100644 index 00000000..46314053 --- /dev/null +++ b/libos/include/os/LocalClient.h @@ -0,0 +1,90 @@ +#pragma once +#include +#include + +namespace os +{ + /** + * @brief A client used to connect to a local server socket. + */ + class LocalClient : public Shareable + { + public: + /** + * @brief Create a new client object and connect it to a local server. + * + * @param path The path of the server socket to connect to. + * @param blocking Whether the client should block if no data is available and recv() is called. + * @return Result> An error, or a new client object. + */ + static Result> connect(StringView path, bool blocking); + + /** + * @brief Return the underlying socket file descriptor used by this object. + * + * @return int The file descriptor. + */ + int fd() const + { + return m_fd; + } + + /** + * @brief Read arbitrary data from the server. The call will block if there is no data and this object has not + * been created as non-blocking. + * + * @param buf The buffer to read data into. + * @param length The maximum amount of bytes to read. + * @return Result An error, or the number of bytes read. + */ + Result recv(u8* buf, usize length); + + /** + * @brief Read an object from the server. The call will block if there is no data and this object has not been + * created as non-blocking. + * + * @tparam T The type of the object. + * @param out A reference to the object to read data into. + * @return Result Whether the operation succeded. + */ + template Result recv_typed(T& out) + { + TRY(recv((u8*)&out, sizeof(T))); + return {}; + } + + /** + * @brief Send arbitrary data to the server. + * + * @param buf The buffer to send data from. + * @param length The amount of bytes to send. + * @return Result An error, or the number of bytes actually sent. + */ + Result send(const u8* buf, usize length); + + /** + * @brief Send an object to the server. + * + * @tparam T The type of the object. + * @param out A reference to the object to send data from. + * @return Result Whether the operation succeded. + */ + template Result send_typed(const T& out) + { + TRY(send((const u8*)&out, sizeof(T))); + return {}; + } + + /** + * @brief Disconnect from the attached server. + * + * This will make any further reads on this connection return ECONNRESET, and will make this object invalid. + */ + void disconnect(); + + ~LocalClient(); + + private: + int m_fd; + }; +} diff --git a/libos/src/LocalClient.cpp b/libos/src/LocalClient.cpp new file mode 100644 index 00000000..42d61a9c --- /dev/null +++ b/libos/src/LocalClient.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +namespace os +{ + Result> LocalClient::connect(StringView path, bool blocking) + { + auto client = TRY(make_shared()); + + int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) return err(errno); + + struct sockaddr_un un; + un.sun_family = AF_UNIX; + strncpy(un.sun_path, path.chars(), sizeof(un.sun_path)); + + if (::connect(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0) + { + close(sockfd); + return err(errno); + } + + if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); } + + fcntl(sockfd, F_SETFD, FD_CLOEXEC); + + client->m_fd = sockfd; + return client; + } + + LocalClient::~LocalClient() + { + close(m_fd); + } + + Result LocalClient::recv(u8* buf, usize length) + { + ssize_t nread = read(m_fd, buf, length); + if (nread < 0) return err(errno); + return nread; + } + + Result LocalClient::send(const u8* buf, usize length) + { + ssize_t nwrite = write(m_fd, buf, length); + if (nwrite < 0) return err(errno); + return nwrite; + } + + void LocalClient::disconnect() + { + close(m_fd); + m_fd = -1; + } +} -- 2.34.1 From 60c3bcb3a9d482a55c2f0a60ccb0245ed0d1d7bc Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 7 Aug 2023 22:45:00 +0200 Subject: [PATCH 032/103] apps: Add gclient --- apps/CMakeLists.txt | 1 + apps/gclient.cpp | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 apps/gclient.cpp diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index cfe1853d..d9c430f8 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -40,3 +40,4 @@ luna_app(kill.cpp kill) luna_app(gol.cpp gol) luna_app(touch.cpp touch) luna_app(free.cpp free) +luna_app(gclient.cpp gclient) diff --git a/apps/gclient.cpp b/apps/gclient.cpp new file mode 100644 index 00000000..aeb0a9b2 --- /dev/null +++ b/apps/gclient.cpp @@ -0,0 +1,20 @@ +#include +#include + +Result luna_main(int argc, char** argv) +{ + StringView socket_path = "/tmp/wind.sock"; + + os::ArgumentParser parser; + parser.add_description("A graphical user interface client."_sv); + parser.add_system_program_info("gclient"_sv); + parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv); + parser.parse(argc, argv); + + auto client = TRY(os::LocalClient::connect(socket_path, false)); + + StringView message = "hello"; + TRY(client->send((const u8*)message.chars(), message.length())); + + return 0; +} -- 2.34.1 From 6cf5fa3097923eda0cc34efd8f0cdb126974eec0 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 7 Aug 2023 22:51:28 +0200 Subject: [PATCH 033/103] wind: Spawn a new client process after startup Also, create the socket after dropping privileges. --- wind/main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/wind/main.cpp b/wind/main.cpp index cecb4a22..6df9d762 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -45,9 +46,6 @@ Result luna_main(int argc, char** argv) auto screen = TRY(Screen::open()); - auto server = TRY(os::LocalServer::create(socket_path, false)); - TRY(server->listen(20)); - Mouse mouse_pointer { screen.canvas() }; ioctl(STDIN_FILENO, TTYSETGFX, 1); @@ -73,6 +71,12 @@ Result luna_main(int argc, char** argv) } } + auto server = TRY(os::LocalServer::create(socket_path, false)); + TRY(server->listen(20)); + + StringView args[] = { "/usr/bin/gclient"_sv }; + TRY(os::Process::spawn("/usr/bin/gclient"_sv, Slice { args, 1 }, false)); + ui::Color background = ui::BLACK; TRY(make(ui::Rect { 200, 200, 600, 400 }, ui::GREEN, "Calculator"_sv)); @@ -96,7 +100,7 @@ Result luna_main(int argc, char** argv) int rc = poll(fds, 3, 1000); if (!rc) continue; - if (rc < 0) { os::println("poll: error: %s", strerror(errno)); } + if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); } if (fds[0].revents & POLLIN) { -- 2.34.1 From 1c50d5133f652186539976ddd7755680cf45a1dd Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 8 Aug 2023 11:40:22 +0200 Subject: [PATCH 034/103] libos: Remove some shared pointers and change them to owned/live on the stack --- libos/include/os/LocalClient.h | 8 ++++---- libos/include/os/LocalServer.h | 15 ++++++++------- libos/src/LocalClient.cpp | 4 ++-- libos/src/LocalServer.cpp | 13 +++++++++---- wind/main.cpp | 4 ++-- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/libos/include/os/LocalClient.h b/libos/include/os/LocalClient.h index 46314053..e816db59 100644 --- a/libos/include/os/LocalClient.h +++ b/libos/include/os/LocalClient.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include namespace os @@ -7,7 +7,7 @@ namespace os /** * @brief A client used to connect to a local server socket. */ - class LocalClient : public Shareable + class LocalClient { public: /** @@ -15,9 +15,9 @@ namespace os * * @param path The path of the server socket to connect to. * @param blocking Whether the client should block if no data is available and recv() is called. - * @return Result> An error, or a new client object. + * @return Result> An error, or a new client object. */ - static Result> connect(StringView path, bool blocking); + static Result> connect(StringView path, bool blocking); /** * @brief Return the underlying socket file descriptor used by this object. diff --git a/libos/include/os/LocalServer.h b/libos/include/os/LocalServer.h index 9a1c9a7e..29f673a2 100644 --- a/libos/include/os/LocalServer.h +++ b/libos/include/os/LocalServer.h @@ -1,6 +1,6 @@ #pragma once +#include #include -#include #include namespace os @@ -8,7 +8,7 @@ namespace os /** * @brief A local domain server, used to communicate between processes on the same machine. */ - class LocalServer : public Shareable + class LocalServer { public: /** @@ -16,9 +16,9 @@ namespace os * * @param path The path to use for the server socket. * @param blocking Whether the server should block if no connections are available when calling accept(). - * @return Result> An error, or a new server object. + * @return Result> An error, or a new server object. */ - static Result> create(StringView path, bool blocking); + static Result> create(StringView path, bool blocking); /** * @brief Activate the server and start listening for connections. @@ -41,7 +41,7 @@ namespace os /** * @brief An interface to communicate with clients connected to a local server. */ - class Client : public Shareable + class Client { public: /** @@ -107,6 +107,7 @@ namespace os return m_fd; } + Client(Client&& other); Client(int fd); ~Client(); @@ -119,9 +120,9 @@ namespace os * accept() either blocks until there is one (if the object was created with blocking=true), or returns EAGAIN * (if the object was created with blocking=false). * - * @return Result> An error, or a handle to the new connection. + * @return Result An error, or a handle to the new connection. */ - Result> accept(); + Result accept(); ~LocalServer(); diff --git a/libos/src/LocalClient.cpp b/libos/src/LocalClient.cpp index 42d61a9c..a7b0aff4 100644 --- a/libos/src/LocalClient.cpp +++ b/libos/src/LocalClient.cpp @@ -7,9 +7,9 @@ namespace os { - Result> LocalClient::connect(StringView path, bool blocking) + Result> LocalClient::connect(StringView path, bool blocking) { - auto client = TRY(make_shared()); + auto client = TRY(make_owned()); int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) return err(errno); diff --git a/libos/src/LocalServer.cpp b/libos/src/LocalServer.cpp index fba62c57..07bccd5e 100644 --- a/libos/src/LocalServer.cpp +++ b/libos/src/LocalServer.cpp @@ -8,9 +8,9 @@ namespace os { - Result> LocalServer::create(StringView path, bool blocking) + Result> LocalServer::create(StringView path, bool blocking) { - auto server = TRY(make_shared()); + auto server = TRY(make_owned()); (void)os::FileSystem::remove(path); // We explicitly ignore any error here, either it doesn't exist (which is // fine), or it cannot be removed, which will make bind() fail later. @@ -43,12 +43,12 @@ namespace os return {}; } - Result> LocalServer::accept() + Result LocalServer::accept() { int fd = ::accept(m_fd, nullptr, nullptr); if (fd < 0) return err(errno); if (!m_blocking) fcntl(fd, F_SETFL, O_NONBLOCK); - return make_shared(fd); + return Client { fd }; } LocalServer::~LocalServer() @@ -56,6 +56,11 @@ namespace os close(m_fd); } + LocalServer::Client::Client(Client&& other) : m_fd(other.m_fd) + { + other.m_fd = -1; + } + LocalServer::Client::Client(int fd) : m_fd(fd) { } diff --git a/wind/main.cpp b/wind/main.cpp index 6df9d762..e8d4442f 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -83,7 +83,7 @@ Result luna_main(int argc, char** argv) TRY(make(ui::Rect { 100, 100, 300, 200 }, ui::RED, "Settings"_sv)); TRY(make(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv)); - Vector> clients; + Vector clients; while (1) { @@ -125,7 +125,7 @@ Result luna_main(int argc, char** argv) { auto client = TRY(server->accept()); os::println("wind: New client connected!"); - TRY(clients.try_append(client)); + TRY(clients.try_append(move(client))); } } } -- 2.34.1 From 7d883fe33bc5ca42b61188f9e4ae93f288b84a42 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 10:56:55 +0200 Subject: [PATCH 035/103] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 845a9e47..247a2ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ base/usr/include/** base/usr/lib/** base/usr/share/pkgdb/** !base/usr/share/fonts/* +base/usr/share/** +base/usr/x86_64-luna/** base/etc/skel/LICENSE .fakeroot kernel/config.cmake -- 2.34.1 From 774177ba1f5b1e1960d61504d7733f42b9b00f55 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 11:07:19 +0200 Subject: [PATCH 036/103] wind: Use init --user and pledge() --- base/etc/user/00-gclient | 3 +++ wind/main.cpp | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 base/etc/user/00-gclient diff --git a/base/etc/user/00-gclient b/base/etc/user/00-gclient new file mode 100644 index 00000000..fcd404ac --- /dev/null +++ b/base/etc/user/00-gclient @@ -0,0 +1,3 @@ +Name=gclient +Description=Sample user application. +Command=/usr/bin/gclient diff --git a/wind/main.cpp b/wind/main.cpp index e8d4442f..943cf93c 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,8 @@ Result luna_main(int argc, char** argv) { srand((unsigned)time(NULL)); + TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL)); + StringView socket_path = "/tmp/wind.sock"; StringView user; @@ -74,8 +77,8 @@ Result luna_main(int argc, char** argv) auto server = TRY(os::LocalServer::create(socket_path, false)); TRY(server->listen(20)); - StringView args[] = { "/usr/bin/gclient"_sv }; - TRY(os::Process::spawn("/usr/bin/gclient"_sv, Slice { args, 1 }, false)); + StringView args[] = { "/usr/bin/init"_sv, "--user"_sv }; + TRY(os::Process::spawn("/usr/bin/init"_sv, Slice { args, 2 }, false)); ui::Color background = ui::BLACK; @@ -85,6 +88,8 @@ Result luna_main(int argc, char** argv) Vector clients; + TRY(os::Security::pledge("stdio rpath unix", NULL)); + while (1) { screen.canvas().fill(background); -- 2.34.1 From 02b9dc579b3958d6258a7c4cd4eb75ae58f4ac6a Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 12:31:09 +0200 Subject: [PATCH 037/103] libos: Add copyright/author comments to LocalServer and LocalClient --- libos/include/os/LocalClient.h | 9 +++++++++ libos/include/os/LocalServer.h | 9 +++++++++ libos/src/LocalClient.cpp | 9 +++++++++ libos/src/LocalServer.cpp | 9 +++++++++ 4 files changed, 36 insertions(+) diff --git a/libos/include/os/LocalClient.h b/libos/include/os/LocalClient.h index e816db59..7ba1791d 100644 --- a/libos/include/os/LocalClient.h +++ b/libos/include/os/LocalClient.h @@ -1,3 +1,12 @@ +/** + * @file LocalClient.h + * @author apio (cloudapio.eu) + * @brief UNIX local domain client class. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include #include diff --git a/libos/include/os/LocalServer.h b/libos/include/os/LocalServer.h index 29f673a2..1adfa92a 100644 --- a/libos/include/os/LocalServer.h +++ b/libos/include/os/LocalServer.h @@ -1,3 +1,12 @@ +/** + * @file LocalServer.h + * @author apio (cloudapio.eu) + * @brief UNIX local domain server class. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include #include diff --git a/libos/src/LocalClient.cpp b/libos/src/LocalClient.cpp index a7b0aff4..f2263b09 100644 --- a/libos/src/LocalClient.cpp +++ b/libos/src/LocalClient.cpp @@ -1,3 +1,12 @@ +/** + * @file LocalClient.cpp + * @author apio (cloudapio.eu) + * @brief UNIX local domain client class. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #include #include #include diff --git a/libos/src/LocalServer.cpp b/libos/src/LocalServer.cpp index 07bccd5e..94e5d555 100644 --- a/libos/src/LocalServer.cpp +++ b/libos/src/LocalServer.cpp @@ -1,3 +1,12 @@ +/** + * @file LocalServer.cpp + * @author apio (cloudapio.eu) + * @brief UNIX local domain server class. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #include #include #include -- 2.34.1 From 5e6ce50c704462d7f1b16765c0969d78be9886fa Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 12:34:27 +0200 Subject: [PATCH 038/103] libui: Add copyright/author text --- libui/include/ui/Canvas.h | 9 +++++++++ libui/include/ui/Color.h | 9 +++++++++ libui/include/ui/Font.h | 9 +++++++++ libui/include/ui/Image.h | 9 +++++++++ libui/include/ui/Point.h | 9 +++++++++ libui/include/ui/Rect.h | 9 +++++++++ libui/src/Canvas.cpp | 9 +++++++++ libui/src/Font.cpp | 9 +++++++++ libui/src/Image.cpp | 9 +++++++++ libui/src/Rect.cpp | 9 +++++++++ 10 files changed, 90 insertions(+) diff --git a/libui/include/ui/Canvas.h b/libui/include/ui/Canvas.h index 74a34217..fb410638 100644 --- a/libui/include/ui/Canvas.h +++ b/libui/include/ui/Canvas.h @@ -1,3 +1,12 @@ +/** + * @file Canvas.h + * @author apio (cloudapio.eu) + * @brief Drawable surfaces. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include #include diff --git a/libui/include/ui/Color.h b/libui/include/ui/Color.h index f549abb5..47a6d715 100644 --- a/libui/include/ui/Color.h +++ b/libui/include/ui/Color.h @@ -1,3 +1,12 @@ +/** + * @file Color.h + * @author apio (cloudapio.eu) + * @brief RGBA colors. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include diff --git a/libui/include/ui/Font.h b/libui/include/ui/Font.h index 44b13163..c8929190 100644 --- a/libui/include/ui/Font.h +++ b/libui/include/ui/Font.h @@ -1,3 +1,12 @@ +/** + * @file Font.h + * @author apio (cloudapio.eu) + * @brief PSF font loading and rendering. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include #include diff --git a/libui/include/ui/Image.h b/libui/include/ui/Image.h index fa1cdb39..bebffa84 100644 --- a/libui/include/ui/Image.h +++ b/libui/include/ui/Image.h @@ -1,3 +1,12 @@ +/** + * @file Image.h + * @author apio (cloudapio.eu) + * @brief TGA image loading and rendering. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include #include diff --git a/libui/include/ui/Point.h b/libui/include/ui/Point.h index 146ea0b8..f5837793 100644 --- a/libui/include/ui/Point.h +++ b/libui/include/ui/Point.h @@ -1,3 +1,12 @@ +/** + * @file Point.h + * @author apio (cloudapio.eu) + * @brief 2D space points. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once namespace ui diff --git a/libui/include/ui/Rect.h b/libui/include/ui/Rect.h index 5c99f042..8ac8d805 100644 --- a/libui/include/ui/Rect.h +++ b/libui/include/ui/Rect.h @@ -1,3 +1,12 @@ +/** + * @file Rect.h + * @author apio (cloudapio.eu) + * @brief A simple 2D rectangle representation. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #pragma once #include diff --git a/libui/src/Canvas.cpp b/libui/src/Canvas.cpp index d14bc768..002f1592 100644 --- a/libui/src/Canvas.cpp +++ b/libui/src/Canvas.cpp @@ -1,3 +1,12 @@ +/** + * @file Canvas.cpp + * @author apio (cloudapio.eu) + * @brief Drawable surfaces. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #include namespace ui diff --git a/libui/src/Font.cpp b/libui/src/Font.cpp index 2e99d921..0534680c 100644 --- a/libui/src/Font.cpp +++ b/libui/src/Font.cpp @@ -1,3 +1,12 @@ +/** + * @file Font.cpp + * @author apio (cloudapio.eu) + * @brief PSF font loading and rendering. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #include #include #include diff --git a/libui/src/Image.cpp b/libui/src/Image.cpp index de6051ad..4a284736 100644 --- a/libui/src/Image.cpp +++ b/libui/src/Image.cpp @@ -1,3 +1,12 @@ +/** + * @file Image.cpp + * @author apio (cloudapio.eu) + * @brief TGA image loading and rendering. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #include #include diff --git a/libui/src/Rect.cpp b/libui/src/Rect.cpp index 36f7f225..14cf47c5 100644 --- a/libui/src/Rect.cpp +++ b/libui/src/Rect.cpp @@ -1,3 +1,12 @@ +/** + * @file Rect.cpp + * @author apio (cloudapio.eu) + * @brief A simple 2D rectangle representation. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + #include namespace ui -- 2.34.1 From 6a35cad8d5b8ff75c69fbd837b1df3b880d09f8a Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 13:22:41 +0200 Subject: [PATCH 039/103] kernel: Add POLLHUP and store it when a polled socket's peer disconnects --- kernel/src/net/Socket.h | 2 ++ kernel/src/net/UnixSocket.h | 5 +++++ kernel/src/sys/poll.cpp | 8 ++++++++ libc/include/bits/poll.h | 1 + 4 files changed, 16 insertions(+) diff --git a/kernel/src/net/Socket.h b/kernel/src/net/Socket.h index 5b980958..47c66807 100644 --- a/kernel/src/net/Socket.h +++ b/kernel/src/net/Socket.h @@ -69,6 +69,8 @@ class Socket : public VFS::FileInode virtual bool can_read_data() const = 0; + virtual bool peer_disconnected() const = 0; + virtual ~Socket() = default; protected: diff --git a/kernel/src/net/UnixSocket.h b/kernel/src/net/UnixSocket.h index 9ec88000..953947d6 100644 --- a/kernel/src/net/UnixSocket.h +++ b/kernel/src/net/UnixSocket.h @@ -27,6 +27,11 @@ class UnixSocket : public Socket return !m_listen_queue.is_empty(); } + bool peer_disconnected() const override + { + return m_state == Reset; + } + Result send(const u8*, usize, int) override; Result recv(u8*, usize, int) const override; diff --git a/kernel/src/sys/poll.cpp b/kernel/src/sys/poll.cpp index 30ac2f24..4e55744e 100644 --- a/kernel/src/sys/poll.cpp +++ b/kernel/src/sys/poll.cpp @@ -50,6 +50,9 @@ Result sys_poll(Registers*, SyscallArgs args) if (kfds[i].events & POLLIN) { + fds_with_events++; + kfds[i].revents |= POLLIN; + if (inode->type() == VFS::InodeType::Socket) { auto socket = (Socket*)inode.ptr(); @@ -58,6 +61,11 @@ Result sys_poll(Registers*, SyscallArgs args) fds_with_events++; kfds[i].revents |= POLLIN; } + if (socket->peer_disconnected()) + { + fds_with_events++; + kfds[i].revents |= POLLHUP; + } } else { diff --git a/libc/include/bits/poll.h b/libc/include/bits/poll.h index 35600ed8..e0c6b84a 100644 --- a/libc/include/bits/poll.h +++ b/libc/include/bits/poll.h @@ -8,6 +8,7 @@ #define POLLIN (1 << 0) #define POLLERR (1 << 1) #define POLLNVAL (1 << 2) +#define POLLHUP (1 << 3) typedef __u64_t nfds_t; -- 2.34.1 From e931d11ae103836c97930bbdcfc13318a4393ec3 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 13:24:26 +0200 Subject: [PATCH 040/103] wind: Monitor data on client connections --- wind/main.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/wind/main.cpp b/wind/main.cpp index 943cf93c..39e51aff 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -87,6 +87,10 @@ Result luna_main(int argc, char** argv) TRY(make(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv)); Vector clients; + Vector fds; + TRY(fds.try_append({ .fd = mouse->fd(), .events = POLLIN, .revents = 0 })); + TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 })); + TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 })); TRY(os::Security::pledge("stdio rpath unix", NULL)); @@ -97,13 +101,9 @@ Result luna_main(int argc, char** argv) mouse_pointer.draw(screen.canvas()); screen.sync(); - struct pollfd fds[] = { - { .fd = mouse->fd(), .events = POLLIN, .revents = 0 }, - { .fd = keyboard->fd(), .events = POLLIN, .revents = 0 }, - { .fd = server->fd(), .events = POLLIN, .revents = 0 }, - }; + for (auto& pfd : fds) { pfd.revents = 0; } - int rc = poll(fds, 3, 1000); + int rc = poll(fds.data(), fds.size(), 1000); if (!rc) continue; if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); } @@ -126,10 +126,22 @@ Result luna_main(int argc, char** argv) strerror(packet.key))); } } + for (usize i = 0; i < clients.size(); i++) + { + if (fds[i + 3].revents & POLLIN) os::println("wind: Client %d sent data!", i); + if (fds[i + 3].revents & POLLHUP) + { + os::println("wind: Client %d disconnected", i); + fds.remove_at(i + 3); + auto client = clients.remove_at(i); + client.disconnect(); + } + } if (fds[2].revents & POLLIN) { auto client = TRY(server->accept()); os::println("wind: New client connected!"); + TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 })); TRY(clients.try_append(move(client))); } } -- 2.34.1 From 3d90d7f98ea8732fca8653abe8cb8e3bb4df04e6 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 18:14:16 +0200 Subject: [PATCH 041/103] kernel: Fix poll syscall --- kernel/src/sys/poll.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/kernel/src/sys/poll.cpp b/kernel/src/sys/poll.cpp index 4e55744e..4f09241c 100644 --- a/kernel/src/sys/poll.cpp +++ b/kernel/src/sys/poll.cpp @@ -50,9 +50,6 @@ Result sys_poll(Registers*, SyscallArgs args) if (kfds[i].events & POLLIN) { - fds_with_events++; - kfds[i].revents |= POLLIN; - if (inode->type() == VFS::InodeType::Socket) { auto socket = (Socket*)inode.ptr(); -- 2.34.1 From 9125561cab5c2a92983571701f814500143fdda6 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 18:14:35 +0200 Subject: [PATCH 042/103] libos: Add basic IPC message framework --- libos/CMakeLists.txt | 1 + libos/include/os/IPC.h | 157 +++++++++++++++++++++++++++++++++++++++++ libos/src/IPC.cpp | 39 ++++++++++ 3 files changed, 197 insertions(+) create mode 100644 libos/include/os/IPC.h create mode 100644 libos/src/IPC.cpp diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index 4a2a10fd..76bbe50d 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCES src/Security.cpp src/LocalServer.cpp src/LocalClient.cpp + src/IPC.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/IPC.h b/libos/include/os/IPC.h new file mode 100644 index 00000000..7e937bb0 --- /dev/null +++ b/libos/include/os/IPC.h @@ -0,0 +1,157 @@ +/** + * @file IPC.h + * @author apio (cloudapio.eu) + * @brief Inter-process communication primitives. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include + +#define IPC_ENUM_SERVER(name) __##name##_SERVER_ERROR = 0 +#define IPC_ENUM_CLIENT(name) __##name##_CLIENT_ERROR = 0 + +/** + * @brief Called to handle IPC events (client-side). + * + * @param conn The connection object being used. + * @param id The ID of the message. + * @return Result Whether the operation succeded. + */ +extern Result handle_ipc_client_event(os::LocalClient& conn, u8 id); + +/** + * @brief Called to handle IPC events (server-side). + * + * @param conn The connection object being used. + * @param id The ID of the message. + * @return Result Whether the operation succeded. + */ +extern Result handle_ipc_server_event(os::LocalServer::Client& conn, u8 id); + +namespace os +{ + namespace IPC + { + static constexpr usize IPC_STRING_LENGTH = 256; + +#define IPC_STRING(name) char name[os::IPC::IPC_STRING_LENGTH]; +#define COPY_IPC_STRING(name) \ + TRY(String::from_string_view(StringView::from_fixed_size_cstring(name, os::IPC::IPC_STRING_LENGTH))) +#define SET_IPC_STRING(name, value) strlcpy(name, value, os::IPC::IPC_STRING_LENGTH) + + /** + * @brief Sends an IPC message without waiting for a reply. + * + * @tparam Client The type of the client interface being used to communicate. + * @tparam T The type of the message. + * @param client The connection object being used to communicate. + * @param message The IPC message. + * @return Result Whether the operation succeded. + */ + template Result send_async(Client& client, const T& message) + { + u8 id = T::id; + TRY(client.send_typed(id)); + TRY(client.send_typed(message)); + return {}; + } + + /** + * @brief Sends an error result to the IPC connection, indicating that an operation could not be performed. + * + * @tparam Client The type of the client interface being used to communicate. + * @param client The connection object being used to communicate. + * @param error The error code. + * @return Result Whether the operation succeded. + */ + template Result send_error(Client& client, int error) + { + u8 id = 0; + TRY(client.send_typed(id)); + TRY(client.send_typed(error)); + return {}; + } + + /** + * @brief Sends an IPC message and waits for a reply (client-only). + * + * @tparam ResponseType The type of the response. + * @tparam T The type of the message. + * @param client The connection object being used to communicate. + * @param message The IPC message. + * @param handler The function used to handle messages that do not match the reply. + * @return Result An error, or the response. + */ + template + Result send_sync(os::LocalClient& client, const T& message, + decltype(handle_ipc_client_event) handler = handle_ipc_client_event) + { + u8 id = T::id; + TRY(client.send_typed(id)); + TRY(client.send_typed(message)); + + // We allow receiving 5 messages of different types, but if those have passed and we still don't have a + // reply, fail with ENOMSG. + int max_other_messages = 5; + + while (max_other_messages) + { + u8 response_id; + auto rc = client.recv_typed(response_id); + if (rc.has_error() && rc.error() == EAGAIN) continue; + + if (response_id == 0) // Error result + { + while (1) + { + int code; + rc = client.recv_typed(code); + if (rc.has_error() && rc.error() == EAGAIN) continue; + return err(code); + } + } + + if (response_id != ResponseType::id) + { + TRY(handler(client, response_id)); + max_other_messages--; + continue; + } + + while (1) + { + ResponseType response; + rc = client.recv_typed(response); + if (rc.has_error() && rc.error() == EAGAIN) continue; + return response; + } + } + + return err(ENOMSG); + } + + /** + * @brief Check for new IPC messages on a connection and handle them appropriately. + * + * @param client The client connection. + * @param handler The function used to handle messages. + * @return Result Whether the operation succeded. + */ + Result check_for_messages(os::LocalClient& client, + decltype(handle_ipc_client_event) handler = handle_ipc_client_event); + + /** + * @brief Check for new IPC messages on a connection and handle them appropriately. + * + * @param server The server connection. + * @param handler The function used to handle messages. + * @return Result Whether the operation succeded. + */ + Result check_for_messages(os::LocalServer::Client& server, + decltype(handle_ipc_server_event) handler = handle_ipc_server_event); + } +} diff --git a/libos/src/IPC.cpp b/libos/src/IPC.cpp new file mode 100644 index 00000000..fbe6b164 --- /dev/null +++ b/libos/src/IPC.cpp @@ -0,0 +1,39 @@ +/** + * @file IPC.cpp + * @author apio (cloudapio.eu) + * @brief Inter-process communication primitives. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include + +namespace os::IPC +{ + Result check_for_messages(os::LocalClient& client, decltype(handle_ipc_client_event) handler) + { + u8 id; + auto rc = client.recv_typed(id); + if (rc.has_error()) + { + if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block. + return rc.release_error(); + } + + return handler(client, id); + } + + Result check_for_messages(os::LocalServer::Client& client, decltype(handle_ipc_server_event) handler) + { + u8 id; + auto rc = client.recv_typed(id); + if (rc.has_error()) + { + if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block. + return rc.release_error(); + } + + return handler(client, id); + } +} -- 2.34.1 From 1eb00eabfab364f35ef260f9cf61a1e88463652c Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 18:15:10 +0200 Subject: [PATCH 043/103] libui: Add CreateWindow IPC message definitions --- libui/CMakeLists.txt | 2 ++ libui/include/ui/ipc/Client.h | 27 +++++++++++++++++++++++++++ libui/include/ui/ipc/Server.h | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 libui/include/ui/ipc/Client.h create mode 100644 libui/include/ui/ipc/Server.h diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 79c0deae..4a1adaa2 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -4,6 +4,8 @@ file(GLOB HEADERS include/ui/*.h) set(SOURCES ${HEADERS} + include/ui/ipc/Server.h + include/ui/ipc/Client.h src/Canvas.cpp src/Rect.cpp src/Font.cpp diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h new file mode 100644 index 00000000..876f50ef --- /dev/null +++ b/libui/include/ui/ipc/Client.h @@ -0,0 +1,27 @@ +/** + * @file ipc/Client.h + * @author apio (cloudapio.eu) + * @brief IPC message definitions for UI messages sent to the client. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +namespace ui +{ + enum ClientMessages : u8 + { + IPC_ENUM_CLIENT(ui), + CREATE_WINDOW_RESPONSE_ID, + }; + + struct CreateWindowResponse + { + static constexpr u8 id = CREATE_WINDOW_RESPONSE_ID; + + int window; + }; +} diff --git a/libui/include/ui/ipc/Server.h b/libui/include/ui/ipc/Server.h new file mode 100644 index 00000000..bfa4afbe --- /dev/null +++ b/libui/include/ui/ipc/Server.h @@ -0,0 +1,33 @@ +/** + * @file ipc/Server.h + * @author apio (cloudapio.eu) + * @brief IPC message definitions for UI messages sent to the server. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include +#include +#include + +namespace ui +{ + enum ServerMessages : u8 + { + IPC_ENUM_SERVER(ui), + CREATE_WINDOW_ID, + }; + + struct CreateWindowRequest + { + using ResponseType = CreateWindowResponse; + static constexpr u8 id = CREATE_WINDOW_ID; + + ui::Rect rect; + IPC_STRING(name); + ui::Color color; + }; +} -- 2.34.1 From d3dd257dc19b9c1e055428fdb837f4827523f763 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 18:15:29 +0200 Subject: [PATCH 044/103] wind: Handle CreateWindow IPC messages --- wind/CMakeLists.txt | 3 ++ wind/Client.h | 11 +++++++ wind/IPC.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++++ wind/IPC.h | 10 ++++++ wind/Window.cpp | 8 ++++- wind/Window.h | 8 +++-- wind/main.cpp | 33 +++++++++----------- 7 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 wind/Client.h create mode 100644 wind/IPC.cpp create mode 100644 wind/IPC.h diff --git a/wind/CMakeLists.txt b/wind/CMakeLists.txt index 0ddb710f..5d79b2c8 100644 --- a/wind/CMakeLists.txt +++ b/wind/CMakeLists.txt @@ -6,6 +6,9 @@ set(SOURCES Mouse.cpp Window.h Window.cpp + IPC.cpp + IPC.h + Client.h ) add_executable(wind ${SOURCES}) diff --git a/wind/Client.h b/wind/Client.h new file mode 100644 index 00000000..30b402ad --- /dev/null +++ b/wind/Client.h @@ -0,0 +1,11 @@ +#pragma once +#include "Window.h" +#include + +struct Client +{ + os::LocalServer::Client conn; + Vector windows; + bool rpc_in_progress { false }; + u8 rpc_id { 0 }; +}; diff --git a/wind/IPC.cpp b/wind/IPC.cpp new file mode 100644 index 00000000..e63dd3c0 --- /dev/null +++ b/wind/IPC.cpp @@ -0,0 +1,75 @@ +#include "IPC.h" +#include +#include + +static Result handle_create_window_message(Client& client) +{ + ui::CreateWindowRequest request; + auto rc = client.conn.recv_typed(request); + if (rc.has_error()) + { + if (rc.error() == EAGAIN) + { + client.rpc_in_progress = true; + client.rpc_id = ui::CREATE_WINDOW_ID; + return {}; + } + else + return rc.release_error(); + } + + request.rect = request.rect.normalized(); + request.rect.height += Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for. + + auto name = COPY_IPC_STRING(request.name); + + auto* window = new (std::nothrow) Window(request.rect, request.color, move(name)); + if (!window) + { + os::IPC::send_error(client.conn, ENOMEM); + return {}; + } + + int id = static_cast(client.windows.size()); + rc = client.windows.try_append(window); + if (rc.has_error()) + { + delete window; + os::IPC::send_error(client.conn, rc.error()); + return {}; + } + + ui::CreateWindowResponse response; + response.window = id; + os::IPC::send_async(client.conn, response); + return {}; +} + +namespace wind +{ + Result handle_ipc_message(Client& client, u8 id) + { + client.rpc_in_progress = false; + switch (id) + { + case ui::CREATE_WINDOW_ID: return handle_create_window_message(client); + default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL); + } + } + + Result handle_ipc(Client& client) + { + if (client.rpc_in_progress) return handle_ipc_message(client, client.rpc_id); + + u8 id; + auto rc = client.conn.recv_typed(id); + if (rc.has_error()) + { + if (rc.error() == EAGAIN) { return {}; } + else + return rc.release_error(); + } + + return handle_ipc_message(client, id); + } +} diff --git a/wind/IPC.h b/wind/IPC.h new file mode 100644 index 00000000..4c085302 --- /dev/null +++ b/wind/IPC.h @@ -0,0 +1,10 @@ +#pragma once +#include "Client.h" +#include + +namespace wind +{ + Result handle_ipc_message(Client& client, u8 id); + + Result handle_ipc(Client& client); +} diff --git a/wind/Window.cpp b/wind/Window.cpp index 43141247..bfe6a8b1 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -38,7 +38,7 @@ void Window::focus() g_windows.append(this); } -Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), name(n) +Window::Window(ui::Rect r, ui::Color c, String&& n) : surface(r), color(c), name(move(n)) { auto font = ui::Font::default_font(); if (surface.width < 36) surface.width = 36; @@ -48,3 +48,9 @@ Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), na contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }; g_windows.append(this); } + +int Window::titlebar_height() +{ + auto font = ui::Font::default_font(); + return font->height() + 20; +} diff --git a/wind/Window.h b/wind/Window.h index 86f638d2..b181e3e0 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include #include @@ -12,9 +12,11 @@ struct Window : public LinkedListNode ui::Rect close_button; ui::Rect contents; ui::Color color; - StringView name; + String name; - Window(ui::Rect, ui::Color, StringView); + static int titlebar_height(); + + Window(ui::Rect, ui::Color, String&&); void focus(); diff --git a/wind/main.cpp b/wind/main.cpp index 39e51aff..90889c89 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -1,3 +1,5 @@ +#include "Client.h" +#include "IPC.h" #include "Mouse.h" #include "Screen.h" #include "Window.h" @@ -20,7 +22,7 @@ Result luna_main(int argc, char** argv) { srand((unsigned)time(NULL)); - TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL)); + TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty signal id", NULL)); StringView socket_path = "/tmp/wind.sock"; StringView user; @@ -82,17 +84,13 @@ Result luna_main(int argc, char** argv) ui::Color background = ui::BLACK; - TRY(make(ui::Rect { 200, 200, 600, 400 }, ui::GREEN, "Calculator"_sv)); - TRY(make(ui::Rect { 100, 100, 300, 200 }, ui::RED, "Settings"_sv)); - TRY(make(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv)); - - Vector clients; + Vector clients; Vector fds; TRY(fds.try_append({ .fd = mouse->fd(), .events = POLLIN, .revents = 0 })); TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 })); TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 })); - TRY(os::Security::pledge("stdio rpath unix", NULL)); + TRY(os::Security::pledge("stdio rpath unix signal proc", NULL)); while (1) { @@ -117,24 +115,22 @@ Result luna_main(int argc, char** argv) { moon::KeyboardPacket packet; TRY(keyboard->read_typed(packet)); - if (!packet.released) - { - TRY(make(ui::Rect { rand() % screen.canvas().width, rand() % screen.canvas().height, - rand() % screen.canvas().width, rand() % screen.canvas().height }, - ui::Color::from_rgb(static_cast(rand() % 256), static_cast(rand() % 256), - static_cast(rand() % 256)), - strerror(packet.key))); - } + os::println("%s key %d", packet.released ? "Released" : "Pressed", packet.key); } for (usize i = 0; i < clients.size(); i++) { - if (fds[i + 3].revents & POLLIN) os::println("wind: Client %d sent data!", i); + if (fds[i + 3].revents & POLLIN) wind::handle_ipc(clients[i]); if (fds[i + 3].revents & POLLHUP) { os::println("wind: Client %d disconnected", i); fds.remove_at(i + 3); auto client = clients.remove_at(i); - client.disconnect(); + client.conn.disconnect(); + for (auto& window : client.windows) + { + g_windows.remove(window); + delete window; + } } } if (fds[2].revents & POLLIN) @@ -142,7 +138,8 @@ Result luna_main(int argc, char** argv) auto client = TRY(server->accept()); os::println("wind: New client connected!"); TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 })); - TRY(clients.try_append(move(client))); + Client c { move(client), {} }; + TRY(clients.try_append(move(c))); } } } -- 2.34.1 From 012706817713f026d2f5ded69bdbde492f2b712d Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 18:15:38 +0200 Subject: [PATCH 045/103] gclient: Create two example windows --- apps/gclient.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index aeb0a9b2..3d4fde4b 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -1,5 +1,23 @@ #include +#include #include +#include +#include + +Result handle_ipc_client_event(os::LocalClient&, u8) +{ + todo(); +} + +Result create_window(os::LocalClient& client, ui::Rect rect, StringView name, ui::Color color) +{ + ui::CreateWindowRequest request; + request.rect = rect; + SET_IPC_STRING(request.name, name.chars()); + request.color = color; + auto response = TRY(os::IPC::send_sync(client, request)); + return response.window; +} Result luna_main(int argc, char** argv) { @@ -13,8 +31,16 @@ Result luna_main(int argc, char** argv) auto client = TRY(os::LocalClient::connect(socket_path, false)); - StringView message = "hello"; - TRY(client->send((const u8*)message.chars(), message.length())); + int id = TRY(create_window(*client, ui::Rect { 200, 200, 400, 300 }, "My Window", ui::CYAN)); + os::println("Created new window with id %d!", id); + + sleep(3); + + id = + TRY(create_window(*client, ui::Rect { 100, 100, 200, 150 }, "Super Long Name that is Way Too Long ", ui::BLUE)); + os::println("Created new window with id %d!", id); + + sleep(6); return 0; } -- 2.34.1 From 0fb47d90a72cebf92af269665af2f2d2532e7661 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 14 Aug 2023 20:08:05 +0200 Subject: [PATCH 046/103] wind+gclient: Add SetWindowTitle and support shm buffers --- apps/CMakeLists.txt | 1 + apps/gclient.cpp | 68 ++++++++++++++--- libui/include/ui/ipc/Client.h | 1 + libui/include/ui/ipc/Server.h | 19 ++++- wind/IPC.cpp | 140 ++++++++++++++++++++++++++++------ wind/Window.cpp | 14 +++- wind/Window.h | 6 +- wind/main.cpp | 2 +- 8 files changed, 212 insertions(+), 39 deletions(-) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index d9c430f8..db53aa7f 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -41,3 +41,4 @@ luna_app(gol.cpp gol) luna_app(touch.cpp touch) luna_app(free.cpp free) luna_app(gclient.cpp gclient) +target_link_libraries(gclient PUBLIC ui) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 3d4fde4b..9afe7e68 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -1,22 +1,67 @@ +#include #include #include #include +#include +#include #include #include +struct Window +{ + ui::Canvas canvas; + int id; + + void set_title(os::LocalClient& client, const char* title) + { + ui::SetWindowTitleRequest request; + request.window = id; + SET_IPC_STRING(request.title, title); + os::IPC::send_async(client, request); + } + + void redraw(os::LocalClient& client) + { + ui::InvalidateRequest request; + request.window = id; + os::IPC::send_async(client, request); + } +}; + Result handle_ipc_client_event(os::LocalClient&, u8) { todo(); } -Result create_window(os::LocalClient& client, ui::Rect rect, StringView name, ui::Color color) +static Result create_shm_region(const char* path, int* outfd, ui::Rect rect) +{ + int fd = shm_open(path, O_RDWR, 0600); + shm_unlink(path); + if (fd < 0) return err(errno); + + usize size = rect.width * rect.height * 4; // 4 bytes per pixel + + void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + shm_unlink(path); + close(fd); + return 0; + } + + if (outfd) *outfd = fd; + else + close(fd); + return (u32*)p; +} + +Result create_window(os::LocalClient& client, ui::Rect rect) { ui::CreateWindowRequest request; request.rect = rect; - SET_IPC_STRING(request.name, name.chars()); - request.color = color; auto response = TRY(os::IPC::send_sync(client, request)); - return response.window; + u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect)); + return Window { ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }, response.window }; } Result luna_main(int argc, char** argv) @@ -31,16 +76,19 @@ Result luna_main(int argc, char** argv) auto client = TRY(os::LocalClient::connect(socket_path, false)); - int id = TRY(create_window(*client, ui::Rect { 200, 200, 400, 300 }, "My Window", ui::CYAN)); - os::println("Created new window with id %d!", id); + Window window = TRY(create_window(*client, ui::Rect { 200, 200, 400, 300 })); + os::println("Created new window with id %d!", window.id); sleep(3); - id = - TRY(create_window(*client, ui::Rect { 100, 100, 200, 150 }, "Super Long Name that is Way Too Long ", ui::BLUE)); - os::println("Created new window with id %d!", id); + window.set_title(*client, "Example Window"); - sleep(6); + sleep(3); + + window.canvas.fill(ui::CYAN); + window.redraw(*client); + + sleep(3); return 0; } diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 876f50ef..7ebaf919 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -23,5 +23,6 @@ namespace ui static constexpr u8 id = CREATE_WINDOW_RESPONSE_ID; int window; + IPC_STRING(shm_path); }; } diff --git a/libui/include/ui/ipc/Server.h b/libui/include/ui/ipc/Server.h index bfa4afbe..da73b759 100644 --- a/libui/include/ui/ipc/Server.h +++ b/libui/include/ui/ipc/Server.h @@ -19,6 +19,8 @@ namespace ui { IPC_ENUM_SERVER(ui), CREATE_WINDOW_ID, + SET_WINDOW_TITLE_ID, + INVALIDATE_ID, }; struct CreateWindowRequest @@ -27,7 +29,20 @@ namespace ui static constexpr u8 id = CREATE_WINDOW_ID; ui::Rect rect; - IPC_STRING(name); - ui::Color color; + }; + + struct SetWindowTitleRequest + { + static constexpr u8 id = SET_WINDOW_TITLE_ID; + + int window; + IPC_STRING(title); + }; + + struct InvalidateRequest + { + static constexpr u8 id = INVALIDATE_ID; + + int window; }; } diff --git a/wind/IPC.cpp b/wind/IPC.cpp index e63dd3c0..ba265a1f 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -1,50 +1,144 @@ #include "IPC.h" +#include +#include #include #include +#include +#include +#include +#include + +#define TRY_OR_IPC_ERROR(expr) \ + ({ \ + auto _expr_rc = (expr); \ + if (!_expr_rc.has_value()) \ + { \ + delete window; \ + os::IPC::send_error(client.conn, _expr_rc.error()); \ + return {}; \ + } \ + _expr_rc.release_value(); \ + }) + +static Result create_shm_region(const char* path, int* outfd, ui::Rect rect) +{ + int fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0) + { + os::eprintln("wind: could not create shared memory region: shm_open failed (%s) - %s", path, strerror(errno)); + return err(errno); + } + + usize size = align_up(rect.width * rect.height * 4); // 4 bytes per pixel + + if (ftruncate(fd, size) < 0) + { + os::eprintln("wind: could not create shared memory region: ftruncate failed (%d, %zu) - %s", fd, size, + strerror(errno)); + shm_unlink(path); + close(fd); + return 0; + } + + void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + os::eprintln("wind: could not create shared memory region: mmap failed (%zu, %d) - %s", size, fd, + strerror(errno)); + shm_unlink(path); + close(fd); + return 0; + } + + if (outfd) *outfd = fd; + else + close(fd); + return (u32*)p; +} + +#define READ_MESSAGE(request) \ + do { \ + auto rc = client.conn.recv_typed(request); \ + if (rc.has_error()) \ + { \ + if (rc.error() == EAGAIN) \ + { \ + client.rpc_in_progress = true; \ + client.rpc_id = decltype(request)::id; \ + return {}; \ + } \ + else \ + return rc.release_error(); \ + } \ + } while (0) static Result handle_create_window_message(Client& client) { ui::CreateWindowRequest request; - auto rc = client.conn.recv_typed(request); - if (rc.has_error()) - { - if (rc.error() == EAGAIN) - { - client.rpc_in_progress = true; - client.rpc_id = ui::CREATE_WINDOW_ID; - return {}; - } - else - return rc.release_error(); - } + READ_MESSAGE(request); request.rect = request.rect.normalized(); request.rect.height += Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for. - auto name = COPY_IPC_STRING(request.name); + auto name = TRY(String::from_cstring("Window")); - auto* window = new (std::nothrow) Window(request.rect, request.color, move(name)); + auto* window = new (std::nothrow) Window(request.rect, move(name)); if (!window) { os::IPC::send_error(client.conn, ENOMEM); return {}; } - int id = static_cast(client.windows.size()); - rc = client.windows.try_append(window); - if (rc.has_error()) - { - delete window; - os::IPC::send_error(client.conn, rc.error()); - return {}; - } + auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL))); + + window->pixels = TRY_OR_IPC_ERROR(create_shm_region(shm_path.chars(), nullptr, window->contents)); + + TRY_OR_IPC_ERROR(client.windows.try_append(window)); + int id = static_cast(client.windows.size() - 1); ui::CreateWindowResponse response; response.window = id; + SET_IPC_STRING(response.shm_path, shm_path.chars()); os::IPC::send_async(client.conn, response); return {}; } +static Result handle_set_window_title_message(Client& client) +{ + ui::SetWindowTitleRequest request; + READ_MESSAGE(request); + + auto name = COPY_IPC_STRING(request.title); + + os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window); + + if ((usize)request.window >= client.windows.size()) + { + os::eprintln("wind: Window id out of range!"); + return {}; + } + + client.windows[request.window]->name = move(name); + + return {}; +} + +static Result handle_invalidate_message(Client& client) +{ + ui::InvalidateRequest request; + READ_MESSAGE(request); + + if ((usize)request.window >= client.windows.size()) + { + os::eprintln("wind: Window id out of range!"); + return {}; + } + + client.windows[request.window]->dirty = true; + + return {}; +} + namespace wind { Result handle_ipc_message(Client& client, u8 id) @@ -53,6 +147,8 @@ namespace wind switch (id) { case ui::CREATE_WINDOW_ID: return handle_create_window_message(client); + case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_message(client); + case ui::INVALIDATE_ID: return handle_invalidate_message(client); default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL); } } diff --git a/wind/Window.cpp b/wind/Window.cpp index bfe6a8b1..6e6e4eca 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -1,6 +1,7 @@ #include "Window.h" #include #include +#include #include #include @@ -8,8 +9,10 @@ LinkedList g_windows; void Window::draw(ui::Canvas& screen) { + dirty = false; + auto window = screen.subcanvas(surface); - window.subcanvas(contents).fill(color); + window.subcanvas(contents).fill(pixels, contents.width); wchar_t buffer[4096]; Utf8StringDecoder decoder(name.chars()); @@ -38,7 +41,7 @@ void Window::focus() g_windows.append(this); } -Window::Window(ui::Rect r, ui::Color c, String&& n) : surface(r), color(c), name(move(n)) +Window::Window(ui::Rect r, String&& n) : surface(r), name(move(n)) { auto font = ui::Font::default_font(); if (surface.width < 36) surface.width = 36; @@ -54,3 +57,10 @@ int Window::titlebar_height() auto font = ui::Font::default_font(); return font->height() + 20; } + +Window::~Window() +{ + usize size = contents.width * contents.height * 4; + + munmap(pixels, size); +} diff --git a/wind/Window.h b/wind/Window.h index b181e3e0..e4458fa7 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -11,12 +11,14 @@ struct Window : public LinkedListNode ui::Rect titlebar; ui::Rect close_button; ui::Rect contents; - ui::Color color; + u32* pixels; String name; + bool dirty { false }; static int titlebar_height(); - Window(ui::Rect, ui::Color, String&&); + Window(ui::Rect, String&&); + ~Window(); void focus(); diff --git a/wind/main.cpp b/wind/main.cpp index 90889c89..a6f64cc3 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -90,7 +90,7 @@ Result luna_main(int argc, char** argv) TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 })); TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 })); - TRY(os::Security::pledge("stdio rpath unix signal proc", NULL)); + TRY(os::Security::pledge("stdio rpath wpath cpath unix signal proc", NULL)); while (1) { -- 2.34.1 From 820b1ae2ba383a9122aed12c10135ecc0c7e3a53 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 10:23:37 +0200 Subject: [PATCH 047/103] libui+gclient: Add basic OOP wrappers around the IPC protocol --- apps/gclient.cpp | 92 +++++---------------------------------- libui/CMakeLists.txt | 2 + libui/include/ui/App.h | 41 +++++++++++++++++ libui/include/ui/Window.h | 38 ++++++++++++++++ libui/src/App.cpp | 54 +++++++++++++++++++++++ libui/src/Window.cpp | 77 ++++++++++++++++++++++++++++++++ 6 files changed, 223 insertions(+), 81 deletions(-) create mode 100644 libui/include/ui/App.h create mode 100644 libui/include/ui/Window.h create mode 100644 libui/src/App.cpp create mode 100644 libui/src/Window.cpp diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 9afe7e68..3d9558a8 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -1,94 +1,24 @@ -#include -#include #include -#include -#include -#include -#include -#include - -struct Window -{ - ui::Canvas canvas; - int id; - - void set_title(os::LocalClient& client, const char* title) - { - ui::SetWindowTitleRequest request; - request.window = id; - SET_IPC_STRING(request.title, title); - os::IPC::send_async(client, request); - } - - void redraw(os::LocalClient& client) - { - ui::InvalidateRequest request; - request.window = id; - os::IPC::send_async(client, request); - } -}; +#include +#include +#include Result handle_ipc_client_event(os::LocalClient&, u8) { todo(); } -static Result create_shm_region(const char* path, int* outfd, ui::Rect rect) -{ - int fd = shm_open(path, O_RDWR, 0600); - shm_unlink(path); - if (fd < 0) return err(errno); - - usize size = rect.width * rect.height * 4; // 4 bytes per pixel - - void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) - { - shm_unlink(path); - close(fd); - return 0; - } - - if (outfd) *outfd = fd; - else - close(fd); - return (u32*)p; -} - -Result create_window(os::LocalClient& client, ui::Rect rect) -{ - ui::CreateWindowRequest request; - request.rect = rect; - auto response = TRY(os::IPC::send_sync(client, request)); - u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect)); - return Window { ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }, response.window }; -} - Result luna_main(int argc, char** argv) { - StringView socket_path = "/tmp/wind.sock"; + ui::App app; + TRY(app.init(argc, argv)); - os::ArgumentParser parser; - parser.add_description("A graphical user interface client."_sv); - parser.add_system_program_info("gclient"_sv); - parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv); - parser.parse(argc, argv); + auto window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 })); + os::println("gclient: Created new window!"); - auto client = TRY(os::LocalClient::connect(socket_path, false)); + window->set_title("Example Window"); + window->canvas().fill(ui::CYAN); + window->update(); - Window window = TRY(create_window(*client, ui::Rect { 200, 200, 400, 300 })); - os::println("Created new window with id %d!", window.id); - - sleep(3); - - window.set_title(*client, "Example Window"); - - sleep(3); - - window.canvas.fill(ui::CYAN); - window.redraw(*client); - - sleep(3); - - return 0; + return app.run(); } diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 4a1adaa2..81270417 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -10,6 +10,8 @@ set(SOURCES src/Rect.cpp src/Font.cpp src/Image.cpp + src/App.cpp + src/Window.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/App.h b/libui/include/ui/App.h new file mode 100644 index 00000000..1f3b899f --- /dev/null +++ b/libui/include/ui/App.h @@ -0,0 +1,41 @@ +/** + * @file App.h + * @author apio (cloudapio.eu) + * @brief UI application event loop. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +namespace ui +{ + class App + { + public: + App(); + ~App(); + + Result init(int, char**); + Result run(); + + os::LocalClient& client() + { + return *m_client; + } + + void set_should_close(bool b) + { + m_should_close = b; + } + + static App& the(); + + private: + static App* s_app; + OwnedPtr m_client; + bool m_should_close { false }; + }; +} diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h new file mode 100644 index 00000000..0c0dae1c --- /dev/null +++ b/libui/include/ui/Window.h @@ -0,0 +1,38 @@ +/** + * @file Window.h + * @author apio (cloudapio.eu) + * @brief UI windows. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include +#include +#include + +namespace ui +{ + class Window + { + public: + static Result> create(Rect rect); + + void set_title(StringView title); + + Canvas& canvas() + { + return m_canvas; + } + + void update(); + + ~Window(); + + private: + int m_id; + Canvas m_canvas; + }; +} diff --git a/libui/src/App.cpp b/libui/src/App.cpp new file mode 100644 index 00000000..d7686c98 --- /dev/null +++ b/libui/src/App.cpp @@ -0,0 +1,54 @@ +/** + * @file App.cpp + * @author apio (cloudapio.eu) + * @brief UI application event loop. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include +#include + +namespace ui +{ + App* App::s_app { nullptr }; + + App::App() + { + s_app = this; + } + + App::~App() + { + s_app = nullptr; + } + + Result App::init(int argc, char** argv) + { + StringView socket_path = "/tmp/wind.sock"; + + os::ArgumentParser parser; + parser.add_description("A UI application."_sv); + parser.add_system_program_info(argv[0]); + parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv); + parser.parse(argc, argv); + + m_client = TRY(os::LocalClient::connect(socket_path, true)); + + return {}; + } + + Result App::run() + { + while (!m_should_close) { TRY(os::IPC::check_for_messages(*m_client)); } + return 0; + } + + App& App::the() + { + check(s_app); + return *s_app; + } +} diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp new file mode 100644 index 00000000..d56c1941 --- /dev/null +++ b/libui/src/Window.cpp @@ -0,0 +1,77 @@ +/** + * @file Window.cpp + * @author apio (cloudapio.eu) + * @brief UI windows. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +static Result create_shm_region(const char* path, int* outfd, ui::Rect rect) +{ + int fd = shm_open(path, O_RDWR, 0600); + shm_unlink(path); + if (fd < 0) return err(errno); + + usize size = rect.width * rect.height * 4; // 4 bytes per pixel + + void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + shm_unlink(path); + close(fd); + return 0; + } + + if (outfd) *outfd = fd; + else + close(fd); + return (u32*)p; +} + +namespace ui +{ + Result> Window::create(Rect rect) + { + auto window = TRY(make_owned()); + + ui::CreateWindowRequest request; + request.rect = rect; + auto response = TRY(os::IPC::send_sync(App::the().client(), request)); + + u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect)); + + window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }; + window->m_id = response.window; + + return window; + } + + Window::~Window() + { + if (m_canvas.ptr) munmap(m_canvas.ptr, ((usize)m_canvas.width) * ((usize)m_canvas.height) * 4); + } + + void Window::set_title(StringView title) + { + ui::SetWindowTitleRequest request; + request.window = m_id; + SET_IPC_STRING(request.title, title.chars()); + os::IPC::send_async(App::the().client(), request); + } + + void Window::update() + { + ui::InvalidateRequest request; + request.window = m_id; + os::IPC::send_async(App::the().client(), request); + } +} -- 2.34.1 From 2328987d8157806036c016bea7ce998549d7dd47 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 10:28:11 +0200 Subject: [PATCH 048/103] libos+libui+wind: Use uppercase for static struct IDs to avoid confusion with fields --- libos/include/os/IPC.h | 6 +++--- libui/include/ui/ipc/Client.h | 2 +- libui/include/ui/ipc/Server.h | 6 +++--- wind/IPC.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libos/include/os/IPC.h b/libos/include/os/IPC.h index 7e937bb0..260d9c3f 100644 --- a/libos/include/os/IPC.h +++ b/libos/include/os/IPC.h @@ -54,7 +54,7 @@ namespace os */ template Result send_async(Client& client, const T& message) { - u8 id = T::id; + u8 id = T::ID; TRY(client.send_typed(id)); TRY(client.send_typed(message)); return {}; @@ -90,7 +90,7 @@ namespace os Result send_sync(os::LocalClient& client, const T& message, decltype(handle_ipc_client_event) handler = handle_ipc_client_event) { - u8 id = T::id; + u8 id = T::ID; TRY(client.send_typed(id)); TRY(client.send_typed(message)); @@ -115,7 +115,7 @@ namespace os } } - if (response_id != ResponseType::id) + if (response_id != ResponseType::ID) { TRY(handler(client, response_id)); max_other_messages--; diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 7ebaf919..3320c796 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -20,7 +20,7 @@ namespace ui struct CreateWindowResponse { - static constexpr u8 id = CREATE_WINDOW_RESPONSE_ID; + static constexpr u8 ID = CREATE_WINDOW_RESPONSE_ID; int window; IPC_STRING(shm_path); diff --git a/libui/include/ui/ipc/Server.h b/libui/include/ui/ipc/Server.h index da73b759..5cde93e9 100644 --- a/libui/include/ui/ipc/Server.h +++ b/libui/include/ui/ipc/Server.h @@ -26,14 +26,14 @@ namespace ui struct CreateWindowRequest { using ResponseType = CreateWindowResponse; - static constexpr u8 id = CREATE_WINDOW_ID; + static constexpr u8 ID = CREATE_WINDOW_ID; ui::Rect rect; }; struct SetWindowTitleRequest { - static constexpr u8 id = SET_WINDOW_TITLE_ID; + static constexpr u8 ID = SET_WINDOW_TITLE_ID; int window; IPC_STRING(title); @@ -41,7 +41,7 @@ namespace ui struct InvalidateRequest { - static constexpr u8 id = INVALIDATE_ID; + static constexpr u8 ID = INVALIDATE_ID; int window; }; diff --git a/wind/IPC.cpp b/wind/IPC.cpp index ba265a1f..77392736 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -64,7 +64,7 @@ static Result create_shm_region(const char* path, int* outfd, ui::Rect rec if (rc.error() == EAGAIN) \ { \ client.rpc_in_progress = true; \ - client.rpc_id = decltype(request)::id; \ + client.rpc_id = decltype(request)::ID; \ return {}; \ } \ else \ -- 2.34.1 From 062b09e20c85a50cda9a084eab696be29ccfd325 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 11:20:17 +0200 Subject: [PATCH 049/103] wind+libui: Add protocol for window close requests --- apps/gclient.cpp | 19 +++++++-------- libui/include/ui/App.h | 22 ++++++++++++++++++ libui/include/ui/Window.h | 9 ++++++- libui/include/ui/ipc/Client.h | 8 +++++++ libui/include/ui/ipc/Server.h | 8 +++++++ libui/src/App.cpp | 44 +++++++++++++++++++++++++++++++++++ libui/src/Window.cpp | 21 +++++++++++++++-- wind/IPC.cpp | 40 +++++++++++++++++++++++-------- wind/Mouse.cpp | 10 +++++--- wind/Window.h | 4 ++++ wind/main.cpp | 7 ++++-- 11 files changed, 163 insertions(+), 29 deletions(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 3d9558a8..52f224ce 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -1,24 +1,21 @@ -#include #include -#include -#include - -Result handle_ipc_client_event(os::LocalClient&, u8) -{ - todo(); -} Result luna_main(int argc, char** argv) { ui::App app; TRY(app.init(argc, argv)); - auto window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 })); - os::println("gclient: Created new window!"); + auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 })); + app.set_main_window(window); - window->set_title("Example Window"); + window->set_title("Main Window"); window->canvas().fill(ui::CYAN); window->update(); + auto* dialog = TRY(ui::Window::create(ui::Rect { 400, 400, 200, 150 })); + dialog->set_title("Error: Unknown Error"); + dialog->canvas().fill(ui::RED); + dialog->update(); + return app.run(); } diff --git a/libui/include/ui/App.h b/libui/include/ui/App.h index 1f3b899f..0e1e1262 100644 --- a/libui/include/ui/App.h +++ b/libui/include/ui/App.h @@ -8,7 +8,9 @@ */ #pragma once +#include #include +#include namespace ui { @@ -31,11 +33,31 @@ namespace ui m_should_close = b; } + void set_main_window(Window* window) + { + check(!m_main_window); + m_main_window = window; + } + + Window* main_window() + { + return m_main_window; + } + + Result register_window(OwnedPtr&& window, Badge); + void unregister_window(Window* window, Badge); + + Result handle_ipc_event(u8 id); + static App& the(); private: static App* s_app; OwnedPtr m_client; + Window* m_main_window { nullptr }; + HashMap> m_windows; bool m_should_close { false }; + + Window* find_window(int id); }; } diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index 0c0dae1c..bf13d32b 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -18,7 +18,7 @@ namespace ui class Window { public: - static Result> create(Rect rect); + static Result create(Rect rect); void set_title(StringView title); @@ -29,6 +29,13 @@ namespace ui void update(); + void close(); + + int id() const + { + return m_id; + } + ~Window(); private: diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 3320c796..35fbbef9 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -16,6 +16,7 @@ namespace ui { IPC_ENUM_CLIENT(ui), CREATE_WINDOW_RESPONSE_ID, + WINDOW_CLOSE_REQUEST_ID, }; struct CreateWindowResponse @@ -25,4 +26,11 @@ namespace ui int window; IPC_STRING(shm_path); }; + + struct WindowCloseRequest + { + static constexpr u8 ID = WINDOW_CLOSE_REQUEST_ID; + + int window; + }; } diff --git a/libui/include/ui/ipc/Server.h b/libui/include/ui/ipc/Server.h index 5cde93e9..cd18e336 100644 --- a/libui/include/ui/ipc/Server.h +++ b/libui/include/ui/ipc/Server.h @@ -21,6 +21,7 @@ namespace ui CREATE_WINDOW_ID, SET_WINDOW_TITLE_ID, INVALIDATE_ID, + CLOSE_WINDOW_ID, }; struct CreateWindowRequest @@ -45,4 +46,11 @@ namespace ui int window; }; + + struct CloseWindowRequest + { + static constexpr u8 ID = CLOSE_WINDOW_ID; + + int window; + }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index d7686c98..79c00d23 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -8,8 +8,15 @@ */ #include +#include #include #include +#include + +Result handle_ipc_client_event(os::LocalClient&, u8 id) +{ + return ui::App::the().handle_ipc_event(id); +} namespace ui { @@ -42,6 +49,7 @@ namespace ui Result App::run() { + check(m_main_window); while (!m_should_close) { TRY(os::IPC::check_for_messages(*m_client)); } return 0; } @@ -51,4 +59,40 @@ namespace ui check(s_app); return *s_app; } + + Result App::register_window(OwnedPtr&& window, Badge) + { + int id = window->id(); + check(TRY(m_windows.try_set(id, move(window)))); + return {}; + } + + void App::unregister_window(Window* window, Badge) + { + int id = window->id(); + check(m_windows.try_remove(id)); + } + + Window* App::find_window(int id) + { + auto* window = m_windows.try_get_ref(id); + check(window); + return window->ptr(); + } + + Result App::handle_ipc_event(u8 id) + { + switch (id) + { + case WINDOW_CLOSE_REQUEST_ID: { + WindowCloseRequest request; + TRY(m_client->recv_typed(request)); + os::eprintln("ui: Window close request from server! Shall comply."); + auto* window = find_window(request.window); + window->close(); + return {}; + } + default: fail("Unexpected IPC request from server!"); + } + } } diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index d56c1941..f1c97b78 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -39,7 +39,7 @@ static Result create_shm_region(const char* path, int* outfd, ui::Rect rec namespace ui { - Result> Window::create(Rect rect) + Result Window::create(Rect rect) { auto window = TRY(make_owned()); @@ -52,7 +52,11 @@ namespace ui window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }; window->m_id = response.window; - return window; + Window* p = window.ptr(); + + App::the().register_window(move(window), {}); + + return p; } Window::~Window() @@ -74,4 +78,17 @@ namespace ui request.window = m_id; os::IPC::send_async(App::the().client(), request); } + + void Window::close() + { + App& app = App::the(); + + ui::CloseWindowRequest request; + request.window = m_id; + os::IPC::send_async(app.client(), request); + + if (this == app.main_window()) app.set_should_close(true); + + app.unregister_window(this, {}); + } } diff --git a/wind/IPC.cpp b/wind/IPC.cpp index 77392736..3abeedf4 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -72,6 +72,15 @@ static Result create_shm_region(const char* path, int* outfd, ui::Rect rec } \ } while (0) +#define CHECK_WINDOW_ID(request) \ + do { \ + if ((usize)request.window >= client.windows.size() || !client.windows[request.window]) \ + { \ + os::eprintln("wind: Window id is invalid!"); \ + return {}; \ + } \ + } while (0) + static Result handle_create_window_message(Client& client) { ui::CreateWindowRequest request; @@ -96,6 +105,9 @@ static Result handle_create_window_message(Client& client) TRY_OR_IPC_ERROR(client.windows.try_append(window)); int id = static_cast(client.windows.size() - 1); + window->client = &client; + window->id = id; + ui::CreateWindowResponse response; response.window = id; SET_IPC_STRING(response.shm_path, shm_path.chars()); @@ -112,11 +124,7 @@ static Result handle_set_window_title_message(Client& client) os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window); - if ((usize)request.window >= client.windows.size()) - { - os::eprintln("wind: Window id out of range!"); - return {}; - } + CHECK_WINDOW_ID(request); client.windows[request.window]->name = move(name); @@ -128,17 +136,28 @@ static Result handle_invalidate_message(Client& client) ui::InvalidateRequest request; READ_MESSAGE(request); - if ((usize)request.window >= client.windows.size()) - { - os::eprintln("wind: Window id out of range!"); - return {}; - } + CHECK_WINDOW_ID(request); client.windows[request.window]->dirty = true; return {}; } +static Result handle_close_window_message(Client& client) +{ + ui::CloseWindowRequest request; + READ_MESSAGE(request); + + CHECK_WINDOW_ID(request); + + auto* window = client.windows[request.window]; + client.windows[request.window] = nullptr; + g_windows.remove(window); + delete window; + + return {}; +} + namespace wind { Result handle_ipc_message(Client& client, u8 id) @@ -149,6 +168,7 @@ namespace wind case ui::CREATE_WINDOW_ID: return handle_create_window_message(client); case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_message(client); case ui::INVALIDATE_ID: return handle_invalidate_message(client); + case ui::CLOSE_WINDOW_ID: return handle_close_window_message(client); default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL); } } diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index 2d1bb076..63ea0aad 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -1,6 +1,9 @@ #include "Mouse.h" +#include "Client.h" #include +#include #include +#include static SharedPtr g_mouse_cursor; @@ -51,9 +54,10 @@ void Mouse::update(const moon::MousePacket& packet) { if (window->surface.absolute(window->close_button).contains(m_position)) { - // Close button pressed - g_windows.remove(window); - delete window; + ui::WindowCloseRequest request; + request.window = window->id; + auto& client = *window->client; + os::IPC::send_async(client.conn, request); break; } else if (window->surface.absolute(window->titlebar).contains(m_position)) diff --git a/wind/Window.h b/wind/Window.h index e4458fa7..a5cddaaf 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -5,6 +5,8 @@ #include #include +struct Client; + struct Window : public LinkedListNode { ui::Rect surface; @@ -14,6 +16,8 @@ struct Window : public LinkedListNode u32* pixels; String name; bool dirty { false }; + Client* client; + int id; static int titlebar_height(); diff --git a/wind/main.cpp b/wind/main.cpp index a6f64cc3..9ebd3e6a 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -128,8 +128,11 @@ Result luna_main(int argc, char** argv) client.conn.disconnect(); for (auto& window : client.windows) { - g_windows.remove(window); - delete window; + if (window) + { + g_windows.remove(window); + delete window; + } } } } -- 2.34.1 From 69bb22095fe428316accc3ecb4f2d831aabd6137 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 11:31:01 +0200 Subject: [PATCH 050/103] ui+wind: Send mouse move events through IPC --- libui/include/ui/ipc/Client.h | 11 +++++++++++ libui/src/App.cpp | 6 ++++++ wind/Mouse.cpp | 17 +++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 35fbbef9..35b09642 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace ui { @@ -17,6 +18,7 @@ namespace ui IPC_ENUM_CLIENT(ui), CREATE_WINDOW_RESPONSE_ID, WINDOW_CLOSE_REQUEST_ID, + MOUSE_EVENT_REQUEST_ID, }; struct CreateWindowResponse @@ -33,4 +35,13 @@ namespace ui int window; }; + + struct MouseEventRequest + { + static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID; + + int window; + Point position; + int buttons; + }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 79c00d23..7d934f9f 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -92,6 +92,12 @@ namespace ui window->close(); return {}; } + case MOUSE_EVENT_REQUEST_ID: { + MouseEventRequest request; + TRY(m_client->recv_typed(request)); + os::eprintln("ui: Mouse move to (%d, %d)", request.position.x, request.position.y); + return {}; + } default: fail("Unexpected IPC request from server!"); } } diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index 63ea0aad..7f3d72e2 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -78,4 +78,21 @@ void Mouse::update(const moon::MousePacket& packet) } } } + + for (Window* window = g_windows.last().value_or(nullptr); window; + window = g_windows.previous(window).value_or(nullptr)) + { + auto titlebar = window->surface.absolute(window->titlebar); + auto contents = window->surface.absolute(window->contents); + if (titlebar.contains(m_position)) break; + if (contents.contains(m_position)) + { + ui::MouseEventRequest request; + request.window = window->id; + request.position = contents.relative(m_position); + request.buttons = packet.buttons; + os::IPC::send_async(window->client->conn, request); + break; + } + } } -- 2.34.1 From 819baa0cd587470578bd4a79426503246ef1dbb5 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 12:28:47 +0200 Subject: [PATCH 051/103] libui: Add basic widget and layout system =D --- apps/gclient.cpp | 47 +++++++++++++++++++++---- libui/CMakeLists.txt | 1 + libui/include/ui/Layout.h | 45 ++++++++++++++++++++++++ libui/include/ui/Widget.h | 65 +++++++++++++++++++++++++++++++++++ libui/include/ui/Window.h | 18 ++++++++++ libui/src/App.cpp | 4 ++- libui/src/Layout.cpp | 72 +++++++++++++++++++++++++++++++++++++++ libui/src/Window.cpp | 13 +++++++ 8 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 libui/include/ui/Layout.h create mode 100644 libui/include/ui/Widget.h create mode 100644 libui/src/Layout.cpp diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 52f224ce..0bdeddaf 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -1,4 +1,27 @@ #include +#include + +struct ColorWidget : public ui::Widget +{ + public: + ColorWidget(ui::Color color) : m_color(color) + { + } + + Result handle_mousemove(ui::Point) override + { + return ui::EventResult::DidNotHandle; + } + + Result draw(ui::Canvas& canvas) override + { + canvas.fill(m_color); + return {}; + } + + private: + ui::Color m_color; +}; Result luna_main(int argc, char** argv) { @@ -9,13 +32,25 @@ Result luna_main(int argc, char** argv) app.set_main_window(window); window->set_title("Main Window"); - window->canvas().fill(ui::CYAN); - window->update(); + window->set_background(ui::CYAN); - auto* dialog = TRY(ui::Window::create(ui::Rect { 400, 400, 200, 150 })); - dialog->set_title("Error: Unknown Error"); - dialog->canvas().fill(ui::RED); - dialog->update(); + ui::HorizontalLayout layout; + window->set_main_widget(layout); + + ColorWidget green(ui::GREEN); + layout.add_widget(green); + ColorWidget blue(ui::BLUE); + layout.add_widget(blue); + + ui::HorizontalLayout sublayout; + layout.add_widget(sublayout); + + ColorWidget red(ui::RED); + sublayout.add_widget(red); + ColorWidget white(ui::WHITE); + sublayout.add_widget(white); + + window->draw(); return app.run(); } diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 81270417..ad9cb181 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES src/Image.cpp src/App.cpp src/Window.cpp + src/Layout.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h new file mode 100644 index 00000000..98ce132e --- /dev/null +++ b/libui/include/ui/Layout.h @@ -0,0 +1,45 @@ +/** + * @file Layout.h + * @author apio (cloudapio.eu) + * @brief Layout widgets to organize content. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include + +namespace ui +{ + enum class AdjustHeight + { + No, + Yes + }; + + enum class AdjustWidth + { + No, + Yes + }; + + class HorizontalLayout final : public Widget + { + public: + HorizontalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); + + Result handle_mousemove(Point position) override; + + Result draw(Canvas& canvas) override; + + Result add_widget(Widget& widget); + + private: + Vector m_widgets; + AdjustHeight m_adjust_height; + AdjustWidth m_adjust_width; + int m_used_width; + }; +} diff --git a/libui/include/ui/Widget.h b/libui/include/ui/Widget.h new file mode 100644 index 00000000..24b1f31f --- /dev/null +++ b/libui/include/ui/Widget.h @@ -0,0 +1,65 @@ +/** + * @file Widget.h + * @author apio (cloudapio.eu) + * @brief Abstract widget class. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include +#include +#include + +namespace ui +{ + class Window; + + enum class EventResult + { + DidHandle, + DidNotHandle, + }; + + class Widget + { + public: + virtual Result handle_mousemove(Point position); + + virtual Result draw(Canvas& canvas); + + void set_window(Window* window, Rect rect, Badge) + { + m_window = window; + m_rect = rect; + } + + void set_parent(Widget* parent) + { + m_parent = parent; + m_window = parent->m_window; + } + + Widget* parent() + { + return m_parent; + } + + Window* window() + { + return m_window; + } + + Rect& rect() + { + return m_rect; + } + + protected: + Widget* m_parent { nullptr }; + Window* m_window; + Rect m_rect { 0, 0, 50, 50 }; + }; +} diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index bf13d32b..ee08b0e6 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace ui { @@ -22,6 +23,18 @@ namespace ui void set_title(StringView title); + void set_background(Color color) + { + m_background = color; + } + + void set_main_widget(Widget& widget) + { + check(!m_main_widget); + widget.set_window(this, m_canvas.rect(), {}); + m_main_widget = &widget; + } + Canvas& canvas() { return m_canvas; @@ -31,6 +44,9 @@ namespace ui void close(); + Result draw(); + Result handle_mousemove(ui::Point position); + int id() const { return m_id; @@ -41,5 +57,7 @@ namespace ui private: int m_id; Canvas m_canvas; + Widget* m_main_widget { nullptr }; + Color m_background { ui::BLACK }; }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 7d934f9f..30ea1ff4 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -95,7 +95,9 @@ namespace ui case MOUSE_EVENT_REQUEST_ID: { MouseEventRequest request; TRY(m_client->recv_typed(request)); - os::eprintln("ui: Mouse move to (%d, %d)", request.position.x, request.position.y); + auto* window = find_window(request.window); + window->handle_mousemove(request.position); + window->draw(); return {}; } default: fail("Unexpected IPC request from server!"); diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp new file mode 100644 index 00000000..9d5d124b --- /dev/null +++ b/libui/src/Layout.cpp @@ -0,0 +1,72 @@ +/** + * @file Layout.cpp + * @author apio (cloudapio.eu) + * @brief Layout widgets to organize content. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include + +namespace ui +{ + HorizontalLayout::HorizontalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width) + : m_adjust_height(adjust_height), m_adjust_width(adjust_width) + { + } + + Result HorizontalLayout::handle_mousemove(Point position) + { + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) return widget->handle_mousemove(position); + } + + return ui::EventResult::DidNotHandle; + } + + Result HorizontalLayout::draw(Canvas& canvas) + { + for (auto widget : m_widgets) + { + ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height }; + auto subcanvas = canvas.subcanvas(rect); + TRY(widget->draw(subcanvas)); + } + + return {}; + } + + Result HorizontalLayout::add_widget(Widget& widget) + { + TRY(m_widgets.try_append(&widget)); + + if (m_adjust_width == AdjustWidth::No) + { + widget.rect().pos.x = m_rect.pos.x + m_used_width; + m_used_width += widget.rect().width; + } + else + { + int used_width = 0; + div_t result = div(m_rect.width, (int)m_widgets.size()); + for (auto w : m_widgets) + { + w->rect().pos.x = m_rect.pos.x + used_width; + w->rect().width = result.quot; + used_width += result.quot; + } + m_widgets[m_widgets.size() - 1]->rect().width += result.rem; + } + + widget.rect().pos.y = m_rect.pos.y; + + if (m_adjust_height == AdjustHeight::Yes) { widget.rect().height = m_rect.height; } + + widget.set_parent(this); + + return {}; + } +} diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index f1c97b78..be8fcd0f 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -91,4 +91,17 @@ namespace ui app.unregister_window(this, {}); } + + Result Window::draw() + { + if (m_main_widget) return m_main_widget->draw(m_canvas); + update(); + return {}; + } + + Result Window::handle_mousemove(ui::Point position) + { + if (m_main_widget) TRY(m_main_widget->handle_mousemove(position)); + return {}; + } } -- 2.34.1 From 4d068beaaff82d4478a66e6ac1cca7de1a439a4a Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 12:30:53 +0200 Subject: [PATCH 052/103] libui: Actually fill window backgrounds with the correct color --- apps/gclient.cpp | 2 +- libui/src/Window.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 0bdeddaf..86bf30bf 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -42,7 +42,7 @@ Result luna_main(int argc, char** argv) ColorWidget blue(ui::BLUE); layout.add_widget(blue); - ui::HorizontalLayout sublayout; + ui::HorizontalLayout sublayout(ui::AdjustHeight::No, ui::AdjustWidth::Yes); layout.add_widget(sublayout); ColorWidget red(ui::RED); diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index be8fcd0f..8d50ce72 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -94,6 +94,7 @@ namespace ui Result Window::draw() { + m_canvas.fill(m_background); if (m_main_widget) return m_main_widget->draw(m_canvas); update(); return {}; -- 2.34.1 From 5703faf50fc82b6b772ae8decb5bc9d6d206591c Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 12:56:55 +0200 Subject: [PATCH 053/103] wind+libui+taskbar: Add GetScreenRect IPC, non-decorated windows, taskbar --- apps/CMakeLists.txt | 2 ++ apps/taskbar.cpp | 23 +++++++++++++++++++++++ base/etc/user/00-taskbar | 4 ++++ base/etc/user/{00-gclient => 01-gclient} | 0 libui/include/ui/App.h | 2 ++ libui/include/ui/Window.h | 2 +- libui/include/ui/ipc/Client.h | 8 ++++++++ libui/include/ui/ipc/Server.h | 10 ++++++++++ libui/src/App.cpp | 8 ++++++++ libui/src/Window.cpp | 3 ++- wind/IPC.cpp | 24 ++++++++++++++++++++++-- wind/Screen.cpp | 8 ++++++-- wind/Screen.h | 9 ++++++++- wind/Window.cpp | 13 +++++++------ wind/Window.h | 3 ++- wind/main.cpp | 3 ++- 16 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 apps/taskbar.cpp create mode 100644 base/etc/user/00-taskbar rename base/etc/user/{00-gclient => 01-gclient} (100%) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index db53aa7f..14d4b50c 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -42,3 +42,5 @@ luna_app(touch.cpp touch) luna_app(free.cpp free) luna_app(gclient.cpp gclient) target_link_libraries(gclient PUBLIC ui) +luna_app(taskbar.cpp taskbar) +target_link_libraries(taskbar PUBLIC ui) diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp new file mode 100644 index 00000000..f6e7ba38 --- /dev/null +++ b/apps/taskbar.cpp @@ -0,0 +1,23 @@ +#include +#include + +Result luna_main(int argc, char** argv) +{ + ui::App app; + TRY(app.init(argc, argv)); + + ui::Rect screen = app.screen_rect(); + + ui::Rect bar = ui::Rect { ui::Point { 0, screen.height - 50 }, screen.width, 50 }; + + auto window = TRY(ui::Window::create(bar, false)); + app.set_main_window(window); + window->set_background(ui::GRAY); + + ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No); + window->set_main_widget(layout); + + window->draw(); + + return app.run(); +} diff --git a/base/etc/user/00-taskbar b/base/etc/user/00-taskbar new file mode 100644 index 00000000..19614216 --- /dev/null +++ b/base/etc/user/00-taskbar @@ -0,0 +1,4 @@ +Name=taskbar +Description=Start the taskbar. +Command=/usr/bin/taskbar +Restart=true diff --git a/base/etc/user/00-gclient b/base/etc/user/01-gclient similarity index 100% rename from base/etc/user/00-gclient rename to base/etc/user/01-gclient diff --git a/libui/include/ui/App.h b/libui/include/ui/App.h index 0e1e1262..231c9af3 100644 --- a/libui/include/ui/App.h +++ b/libui/include/ui/App.h @@ -23,6 +23,8 @@ namespace ui Result init(int, char**); Result run(); + Rect screen_rect(); + os::LocalClient& client() { return *m_client; diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index ee08b0e6..490cf3ec 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -19,7 +19,7 @@ namespace ui class Window { public: - static Result create(Rect rect); + static Result create(Rect rect, bool decorated = true); void set_title(StringView title); diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 35b09642..16f6bf68 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -19,6 +19,7 @@ namespace ui CREATE_WINDOW_RESPONSE_ID, WINDOW_CLOSE_REQUEST_ID, MOUSE_EVENT_REQUEST_ID, + GET_SCREEN_RECT_RESPONSE_ID }; struct CreateWindowResponse @@ -44,4 +45,11 @@ namespace ui Point position; int buttons; }; + + struct GetScreenRectResponse + { + static constexpr u8 ID = GET_SCREEN_RECT_RESPONSE_ID; + + Rect rect; + }; } diff --git a/libui/include/ui/ipc/Server.h b/libui/include/ui/ipc/Server.h index cd18e336..7e675c53 100644 --- a/libui/include/ui/ipc/Server.h +++ b/libui/include/ui/ipc/Server.h @@ -22,6 +22,7 @@ namespace ui SET_WINDOW_TITLE_ID, INVALIDATE_ID, CLOSE_WINDOW_ID, + GET_SCREEN_RECT_ID, }; struct CreateWindowRequest @@ -30,6 +31,7 @@ namespace ui static constexpr u8 ID = CREATE_WINDOW_ID; ui::Rect rect; + bool decorated; }; struct SetWindowTitleRequest @@ -53,4 +55,12 @@ namespace ui int window; }; + + struct GetScreenRectRequest + { + using ResponseType = GetScreenRectResponse; + static constexpr u8 ID = GET_SCREEN_RECT_ID; + + int _shadow; // Unused. + }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 30ea1ff4..7a979206 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -12,6 +12,7 @@ #include #include #include +#include Result handle_ipc_client_event(os::LocalClient&, u8 id) { @@ -60,6 +61,13 @@ namespace ui return *s_app; } + Rect App::screen_rect() + { + ui::GetScreenRectRequest request {}; + auto response = os::IPC::send_sync(*m_client, request).release_value(); + return response.rect; + } + Result App::register_window(OwnedPtr&& window, Badge) { int id = window->id(); diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index 8d50ce72..99d2e66d 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -39,12 +39,13 @@ static Result create_shm_region(const char* path, int* outfd, ui::Rect rec namespace ui { - Result Window::create(Rect rect) + Result Window::create(Rect rect, bool decorated) { auto window = TRY(make_owned()); ui::CreateWindowRequest request; request.rect = rect; + request.decorated = decorated; auto response = TRY(os::IPC::send_sync(App::the().client(), request)); u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect)); diff --git a/wind/IPC.cpp b/wind/IPC.cpp index 3abeedf4..dbec731f 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -1,4 +1,5 @@ #include "IPC.h" +#include "Screen.h" #include #include #include @@ -87,11 +88,17 @@ static Result handle_create_window_message(Client& client) READ_MESSAGE(request); request.rect = request.rect.normalized(); - request.rect.height += Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for. + + if (request.decorated) + { + request.rect.height += + Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for. + request.rect.pos.y -= Window::titlebar_height(); // Adjust it so the contents begin at the expected coordinates. + } auto name = TRY(String::from_cstring("Window")); - auto* window = new (std::nothrow) Window(request.rect, move(name)); + auto* window = new (std::nothrow) Window(request.rect, move(name), request.decorated); if (!window) { os::IPC::send_error(client.conn, ENOMEM); @@ -158,6 +165,18 @@ static Result handle_close_window_message(Client& client) return {}; } +static Result handle_get_screen_rect_message(Client& client) +{ + ui::GetScreenRectRequest request; + READ_MESSAGE(request); // Kinda pointless, but required. + + ui::GetScreenRectResponse response; + response.rect = Screen::the().canvas().rect(); + os::IPC::send_async(client.conn, response); + + return {}; +} + namespace wind { Result handle_ipc_message(Client& client, u8 id) @@ -169,6 +188,7 @@ namespace wind case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_message(client); case ui::INVALIDATE_ID: return handle_invalidate_message(client); case ui::CLOSE_WINDOW_ID: return handle_close_window_message(client); + case ui::GET_SCREEN_RECT_ID: return handle_get_screen_rect_message(client); default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL); } } diff --git a/wind/Screen.cpp b/wind/Screen.cpp index 8d0e224c..48ce774a 100644 --- a/wind/Screen.cpp +++ b/wind/Screen.cpp @@ -5,7 +5,9 @@ #include #include -Result Screen::open() +Screen Screen::s_the; + +Result Screen::open() { int fd = ::open("/dev/fb0", O_RDWR); if (fd < 0) return err(errno); @@ -23,7 +25,9 @@ Result Screen::open() screen.m_canvas = ui::Canvas::create((u8*)p, width, height); screen.m_size = width * height * BYTES_PER_PIXEL; - return screen; + s_the = screen; + + return {}; } void Screen::sync() diff --git a/wind/Screen.h b/wind/Screen.h index a98295c6..543b24f9 100644 --- a/wind/Screen.h +++ b/wind/Screen.h @@ -7,7 +7,7 @@ constexpr int BYTES_PER_PIXEL = 4; class Screen { public: - static Result open(); + static Result open(); ui::Canvas& canvas() { @@ -19,9 +19,16 @@ class Screen return m_size; } + static Screen& the() + { + return s_the; + } + void sync(); private: ui::Canvas m_canvas; int m_size; + + static Screen s_the; }; diff --git a/wind/Window.cpp b/wind/Window.cpp index 6e6e4eca..43e0e5ad 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -41,14 +41,15 @@ void Window::focus() g_windows.append(this); } -Window::Window(ui::Rect r, String&& n) : surface(r), name(move(n)) +Window::Window(ui::Rect r, String&& n, bool d) : surface(r), name(move(n)), decorated(d) { auto font = ui::Font::default_font(); - if (surface.width < 36) surface.width = 36; - if (surface.height < (font->height() + 20)) surface.height = font->height() + 20; - titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 }; - close_button = ui::Rect { surface.width - 26, 10, 16, 16 }; - contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) }; + if (decorated && surface.width < 36) surface.width = 36; + if (decorated && surface.height < (font->height() + 20)) surface.height = font->height() + 20; + titlebar = decorated ? ui::Rect { 0, 0, surface.width, font->height() + 20 } : ui::Rect { 0, 0, 0, 0 }; + close_button = decorated ? ui::Rect { surface.width - 26, 10, 16, 16 } : ui::Rect { 0, 0, 0, 0 }; + contents = decorated ? ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) } + : ui::Rect { 0, 0, surface.width, surface.height }; g_windows.append(this); } diff --git a/wind/Window.h b/wind/Window.h index a5cddaaf..9fcf33b2 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -18,10 +18,11 @@ struct Window : public LinkedListNode bool dirty { false }; Client* client; int id; + bool decorated; static int titlebar_height(); - Window(ui::Rect, String&&); + Window(ui::Rect, String&&, bool); ~Window(); void focus(); diff --git a/wind/main.cpp b/wind/main.cpp index 9ebd3e6a..0dd625dc 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -49,7 +49,8 @@ Result luna_main(int argc, char** argv) keyboard->set_buffer(os::File::NotBuffered); keyboard->set_close_on_exec(); - auto screen = TRY(Screen::open()); + TRY(Screen::open()); + auto& screen = Screen::the(); Mouse mouse_pointer { screen.canvas() }; -- 2.34.1 From f657ee9ba9f3f2cf8ff912bada60ae8df7524b70 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 13:01:41 +0200 Subject: [PATCH 054/103] libui: Add VerticalLayout --- apps/gclient.cpp | 2 +- libui/include/ui/Layout.h | 18 ++++++++++++ libui/src/Layout.cpp | 58 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 86bf30bf..23821081 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -42,7 +42,7 @@ Result luna_main(int argc, char** argv) ColorWidget blue(ui::BLUE); layout.add_widget(blue); - ui::HorizontalLayout sublayout(ui::AdjustHeight::No, ui::AdjustWidth::Yes); + ui::VerticalLayout sublayout; layout.add_widget(sublayout); ColorWidget red(ui::RED); diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h index 98ce132e..83cb0fef 100644 --- a/libui/include/ui/Layout.h +++ b/libui/include/ui/Layout.h @@ -42,4 +42,22 @@ namespace ui AdjustWidth m_adjust_width; int m_used_width; }; + + class VerticalLayout final : public Widget + { + public: + VerticalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); + + Result handle_mousemove(Point position) override; + + Result draw(Canvas& canvas) override; + + Result add_widget(Widget& widget); + + private: + Vector m_widgets; + AdjustHeight m_adjust_height; + AdjustWidth m_adjust_width; + int m_used_height; + }; } diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp index 9d5d124b..4eeca17f 100644 --- a/libui/src/Layout.cpp +++ b/libui/src/Layout.cpp @@ -69,4 +69,62 @@ namespace ui return {}; } + + VerticalLayout::VerticalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width) + : m_adjust_height(adjust_height), m_adjust_width(adjust_width) + { + } + + Result VerticalLayout::handle_mousemove(Point position) + { + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) return widget->handle_mousemove(position); + } + + return ui::EventResult::DidNotHandle; + } + + Result VerticalLayout::draw(Canvas& canvas) + { + for (auto widget : m_widgets) + { + ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height }; + auto subcanvas = canvas.subcanvas(rect); + TRY(widget->draw(subcanvas)); + } + + return {}; + } + + Result VerticalLayout::add_widget(Widget& widget) + { + TRY(m_widgets.try_append(&widget)); + + if (m_adjust_height == AdjustHeight::No) + { + widget.rect().pos.y = m_rect.pos.y + m_used_height; + m_used_height += widget.rect().height; + } + else + { + int used_height = 0; + div_t result = div(m_rect.height, (int)m_widgets.size()); + for (auto w : m_widgets) + { + w->rect().pos.y = m_rect.pos.y + used_height; + w->rect().height = result.quot; + used_height += result.quot; + } + m_widgets[m_widgets.size() - 1]->rect().height += result.rem; + } + + widget.rect().pos.x = m_rect.pos.x; + + if (m_adjust_width == AdjustWidth::Yes) { widget.rect().width = m_rect.width; } + + widget.set_parent(this); + + return {}; + } } -- 2.34.1 From 35c7011997a99bbe94a71e03ce4fcfac75f13b76 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 14:42:21 +0200 Subject: [PATCH 055/103] libui: Add aligned items using Containers, ImageWidget --- apps/taskbar.cpp | 8 ++++++++ libui/CMakeLists.txt | 2 ++ libui/include/ui/Alignment.h | 30 +++++++++++++++++++++++++++ libui/include/ui/Container.h | 31 ++++++++++++++++++++++++++++ libui/include/ui/Image.h | 13 ++++++++++++ libui/src/Alignment.cpp | 40 ++++++++++++++++++++++++++++++++++++ libui/src/Container.cpp | 39 +++++++++++++++++++++++++++++++++++ libui/src/Image.cpp | 20 ++++++++++++++++++ 8 files changed, 183 insertions(+) create mode 100644 libui/include/ui/Alignment.h create mode 100644 libui/include/ui/Container.h create mode 100644 libui/src/Alignment.cpp create mode 100644 libui/src/Container.cpp diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp index f6e7ba38..975df284 100644 --- a/apps/taskbar.cpp +++ b/apps/taskbar.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include Result luna_main(int argc, char** argv) @@ -17,6 +19,12 @@ Result luna_main(int argc, char** argv) ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No); window->set_main_widget(layout); + ui::Container container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); + layout.add_widget(container); + + auto image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga")); + container.set_widget(*image); + window->draw(); return app.run(); diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index ad9cb181..9885e476 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -13,6 +13,8 @@ set(SOURCES src/App.cpp src/Window.cpp src/Layout.cpp + src/Alignment.cpp + src/Container.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/Alignment.h b/libui/include/ui/Alignment.h new file mode 100644 index 00000000..660eb605 --- /dev/null +++ b/libui/include/ui/Alignment.h @@ -0,0 +1,30 @@ +/** + * @file Alignment.h + * @author apio (cloudapio.eu) + * @brief UI component alignment. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +namespace ui +{ + enum class VerticalAlignment + { + Top, + Center, + Bottom + }; + + enum class HorizontalAlignment + { + Left, + Center, + Right + }; + + Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign); +} diff --git a/libui/include/ui/Container.h b/libui/include/ui/Container.h new file mode 100644 index 00000000..6a4e565c --- /dev/null +++ b/libui/include/ui/Container.h @@ -0,0 +1,31 @@ +/** + * @file Container.h + * @author apio (cloudapio.eu) + * @brief A container widget to pad and align objects inside it. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include + +namespace ui +{ + class Container : public Widget + { + public: + Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign); + + void set_widget(Widget& widget); + + Result handle_mousemove(Point position) override; + Result draw(Canvas& canvas) override; + + private: + Widget* m_widget; + VerticalAlignment m_valign; + HorizontalAlignment m_halign; + }; +} diff --git a/libui/include/ui/Image.h b/libui/include/ui/Image.h index bebffa84..6ad0bd13 100644 --- a/libui/include/ui/Image.h +++ b/libui/include/ui/Image.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace ui { @@ -77,4 +78,16 @@ namespace ui TGAHeader m_tga_header; Buffer m_image_data; }; + + class ImageWidget final : public Widget + { + public: + static Result> load(const os::Path& path); + + Result handle_mousemove(Point position) override; + Result draw(Canvas& canvas) override; + + private: + SharedPtr m_image; + }; } diff --git a/libui/src/Alignment.cpp b/libui/src/Alignment.cpp new file mode 100644 index 00000000..87292fdc --- /dev/null +++ b/libui/src/Alignment.cpp @@ -0,0 +1,40 @@ +/** + * @file Alignment.cpp + * @author apio (cloudapio.eu) + * @brief UI component alignment. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include + +namespace ui +{ + Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign) + { + Rect result; + result.width = contained.width; + result.height = contained.height; + result.pos.y = container.pos.y; + result.pos.x = container.pos.x; + + switch (valign) + { + case VerticalAlignment::Top: break; + case VerticalAlignment::Center: result.pos.y += (container.height - contained.height) / 2; break; + case VerticalAlignment::Bottom: result.pos.y += container.height - contained.height; break; + default: break; + } + + switch (halign) + { + case HorizontalAlignment::Left: break; + case HorizontalAlignment::Center: result.pos.x += (container.width - contained.width) / 2; break; + case HorizontalAlignment::Right: result.pos.x += container.width - contained.width; break; + default: break; + } + + return result; + } +} diff --git a/libui/src/Container.cpp b/libui/src/Container.cpp new file mode 100644 index 00000000..f6fe7abd --- /dev/null +++ b/libui/src/Container.cpp @@ -0,0 +1,39 @@ +/** + * @file Container.cpp + * @author apio (cloudapio.eu) + * @brief A container widget to pad and align objects inside it. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include + +namespace ui +{ + Container::Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign) + : m_valign(valign), m_halign(halign) + { + m_rect = rect; + } + + void Container::set_widget(Widget& widget) + { + m_widget = &widget; + widget.rect() = ui::align(m_rect, widget.rect(), m_valign, m_halign); + } + + Result Container::handle_mousemove(Point position) + { + return m_widget->handle_mousemove(position); + } + + Result Container::draw(Canvas& canvas) + { + auto rect = ui::Rect { m_widget->rect().pos.x - m_rect.pos.x, m_widget->rect().pos.y - m_rect.pos.y, + m_widget->rect().width, m_widget->rect().height }; + auto subcanvas = canvas.subcanvas(rect); + return m_widget->draw(subcanvas); + } + +} diff --git a/libui/src/Image.cpp b/libui/src/Image.cpp index 4a284736..e8f9b6ba 100644 --- a/libui/src/Image.cpp +++ b/libui/src/Image.cpp @@ -8,6 +8,7 @@ */ #include +#include #include namespace ui @@ -30,4 +31,23 @@ namespace ui return image; } + + Result> ImageWidget::load(const os::Path& path) + { + auto widget = TRY(make_owned()); + widget->m_image = TRY(Image::load(path)); + widget->m_rect = { 0, 0, widget->m_image->width(), widget->m_image->height() }; + return widget; + } + + Result ImageWidget::handle_mousemove(Point) + { + return EventResult::DidNotHandle; + } + + Result ImageWidget::draw(Canvas& canvas) + { + canvas.subcanvas({ 0, 0, m_image->width(), m_image->height() }).fill(m_image->pixels(), m_image->width()); + return {}; + } } -- 2.34.1 From d6f63c0a5d53d903250ed5151ca1d2609d261987 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 15:10:13 +0200 Subject: [PATCH 056/103] libui: Handle other mouse events --- apps/gclient.cpp | 31 ++++++++++++---- libui/include/ui/Container.h | 5 ++- libui/include/ui/Image.h | 5 ++- libui/include/ui/Layout.h | 10 ++++-- libui/include/ui/Mouse.h | 21 +++++++++++ libui/include/ui/Widget.h | 5 ++- libui/include/ui/Window.h | 5 ++- libui/src/App.cpp | 2 +- libui/src/Container.cpp | 19 ++++++++-- libui/src/Image.cpp | 17 ++++++++- libui/src/Layout.cpp | 70 +++++++++++++++++++++++++++++++++--- libui/src/Window.cpp | 19 ++++++++-- 12 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 libui/include/ui/Mouse.h diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 23821081..404f6f8c 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -4,11 +4,28 @@ struct ColorWidget : public ui::Widget { public: - ColorWidget(ui::Color color) : m_color(color) + ColorWidget(ui::Color first, ui::Color second) : m_color(first), m_first_color(first), m_second_color(second) { } - Result handle_mousemove(ui::Point) override + Result handle_mouse_move(ui::Point) override + { + m_color = m_second_color; + return ui::EventResult::DidHandle; + } + + Result handle_mouse_leave(ui::Point) override + { + m_color = m_first_color; + return ui::EventResult::DidHandle; + } + + Result handle_mouse_down(ui::Point, int) override + { + return ui::EventResult::DidNotHandle; + } + + Result handle_mouse_up(ui::Point, int) override { return ui::EventResult::DidNotHandle; } @@ -21,6 +38,8 @@ struct ColorWidget : public ui::Widget private: ui::Color m_color; + ui::Color m_first_color; + ui::Color m_second_color; }; Result luna_main(int argc, char** argv) @@ -37,17 +56,17 @@ Result luna_main(int argc, char** argv) ui::HorizontalLayout layout; window->set_main_widget(layout); - ColorWidget green(ui::GREEN); + ColorWidget green(ui::GREEN, ui::WHITE); layout.add_widget(green); - ColorWidget blue(ui::BLUE); + ColorWidget blue(ui::BLUE, ui::GRAY); layout.add_widget(blue); ui::VerticalLayout sublayout; layout.add_widget(sublayout); - ColorWidget red(ui::RED); + ColorWidget red(ui::RED, ui::CYAN); sublayout.add_widget(red); - ColorWidget white(ui::WHITE); + ColorWidget white(ui::WHITE, ui::GREEN); sublayout.add_widget(white); window->draw(); diff --git a/libui/include/ui/Container.h b/libui/include/ui/Container.h index 6a4e565c..6bd703a2 100644 --- a/libui/include/ui/Container.h +++ b/libui/include/ui/Container.h @@ -20,7 +20,10 @@ namespace ui void set_widget(Widget& widget); - Result handle_mousemove(Point position) override; + Result handle_mouse_move(Point position) override; + Result handle_mouse_leave(Point position) override; + Result handle_mouse_down(Point position, int buttons) override; + Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; private: diff --git a/libui/include/ui/Image.h b/libui/include/ui/Image.h index 6ad0bd13..2db7579f 100644 --- a/libui/include/ui/Image.h +++ b/libui/include/ui/Image.h @@ -84,7 +84,10 @@ namespace ui public: static Result> load(const os::Path& path); - Result handle_mousemove(Point position) override; + Result handle_mouse_move(Point position) override; + Result handle_mouse_leave(Point position) override; + Result handle_mouse_down(Point position, int buttons) override; + Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; private: diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h index 83cb0fef..47b54fda 100644 --- a/libui/include/ui/Layout.h +++ b/libui/include/ui/Layout.h @@ -30,7 +30,10 @@ namespace ui public: HorizontalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); - Result handle_mousemove(Point position) override; + Result handle_mouse_move(Point position) override; + Result handle_mouse_leave(Point position) override; + Result handle_mouse_down(Point position, int buttons) override; + Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; @@ -48,7 +51,10 @@ namespace ui public: VerticalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); - Result handle_mousemove(Point position) override; + Result handle_mouse_move(Point position) override; + Result handle_mouse_leave(Point position) override; + Result handle_mouse_down(Point position, int buttons) override; + Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; diff --git a/libui/include/ui/Mouse.h b/libui/include/ui/Mouse.h new file mode 100644 index 00000000..db28dda7 --- /dev/null +++ b/libui/include/ui/Mouse.h @@ -0,0 +1,21 @@ +/** + * @file Mouse.h + * @author apio (cloudapio.eu) + * @brief Mouse buttons. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +namespace ui +{ + enum MouseButtons + { + LEFT = moon::Left, + MIDDLE = moon::Middle, + RIGHT = moon::Right, + }; +} diff --git a/libui/include/ui/Widget.h b/libui/include/ui/Widget.h index 24b1f31f..74ee1435 100644 --- a/libui/include/ui/Widget.h +++ b/libui/include/ui/Widget.h @@ -26,7 +26,10 @@ namespace ui class Widget { public: - virtual Result handle_mousemove(Point position); + virtual Result handle_mouse_move(Point position); + virtual Result handle_mouse_down(Point position, int buttons); + virtual Result handle_mouse_up(Point position, int buttons); + virtual Result handle_mouse_leave(Point position); virtual Result draw(Canvas& canvas); diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index 490cf3ec..9f440a36 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,8 @@ namespace ui void close(); Result draw(); - Result handle_mousemove(ui::Point position); + Result handle_mouse_move(ui::Point position); + Result handle_mouse_buttons(ui::Point position, int buttons); int id() const { @@ -59,5 +61,6 @@ namespace ui Canvas m_canvas; Widget* m_main_widget { nullptr }; Color m_background { ui::BLACK }; + Option m_old_mouse_buttons; }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 7a979206..035820d3 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -104,7 +104,7 @@ namespace ui MouseEventRequest request; TRY(m_client->recv_typed(request)); auto* window = find_window(request.window); - window->handle_mousemove(request.position); + window->handle_mouse_move(request.position); window->draw(); return {}; } diff --git a/libui/src/Container.cpp b/libui/src/Container.cpp index f6fe7abd..e40b1b55 100644 --- a/libui/src/Container.cpp +++ b/libui/src/Container.cpp @@ -23,9 +23,24 @@ namespace ui widget.rect() = ui::align(m_rect, widget.rect(), m_valign, m_halign); } - Result Container::handle_mousemove(Point position) + Result Container::handle_mouse_move(Point position) { - return m_widget->handle_mousemove(position); + return m_widget->handle_mouse_move(position); + } + + Result Container::handle_mouse_leave(Point position) + { + return m_widget->handle_mouse_leave(position); + } + + Result Container::handle_mouse_down(Point position, int buttons) + { + return m_widget->handle_mouse_down(position, buttons); + } + + Result Container::handle_mouse_up(Point position, int buttons) + { + return m_widget->handle_mouse_up(position, buttons); } Result Container::draw(Canvas& canvas) diff --git a/libui/src/Image.cpp b/libui/src/Image.cpp index e8f9b6ba..050bef2b 100644 --- a/libui/src/Image.cpp +++ b/libui/src/Image.cpp @@ -40,7 +40,22 @@ namespace ui return widget; } - Result ImageWidget::handle_mousemove(Point) + Result ImageWidget::handle_mouse_move(Point) + { + return EventResult::DidNotHandle; + } + + Result ImageWidget::handle_mouse_leave(Point) + { + return EventResult::DidNotHandle; + } + + Result ImageWidget::handle_mouse_up(Point, int) + { + return EventResult::DidNotHandle; + } + + Result ImageWidget::handle_mouse_down(Point, int) { return EventResult::DidNotHandle; } diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp index 4eeca17f..4beeb1a4 100644 --- a/libui/src/Layout.cpp +++ b/libui/src/Layout.cpp @@ -17,11 +17,42 @@ namespace ui { } - Result HorizontalLayout::handle_mousemove(Point position) + Result HorizontalLayout::handle_mouse_move(Point position) + { + EventResult result = ui::EventResult::DidNotHandle; + + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position)); + else + TRY(widget->handle_mouse_leave(position)); + } + + return result; + } + + Result HorizontalLayout::handle_mouse_leave(Point position) + { + for (auto widget : m_widgets) TRY(widget->handle_mouse_leave(position)); + + return ui::EventResult::DidNotHandle; + } + + Result HorizontalLayout::handle_mouse_up(Point position, int buttons) { for (auto widget : m_widgets) { - if (widget->rect().contains(position)) return widget->handle_mousemove(position); + if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons); + } + + return ui::EventResult::DidNotHandle; + } + + Result HorizontalLayout::handle_mouse_down(Point position, int buttons) + { + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons); } return ui::EventResult::DidNotHandle; @@ -75,11 +106,42 @@ namespace ui { } - Result VerticalLayout::handle_mousemove(Point position) + Result VerticalLayout::handle_mouse_move(Point position) + { + EventResult result = ui::EventResult::DidNotHandle; + + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position)); + else + TRY(widget->handle_mouse_leave(position)); + } + + return result; + } + + Result VerticalLayout::handle_mouse_leave(Point position) + { + for (auto widget : m_widgets) TRY(widget->handle_mouse_leave(position)); + + return ui::EventResult::DidNotHandle; + } + + Result VerticalLayout::handle_mouse_up(Point position, int buttons) { for (auto widget : m_widgets) { - if (widget->rect().contains(position)) return widget->handle_mousemove(position); + if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons); + } + + return ui::EventResult::DidNotHandle; + } + + Result VerticalLayout::handle_mouse_down(Point position, int buttons) + { + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons); } return ui::EventResult::DidNotHandle; diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index 99d2e66d..cbbfc068 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -101,9 +101,24 @@ namespace ui return {}; } - Result Window::handle_mousemove(ui::Point position) + Result Window::handle_mouse_move(ui::Point position) { - if (m_main_widget) TRY(m_main_widget->handle_mousemove(position)); + if (!m_main_widget) return {}; + TRY(m_main_widget->handle_mouse_move(position)); + return {}; + } + + Result Window::handle_mouse_buttons(ui::Point position, int buttons) + { + if (!m_main_widget) return {}; + if (buttons) TRY(m_main_widget->handle_mouse_down(position, buttons)); + if (m_old_mouse_buttons.has_value()) + { + int old_buttons = m_old_mouse_buttons.value(); + int diff = old_buttons & ~buttons; + if (diff) TRY(m_main_widget->handle_mouse_up(position, diff)); + } + m_old_mouse_buttons = buttons; return {}; } } -- 2.34.1 From 345cf5cae31044e36f67d0eb2e091fc70d871054 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 15:33:44 +0200 Subject: [PATCH 057/103] libui: Add Buttons --- apps/taskbar.cpp | 11 ++++++- libui/CMakeLists.txt | 1 + libui/include/ui/Button.h | 35 ++++++++++++++++++++ libui/src/App.cpp | 1 + libui/src/Button.cpp | 68 +++++++++++++++++++++++++++++++++++++++ libui/src/Container.cpp | 1 + 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 libui/include/ui/Button.h create mode 100644 libui/src/Button.cpp diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp index 975df284..646262af 100644 --- a/apps/taskbar.cpp +++ b/apps/taskbar.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include #include #include @@ -19,8 +21,15 @@ Result luna_main(int argc, char** argv) ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No); window->set_main_widget(layout); + ui::Button button({ 0, 0, 50, 50 }); + layout.add_widget(button); + ui::Container container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); - layout.add_widget(container); + button.set_widget(container); + button.set_action([] { + StringView args[] = { "/usr/bin/gclient" }; + os::Process::spawn("/usr/bin/gclient", Slice { args, 1 }, false); + }); auto image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga")); container.set_widget(*image); diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 9885e476..415f6bde 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES src/Layout.cpp src/Alignment.cpp src/Container.cpp + src/Button.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/Button.h b/libui/include/ui/Button.h new file mode 100644 index 00000000..f2715f5b --- /dev/null +++ b/libui/include/ui/Button.h @@ -0,0 +1,35 @@ +/** + * @file Button.h + * @author apio (cloudapio.eu) + * @brief A clickable component that triggers an action when pressed. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +namespace ui +{ + class Button : public Widget + { + public: + Button(Rect rect); + + void set_widget(Widget& widget); + void set_action(void (*action)(void)); + + Result handle_mouse_move(Point position) override; + Result handle_mouse_leave(Point position) override; + Result handle_mouse_down(Point position, int buttons) override; + Result handle_mouse_up(Point position, int buttons) override; + Result draw(Canvas& canvas) override; + + private: + bool m_hovered { false }; + bool m_clicked { false }; + Widget* m_child; + void (*m_action)(void); + }; +} diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 035820d3..eb067312 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -105,6 +105,7 @@ namespace ui TRY(m_client->recv_typed(request)); auto* window = find_window(request.window); window->handle_mouse_move(request.position); + window->handle_mouse_buttons(request.position, request.buttons); window->draw(); return {}; } diff --git a/libui/src/Button.cpp b/libui/src/Button.cpp new file mode 100644 index 00000000..487cfa53 --- /dev/null +++ b/libui/src/Button.cpp @@ -0,0 +1,68 @@ +/** + * @file Button.cpp + * @author apio (cloudapio.eu) + * @brief A clickable component that triggers an action when pressed. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include + +namespace ui +{ + Button::Button(Rect rect) + { + m_rect = rect; + } + + void Button::set_widget(Widget& widget) + { + widget.rect() = m_rect; + m_child = &widget; + widget.set_parent(this); + } + + void Button::set_action(void (*action)(void)) + { + m_action = action; + } + + Result Button::handle_mouse_move(Point position) + { + m_hovered = true; + return m_child->handle_mouse_move(position); + } + + Result Button::handle_mouse_leave(Point position) + { + m_hovered = m_clicked = false; + return m_child->handle_mouse_leave(position); + } + + Result Button::handle_mouse_down(Point position, int buttons) + { + auto result = TRY(m_child->handle_mouse_down(position, buttons)); + if (result == EventResult::DidNotHandle) + { + if (!m_clicked && (buttons == ui::MouseButtons::LEFT)) + { + m_clicked = true; + m_action(); + } + } + return EventResult::DidHandle; + } + + Result Button::handle_mouse_up(Point position, int buttons) + { + if (buttons & ui::MouseButtons::LEFT) m_clicked = false; + return m_child->handle_mouse_up(position, buttons); + } + + Result Button::draw(Canvas& canvas) + { + return m_child->draw(canvas); + } +} diff --git a/libui/src/Container.cpp b/libui/src/Container.cpp index e40b1b55..876013ac 100644 --- a/libui/src/Container.cpp +++ b/libui/src/Container.cpp @@ -21,6 +21,7 @@ namespace ui { m_widget = &widget; widget.rect() = ui::align(m_rect, widget.rect(), m_valign, m_halign); + widget.set_parent(this); } Result Container::handle_mouse_move(Point position) -- 2.34.1 From 8c4e9dff966cf17d784ca2cd059f20d710ceb719 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 16 Aug 2023 10:02:50 +0200 Subject: [PATCH 058/103] base: Actually add the start icon to source control --- .gitignore | 1 + base/usr/share/icons/32x32/start-icon.tga | Bin 0 -> 4140 bytes 2 files changed, 1 insertion(+) create mode 100644 base/usr/share/icons/32x32/start-icon.tga diff --git a/.gitignore b/.gitignore index 247a2ab1..43437f04 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ base/usr/include/** base/usr/lib/** base/usr/share/pkgdb/** !base/usr/share/fonts/* +!base/usr/share/icons/* base/usr/share/** base/usr/x86_64-luna/** base/etc/skel/LICENSE diff --git a/base/usr/share/icons/32x32/start-icon.tga b/base/usr/share/icons/32x32/start-icon.tga new file mode 100644 index 0000000000000000000000000000000000000000..cad6cf31deff1d7a54858c629730377d39dab439 GIT binary patch literal 4140 zcmeH{OA5j;5QYa&;?j-YqC!9v)QWqLB{zWiqnn8$+?g|#cd%C+17Va-X$>-({E|nr|xA}_` z0L}}3|NLG6USY?6`^-gd&&~Nt{@-|gh?{Ewj--eF|GXJIxAxt6Afz|ftmnva!&+O6o}A@e=XyL+@0yS7B~D8WaalDjPVTXo(Oh(fC_hrK%@Sd3 Wwe<-+@8j@Fk94~p-{*80PV@sfuqD6% literal 0 HcmV?d00001 -- 2.34.1 From 7e7f0a96f5816c2c47ef716af6e64fd17eac7881 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 16 Aug 2023 16:48:39 +0200 Subject: [PATCH 059/103] wind+libos+libui: Handle interrupted reads properly --- libos/include/os/IPC.h | 6 +++--- libos/src/IPC.cpp | 4 ++++ libui/src/App.cpp | 17 +++++++++++++++-- wind/IPC.cpp | 7 +++++++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/libos/include/os/IPC.h b/libos/include/os/IPC.h index 260d9c3f..fe185a15 100644 --- a/libos/include/os/IPC.h +++ b/libos/include/os/IPC.h @@ -102,7 +102,7 @@ namespace os { u8 response_id; auto rc = client.recv_typed(response_id); - if (rc.has_error() && rc.error() == EAGAIN) continue; + if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue; if (response_id == 0) // Error result { @@ -110,7 +110,7 @@ namespace os { int code; rc = client.recv_typed(code); - if (rc.has_error() && rc.error() == EAGAIN) continue; + if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue; return err(code); } } @@ -126,7 +126,7 @@ namespace os { ResponseType response; rc = client.recv_typed(response); - if (rc.has_error() && rc.error() == EAGAIN) continue; + if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue; return response; } } diff --git a/libos/src/IPC.cpp b/libos/src/IPC.cpp index fbe6b164..b0be457d 100644 --- a/libos/src/IPC.cpp +++ b/libos/src/IPC.cpp @@ -18,6 +18,8 @@ namespace os::IPC if (rc.has_error()) { if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block. + if (rc.error() == EINTR) + return {}; // Let the caller check for anything having happened because a signal handler ran. return rc.release_error(); } @@ -31,6 +33,8 @@ namespace os::IPC if (rc.has_error()) { if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block. + if (rc.error() == EINTR) + return {}; // Let the caller check for anything having happened because a signal handler ran. return rc.release_error(); } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index eb067312..78e5f8e5 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -88,13 +88,26 @@ namespace ui return window->ptr(); } +#define READ_MESSAGE(request) \ + do { \ + auto rc = m_client->recv_typed(request); \ + if (rc.has_error()) \ + { \ + if (rc.error() == EAGAIN) { continue; } \ + if (rc.error() == EINTR) { continue; } \ + else \ + return rc.release_error(); \ + } \ + break; \ + } while (true) + Result App::handle_ipc_event(u8 id) { switch (id) { case WINDOW_CLOSE_REQUEST_ID: { WindowCloseRequest request; - TRY(m_client->recv_typed(request)); + READ_MESSAGE(request); os::eprintln("ui: Window close request from server! Shall comply."); auto* window = find_window(request.window); window->close(); @@ -102,7 +115,7 @@ namespace ui } case MOUSE_EVENT_REQUEST_ID: { MouseEventRequest request; - TRY(m_client->recv_typed(request)); + READ_MESSAGE(request); auto* window = find_window(request.window); window->handle_mouse_move(request.position); window->handle_mouse_buttons(request.position, request.buttons); diff --git a/wind/IPC.cpp b/wind/IPC.cpp index dbec731f..3a4e5e59 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -68,6 +68,12 @@ static Result create_shm_region(const char* path, int* outfd, ui::Rect rec client.rpc_id = decltype(request)::ID; \ return {}; \ } \ + if (rc.error() == EINTR) \ + { \ + client.rpc_in_progress = true; \ + client.rpc_id = decltype(request)::ID; \ + return {}; \ + } \ else \ return rc.release_error(); \ } \ @@ -202,6 +208,7 @@ namespace wind if (rc.has_error()) { if (rc.error() == EAGAIN) { return {}; } + if (rc.error() == EINTR) { return {}; } else return rc.release_error(); } -- 2.34.1 From 6375fb965a8ef5e91332381042606663ab009269 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 16 Aug 2023 16:48:55 +0200 Subject: [PATCH 060/103] wind: Add debug keybind --- wind/main.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/wind/main.cpp b/wind/main.cpp index 0dd625dc..419feb1e 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -18,6 +18,34 @@ #include #include +static void debug(const Vector& clients) +{ + os::println("--- wind: DEBUG OUTPUT ---"); + + os::println("-- wind: Listing clients --"); + + for (const auto& client : clients) + { + os::println("Client with fd %d, owns %zu windows", client.conn.fd(), client.windows.size()); + } + + os::println("-- wind: Listing windows --"); + + for (const auto& window : g_windows) + { + os::println("Window of client (fd %d), id %d, %sdecorated, %sdirty (\"%s\") (%d,%d,%d,%d)", + window->client->conn.fd(), window->id, window->decorated ? "" : "not ", window->dirty ? "" : "not ", + window->name.chars(), window->surface.pos.x, window->surface.pos.y, window->surface.width, + window->surface.height); + } + + os::println("-- wind: Listing processes --"); + + system("ps"); + + os::println("--- wind: END DEBUG OUTPUT ---"); +} + Result luna_main(int argc, char** argv) { srand((unsigned)time(NULL)); @@ -91,7 +119,7 @@ Result luna_main(int argc, char** argv) TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 })); TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 })); - TRY(os::Security::pledge("stdio rpath wpath cpath unix signal proc", NULL)); + TRY(os::Security::pledge("stdio rpath wpath cpath unix signal proc exec", NULL)); while (1) { @@ -117,6 +145,7 @@ Result luna_main(int argc, char** argv) moon::KeyboardPacket packet; TRY(keyboard->read_typed(packet)); os::println("%s key %d", packet.released ? "Released" : "Pressed", packet.key); + if (!packet.released && packet.key == moon::K_Tab) debug(clients); } for (usize i = 0; i < clients.size(); i++) { -- 2.34.1 From d43d06604dc1991b4a5da75bb7d553261d902d7b Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 16 Aug 2023 16:49:08 +0200 Subject: [PATCH 061/103] taskbar: Wait for terminated child windows --- apps/taskbar.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp index 646262af..5d158581 100644 --- a/apps/taskbar.cpp +++ b/apps/taskbar.cpp @@ -1,15 +1,24 @@ #include +#include +#include #include #include #include #include #include +void sigchld_handler(int) +{ + wait(nullptr); +} + Result luna_main(int argc, char** argv) { ui::App app; TRY(app.init(argc, argv)); + signal(SIGCHLD, sigchld_handler); + ui::Rect screen = app.screen_rect(); ui::Rect bar = ui::Rect { ui::Point { 0, screen.height - 50 }, screen.width, 50 }; -- 2.34.1 From b656ceedfe7967de7e292726326751357baf71c0 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 16 Aug 2023 20:02:54 +0200 Subject: [PATCH 062/103] wind: Fix client references being out-of-date in windows when disconnecting other clients Classic "keeping a pointer to an element inside a vector after the vector is updated" bug, ah yes. --- wind/Client.h | 7 +++++++ wind/main.cpp | 15 ++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/wind/Client.h b/wind/Client.h index 30b402ad..435590cc 100644 --- a/wind/Client.h +++ b/wind/Client.h @@ -8,4 +8,11 @@ struct Client Vector windows; bool rpc_in_progress { false }; u8 rpc_id { 0 }; + + Client(os::LocalServer::Client&& client) +#ifdef CLIENT_IMPLEMENTATION + : conn(move(client)), windows() {} +#else + ; +#endif }; diff --git a/wind/main.cpp b/wind/main.cpp index 419feb1e..fc24786d 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -1,3 +1,4 @@ +#define CLIENT_IMPLEMENTATION #include "Client.h" #include "IPC.h" #include "Mouse.h" @@ -18,7 +19,7 @@ #include #include -static void debug(const Vector& clients) +static void debug(const Vector>& clients) { os::println("--- wind: DEBUG OUTPUT ---"); @@ -26,7 +27,7 @@ static void debug(const Vector& clients) for (const auto& client : clients) { - os::println("Client with fd %d, owns %zu windows", client.conn.fd(), client.windows.size()); + os::println("Client with fd %d, owns %zu windows", client->conn.fd(), client->windows.size()); } os::println("-- wind: Listing windows --"); @@ -113,7 +114,7 @@ Result luna_main(int argc, char** argv) ui::Color background = ui::BLACK; - Vector clients; + Vector> clients; Vector fds; TRY(fds.try_append({ .fd = mouse->fd(), .events = POLLIN, .revents = 0 })); TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 })); @@ -149,14 +150,14 @@ Result luna_main(int argc, char** argv) } for (usize i = 0; i < clients.size(); i++) { - if (fds[i + 3].revents & POLLIN) wind::handle_ipc(clients[i]); + if (fds[i + 3].revents & POLLIN) wind::handle_ipc(*clients[i]); if (fds[i + 3].revents & POLLHUP) { os::println("wind: Client %d disconnected", i); fds.remove_at(i + 3); auto client = clients.remove_at(i); - client.conn.disconnect(); - for (auto& window : client.windows) + client->conn.disconnect(); + for (auto& window : client->windows) { if (window) { @@ -171,7 +172,7 @@ Result luna_main(int argc, char** argv) auto client = TRY(server->accept()); os::println("wind: New client connected!"); TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 })); - Client c { move(client), {} }; + OwnedPtr c = TRY(adopt_owned_if_nonnull(new Client(move(client)))); TRY(clients.try_append(move(c))); } } -- 2.34.1 From 88a202ba33465c30fc0ea22e30cee5a057a7b66c Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 17 Aug 2023 11:20:32 +0200 Subject: [PATCH 063/103] wind: Handle ftruncate() and mmap() errors properly --- wind/IPC.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/wind/IPC.cpp b/wind/IPC.cpp index 3a4e5e59..d3ba6475 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -26,29 +26,32 @@ static Result create_shm_region(const char* path, int* outfd, ui::Rect rec int fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd < 0) { - os::eprintln("wind: could not create shared memory region: shm_open failed (%s) - %s", path, strerror(errno)); - return err(errno); + int olderr = errno; + os::eprintln("wind: could not create shared memory region: shm_open failed (%s) - %s", path, strerror(olderr)); + return err(olderr); } usize size = align_up(rect.width * rect.height * 4); // 4 bytes per pixel if (ftruncate(fd, size) < 0) { + int olderr = errno; os::eprintln("wind: could not create shared memory region: ftruncate failed (%d, %zu) - %s", fd, size, - strerror(errno)); + strerror(olderr)); shm_unlink(path); close(fd); - return 0; + return err(olderr); } void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { + int olderr = errno; os::eprintln("wind: could not create shared memory region: mmap failed (%zu, %d) - %s", size, fd, - strerror(errno)); + strerror(olderr)); shm_unlink(path); close(fd); - return 0; + return err(olderr); } if (outfd) *outfd = fd; -- 2.34.1 From ad001b4ee784a6f436e95b9e4752f4b086c986c4 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 17 Aug 2023 20:22:22 +0200 Subject: [PATCH 064/103] wind: Show memory usage in debug output --- wind/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wind/main.cpp b/wind/main.cpp index fc24786d..1c77494a 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -44,6 +44,10 @@ static void debug(const Vector>& clients) system("ps"); + os::println("-- wind: Listing memory usage --"); + + system("free -h"); + os::println("--- wind: END DEBUG OUTPUT ---"); } -- 2.34.1 From 5bd2b3d81d0d1eb1d7a64ccc48fccbeb43628bd6 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 21 Aug 2023 14:07:34 +0200 Subject: [PATCH 065/103] libui: Install the built library into the system root --- libui/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 415f6bde..479ee00c 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -23,3 +23,8 @@ target_compile_options(ui PRIVATE ${COMMON_FLAGS} -fno-threadsafe-statics) target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/) target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include) target_link_libraries(ui PUBLIC os) + +add_custom_command( + TARGET ui + COMMAND "${CMAKE_COMMAND}" -E copy ${CMAKE_CURRENT_BINARY_DIR}/libui.a ${LUNA_BASE}/usr/lib/libui.a +) -- 2.34.1 From 5385b1c337cabcc569decff910bc4edb9caa604f Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 22 Aug 2023 13:37:07 +0200 Subject: [PATCH 066/103] wind: Stop using the removed 'signal' pledge --- wind/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wind/main.cpp b/wind/main.cpp index 1c77494a..1ec70451 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -55,7 +55,7 @@ Result luna_main(int argc, char** argv) { srand((unsigned)time(NULL)); - TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty signal id", NULL)); + TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL)); StringView socket_path = "/tmp/wind.sock"; StringView user; @@ -124,7 +124,7 @@ Result luna_main(int argc, char** argv) TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 })); TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 })); - TRY(os::Security::pledge("stdio rpath wpath cpath unix signal proc exec", NULL)); + TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec", NULL)); while (1) { -- 2.34.1 From a023811c264d17d62ab333b2ab3c8ef435bc4708 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 29 Aug 2023 15:26:34 +0200 Subject: [PATCH 067/103] libui+wind: Handle mouse leave events when the mouse leaves a window --- apps/gclient.cpp | 2 +- libui/include/ui/Button.h | 2 +- libui/include/ui/Container.h | 2 +- libui/include/ui/Image.h | 2 +- libui/include/ui/Layout.h | 4 ++-- libui/include/ui/Widget.h | 2 +- libui/include/ui/Window.h | 1 + libui/include/ui/ipc/Client.h | 8 ++++++++ libui/src/App.cpp | 8 ++++++++ libui/src/Button.cpp | 4 ++-- libui/src/Container.cpp | 4 ++-- libui/src/Image.cpp | 2 +- libui/src/Layout.cpp | 12 ++++++------ libui/src/Window.cpp | 7 +++++++ wind/Mouse.cpp | 14 ++++++++++++++ wind/Mouse.h | 2 ++ 16 files changed, 58 insertions(+), 18 deletions(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 404f6f8c..ba08db5d 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -14,7 +14,7 @@ struct ColorWidget : public ui::Widget return ui::EventResult::DidHandle; } - Result handle_mouse_leave(ui::Point) override + Result handle_mouse_leave() override { m_color = m_first_color; return ui::EventResult::DidHandle; diff --git a/libui/include/ui/Button.h b/libui/include/ui/Button.h index f2715f5b..aa01dab1 100644 --- a/libui/include/ui/Button.h +++ b/libui/include/ui/Button.h @@ -21,7 +21,7 @@ namespace ui void set_action(void (*action)(void)); Result handle_mouse_move(Point position) override; - Result handle_mouse_leave(Point position) override; + Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; diff --git a/libui/include/ui/Container.h b/libui/include/ui/Container.h index 6bd703a2..aec537d7 100644 --- a/libui/include/ui/Container.h +++ b/libui/include/ui/Container.h @@ -21,7 +21,7 @@ namespace ui void set_widget(Widget& widget); Result handle_mouse_move(Point position) override; - Result handle_mouse_leave(Point position) override; + Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; diff --git a/libui/include/ui/Image.h b/libui/include/ui/Image.h index 2db7579f..481154f6 100644 --- a/libui/include/ui/Image.h +++ b/libui/include/ui/Image.h @@ -85,7 +85,7 @@ namespace ui static Result> load(const os::Path& path); Result handle_mouse_move(Point position) override; - Result handle_mouse_leave(Point position) override; + Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h index 47b54fda..62ebe8c8 100644 --- a/libui/include/ui/Layout.h +++ b/libui/include/ui/Layout.h @@ -31,7 +31,7 @@ namespace ui HorizontalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); Result handle_mouse_move(Point position) override; - Result handle_mouse_leave(Point position) override; + Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; @@ -52,7 +52,7 @@ namespace ui VerticalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); Result handle_mouse_move(Point position) override; - Result handle_mouse_leave(Point position) override; + Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; diff --git a/libui/include/ui/Widget.h b/libui/include/ui/Widget.h index 74ee1435..cd40f1bd 100644 --- a/libui/include/ui/Widget.h +++ b/libui/include/ui/Widget.h @@ -29,7 +29,7 @@ namespace ui virtual Result handle_mouse_move(Point position); virtual Result handle_mouse_down(Point position, int buttons); virtual Result handle_mouse_up(Point position, int buttons); - virtual Result handle_mouse_leave(Point position); + virtual Result handle_mouse_leave(); virtual Result draw(Canvas& canvas); diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index 9f440a36..ede664d8 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -46,6 +46,7 @@ namespace ui void close(); Result draw(); + Result handle_mouse_leave(); Result handle_mouse_move(ui::Point position); Result handle_mouse_buttons(ui::Point position, int buttons); diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 16f6bf68..6e277066 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -19,6 +19,7 @@ namespace ui CREATE_WINDOW_RESPONSE_ID, WINDOW_CLOSE_REQUEST_ID, MOUSE_EVENT_REQUEST_ID, + MOUSE_LEAVE_REQUEST_ID, GET_SCREEN_RECT_RESPONSE_ID }; @@ -46,6 +47,13 @@ namespace ui int buttons; }; + struct MouseLeaveRequest + { + static constexpr u8 ID = MOUSE_LEAVE_REQUEST_ID; + + int window; + }; + struct GetScreenRectResponse { static constexpr u8 ID = GET_SCREEN_RECT_RESPONSE_ID; diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 78e5f8e5..20496e8e 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -122,6 +122,14 @@ namespace ui window->draw(); return {}; } + case MOUSE_LEAVE_REQUEST_ID: { + MouseLeaveRequest request; + READ_MESSAGE(request); + auto* window = find_window(request.window); + window->handle_mouse_leave(); + window->draw(); + return {}; + } default: fail("Unexpected IPC request from server!"); } } diff --git a/libui/src/Button.cpp b/libui/src/Button.cpp index 487cfa53..08c50d15 100644 --- a/libui/src/Button.cpp +++ b/libui/src/Button.cpp @@ -35,10 +35,10 @@ namespace ui return m_child->handle_mouse_move(position); } - Result Button::handle_mouse_leave(Point position) + Result Button::handle_mouse_leave() { m_hovered = m_clicked = false; - return m_child->handle_mouse_leave(position); + return m_child->handle_mouse_leave(); } Result Button::handle_mouse_down(Point position, int buttons) diff --git a/libui/src/Container.cpp b/libui/src/Container.cpp index 876013ac..d188a07f 100644 --- a/libui/src/Container.cpp +++ b/libui/src/Container.cpp @@ -29,9 +29,9 @@ namespace ui return m_widget->handle_mouse_move(position); } - Result Container::handle_mouse_leave(Point position) + Result Container::handle_mouse_leave() { - return m_widget->handle_mouse_leave(position); + return m_widget->handle_mouse_leave(); } Result Container::handle_mouse_down(Point position, int buttons) diff --git a/libui/src/Image.cpp b/libui/src/Image.cpp index 050bef2b..70db97bc 100644 --- a/libui/src/Image.cpp +++ b/libui/src/Image.cpp @@ -45,7 +45,7 @@ namespace ui return EventResult::DidNotHandle; } - Result ImageWidget::handle_mouse_leave(Point) + Result ImageWidget::handle_mouse_leave() { return EventResult::DidNotHandle; } diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp index 4beeb1a4..8df908c5 100644 --- a/libui/src/Layout.cpp +++ b/libui/src/Layout.cpp @@ -25,15 +25,15 @@ namespace ui { if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position)); else - TRY(widget->handle_mouse_leave(position)); + TRY(widget->handle_mouse_leave()); } return result; } - Result HorizontalLayout::handle_mouse_leave(Point position) + Result HorizontalLayout::handle_mouse_leave() { - for (auto widget : m_widgets) TRY(widget->handle_mouse_leave(position)); + for (auto widget : m_widgets) TRY(widget->handle_mouse_leave()); return ui::EventResult::DidNotHandle; } @@ -114,15 +114,15 @@ namespace ui { if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position)); else - TRY(widget->handle_mouse_leave(position)); + TRY(widget->handle_mouse_leave()); } return result; } - Result VerticalLayout::handle_mouse_leave(Point position) + Result VerticalLayout::handle_mouse_leave() { - for (auto widget : m_widgets) TRY(widget->handle_mouse_leave(position)); + for (auto widget : m_widgets) TRY(widget->handle_mouse_leave()); return ui::EventResult::DidNotHandle; } diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index cbbfc068..29b197e9 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -101,6 +101,13 @@ namespace ui return {}; } + Result Window::handle_mouse_leave() + { + if (!m_main_widget) return {}; + TRY(m_main_widget->handle_mouse_leave()); + return {}; + } + Result Window::handle_mouse_move(ui::Point position) { if (!m_main_widget) return {}; diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index 7f3d72e2..0ba7d1ea 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -79,6 +79,8 @@ void Mouse::update(const moon::MousePacket& packet) } } + Window* new_active_window = nullptr; + for (Window* window = g_windows.last().value_or(nullptr); window; window = g_windows.previous(window).value_or(nullptr)) { @@ -92,7 +94,19 @@ void Mouse::update(const moon::MousePacket& packet) request.position = contents.relative(m_position); request.buttons = packet.buttons; os::IPC::send_async(window->client->conn, request); + new_active_window = window; break; } } + + if (m_active_window != new_active_window) + { + if (m_active_window) + { + ui::MouseLeaveRequest request; + request.window = m_active_window->id; + os::IPC::send_async(m_active_window->client->conn, request); + } + m_active_window = new_active_window; + } } diff --git a/wind/Mouse.h b/wind/Mouse.h index 17d79c6d..bee42f4c 100644 --- a/wind/Mouse.h +++ b/wind/Mouse.h @@ -19,4 +19,6 @@ class Mouse Window* m_dragging_window = nullptr; ui::Point m_initial_drag_position; + + Window* m_active_window = nullptr; }; -- 2.34.1 From 5908b07ee28d8a92192f75d4718bd4afe80a9d7f Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 29 Aug 2023 15:29:17 +0200 Subject: [PATCH 068/103] libui: Propagate Container events only if they are in the child widget's rect --- libui/src/Container.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libui/src/Container.cpp b/libui/src/Container.cpp index d188a07f..d7f320db 100644 --- a/libui/src/Container.cpp +++ b/libui/src/Container.cpp @@ -26,7 +26,8 @@ namespace ui Result Container::handle_mouse_move(Point position) { - return m_widget->handle_mouse_move(position); + if (m_widget->rect().contains(position)) return m_widget->handle_mouse_move(position); + return ui::EventResult::DidNotHandle; } Result Container::handle_mouse_leave() @@ -36,12 +37,14 @@ namespace ui Result Container::handle_mouse_down(Point position, int buttons) { - return m_widget->handle_mouse_down(position, buttons); + if (m_widget->rect().contains(position)) return m_widget->handle_mouse_down(position, buttons); + return ui::EventResult::DidNotHandle; } Result Container::handle_mouse_up(Point position, int buttons) { - return m_widget->handle_mouse_up(position, buttons); + if (m_widget->rect().contains(position)) return m_widget->handle_mouse_up(position, buttons); + return ui::EventResult::DidNotHandle; } Result Container::draw(Canvas& canvas) -- 2.34.1 From 17248e4ccc296480905eb4ebb82c3c67495b7987 Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 29 Aug 2023 15:32:33 +0200 Subject: [PATCH 069/103] libui: Add default handlers for events in Widget --- apps/gclient.cpp | 10 ---------- libui/include/ui/Image.h | 4 ---- libui/include/ui/Widget.h | 27 +++++++++++++++++++++++---- libui/src/Image.cpp | 20 -------------------- 4 files changed, 23 insertions(+), 38 deletions(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index ba08db5d..1439cdca 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -20,16 +20,6 @@ struct ColorWidget : public ui::Widget return ui::EventResult::DidHandle; } - Result handle_mouse_down(ui::Point, int) override - { - return ui::EventResult::DidNotHandle; - } - - Result handle_mouse_up(ui::Point, int) override - { - return ui::EventResult::DidNotHandle; - } - Result draw(ui::Canvas& canvas) override { canvas.fill(m_color); diff --git a/libui/include/ui/Image.h b/libui/include/ui/Image.h index 481154f6..cbcc5485 100644 --- a/libui/include/ui/Image.h +++ b/libui/include/ui/Image.h @@ -84,10 +84,6 @@ namespace ui public: static Result> load(const os::Path& path); - Result handle_mouse_move(Point position) override; - Result handle_mouse_leave() override; - Result handle_mouse_down(Point position, int buttons) override; - Result handle_mouse_up(Point position, int buttons) override; Result draw(Canvas& canvas) override; private: diff --git a/libui/include/ui/Widget.h b/libui/include/ui/Widget.h index cd40f1bd..f93e832b 100644 --- a/libui/include/ui/Widget.h +++ b/libui/include/ui/Widget.h @@ -8,6 +8,7 @@ */ #pragma once +#include #include #include #include @@ -26,10 +27,28 @@ namespace ui class Widget { public: - virtual Result handle_mouse_move(Point position); - virtual Result handle_mouse_down(Point position, int buttons); - virtual Result handle_mouse_up(Point position, int buttons); - virtual Result handle_mouse_leave(); + virtual Result handle_mouse_move(Point position) + { + ignore(position); + return EventResult::DidNotHandle; + } + + virtual Result handle_mouse_down(Point position, int buttons) + { + ignore(position, buttons); + return EventResult::DidNotHandle; + } + + virtual Result handle_mouse_up(Point position, int buttons) + { + ignore(position, buttons); + return EventResult::DidNotHandle; + } + + virtual Result handle_mouse_leave() + { + return EventResult::DidNotHandle; + } virtual Result draw(Canvas& canvas); diff --git a/libui/src/Image.cpp b/libui/src/Image.cpp index 70db97bc..f8b9d7f3 100644 --- a/libui/src/Image.cpp +++ b/libui/src/Image.cpp @@ -40,26 +40,6 @@ namespace ui return widget; } - Result ImageWidget::handle_mouse_move(Point) - { - return EventResult::DidNotHandle; - } - - Result ImageWidget::handle_mouse_leave() - { - return EventResult::DidNotHandle; - } - - Result ImageWidget::handle_mouse_up(Point, int) - { - return EventResult::DidNotHandle; - } - - Result ImageWidget::handle_mouse_down(Point, int) - { - return EventResult::DidNotHandle; - } - Result ImageWidget::draw(Canvas& canvas) { canvas.subcanvas({ 0, 0, m_image->width(), m_image->height() }).fill(m_image->pixels(), m_image->width()); -- 2.34.1 From 5db1c3722c2a25a2c566c50b3741de3ea581376b Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 29 Aug 2023 16:25:30 +0200 Subject: [PATCH 070/103] libui+wind+libos: Move shared memory handling code to os::SharedMemory --- libos/CMakeLists.txt | 1 + libos/include/os/SharedMemory.h | 36 +++++++++++++++ libos/src/SharedMemory.cpp | 77 +++++++++++++++++++++++++++++++++ libui/src/Window.cpp | 31 +++---------- wind/IPC.cpp | 51 +++------------------- 5 files changed, 124 insertions(+), 72 deletions(-) create mode 100644 libos/include/os/SharedMemory.h create mode 100644 libos/src/SharedMemory.cpp diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index 76bbe50d..321f4b4c 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES src/LocalServer.cpp src/LocalClient.cpp src/IPC.cpp + src/SharedMemory.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/SharedMemory.h b/libos/include/os/SharedMemory.h new file mode 100644 index 00000000..c1a9985c --- /dev/null +++ b/libos/include/os/SharedMemory.h @@ -0,0 +1,36 @@ +/** + * @file SharedMemory.h + * @author apio (cloudapio.eu) + * @brief Create and map areas of memory shared between processes. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include + +namespace os +{ + namespace SharedMemory + { + /** + * @brief Create a new shared memory region and map it. + * + * @param path The shared memory path to use. It must be of the same format as shm_open(). + * @param size The amount of bytes to use for the shared memory region. + * @return Result An error, or a pointer to the shared memory region. + */ + Result create(StringView path, usize size); + + /** + * @brief Map an existing shared memory region, possibly created by another process. + * + * @param path The shared memory path to use. It must be of the same format as shm_open(). + * @param size The amount of bytes to map from the shared memory region. + * @param delete_fs Whether to delete the region from the file system so no other processes can open it. + * @return Result An error, or a pointer to the shared memory region. + */ + Result adopt(StringView path, usize size, bool delete_fs = true); + }; +} diff --git a/libos/src/SharedMemory.cpp b/libos/src/SharedMemory.cpp new file mode 100644 index 00000000..743567f7 --- /dev/null +++ b/libos/src/SharedMemory.cpp @@ -0,0 +1,77 @@ +/** + * @file SharedMemory.cpp + * @author apio (cloudapio.eu) + * @brief Create and map areas of memory shared between processes. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace os::SharedMemory +{ + Result create(StringView path, usize size) + { + int fd = shm_open(path.chars(), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0) + { + int olderr = errno; + os::eprintln("os: could not create shared memory region: shm_open failed (%s) - %s", path, + strerror(olderr)); + return err(olderr); + } + + size = align_up(size); + + if (ftruncate(fd, size) < 0) + { + int olderr = errno; + os::eprintln("os: could not create shared memory region: ftruncate failed (%d, %zu) - %s", fd, size, + strerror(olderr)); + shm_unlink(path.chars()); + close(fd); + return err(olderr); + } + + void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + int olderr = errno; + os::eprintln("os: could not create shared memory region: mmap failed (%zu, %d) - %s", size, fd, + strerror(olderr)); + shm_unlink(path.chars()); + close(fd); + return err(olderr); + } + + close(fd); + return (u8*)p; + } + + Result adopt(StringView path, usize size, bool delete_fs) + { + int fd = shm_open(path.chars(), O_RDWR, 0600); + if (delete_fs) shm_unlink(path.chars()); + if (fd < 0) return err(errno); + + void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + int olderr = errno; + os::eprintln("os: could not adopt shared memory region: mmap failed (%zu, %d) - %s", size, fd, + strerror(olderr)); + close(fd); + return 0; + } + + close(fd); + return (u8*)p; + } +} diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index 29b197e9..8c64b7ca 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -7,35 +7,12 @@ * */ -#include -#include +#include +#include #include #include #include #include -#include - -static Result create_shm_region(const char* path, int* outfd, ui::Rect rect) -{ - int fd = shm_open(path, O_RDWR, 0600); - shm_unlink(path); - if (fd < 0) return err(errno); - - usize size = rect.width * rect.height * 4; // 4 bytes per pixel - - void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) - { - shm_unlink(path); - close(fd); - return 0; - } - - if (outfd) *outfd = fd; - else - close(fd); - return (u32*)p; -} namespace ui { @@ -48,7 +25,9 @@ namespace ui request.decorated = decorated; auto response = TRY(os::IPC::send_sync(App::the().client(), request)); - u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect)); + auto path = COPY_IPC_STRING(response.shm_path); + + u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4)); window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }; window->m_id = response.window; diff --git a/wind/IPC.cpp b/wind/IPC.cpp index d3ba6475..50eb986e 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -1,13 +1,10 @@ #include "IPC.h" #include "Screen.h" -#include #include #include #include -#include -#include +#include #include -#include #define TRY_OR_IPC_ERROR(expr) \ ({ \ @@ -21,45 +18,6 @@ _expr_rc.release_value(); \ }) -static Result create_shm_region(const char* path, int* outfd, ui::Rect rect) -{ - int fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd < 0) - { - int olderr = errno; - os::eprintln("wind: could not create shared memory region: shm_open failed (%s) - %s", path, strerror(olderr)); - return err(olderr); - } - - usize size = align_up(rect.width * rect.height * 4); // 4 bytes per pixel - - if (ftruncate(fd, size) < 0) - { - int olderr = errno; - os::eprintln("wind: could not create shared memory region: ftruncate failed (%d, %zu) - %s", fd, size, - strerror(olderr)); - shm_unlink(path); - close(fd); - return err(olderr); - } - - void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) - { - int olderr = errno; - os::eprintln("wind: could not create shared memory region: mmap failed (%zu, %d) - %s", size, fd, - strerror(olderr)); - shm_unlink(path); - close(fd); - return err(olderr); - } - - if (outfd) *outfd = fd; - else - close(fd); - return (u32*)p; -} - #define READ_MESSAGE(request) \ do { \ auto rc = client.conn.recv_typed(request); \ @@ -96,8 +54,6 @@ static Result handle_create_window_message(Client& client) ui::CreateWindowRequest request; READ_MESSAGE(request); - request.rect = request.rect.normalized(); - if (request.decorated) { request.rect.height += @@ -105,6 +61,8 @@ static Result handle_create_window_message(Client& client) request.rect.pos.y -= Window::titlebar_height(); // Adjust it so the contents begin at the expected coordinates. } + request.rect = request.rect.normalized(); + auto name = TRY(String::from_cstring("Window")); auto* window = new (std::nothrow) Window(request.rect, move(name), request.decorated); @@ -116,7 +74,8 @@ static Result handle_create_window_message(Client& client) auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL))); - window->pixels = TRY_OR_IPC_ERROR(create_shm_region(shm_path.chars(), nullptr, window->contents)); + window->pixels = (u32*)TRY_OR_IPC_ERROR( + os::SharedMemory::create(shm_path.view(), window->contents.height * window->contents.width * 4)); TRY_OR_IPC_ERROR(client.windows.try_append(window)); int id = static_cast(client.windows.size() - 1); -- 2.34.1 From 06f3affc7130703886495029327bb624a698c0f2 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 31 Aug 2023 14:55:40 +0200 Subject: [PATCH 071/103] wind: Make sure stdin is always a TTY --- wind/main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wind/main.cpp b/wind/main.cpp index 1ec70451..3986a90e 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -74,6 +74,12 @@ Result luna_main(int argc, char** argv) return 1; } + if (!isatty(STDIN_FILENO)) + { + os::eprintln("error: wind must be run from a TTY!"); + return 1; + } + auto mouse = TRY(os::File::open("/dev/mouse", os::File::ReadOnly)); mouse->set_buffer(os::File::NotBuffered); mouse->set_close_on_exec(); -- 2.34.1 From 669e2747a7366173301a6773d60fd6d0e050a6cf Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 2 Sep 2023 14:02:06 +0200 Subject: [PATCH 072/103] wind: Move more fallible operations before window creation --- wind/IPC.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wind/IPC.cpp b/wind/IPC.cpp index 50eb986e..98d93d68 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -11,6 +11,7 @@ auto _expr_rc = (expr); \ if (!_expr_rc.has_value()) \ { \ + g_windows.remove(window); \ delete window; \ os::IPC::send_error(client.conn, _expr_rc.error()); \ return {}; \ @@ -65,6 +66,8 @@ static Result handle_create_window_message(Client& client) auto name = TRY(String::from_cstring("Window")); + auto shm_path = TRY(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL))); + auto* window = new (std::nothrow) Window(request.rect, move(name), request.decorated); if (!window) { @@ -72,8 +75,6 @@ static Result handle_create_window_message(Client& client) return {}; } - auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL))); - window->pixels = (u32*)TRY_OR_IPC_ERROR( os::SharedMemory::create(shm_path.view(), window->contents.height * window->contents.width * 4)); -- 2.34.1 From 2643f050eba16f9253c4f46b90c68bdad706bb40 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 11 Sep 2023 07:31:31 +0200 Subject: [PATCH 073/103] libui: Zero-initialize counter variables in Layout --- libui/include/ui/Layout.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h index 62ebe8c8..66e4976b 100644 --- a/libui/include/ui/Layout.h +++ b/libui/include/ui/Layout.h @@ -43,7 +43,7 @@ namespace ui Vector m_widgets; AdjustHeight m_adjust_height; AdjustWidth m_adjust_width; - int m_used_width; + int m_used_width { 0 }; }; class VerticalLayout final : public Widget @@ -64,6 +64,6 @@ namespace ui Vector m_widgets; AdjustHeight m_adjust_height; AdjustWidth m_adjust_width; - int m_used_height; + int m_used_height { 0 }; }; } -- 2.34.1 From 67eac983b554c4af50e6f0298aa5215d55d18248 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 11 Sep 2023 19:14:17 +0200 Subject: [PATCH 074/103] libui: Clarify that Font is only used for low-level glyph rendering --- libui/include/ui/Font.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libui/include/ui/Font.h b/libui/include/ui/Font.h index c8929190..0d692972 100644 --- a/libui/include/ui/Font.h +++ b/libui/include/ui/Font.h @@ -18,7 +18,10 @@ namespace ui { /** - * @brief A class holding PSF font data, used for direct rendering of glyphs into a canvas. + * @brief A class holding PSF font data, used for low-level direct rendering of glyphs into a canvas. + * + * This class does not handle special characters such as tabs or newlines. For those, you should be using a more + * high-level component such as ui::Label instead. */ class Font : public Shareable { -- 2.34.1 From 4cf0fac16e4b0c6d3362a3130ec40ceac1a1bd4d Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 11 Sep 2023 19:15:18 +0200 Subject: [PATCH 075/103] libui: Add a basic Label component --- libui/CMakeLists.txt | 1 + libui/include/ui/Label.h | 41 ++++++++++++++++++++++++++++++++++++++++ libui/src/Label.cpp | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 libui/include/ui/Label.h create mode 100644 libui/src/Label.cpp diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 479ee00c..04dcfcdc 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCES src/Alignment.cpp src/Container.cpp src/Button.cpp + src/Label.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/Label.h b/libui/include/ui/Label.h new file mode 100644 index 00000000..b435e1c9 --- /dev/null +++ b/libui/include/ui/Label.h @@ -0,0 +1,41 @@ +/** + * @file Label.h + * @author apio (cloudapio.eu) + * @brief A simple one-line text widget. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include +#include + +namespace ui +{ + /** + * @brief Displays one line of text. + * + * This component does not handle newlines. + */ + class Label final : public Widget + { + public: + Label(StringView text, VerticalAlignment valign = VerticalAlignment::Center, + HorizontalAlignment halign = HorizontalAlignment::Center, SharedPtr font = Font::default_font()); + + void set_text(StringView text) + { + m_text = text; + } + + Result draw(Canvas& canvas) override; + + private: + StringView m_text; + VerticalAlignment m_valign; + HorizontalAlignment m_halign; + SharedPtr m_font; + }; +} diff --git a/libui/src/Label.cpp b/libui/src/Label.cpp new file mode 100644 index 00000000..67226eda --- /dev/null +++ b/libui/src/Label.cpp @@ -0,0 +1,36 @@ +/** + * @file Label.cpp + * @author apio (cloudapio.eu) + * @brief A simple one-line text widget. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include + +namespace ui +{ + Label::Label(StringView text, VerticalAlignment valign, HorizontalAlignment halign, SharedPtr font) + : m_text(text), m_valign(valign), m_halign(halign), m_font(font) + { + } + + Result Label::draw(Canvas& canvas) + { + ui::Rect contained; + contained.pos = { 0, 0 }; + contained.width = static_cast(m_text.length() * m_font->width()); + contained.height = m_font->height(); + auto subcanvas = + canvas.subcanvas(ui::align({ 0, 0, m_rect.width, m_rect.height }, contained, m_valign, m_halign)); + + Utf8StringDecoder decoder(m_text.chars()); + wchar_t buf[4096]; + TRY(decoder.decode(buf, sizeof(buf))); + + m_font->render(buf, ui::BLACK, subcanvas); + return {}; + } +} -- 2.34.1 From a5790d0fb145e454ebb41da7f9ecd67e34fb0054 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 11 Sep 2023 19:15:26 +0200 Subject: [PATCH 076/103] apps: Add about --- apps/CMakeLists.txt | 2 ++ apps/about.cpp | 46 +++++++++++++++++++++++++++++++++++++++++++++ apps/taskbar.cpp | 28 ++++++++++++++++++++------- 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 apps/about.cpp diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 14d4b50c..6b0e60c2 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -42,5 +42,7 @@ luna_app(touch.cpp touch) luna_app(free.cpp free) luna_app(gclient.cpp gclient) target_link_libraries(gclient PUBLIC ui) +luna_app(about.cpp about) +target_link_libraries(about PUBLIC ui) luna_app(taskbar.cpp taskbar) target_link_libraries(taskbar PUBLIC ui) diff --git a/apps/about.cpp b/apps/about.cpp new file mode 100644 index 00000000..89fb9c70 --- /dev/null +++ b/apps/about.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include + +Result luna_main(int argc, char** argv) +{ + ui::App app; + TRY(app.init(argc, argv)); + + auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 })); + app.set_main_window(window); + + window->set_title("About"); + window->set_background(ui::CYAN); + + utsname info; + uname(&info); + + ui::VerticalLayout main_layout; + window->set_main_widget(main_layout); + + ui::Label title("About Luna", ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center, + ui::Font::default_bold_font()); + main_layout.add_widget(title); + + ui::VerticalLayout version_info; + main_layout.add_widget(version_info); + + ui::Label license("Licensed under the BSD-2-Clause license."); + main_layout.add_widget(license); + + String os_release_text = TRY(String::format("OS release: %s"_sv, info.release)); + ui::Label os_release(os_release_text.view()); + version_info.add_widget(os_release); + + String kernel_version_text = TRY(String::format("Kernel version: %s"_sv, info.version)); + ui::Label kernel_version(kernel_version_text.view()); + version_info.add_widget(kernel_version); + + window->draw(); + + return app.run(); +} diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp index 5d158581..81679d52 100644 --- a/apps/taskbar.cpp +++ b/apps/taskbar.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -30,18 +31,31 @@ Result luna_main(int argc, char** argv) ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No); window->set_main_widget(layout); - ui::Button button({ 0, 0, 50, 50 }); - layout.add_widget(button); + ui::Button start_button({ 0, 0, 50, 50 }); + layout.add_widget(start_button); - ui::Container container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); - button.set_widget(container); - button.set_action([] { + ui::Container start_container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); + start_button.set_widget(start_container); + start_button.set_action([] { StringView args[] = { "/usr/bin/gclient" }; os::Process::spawn("/usr/bin/gclient", Slice { args, 1 }, false); }); - auto image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga")); - container.set_widget(*image); + auto start_image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga")); + start_container.set_widget(*start_image); + + ui::Button about_button({ 0, 0, 50, 50 }); + layout.add_widget(about_button); + + ui::Container about_container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); + about_button.set_widget(about_container); + about_button.set_action([] { + StringView args[] = { "/usr/bin/about" }; + os::Process::spawn("/usr/bin/about", Slice { args, 1 }, false); + }); + + auto about_image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/app-about.tga")); + about_container.set_widget(*about_image); window->draw(); -- 2.34.1 From 08b56319c77c742715590f6a67eb7a6dc7a8bceb Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 11 Sep 2023 19:38:29 +0200 Subject: [PATCH 077/103] libui: Reduce redraw calls by doing them only when events are actually handled --- apps/gclient.cpp | 8 +++++--- libui/include/ui/Window.h | 6 +++--- libui/src/App.cpp | 12 +++++++----- libui/src/Layout.cpp | 20 ++++++++++++++++---- libui/src/Window.cpp | 33 ++++++++++++++++++++------------- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 1439cdca..685b7217 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -10,14 +10,16 @@ struct ColorWidget : public ui::Widget Result handle_mouse_move(ui::Point) override { + auto old_color = m_color; m_color = m_second_color; - return ui::EventResult::DidHandle; + return old_color.raw == m_second_color.raw ? ui::EventResult::DidNotHandle : ui::EventResult::DidHandle; } Result handle_mouse_leave() override { + auto old_color = m_color; m_color = m_first_color; - return ui::EventResult::DidHandle; + return old_color.raw == m_first_color.raw ? ui::EventResult::DidNotHandle : ui::EventResult::DidHandle; } Result draw(ui::Canvas& canvas) override @@ -40,7 +42,7 @@ Result luna_main(int argc, char** argv) auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 })); app.set_main_window(window); - window->set_title("Main Window"); + window->set_title("Test Window"); window->set_background(ui::CYAN); ui::HorizontalLayout layout; diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index ede664d8..99058cba 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -46,9 +46,9 @@ namespace ui void close(); Result draw(); - Result handle_mouse_leave(); - Result handle_mouse_move(ui::Point position); - Result handle_mouse_buttons(ui::Point position, int buttons); + Result handle_mouse_leave(); + Result handle_mouse_move(ui::Point position); + Result handle_mouse_buttons(ui::Point position, int buttons); int id() const { diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 20496e8e..886d9b46 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -117,17 +117,19 @@ namespace ui MouseEventRequest request; READ_MESSAGE(request); auto* window = find_window(request.window); - window->handle_mouse_move(request.position); - window->handle_mouse_buttons(request.position, request.buttons); - window->draw(); + auto move_result = window->handle_mouse_move(request.position).value_or(ui::EventResult::DidNotHandle); + auto button_result = + window->handle_mouse_buttons(request.position, request.buttons).value_or(ui::EventResult::DidNotHandle); + if (move_result == ui::EventResult::DidHandle || button_result == ui::EventResult::DidHandle) + window->draw(); return {}; } case MOUSE_LEAVE_REQUEST_ID: { MouseLeaveRequest request; READ_MESSAGE(request); auto* window = find_window(request.window); - window->handle_mouse_leave(); - window->draw(); + if (window->handle_mouse_leave().value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle) + window->draw(); return {}; } default: fail("Unexpected IPC request from server!"); diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp index 8df908c5..2b16c885 100644 --- a/libui/src/Layout.cpp +++ b/libui/src/Layout.cpp @@ -33,9 +33,15 @@ namespace ui Result HorizontalLayout::handle_mouse_leave() { - for (auto widget : m_widgets) TRY(widget->handle_mouse_leave()); + EventResult result = ui::EventResult::DidNotHandle; - return ui::EventResult::DidNotHandle; + for (auto widget : m_widgets) + { + auto rc = TRY(widget->handle_mouse_leave()); + if (rc == ui::EventResult::DidHandle) result = rc; + } + + return result; } Result HorizontalLayout::handle_mouse_up(Point position, int buttons) @@ -122,9 +128,15 @@ namespace ui Result VerticalLayout::handle_mouse_leave() { - for (auto widget : m_widgets) TRY(widget->handle_mouse_leave()); + EventResult result = ui::EventResult::DidNotHandle; - return ui::EventResult::DidNotHandle; + for (auto widget : m_widgets) + { + auto rc = TRY(widget->handle_mouse_leave()); + if (rc == ui::EventResult::DidHandle) result = rc; + } + + return result; } Result VerticalLayout::handle_mouse_up(Point position, int buttons) diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index 8c64b7ca..da6541a2 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -80,31 +80,38 @@ namespace ui return {}; } - Result Window::handle_mouse_leave() + Result Window::handle_mouse_leave() { - if (!m_main_widget) return {}; - TRY(m_main_widget->handle_mouse_leave()); - return {}; + if (!m_main_widget) return ui::EventResult::DidNotHandle; + return m_main_widget->handle_mouse_leave(); } - Result Window::handle_mouse_move(ui::Point position) + Result Window::handle_mouse_move(ui::Point position) { - if (!m_main_widget) return {}; - TRY(m_main_widget->handle_mouse_move(position)); - return {}; + if (!m_main_widget) return ui::EventResult::DidNotHandle; + return m_main_widget->handle_mouse_move(position); } - Result Window::handle_mouse_buttons(ui::Point position, int buttons) + Result Window::handle_mouse_buttons(ui::Point position, int buttons) { - if (!m_main_widget) return {}; - if (buttons) TRY(m_main_widget->handle_mouse_down(position, buttons)); + if (!m_main_widget) return ui::EventResult::DidNotHandle; + auto result = ui::EventResult::DidNotHandle; + if (buttons) + { + auto rc = TRY(m_main_widget->handle_mouse_down(position, buttons)); + if (rc == ui::EventResult::DidHandle) result = rc; + } if (m_old_mouse_buttons.has_value()) { int old_buttons = m_old_mouse_buttons.value(); int diff = old_buttons & ~buttons; - if (diff) TRY(m_main_widget->handle_mouse_up(position, diff)); + if (diff) + { + auto rc = TRY(m_main_widget->handle_mouse_up(position, diff)); + if (rc == ui::EventResult::DidHandle) result = rc; + } } m_old_mouse_buttons = buttons; - return {}; + return result; } } -- 2.34.1 From bb5d726fe832c8d202575d24fa844e94c79b6169 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 14 Sep 2023 21:29:48 +0200 Subject: [PATCH 078/103] libui: Add option to run event processing in a loop instead of in app.run() --- libui/include/ui/App.h | 4 ++++ libui/src/App.cpp | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libui/include/ui/App.h b/libui/include/ui/App.h index 231c9af3..b297c14b 100644 --- a/libui/include/ui/App.h +++ b/libui/include/ui/App.h @@ -35,6 +35,8 @@ namespace ui m_should_close = b; } + void set_nonblocking(); + void set_main_window(Window* window) { check(!m_main_window); @@ -51,6 +53,8 @@ namespace ui Result handle_ipc_event(u8 id); + bool process_events(); + static App& the(); private: diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 886d9b46..43fe9b9b 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -50,8 +50,8 @@ namespace ui Result App::run() { - check(m_main_window); - while (!m_should_close) { TRY(os::IPC::check_for_messages(*m_client)); } + while (process_events()) + ; return 0; } @@ -135,4 +135,16 @@ namespace ui default: fail("Unexpected IPC request from server!"); } } + + void App::set_nonblocking() + { + fcntl(m_client->fd(), F_SETFL, O_NONBLOCK); + } + + bool App::process_events() + { + check(m_main_window); + os::IPC::check_for_messages(*m_client).release_value(); + return !m_should_close; + } } -- 2.34.1 From 0e8183d2bbe89964ef8bca5f391f182648193466 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 14 Sep 2023 21:31:45 +0200 Subject: [PATCH 079/103] shell: Allow running as interactive even if not running in a TTY --- shell/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/main.cpp b/shell/main.cpp index 70c68a2b..026475af 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -85,6 +85,7 @@ Result luna_main(int argc, char** argv) { StringView path; StringView command; + bool interactive { false }; SharedPtr input_file; @@ -93,6 +94,7 @@ Result luna_main(int argc, char** argv) parser.add_system_program_info("sh"_sv); parser.add_positional_argument(path, "path"_sv, "-"_sv); parser.add_value_argument(command, 'c', "command"_sv, "execute a single command and then exit"_sv); + parser.add_switch_argument(interactive, 'i', "interactive"_sv, "run an interactive shell"_sv); parser.parse(argc, argv); // TODO: This does not properly handle builtins. @@ -105,7 +107,7 @@ Result luna_main(int argc, char** argv) input_file->set_close_on_exec(); } - bool interactive = isatty(input_file->fd()); + if (isatty(input_file->fd())) interactive = true; if (interactive) { -- 2.34.1 From 835c39bc475d2517d99a1bc30d66554e7249f94a Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 14 Sep 2023 22:51:19 +0200 Subject: [PATCH 080/103] apps: Add basic terminal app --- CMakeLists.txt | 1 + base/etc/user/01-gclient | 3 - base/etc/user/01-terminal | 3 + terminal/CMakeLists.txt | 12 + terminal/TerminalWidget.cpp | 441 ++++++++++++++++++++++++++++++++++++ terminal/TerminalWidget.h | 60 +++++ terminal/main.cpp | 29 +++ wind/main.cpp | 2 +- 8 files changed, 547 insertions(+), 4 deletions(-) delete mode 100644 base/etc/user/01-gclient create mode 100644 base/etc/user/01-terminal create mode 100644 terminal/CMakeLists.txt create mode 100644 terminal/TerminalWidget.cpp create mode 100644 terminal/TerminalWidget.h create mode 100644 terminal/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dd08c362..4d9d1e48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,3 +52,4 @@ add_subdirectory(apps) add_subdirectory(tests) add_subdirectory(shell) add_subdirectory(wind) +add_subdirectory(terminal) diff --git a/base/etc/user/01-gclient b/base/etc/user/01-gclient deleted file mode 100644 index fcd404ac..00000000 --- a/base/etc/user/01-gclient +++ /dev/null @@ -1,3 +0,0 @@ -Name=gclient -Description=Sample user application. -Command=/usr/bin/gclient diff --git a/base/etc/user/01-terminal b/base/etc/user/01-terminal new file mode 100644 index 00000000..2e93a0e6 --- /dev/null +++ b/base/etc/user/01-terminal @@ -0,0 +1,3 @@ +Name=terminal +Description=Start the terminal. +Command=/usr/bin/terminal diff --git a/terminal/CMakeLists.txt b/terminal/CMakeLists.txt new file mode 100644 index 00000000..d9ebbc1f --- /dev/null +++ b/terminal/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES + main.cpp + TerminalWidget.h + TerminalWidget.cpp +) + +add_executable(terminal ${SOURCES}) +target_compile_options(terminal PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings) +add_dependencies(terminal libc) +target_include_directories(terminal PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(terminal PRIVATE os ui) +install(TARGETS terminal DESTINATION ${LUNA_BASE}/usr/bin) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp new file mode 100644 index 00000000..6587fa0a --- /dev/null +++ b/terminal/TerminalWidget.cpp @@ -0,0 +1,441 @@ +#include "TerminalWidget.h" +#include +#include +#include +#include +#include +#include +#include + +static constexpr auto RED = ui::Color::from_u32(0xffcd0000); +static constexpr auto GREEN = ui::Color::from_u32(0xff00cd00); +static constexpr auto YELLOW = ui::Color::from_u32(0xffcdcd00); +static constexpr auto BLUE = ui::Color::from_u32(0xff0000ee); +static constexpr auto MAGENTA = ui::Color::from_u32(0xffcd00cd); +static constexpr auto CYAN = ui::Color::from_u32(0xff00cdcd); +static constexpr auto GRAY = ui::Color::from_u32(0xffe5e5e5); + +static constexpr auto BRIGHT_BLACK = ui::Color::from_u32(0xff7f7f7f); +static constexpr auto BRIGHT_RED = ui::Color::from_u32(0xffff0000); +static constexpr auto BRIGHT_GREEN = ui::Color::from_u32(0xff00ff00); +static constexpr auto BRIGHT_YELLOW = ui::Color::from_u32(0xffffff00); +static constexpr auto BRIGHT_BLUE = ui::Color::from_u32(0xff5c5cff); +static constexpr auto BRIGHT_MAGENTA = ui::Color::from_u32(0xffff00ff); +static constexpr auto BRIGHT_CYAN = ui::Color::from_u32(0xff00ffff); +static constexpr auto BRIGHT_GRAY = ui::Color::from_u32(0xffffffff); + +static void sigchld_handler(int) +{ + wait(NULL); + ui::App::the().set_should_close(true); +} + +Result TerminalWidget::init(char* const* args) +{ + m_font = ui::Font::default_font(); + m_bold_font = ui::Font::default_bold_font(); + + m_terminal_canvas = ui::Canvas { .width = m_rect.width, + .height = m_rect.height, + .stride = m_rect.width, + .ptr = (u8*)TRY(calloc_impl(m_rect.width, m_rect.height * sizeof(u32), false)) }; + + signal(SIGCHLD, sigchld_handler); + + int infds[2]; + int outfds[2]; + + int result = pipe(infds); + if (result < 0) return err(errno); + + result = pipe(outfds); + if (result < 0) return err(errno); + + pid_t child = TRY(os::Process::fork()); + if (child == 0) + { + dup2(infds[0], STDIN_FILENO); + dup2(outfds[1], STDOUT_FILENO); + dup2(outfds[1], STDERR_FILENO); + + close(infds[0]); + close(infds[1]); + close(outfds[0]); + close(outfds[1]); + + execv(args[0], args); + _exit(127); + } + + close(infds[0]); + close(outfds[1]); + + m_write_fd = infds[1]; + m_read_fd = outfds[0]; + + fcntl(m_read_fd, F_SETFL, O_NONBLOCK); + + m_child_pid = child; + + return {}; +} + +Result TerminalWidget::draw(ui::Canvas& canvas) +{ + canvas.fill((u32*)m_terminal_canvas.ptr, m_terminal_canvas.stride); + return {}; +} + +Result TerminalWidget::process() +{ + char buffer[BUFSIZ]; + ssize_t nread = read(m_read_fd, buffer, BUFSIZ); + if (nread < 0) + { + if (errno == EAGAIN) return {}; + return err(errno); + } + + os::eprintln("terminal: read %zd characters, processing...", nread); + + for (ssize_t i = 0; i < nread; i++) TRY(putchar(buffer[i])); + + ui::App::the().main_window()->draw(); + + return {}; +} + +void TerminalWidget::draw_glyph(wchar_t c, int x, int y) +{ + auto subcanvas = m_terminal_canvas.subcanvas({ x, y, m_font->width(), m_font->height() }); + subcanvas.fill(m_background_color); + (m_bold ? m_bold_font : m_font)->render(c, m_foreground_color, subcanvas); +} + +void TerminalWidget::erase_current_line() +{ + m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }); +} + +void TerminalWidget::scroll() +{ + memcpy(m_terminal_canvas.ptr, m_terminal_canvas.ptr + (m_rect.width * sizeof(u32) * m_font->height()), + (m_rect.width * m_rect.height * sizeof(u32)) - (m_rect.width * sizeof(u32) * m_font->height())); + m_y_position -= m_font->height(); + erase_current_line(); +} + +bool TerminalWidget::should_scroll() +{ + return m_y_position >= m_rect.height; +} + +void TerminalWidget::next_line() +{ + m_x_position = 0; + m_y_position += m_font->height(); +} + +void TerminalWidget::next_char() +{ + m_x_position += m_font->width(); +} + +void TerminalWidget::prev_char() +{ + m_y_position -= m_font->width(); +} + +void TerminalWidget::erase_current_char() +{ + m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::BLACK); +} + +void TerminalWidget::draw_cursor() +{ + m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::WHITE); +} + +bool TerminalWidget::at_end_of_screen() +{ + return (m_x_position + m_font->width()) > m_rect.width; +} + +bool TerminalWidget::handle_escape_sequence(wchar_t c) +{ + auto rc = m_escape_parser->advance(static_cast(c)); + if (rc.has_error()) + { + m_escape_parser = Option {}; + return false; + } + if (!rc.value()) return true; + if (!m_escape_parser->valid()) + { + m_escape_parser = Option {}; + return false; + } + + const auto& params = m_escape_parser->parameters(); + switch (m_escape_parser->code()) + { + case EscapeCode::CursorUp: { + int lines = params.size() ? params[0] : 1; + int pixels = lines * m_font->height(); + if (pixels > m_y_position) m_y_position = 0; + else + m_y_position -= pixels; + }; + break; + case EscapeCode::CursorDown: { + int lines = params.size() ? params[0] : 1; + int pixels = lines * m_font->height(); + if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height(); + else + m_y_position += pixels; + }; + break; + case EscapeCode::CursorBack: { + int chars = params.size() ? params[0] : 1; + int pixels = chars * m_font->width(); + if (pixels > m_x_position) m_x_position = 0; + else + m_x_position -= pixels; + }; + break; + case EscapeCode::CursorForward: { + int chars = params.size() ? params[0] : 1; + int pixels = chars * m_font->width(); + if (pixels + m_x_position >= m_rect.width) m_x_position = m_rect.width - m_font->width(); + else + m_x_position += pixels; + }; + break; + case EscapeCode::CursorNextLine: { + int lines = params.size() ? params[0] : 1; + int pixels = lines * m_font->height(); + if (pixels > m_y_position) m_y_position = 0; + else + m_y_position -= pixels; + m_x_position = 0; + }; + break; + case EscapeCode::CursorPreviousLine: { + int lines = params.size() ? params[0] : 1; + int pixels = lines * m_font->height(); + if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height(); + else + m_y_position += pixels; + m_x_position = 0; + }; + break; + case EscapeCode::CursorHorizontalAbsolute: { + int line = (params.size() ? params[0] : 1) - 1; + if (line < 0) break; + int position = line * m_font->height(); + if (position >= m_rect.height) position = m_rect.height - m_font->height(); + m_y_position = position; + }; + break; + case EscapeCode::SetCursorPosition: { + int x = (params.size() ? params[0] : 1) - 1; + int y = (params.size() > 1 ? params[1] : 1) - 1; + if (x < 0 || y < 0) break; + int x_position = x * m_font->width(); + if (x_position >= m_rect.width) x_position = m_rect.width - m_font->height(); + m_x_position = x_position; + int y_position = y * m_font->height(); + if (y_position >= m_rect.height) y_position = m_rect.height - m_font->height(); + m_y_position = y_position; + }; + break; + case EscapeCode::SelectGraphicRendition: { + if (!params.size()) + { + m_foreground_color = ui::WHITE; + m_background_color = ui::BLACK; + m_bold = false; + break; + } + + for (usize i = 0; i < params.size(); i++) + { + int arg = params[i]; + switch (arg) + { + case 0: { + m_foreground_color = ui::BLACK; + m_background_color = ui::WHITE; + m_bold = false; + break; + } + case 1: { + m_bold = true; + break; + } + case 22: { + m_bold = false; + break; + } + case 30: { + m_foreground_color = m_bold ? BRIGHT_BLACK : ui::BLACK; + break; + } + case 31: { + m_foreground_color = m_bold ? BRIGHT_RED : RED; + break; + } + case 32: { + m_foreground_color = m_bold ? BRIGHT_GREEN : GREEN; + break; + } + case 33: { + m_foreground_color = m_bold ? BRIGHT_YELLOW : YELLOW; + break; + } + case 34: { + m_foreground_color = m_bold ? BRIGHT_BLUE : BLUE; + break; + } + case 35: { + m_foreground_color = m_bold ? BRIGHT_MAGENTA : MAGENTA; + break; + } + case 36: { + m_foreground_color = m_bold ? BRIGHT_CYAN : CYAN; + break; + } + case 37: { + m_foreground_color = m_bold ? BRIGHT_GRAY : GRAY; + break; + } + case 39: { + m_foreground_color = ui::WHITE; + break; + } + case 40: { + m_background_color = m_bold ? BRIGHT_BLACK : ui::BLACK; + break; + } + case 41: { + m_background_color = m_bold ? BRIGHT_RED : RED; + break; + } + case 42: { + m_background_color = m_bold ? BRIGHT_GREEN : GREEN; + break; + } + case 43: { + m_background_color = m_bold ? BRIGHT_YELLOW : YELLOW; + break; + } + case 44: { + m_background_color = m_bold ? BRIGHT_BLUE : BLUE; + break; + } + case 45: { + m_background_color = m_bold ? BRIGHT_MAGENTA : MAGENTA; + break; + } + case 46: { + m_background_color = m_bold ? BRIGHT_CYAN : CYAN; + break; + } + case 47: { + m_background_color = m_bold ? BRIGHT_GRAY : GRAY; + break; + } + case 49: { + m_background_color = ui::BLACK; + break; + } + default: break; + } + } + } + break; + default: break; + } + + m_escape_parser = Option {}; + return true; +} + +Result TerminalWidget::putchar(char c) +{ + auto guard = make_scope_guard([this] { m_decoder.reset(); }); + + bool is_ready = TRY(m_decoder.feed(c)); + + if (is_ready) put_code_point(TRY(m_decoder.extract())); + + guard.deactivate(); + + return {}; +} + +void TerminalWidget::put_code_point(wchar_t c) +{ + if (c > (wchar_t)255) c = (wchar_t)256; + + if (m_escape_parser.has_value()) + { + if (handle_escape_sequence(c)) return; + } + + // Erase the current cursor. + if (m_cursor_enabled) erase_current_char(); + + bool should_draw_cursor = m_cursor_enabled; + + switch (c) + { + case L'\n': { + next_line(); + if (should_scroll()) scroll(); + break; + } + case L'\t': { + for (int i = 0; i < 4; i++) { put_code_point(L' '); } + break; + } + case L'\r': m_x_position = 0; break; + case L'\b': + if (m_x_position != 0) + { + prev_char(); + erase_current_char(); + } + break; + case L'\x1b': + case L'\x9b': + case L'\x90': + case L'\x9d': + m_escape_parser = EscapeSequenceParser { (u8)c }; + should_draw_cursor = false; + break; + default: { + if (iscntrl(c)) return; + draw_glyph(c, m_x_position, m_y_position); + next_char(); + if (at_end_of_screen()) + { + next_line(); + if (should_scroll()) scroll(); + } + break; + } + } + + if (should_draw_cursor) + { + m_current_cursor_timeout = CURSOR_TIMEOUT; + m_cursor_activated = true; + draw_cursor(); + } +} + +void TerminalWidget::quit() +{ + kill(m_child_pid, SIGHUP); +} diff --git a/terminal/TerminalWidget.h b/terminal/TerminalWidget.h new file mode 100644 index 00000000..aaf26c5b --- /dev/null +++ b/terminal/TerminalWidget.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +class TerminalWidget : public ui::Widget +{ + public: + Result init(char* const* args); + + Result draw(ui::Canvas& canvas) override; + + Result process(); + + void quit(); + + private: + ui::Canvas m_terminal_canvas; + Vector m_line_buffer; + int m_write_fd; + int m_read_fd; + pid_t m_child_pid; + + SharedPtr m_font; + SharedPtr m_bold_font; + + static constexpr int CURSOR_TIMEOUT = 500; + + int m_current_cursor_timeout = CURSOR_TIMEOUT; + bool m_cursor_activated = true; + bool m_cursor_enabled = false; + + int m_x_position { 0 }; + int m_y_position { 0 }; + + bool m_bold { false }; + + ui::Color m_foreground_color { ui::WHITE }; + ui::Color m_background_color { ui::BLACK }; + + Utf8StateDecoder m_decoder; + Option m_escape_parser; + + void draw_glyph(wchar_t c, int x, int y); + void erase_current_line(); + void scroll(); + bool should_scroll(); + void next_line(); + void next_char(); + void prev_char(); + void erase_current_char(); + void draw_cursor(); + bool at_end_of_screen(); + bool handle_escape_sequence(wchar_t c); + Result putchar(char c); + void put_code_point(wchar_t c); +}; diff --git a/terminal/main.cpp b/terminal/main.cpp new file mode 100644 index 00000000..20986d35 --- /dev/null +++ b/terminal/main.cpp @@ -0,0 +1,29 @@ +#include "TerminalWidget.h" +#include +#include + +Result luna_main(int argc, char** argv) +{ + ui::App app; + TRY(app.init(argc, argv)); + app.set_nonblocking(); + + auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 500, 300 })); + app.set_main_window(window); + window->set_background(ui::BLACK); + window->set_title("Terminal"); + + TerminalWidget terminal; + window->set_main_widget(terminal); + + char* args[] = { "/bin/sh", "-i", nullptr }; + TRY(terminal.init(args)); + + window->draw(); + + while (app.process_events()) TRY(terminal.process()); + + terminal.quit(); + + return 0; +} diff --git a/wind/main.cpp b/wind/main.cpp index 3986a90e..4354f7f5 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -122,7 +122,7 @@ Result luna_main(int argc, char** argv) StringView args[] = { "/usr/bin/init"_sv, "--user"_sv }; TRY(os::Process::spawn("/usr/bin/init"_sv, Slice { args, 2 }, false)); - ui::Color background = ui::BLACK; + ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10); Vector> clients; Vector fds; -- 2.34.1 From a4b5e68e1b6af7f997d0f54487dbc29ac5414411 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 14 Sep 2023 22:55:54 +0200 Subject: [PATCH 081/103] kernel: Allow performing extra actions when opening an inode --- kernel/src/fs/VFS.h | 5 +++++ kernel/src/sys/open.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/kernel/src/fs/VFS.h b/kernel/src/fs/VFS.h index 2a63f68e..dbe471ee 100644 --- a/kernel/src/fs/VFS.h +++ b/kernel/src/fs/VFS.h @@ -118,6 +118,11 @@ namespace VFS return err(EACCES); } + virtual Result> open() + { + return SharedPtr { this }; + } + // Directory-specific methods virtual Result> find(const char* name) const = 0; diff --git a/kernel/src/sys/open.cpp b/kernel/src/sys/open.cpp index 90d5d84a..0f0d03f2 100644 --- a/kernel/src/sys/open.cpp +++ b/kernel/src/sys/open.cpp @@ -62,6 +62,8 @@ Result sys_openat(Registers*, SyscallArgs args) if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth)) return err(EACCES); } + inode = TRY(inode->open()); + // This should only be possible if O_NOFOLLOW was in flags. if (inode->type() == VFS::InodeType::Symlink) return err(ELOOP); -- 2.34.1 From 5a49e9748346de027d5564917e51dec796a35ec4 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:41:51 +0200 Subject: [PATCH 082/103] taskbar: Add a button to open terminal instead of gclient --- apps/taskbar.cpp | 18 +++++++++--------- base/usr/share/icons/32x32/app-terminal.tga | Bin 0 -> 4140 bytes base/usr/share/icons/32x32/start-icon.tga | Bin 4140 -> 0 bytes 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 base/usr/share/icons/32x32/app-terminal.tga delete mode 100644 base/usr/share/icons/32x32/start-icon.tga diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp index 81679d52..9283d6e6 100644 --- a/apps/taskbar.cpp +++ b/apps/taskbar.cpp @@ -31,18 +31,18 @@ Result luna_main(int argc, char** argv) ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No); window->set_main_widget(layout); - ui::Button start_button({ 0, 0, 50, 50 }); - layout.add_widget(start_button); + ui::Button term_button({ 0, 0, 50, 50 }); + layout.add_widget(term_button); - ui::Container start_container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); - start_button.set_widget(start_container); - start_button.set_action([] { - StringView args[] = { "/usr/bin/gclient" }; - os::Process::spawn("/usr/bin/gclient", Slice { args, 1 }, false); + ui::Container term_container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center); + term_button.set_widget(term_container); + term_button.set_action([] { + StringView args[] = { "/usr/bin/terminal" }; + os::Process::spawn("/usr/bin/terminal", Slice { args, 1 }, false); }); - auto start_image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga")); - start_container.set_widget(*start_image); + auto term_image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/app-terminal.tga")); + term_container.set_widget(*term_image); ui::Button about_button({ 0, 0, 50, 50 }); layout.add_widget(about_button); diff --git a/base/usr/share/icons/32x32/app-terminal.tga b/base/usr/share/icons/32x32/app-terminal.tga new file mode 100644 index 0000000000000000000000000000000000000000..44f522d8d6324a909bd9b79dc5962f767746a479 GIT binary patch literal 4140 zcmeH`&kBGb42MTg(y>dug@F(S{iFAP5z}I~=}terY%(O1zWKR`I^yUlj)9+SHUq5< zgi293XNd1=WUho7?8L~$$4}P~Hz2dfPi$}vDj!^f%5QmoFk9`vauk+05x##m%IJpw{?7GzqpBBD%zX- literal 0 HcmV?d00001 diff --git a/base/usr/share/icons/32x32/start-icon.tga b/base/usr/share/icons/32x32/start-icon.tga deleted file mode 100644 index cad6cf31deff1d7a54858c629730377d39dab439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4140 zcmeH{OA5j;5QYa&;?j-YqC!9v)QWqLB{zWiqnn8$+?g|#cd%C+17Va-X$>-({E|nr|xA}_` z0L}}3|NLG6USY?6`^-gd&&~Nt{@-|gh?{Ewj--eF|GXJIxAxt6Afz|ftmnva!&+O6o}A@e=XyL+@0yS7B~D8WaalDjPVTXo(Oh(fC_hrK%@Sd3 Wwe<-+@8j@Fk94~p-{*80PV@sfuqD6% -- 2.34.1 From 1b633212f63e6d8a42598e80b2ef4bc18d4a0beb Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:42:19 +0200 Subject: [PATCH 083/103] apps: Remove gclient --- apps/CMakeLists.txt | 2 -- apps/gclient.cpp | 67 --------------------------------------------- 2 files changed, 69 deletions(-) delete mode 100644 apps/gclient.cpp diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 6b0e60c2..da5a312f 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -40,8 +40,6 @@ luna_app(kill.cpp kill) luna_app(gol.cpp gol) luna_app(touch.cpp touch) luna_app(free.cpp free) -luna_app(gclient.cpp gclient) -target_link_libraries(gclient PUBLIC ui) luna_app(about.cpp about) target_link_libraries(about PUBLIC ui) luna_app(taskbar.cpp taskbar) diff --git a/apps/gclient.cpp b/apps/gclient.cpp deleted file mode 100644 index 685b7217..00000000 --- a/apps/gclient.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include - -struct ColorWidget : public ui::Widget -{ - public: - ColorWidget(ui::Color first, ui::Color second) : m_color(first), m_first_color(first), m_second_color(second) - { - } - - Result handle_mouse_move(ui::Point) override - { - auto old_color = m_color; - m_color = m_second_color; - return old_color.raw == m_second_color.raw ? ui::EventResult::DidNotHandle : ui::EventResult::DidHandle; - } - - Result handle_mouse_leave() override - { - auto old_color = m_color; - m_color = m_first_color; - return old_color.raw == m_first_color.raw ? ui::EventResult::DidNotHandle : ui::EventResult::DidHandle; - } - - Result draw(ui::Canvas& canvas) override - { - canvas.fill(m_color); - return {}; - } - - private: - ui::Color m_color; - ui::Color m_first_color; - ui::Color m_second_color; -}; - -Result luna_main(int argc, char** argv) -{ - ui::App app; - TRY(app.init(argc, argv)); - - auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 })); - app.set_main_window(window); - - window->set_title("Test Window"); - window->set_background(ui::CYAN); - - ui::HorizontalLayout layout; - window->set_main_widget(layout); - - ColorWidget green(ui::GREEN, ui::WHITE); - layout.add_widget(green); - ColorWidget blue(ui::BLUE, ui::GRAY); - layout.add_widget(blue); - - ui::VerticalLayout sublayout; - layout.add_widget(sublayout); - - ColorWidget red(ui::RED, ui::CYAN); - sublayout.add_widget(red); - ColorWidget white(ui::WHITE, ui::GREEN); - sublayout.add_widget(white); - - window->draw(); - - return app.run(); -} -- 2.34.1 From e2a1cb0d3457d13027be03eaa03a9dc714a457b1 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:45:19 +0200 Subject: [PATCH 084/103] wind+libui: Add support for keyboard events --- libui/include/ui/Button.h | 1 + libui/include/ui/Container.h | 1 + libui/include/ui/Key.h | 14 ++ libui/include/ui/Layout.h | 2 + libui/include/ui/Widget.h | 7 + libui/include/ui/Window.h | 1 + libui/include/ui/ipc/Client.h | 19 ++- libui/src/App.cpp | 8 + libui/src/Button.cpp | 5 + libui/src/Container.cpp | 5 + libui/src/Layout.cpp | 26 +++ libui/src/Window.cpp | 6 + wind/CMakeLists.txt | 2 + wind/Keyboard.cpp | 305 ++++++++++++++++++++++++++++++++++ wind/Keyboard.h | 10 ++ wind/main.cpp | 9 +- 16 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 libui/include/ui/Key.h create mode 100644 wind/Keyboard.cpp create mode 100644 wind/Keyboard.h diff --git a/libui/include/ui/Button.h b/libui/include/ui/Button.h index aa01dab1..0fbcd2df 100644 --- a/libui/include/ui/Button.h +++ b/libui/include/ui/Button.h @@ -24,6 +24,7 @@ namespace ui Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; + Result handle_key_event(const ui::KeyEventRequest& request) override; Result draw(Canvas& canvas) override; private: diff --git a/libui/include/ui/Container.h b/libui/include/ui/Container.h index aec537d7..36337d67 100644 --- a/libui/include/ui/Container.h +++ b/libui/include/ui/Container.h @@ -24,6 +24,7 @@ namespace ui Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; + Result handle_key_event(const ui::KeyEventRequest& request) override; Result draw(Canvas& canvas) override; private: diff --git a/libui/include/ui/Key.h b/libui/include/ui/Key.h new file mode 100644 index 00000000..52aca98d --- /dev/null +++ b/libui/include/ui/Key.h @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace ui +{ + enum Modifier + { + Mod_Shift = (1 << 0), + Mod_Alt = (1 << 1), + Mod_Super = (1 << 2), + Mod_AltGr = (1 << 3), + Mod_Ctrl = (1 << 4) + }; +} diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h index 66e4976b..3b0e8ad7 100644 --- a/libui/include/ui/Layout.h +++ b/libui/include/ui/Layout.h @@ -34,6 +34,7 @@ namespace ui Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; + Result handle_key_event(const ui::KeyEventRequest& request) override; Result draw(Canvas& canvas) override; @@ -55,6 +56,7 @@ namespace ui Result handle_mouse_leave() override; Result handle_mouse_down(Point position, int buttons) override; Result handle_mouse_up(Point position, int buttons) override; + Result handle_key_event(const ui::KeyEventRequest& request) override; Result draw(Canvas& canvas) override; diff --git a/libui/include/ui/Widget.h b/libui/include/ui/Widget.h index f93e832b..ea690f6e 100644 --- a/libui/include/ui/Widget.h +++ b/libui/include/ui/Widget.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace ui { @@ -50,6 +51,12 @@ namespace ui return EventResult::DidNotHandle; } + virtual Result handle_key_event(const ui::KeyEventRequest& request) + { + ignore(request); + return EventResult::DidNotHandle; + } + virtual Result draw(Canvas& canvas); void set_window(Window* window, Rect rect, Badge) diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index 99058cba..fd914b0a 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -49,6 +49,7 @@ namespace ui Result handle_mouse_leave(); Result handle_mouse_move(ui::Point position); Result handle_mouse_buttons(ui::Point position, int buttons); + Result handle_key_event(const ui::KeyEventRequest& request); int id() const { diff --git a/libui/include/ui/ipc/Client.h b/libui/include/ui/ipc/Client.h index 6e277066..f042d3f1 100644 --- a/libui/include/ui/ipc/Client.h +++ b/libui/include/ui/ipc/Client.h @@ -9,7 +9,9 @@ #pragma once #include +#include #include +#include namespace ui { @@ -20,7 +22,8 @@ namespace ui WINDOW_CLOSE_REQUEST_ID, MOUSE_EVENT_REQUEST_ID, MOUSE_LEAVE_REQUEST_ID, - GET_SCREEN_RECT_RESPONSE_ID + GET_SCREEN_RECT_RESPONSE_ID, + KEY_EVENT_REQUEST_ID, }; struct CreateWindowResponse @@ -60,4 +63,18 @@ namespace ui Rect rect; }; + + struct KeyEventRequest + { + static constexpr u8 ID = KEY_EVENT_REQUEST_ID; + + int window; + + bool pressed; + + char letter; + char key; + moon::KeyCode code; + int modifiers; + }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 43fe9b9b..7f4c9c98 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -132,6 +132,14 @@ namespace ui window->draw(); return {}; } + case KEY_EVENT_REQUEST_ID: { + KeyEventRequest request; + READ_MESSAGE(request); + auto* window = find_window(request.window); + if (window->handle_key_event(request).value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle) + window->draw(); + return {}; + } default: fail("Unexpected IPC request from server!"); } } diff --git a/libui/src/Button.cpp b/libui/src/Button.cpp index 08c50d15..dd99d65b 100644 --- a/libui/src/Button.cpp +++ b/libui/src/Button.cpp @@ -61,6 +61,11 @@ namespace ui return m_child->handle_mouse_up(position, buttons); } + Result Button::handle_key_event(const ui::KeyEventRequest& request) + { + return m_child->handle_key_event(request); + } + Result Button::draw(Canvas& canvas) { return m_child->draw(canvas); diff --git a/libui/src/Container.cpp b/libui/src/Container.cpp index d7f320db..9b4fb07f 100644 --- a/libui/src/Container.cpp +++ b/libui/src/Container.cpp @@ -47,6 +47,11 @@ namespace ui return ui::EventResult::DidNotHandle; } + Result Container::handle_key_event(const ui::KeyEventRequest& request) + { + return m_widget->handle_key_event(request); + } + Result Container::draw(Canvas& canvas) { auto rect = ui::Rect { m_widget->rect().pos.x - m_rect.pos.x, m_widget->rect().pos.y - m_rect.pos.y, diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp index 2b16c885..300739ca 100644 --- a/libui/src/Layout.cpp +++ b/libui/src/Layout.cpp @@ -64,6 +64,19 @@ namespace ui return ui::EventResult::DidNotHandle; } + Result HorizontalLayout::handle_key_event(const ui::KeyEventRequest& request) + { + EventResult result = ui::EventResult::DidNotHandle; + + for (auto widget : m_widgets) + { + auto rc = TRY(widget->handle_key_event(request)); + if (rc == ui::EventResult::DidHandle) result = rc; + } + + return result; + } + Result HorizontalLayout::draw(Canvas& canvas) { for (auto widget : m_widgets) @@ -159,6 +172,19 @@ namespace ui return ui::EventResult::DidNotHandle; } + Result VerticalLayout::handle_key_event(const ui::KeyEventRequest& request) + { + EventResult result = ui::EventResult::DidNotHandle; + + for (auto widget : m_widgets) + { + auto rc = TRY(widget->handle_key_event(request)); + if (rc == ui::EventResult::DidHandle) result = rc; + } + + return result; + } + Result VerticalLayout::draw(Canvas& canvas) { for (auto widget : m_widgets) diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index da6541a2..441e47db 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -114,4 +114,10 @@ namespace ui m_old_mouse_buttons = buttons; return result; } + + Result Window::handle_key_event(const ui::KeyEventRequest& request) + { + if (!m_main_widget) return ui::EventResult::DidNotHandle; + return m_main_widget->handle_key_event(request); + } } diff --git a/wind/CMakeLists.txt b/wind/CMakeLists.txt index 5d79b2c8..2999afb8 100644 --- a/wind/CMakeLists.txt +++ b/wind/CMakeLists.txt @@ -8,6 +8,8 @@ set(SOURCES Window.cpp IPC.cpp IPC.h + Keyboard.cpp + Keyboard.h Client.h ) diff --git a/wind/Keyboard.cpp b/wind/Keyboard.cpp new file mode 100644 index 00000000..e0941b49 --- /dev/null +++ b/wind/Keyboard.cpp @@ -0,0 +1,305 @@ +#include "Keyboard.h" +#include + +static const char table[] = { + // Function keys + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + // System keys + '\x1b', + '\0', + '\0', + '\0', + '\0', + // Modifier keys + '\0', + '\0', + '\0', + '\0', // or AltGr on some keyboards + '\0', + '\0', + // Navigation keys + '\t', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + // Editing keys + '\b', + '\n', + '\0', + '\x7f', + '\n', + // Lock keys + '\0', + '\0', + '\0', + // Keypad keys + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '.', + '+', + '-', + '*', + '/', + // Character keys (depending on keyboard layout), examples in US QWERTY + '`', // ` + '1', // 1 + '2', // 2 + '3', // 3 + '4', // 4 + '5', // 5 + '6', // 6 + '7', // 7 + '8', // 8 + '9', // 9 + '0', // 0 + '-', // - + '=', // = + 'q', // Q + 'w', // W + 'e', // E + 'r', // R + 't', // T + 'y', // Y + 'u', // U + 'i', // I + 'o', // O + 'p', // P + '[', // [ + ']', // ] + 'a', // A + 's', // S + 'd', // D + 'f', // F + 'g', // G + 'h', // H + 'j', // J + 'k', // K + 'l', // L + ';', // ; + '\'', // ' + '#', // # + '\\', // Backslash + 'z', // Z + 'x', // X + 'c', // C + 'v', // V + 'b', // B + 'n', // N + 'm', // M + ',', // , + '.', // . + '/', // / + ' ', // Space + // Unknown key + '\0', +}; + +static const char shift_table[] = { + // Function keys + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + // System keys + '\x1b', + '\0', + '\0', + '\0', + '\0', + // Modifier keys + '\0', + '\0', + '\0', + '\0', // or AltGr on some keyboards + '\0', + '\0', + // Navigation keys + '\t', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + '\0', + // Editing keys + '\b', + '\n', + '\0', + '\x7f', + '\n', + // Lock keys + '\0', + '\0', + '\0', + // Keypad keys + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '.', + '+', + '-', + '*', + '/', + // Character keys (depending on keyboard layout), examples in US QWERTY + '~', // ` + '!', + '@', + '#', + '$', + '%', + '^', + '&', + '*', + '(', + ')', + '_', + '+', + 'Q', + 'W', + 'E', + 'R', + 'T', + 'Y', + 'U', + 'I', + 'O', + 'P', + '{', + '}', + 'A', + 'S', + 'D', + 'F', + 'G', + 'H', + 'J', + 'K', + 'L', + ':', + '"', + ' ', // # + '|', // Backslash + 'Z', + 'X', + 'C', + 'V', + 'B', + 'N', + 'M', + '<', + '>', + '?', + ' ', // Space + // Unknown key + '\0', +}; + +namespace wind::Keyboard +{ + static bool g_caps_lock = false; + static bool g_right_shift = false; + static bool g_left_shift = false; + static bool g_right_control = false; + static bool g_left_control = false; + static bool g_altgr = false; + static bool g_alt = false; + static bool g_super = false; + + ui::KeyEventRequest decode_keyboard_event(moon::KeyCode code, bool released) + { + ui::KeyEventRequest request; + request.code = code; + request.pressed = !released; + request.modifiers = 0; + + switch (code) + { + case moon::K_CapsLock: + if (!released) { g_caps_lock = !g_caps_lock; } + break; + case moon::K_RightShift: g_right_shift = !released; break; + case moon::K_LeftShift: g_left_shift = !released; break; + case moon::K_RightControl: g_right_control = !released; break; + case moon::K_LeftControl: g_left_control = !released; break; + case moon::K_RightAlt: g_altgr = !released; break; + case moon::K_LeftAlt: g_alt = !released; break; + case moon::K_Super: g_super = !released; break; + default: break; + } + + if ((g_caps_lock && !(g_left_shift || g_right_shift)) || (g_left_shift || g_right_shift)) + { + request.modifiers |= ui::Mod_Shift; + } + + if (g_right_control || g_left_control) request.modifiers |= ui::Mod_Ctrl; + + if (g_alt) request.modifiers |= ui::Mod_Alt; + if (g_altgr) request.modifiers |= ui::Mod_AltGr; + if (g_super) request.modifiers |= ui::Mod_Super; + + request.key = table[code]; + + if (request.modifiers & ui::Mod_Ctrl) + { + char letter; + if (request.modifiers & ui::Mod_Shift) letter = shift_table[code]; + else + letter = table[code]; + if (_islower(letter)) letter = (char)_toupper(letter); + if (_isupper(letter)) letter = 0x40; + if (letter == '@') letter = 0x40; + if (letter > 'Z' && letter < '`') letter = 0x40; + if (letter == '?') letter = 0x7f; + request.letter = letter; + return request; + } + + if (request.modifiers & ui::Mod_Shift) request.letter = shift_table[code]; + else + request.letter = table[code]; + + return request; + } +} diff --git a/wind/Keyboard.h b/wind/Keyboard.h new file mode 100644 index 00000000..3259b669 --- /dev/null +++ b/wind/Keyboard.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace wind +{ + namespace Keyboard + { + ui::KeyEventRequest decode_keyboard_event(moon::KeyCode code, bool released); + } +} diff --git a/wind/main.cpp b/wind/main.cpp index 4354f7f5..d65dd551 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -1,6 +1,7 @@ #define CLIENT_IMPLEMENTATION #include "Client.h" #include "IPC.h" +#include "Keyboard.h" #include "Mouse.h" #include "Screen.h" #include "Window.h" @@ -155,8 +156,14 @@ Result luna_main(int argc, char** argv) { moon::KeyboardPacket packet; TRY(keyboard->read_typed(packet)); - os::println("%s key %d", packet.released ? "Released" : "Pressed", packet.key); if (!packet.released && packet.key == moon::K_Tab) debug(clients); + auto request = wind::Keyboard::decode_keyboard_event((moon::KeyCode)packet.key, packet.released); + if (g_windows.last().has_value()) + { + auto* window = g_windows.last().value(); + request.window = window->id; + os::IPC::send_async(window->client->conn, request); + } } for (usize i = 0; i < clients.size(); i++) { -- 2.34.1 From ab738772b997e9d318ca63fc56c6dc565b25ab93 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:45:51 +0200 Subject: [PATCH 085/103] wind: Stop tracking windows after they're closed --- wind/IPC.cpp | 2 ++ wind/Mouse.cpp | 17 +++++++++++++++++ wind/Mouse.h | 4 ++++ wind/main.cpp | 1 + 4 files changed, 24 insertions(+) diff --git a/wind/IPC.cpp b/wind/IPC.cpp index 98d93d68..e04d0300 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -1,4 +1,5 @@ #include "IPC.h" +#include "Mouse.h" #include "Screen.h" #include #include @@ -129,6 +130,7 @@ static Result handle_close_window_message(Client& client) auto* window = client.windows[request.window]; client.windows[request.window] = nullptr; g_windows.remove(window); + Mouse::the().window_did_close(window); delete window; return {}; diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index 0ba7d1ea..125a37a7 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -7,6 +7,8 @@ static SharedPtr g_mouse_cursor; +static Mouse* s_mouse; + Mouse::Mouse(ui::Canvas& screen) { m_position.x = screen.width / 2; @@ -14,6 +16,14 @@ Mouse::Mouse(ui::Canvas& screen) m_screen_rect = screen.rect(); g_mouse_cursor = ui::Image::load("/usr/share/cursors/default.tga").value_or({}); + + s_mouse = this; +} + +Mouse& Mouse::the() +{ + check(s_mouse); + return *s_mouse; } void Mouse::draw(ui::Canvas& screen) @@ -110,3 +120,10 @@ void Mouse::update(const moon::MousePacket& packet) m_active_window = new_active_window; } } + +void Mouse::window_did_close(Window* window) +{ + if (m_dragging_window == window) { m_dragging_window = nullptr; } + + if (m_active_window == window) { m_active_window = nullptr; } +} diff --git a/wind/Mouse.h b/wind/Mouse.h index bee42f4c..3b068133 100644 --- a/wind/Mouse.h +++ b/wind/Mouse.h @@ -13,6 +13,10 @@ class Mouse void draw(ui::Canvas& screen); + void window_did_close(Window* window); + + static Mouse& the(); + private: ui::Point m_position; ui::Rect m_screen_rect; diff --git a/wind/main.cpp b/wind/main.cpp index d65dd551..b0b4930c 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -179,6 +179,7 @@ Result luna_main(int argc, char** argv) if (window) { g_windows.remove(window); + mouse_pointer.window_did_close(window); delete window; } } -- 2.34.1 From a93626fc4102667a664adb294d2e04a6f2984542 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:46:52 +0200 Subject: [PATCH 086/103] kernel: Add pseudoterminals and a /dev/pts filesystem --- kernel/CMakeLists.txt | 5 + kernel/src/fs/devices/DeviceRegistry.cpp | 7 ++ kernel/src/fs/devices/DeviceRegistry.h | 1 + kernel/src/fs/devices/MasterPTY.cpp | 125 +++++++++++++++++++++++ kernel/src/fs/devices/MasterPTY.h | 75 ++++++++++++++ kernel/src/fs/devices/PTYMultiplexer.cpp | 36 +++++++ kernel/src/fs/devices/PTYMultiplexer.h | 80 +++++++++++++++ kernel/src/fs/devices/SlavePTY.cpp | 71 +++++++++++++ kernel/src/fs/devices/SlavePTY.h | 69 +++++++++++++ kernel/src/fs/devpts/FileSystem.cpp | 62 +++++++++++ kernel/src/fs/devpts/FileSystem.h | 61 +++++++++++ kernel/src/fs/devpts/Inode.cpp | 78 ++++++++++++++ kernel/src/fs/devpts/Inode.h | 93 +++++++++++++++++ kernel/src/main.cpp | 2 + kernel/src/sys/mount.cpp | 3 + libc/include/bits/termios.h | 1 + 16 files changed, 769 insertions(+) create mode 100644 kernel/src/fs/devices/MasterPTY.cpp create mode 100644 kernel/src/fs/devices/MasterPTY.h create mode 100644 kernel/src/fs/devices/PTYMultiplexer.cpp create mode 100644 kernel/src/fs/devices/PTYMultiplexer.h create mode 100644 kernel/src/fs/devices/SlavePTY.cpp create mode 100644 kernel/src/fs/devices/SlavePTY.h create mode 100644 kernel/src/fs/devpts/FileSystem.cpp create mode 100644 kernel/src/fs/devpts/FileSystem.h create mode 100644 kernel/src/fs/devpts/Inode.cpp create mode 100644 kernel/src/fs/devpts/Inode.h diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 438a512e..35571e8d 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -58,6 +58,8 @@ set(SOURCES src/net/UnixSocket.cpp src/fs/tmpfs/FileSystem.cpp src/fs/tmpfs/Inode.cpp + src/fs/devpts/FileSystem.cpp + src/fs/devpts/Inode.cpp src/fs/ext2/FileSystem.cpp src/fs/ext2/Inode.cpp src/fs/devices/DeviceRegistry.cpp @@ -70,6 +72,9 @@ set(SOURCES src/fs/devices/UARTDevice.cpp src/fs/devices/MouseDevice.cpp src/fs/devices/KeyboardDevice.cpp + src/fs/devices/PTYMultiplexer.cpp + src/fs/devices/MasterPTY.cpp + src/fs/devices/SlavePTY.cpp src/fs/InitRD.cpp src/binfmt/ELF.cpp src/binfmt/BinaryFormat.cpp diff --git a/kernel/src/fs/devices/DeviceRegistry.cpp b/kernel/src/fs/devices/DeviceRegistry.cpp index 33d6a946..a46b1d10 100644 --- a/kernel/src/fs/devices/DeviceRegistry.cpp +++ b/kernel/src/fs/devices/DeviceRegistry.cpp @@ -7,6 +7,7 @@ #include "fs/devices/KeyboardDevice.h" #include "fs/devices/MouseDevice.h" #include "fs/devices/NullDevice.h" +#include "fs/devices/PTYMultiplexer.h" #include "fs/devices/UARTDevice.h" #include "fs/devices/ZeroDevice.h" #include "fs/tmpfs/FileSystem.h" @@ -92,6 +93,12 @@ namespace DeviceRegistry for (const auto& descriptor : g_available_devices) TRY(create_special_device_inode(fs, descriptor)); + auto multiplexer = TRY(make_shared()); + multiplexer->set_fs(*fs); + multiplexer->set_inode_number(TRY(fs->allocate_inode_number())); + + TRY(fs->root_inode()->add_entry(multiplexer, "ptmx")); + return fs; } } diff --git a/kernel/src/fs/devices/DeviceRegistry.h b/kernel/src/fs/devices/DeviceRegistry.h index e4a8504d..7ee4a89d 100644 --- a/kernel/src/fs/devices/DeviceRegistry.h +++ b/kernel/src/fs/devices/DeviceRegistry.h @@ -17,6 +17,7 @@ namespace DeviceRegistry DiskPartition = 5, Serial = 6, Input = 7, + Terminal = 8, }; Result> fetch_special_device(u32 major, u32 minor); diff --git a/kernel/src/fs/devices/MasterPTY.cpp b/kernel/src/fs/devices/MasterPTY.cpp new file mode 100644 index 00000000..6cf7dc20 --- /dev/null +++ b/kernel/src/fs/devices/MasterPTY.cpp @@ -0,0 +1,125 @@ +#include "fs/devices/MasterPTY.h" +#include "Pledge.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devices/PTYMultiplexer.h" +#include "fs/devpts/FileSystem.h" +#include "memory/MemoryManager.h" +#include "thread/Scheduler.h" + +Result> MasterPTY::create_pair(int index) +{ + auto master = TRY(make_shared()); + auto slave = TRY(make_shared()); + + auto name = TRY(String::format("%d"_sv, index)); + for (auto& fs : g_devpts_instances) { fs->root_inode()->add_entry(slave, name.chars()); } + slave->m_name = move(name); + + master->m_metadata.mode = 0666; + master->m_index = index; + master->m_slave = slave; + master->m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, 0); + master->m_settings.c_lflag = ECHO | ECHOE | ECHOCTL | ISIG | ICANON; + master->m_settings.c_cc[VEOF] = '\4'; + master->m_settings.c_cc[VERASE] = '\b'; + master->m_settings.c_cc[VINTR] = '\3'; + master->m_settings.c_cc[VQUIT] = '\x1c'; + master->m_window.ws_col = 80; + master->m_window.ws_row = 25; + + slave->m_master = master.ptr(); + slave->m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, index + 1); + slave->m_metadata.uid = Scheduler::current()->auth.euid; + slave->m_metadata.gid = Scheduler::current()->auth.egid; + slave->m_metadata.mode = 0620; + + return (SharedPtr)master; +} + +Result MasterPTY::handle_background_process_group(bool can_succeed, int signo) const +{ + if (!m_foreground_process_group.has_value()) return {}; + + auto foreground_pgrp = m_foreground_process_group.value(); + + auto* current = Scheduler::current(); + if (current->pgid == foreground_pgrp) return {}; + + if ((current->signal_mask.get(signo - 1)) || (current->signal_handlers[signo - 1].sa_handler == SIG_IGN)) + { + if (can_succeed) return {}; + return err(EIO); + } + + current->send_signal(signo); + + if (can_succeed) return err(EINTR); + return err(EIO); +} + +Result MasterPTY::read(u8* buf, usize, usize length) const +{ + length = m_buffer.dequeue_data(buf, length); + + return length; +} + +Result MasterPTY::write(const u8* buf, usize, usize length) +{ + TRY(m_slave->m_buffer.append_data(buf, length)); + + return length; +} + +Result MasterPTY::ioctl(int request, void* arg) +{ + auto* current = Scheduler::current(); + TRY(check_pledge(current, Promise::p_tty)); + + switch (request) + { + case TCGETS: { + return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT); + } + case TCSETS: { + if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT); + + return 0; + } + case TIOCSPGRP: { + pid_t pgid; + if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT); + + bool pgid_exists = false; + Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) { + pgid_exists = true; + return false; + }); + if (!pgid_exists) return err(EPERM); + + m_foreground_process_group = pgid; + return 0; + } + case TIOCGPGRP: { + pid_t pgid = m_foreground_process_group.value_or((pid_t)next_thread_id()); + if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT); + return 0; + } + case TIOCGWINSZ: { + if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &m_window)) return err(EFAULT); + return 0; + } + case TIOCGPTN: { + if (!MemoryManager::copy_to_user_typed((int*)arg, &m_index)) return err(EFAULT); + return 0; + } + default: return err(EINVAL); + } +} + +MasterPTY::~MasterPTY() +{ + m_slave->m_master = nullptr; + for (auto& fs : g_devpts_instances) { fs->root_inode()->remove_entry(m_slave->m_name.chars()); } + PTYMultiplexer::did_remove_pty(m_index); +} diff --git a/kernel/src/fs/devices/MasterPTY.h b/kernel/src/fs/devices/MasterPTY.h new file mode 100644 index 00000000..a4131a09 --- /dev/null +++ b/kernel/src/fs/devices/MasterPTY.h @@ -0,0 +1,75 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/SlavePTY.h" +#include +#include + +class MasterPTY : public VFS::DeviceInode +{ + public: + MasterPTY() = default; + + static Result> create_pair(int index); + + VFS::InodeType type() const override + { + return VFS::InodeType::CharacterDevice; + } + + Result query_shared_memory(off_t, usize) override + { + return err(ENOTSUP); + } + + VFS::FileSystem* fs() const override + { + return nullptr; + } + + Result read(u8* buf, usize offset, usize length) const override; + + Result write(const u8* buf, usize offset, usize length) override; + + Result ioctl(int request, void* arg) override; + + Result isatty() const override + { + return 1; + } + + Result truncate(usize) override + { + // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files. + return err(EINVAL); + } + + bool will_block_if_read() const override + { + return m_buffer.is_empty(); + } + + void did_link() override + { + m_metadata.nlinks++; + } + + void did_unlink() override + { + m_metadata.nlinks--; + } + + virtual ~MasterPTY(); + + private: + struct termios m_settings; + mutable Buffer m_buffer; + SharedPtr m_slave; + mutable Option m_foreground_process_group; + struct winsize m_window; + + Result handle_background_process_group(bool can_succeed, int signo) const; + + int m_index; + + friend class SlavePTY; +}; diff --git a/kernel/src/fs/devices/PTYMultiplexer.cpp b/kernel/src/fs/devices/PTYMultiplexer.cpp new file mode 100644 index 00000000..74216560 --- /dev/null +++ b/kernel/src/fs/devices/PTYMultiplexer.cpp @@ -0,0 +1,36 @@ +#include "fs/devices/PTYMultiplexer.h" + +Bitset PTYMultiplexer::m_available_indexes = 0; + +PTYMultiplexer::PTYMultiplexer() +{ + m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, 0); + m_metadata.mode = 0666; +} + +Result> PTYMultiplexer::open() +{ + int index = -1; + for (int i = 0; i < 64; i++) + { + if (!m_available_indexes.get(i)) + { + index = i; + m_available_indexes.set(i, true); + break; + } + } + if (index == -1) return err(ENOSPC); + + return MasterPTY::create_pair(index); +} + +void PTYMultiplexer::init() +{ + m_available_indexes.clear(); +} + +void PTYMultiplexer::did_remove_pty(int index) +{ + m_available_indexes.set(index, false); +} diff --git a/kernel/src/fs/devices/PTYMultiplexer.h b/kernel/src/fs/devices/PTYMultiplexer.h new file mode 100644 index 00000000..1ec53dad --- /dev/null +++ b/kernel/src/fs/devices/PTYMultiplexer.h @@ -0,0 +1,80 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devices/MasterPTY.h" +#include + +class PTYMultiplexer : public VFS::DeviceInode +{ + public: + PTYMultiplexer(); + + VFS::InodeType type() const override + { + return VFS::InodeType::CharacterDevice; + } + + void set_fs(VFS::FileSystem& fs) + { + m_fs = &fs; + } + + void set_inode_number(usize inum) + { + m_metadata.inum = inum; + } + + Result query_shared_memory(off_t, usize) override + { + unreachable(); + } + + Result> open() override; + + VFS::FileSystem* fs() const override + { + return m_fs; + } + + Result read(u8*, usize, usize) const override + { + unreachable(); + } + + Result write(const u8*, usize, usize) override + { + unreachable(); + } + + Result truncate(usize) override + { + // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files. + return err(EINVAL); + } + + bool will_block_if_read() const override + { + unreachable(); + } + + void did_link() override + { + m_metadata.nlinks++; + } + + void did_unlink() override + { + m_metadata.nlinks--; + } + + static void init(); + + static void did_remove_pty(int index); + + virtual ~PTYMultiplexer() = default; + + private: + VFS::FileSystem* m_fs; + + static Bitset m_available_indexes; +}; diff --git a/kernel/src/fs/devices/SlavePTY.cpp b/kernel/src/fs/devices/SlavePTY.cpp new file mode 100644 index 00000000..c5f7d4a7 --- /dev/null +++ b/kernel/src/fs/devices/SlavePTY.cpp @@ -0,0 +1,71 @@ +#include "fs/devices/SlavePTY.h" +#include "Pledge.h" +#include "fs/devices/MasterPTY.h" +#include "memory/MemoryManager.h" +#include "thread/Scheduler.h" + +Result SlavePTY::read(u8* buf, usize, usize length) const +{ + if (!m_master) return err(EIO); + + TRY(m_master->handle_background_process_group(false, SIGTTIN)); + + length = m_buffer.dequeue_data(buf, length); + + return length; +} + +Result SlavePTY::write(const u8* buf, usize, usize length) +{ + if (!m_master) return err(EIO); + + if (m_master->m_settings.c_lflag & TOSTOP) TRY(m_master->handle_background_process_group(true, SIGTTOU)); + + TRY(m_master->m_buffer.append_data(buf, length)); + + return length; +} + +Result SlavePTY::ioctl(int request, void* arg) +{ + auto* current = Scheduler::current(); + TRY(check_pledge(current, Promise::p_tty)); + + if (!m_master) return err(EIO); + + switch (request) + { + case TCGETS: { + return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_master->m_settings) ? 0 : err(EFAULT); + } + case TCSETS: { + if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_master->m_settings)) return err(EFAULT); + + return 0; + } + case TIOCSPGRP: { + pid_t pgid; + if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT); + + bool pgid_exists = false; + Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) { + pgid_exists = true; + return false; + }); + if (!pgid_exists) return err(EPERM); + + m_master->m_foreground_process_group = pgid; + return 0; + } + case TIOCGPGRP: { + pid_t pgid = m_master->m_foreground_process_group.value_or((pid_t)next_thread_id()); + if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT); + return 0; + } + case TIOCGWINSZ: { + if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &m_master->m_window)) return err(EFAULT); + return 0; + } + default: return err(EINVAL); + } +} diff --git a/kernel/src/fs/devices/SlavePTY.h b/kernel/src/fs/devices/SlavePTY.h new file mode 100644 index 00000000..dafba4f9 --- /dev/null +++ b/kernel/src/fs/devices/SlavePTY.h @@ -0,0 +1,69 @@ +#pragma once +#include "fs/VFS.h" +#include +#include + +class MasterPTY; + +class SlavePTY : public VFS::DeviceInode +{ + public: + SlavePTY() = default; + + VFS::InodeType type() const override + { + return VFS::InodeType::CharacterDevice; + } + + Result query_shared_memory(off_t, usize) override + { + return err(ENOTSUP); + } + + VFS::FileSystem* fs() const override + { + return nullptr; + } + + Result read(u8* buf, usize offset, usize length) const override; + + Result write(const u8* buf, usize offset, usize length) override; + + Result ioctl(int request, void* arg) override; + + Result isatty() const override + { + return 1; + } + + Result truncate(usize) override + { + // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files. + return err(EINVAL); + } + + bool will_block_if_read() const override + { + return m_buffer.is_empty(); + } + + void did_link() override + { + m_metadata.nlinks++; + } + + void did_unlink() override + { + m_metadata.nlinks--; + } + + virtual ~SlavePTY() = default; + + private: + mutable Buffer m_buffer; + + MasterPTY* m_master; + String m_name; + + friend class MasterPTY; +}; diff --git a/kernel/src/fs/devpts/FileSystem.cpp b/kernel/src/fs/devpts/FileSystem.cpp new file mode 100644 index 00000000..b7d29cac --- /dev/null +++ b/kernel/src/fs/devpts/FileSystem.cpp @@ -0,0 +1,62 @@ +#include "fs/devpts/FileSystem.h" +#include "arch/Timer.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devpts/Inode.h" +#include +#include +#include + +Vector g_devpts_instances; + +namespace DevPTS +{ + Result> FileSystem::create() + { + SharedPtr fs = TRY(adopt_shared_if_nonnull(new (std::nothrow) FileSystem())); + SharedPtr root = TRY(make_shared()); + + TRY(root->add_entry(root, ".")); + TRY(root->add_entry(root, "..")); + + root->set_self(root, {}); + root->set_fs(*fs, {}); + root->set_inode_number(); + root->m_metadata.mode = 0755; + root->m_metadata.atime = root->m_metadata.ctime = root->m_metadata.mtime = *Timer::realtime_clock(); + fs->set_root(root); + + TRY(g_devpts_instances.try_append(fs.ptr())); + + return (SharedPtr)fs; + } + + Result FileSystem::allocate_inode_number() + { + return err(ENOTSUP); + } + + FileSystem::FileSystem() + { + m_host_device_id = DeviceRegistry::next_null_device_id(); + } + + Result FileSystem::set_mount_dir(SharedPtr parent) + { + return m_root_inode->replace_entry(parent, ".."); + } + + Result FileSystem::reset_mount_dir() + { + return m_root_inode->replace_entry(m_root_inode, ".."); + } + + void FileSystem::set_root(SharedPtr root) + { + m_root_inode = root; + } + + FileSystem::~FileSystem() + { + g_devpts_instances.remove_first_matching([this](VFS::FileSystem* ptr) { return ptr == this; }); + } +} diff --git a/kernel/src/fs/devpts/FileSystem.h b/kernel/src/fs/devpts/FileSystem.h new file mode 100644 index 00000000..f9101938 --- /dev/null +++ b/kernel/src/fs/devpts/FileSystem.h @@ -0,0 +1,61 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" + +namespace DevPTS +{ + class FileSystem : public VFS::FileSystem + { + public: + SharedPtr root_inode() const override + { + return m_root_inode; + } + + Result> create_file_inode(mode_t) override + { + return err(ENOTSUP); + } + + Result> create_dir_inode(SharedPtr, mode_t) override + { + return err(ENOTSUP); + } + + Result> create_device_inode(u32, u32, mode_t) override + { + return err(ENOTSUP); + } + + Result> create_symlink_inode(StringView) override + { + return err(ENOTSUP); + } + + Result allocate_inode_number() override; + + Result set_mount_dir(SharedPtr parent) override; + + Result reset_mount_dir() override; + + static Result> create(); + + dev_t host_device_id() const override + { + return m_host_device_id; + } + + virtual ~FileSystem(); + + private: + FileSystem(); + + void set_root(SharedPtr root); + + SharedPtr m_root_inode; + + dev_t m_host_device_id; + }; +} + +extern Vector g_devpts_instances; diff --git a/kernel/src/fs/devpts/Inode.cpp b/kernel/src/fs/devpts/Inode.cpp new file mode 100644 index 00000000..6efb94fe --- /dev/null +++ b/kernel/src/fs/devpts/Inode.cpp @@ -0,0 +1,78 @@ +#include "fs/devpts/Inode.h" + +namespace DevPTS +{ + Result> RootInode::find(const char* name) const + { + for (const auto& entry : m_entries) + { + if (!strcmp(name, entry.name.chars())) return entry.inode; + } + + return err(ENOENT); + } + + Result RootInode::replace_entry(SharedPtr inode, const char* name) + { + for (auto& entry : m_entries) + { + if (!strcmp(name, entry.name.chars())) + { + entry.inode = inode; + return {}; + } + } + + return err(ENOENT); + } + + Option RootInode::get(usize index) const + { + if (index >= m_entries.size()) return {}; + + return m_entries[index]; + } + + Result RootInode::add_entry(SharedPtr inode, const char* name) + { + if (find(name).has_value()) return err(EEXIST); + + VFS::DirectoryEntry entry { inode, name }; + + TRY(m_entries.try_append(move(entry))); + + inode->did_link(); + + m_metadata.mtime = *Timer::realtime_clock(); + + return {}; + } + + Result RootInode::remove_entry(const char* name) + { + SharedPtr inode = TRY(find(name)); + + if (inode->type() == VFS::InodeType::Directory && inode->entries() != 2) return err(ENOTEMPTY); + + if (inode->is_mountpoint()) return err(EBUSY); + + m_entries.remove_first_matching( + [&](const VFS::DirectoryEntry& entry) { return !strcmp(entry.name.chars(), name); }); + + inode->did_unlink(); + + m_metadata.mtime = *Timer::realtime_clock(); + + return {}; + } + + Result> RootInode::create_file(const char*, mode_t) + { + return err(ENOTSUP); + } + + Result> RootInode::create_subdirectory(const char*, mode_t) + { + return err(ENOTSUP); + } +} diff --git a/kernel/src/fs/devpts/Inode.h b/kernel/src/fs/devpts/Inode.h new file mode 100644 index 00000000..0575236a --- /dev/null +++ b/kernel/src/fs/devpts/Inode.h @@ -0,0 +1,93 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devpts/FileSystem.h" + +namespace DevPTS +{ + class RootInode : public VFS::Inode + { + public: + RootInode() = default; + + void set_fs(FileSystem& fs, Badge) + { + m_fs = &fs; + } + + void set_inode_number() + { + m_metadata.inum = 2; + } + + void set_self(SharedPtr self, Badge) + { + m_self = self; + } + + Result> find(const char* name) const override; + Option get(usize index) const override; + + Result read(u8*, usize, usize) const override + { + return err(EISDIR); + } + + Result write(const u8*, usize, usize) override + { + return err(EISDIR); + } + + Result truncate(usize) override + { + return err(EISDIR); + } + + bool will_block_if_read() const override + { + return false; + } + + VFS::FileSystem* fs() const override + { + return m_fs; + } + + VFS::InodeType type() const override + { + return VFS::InodeType::Directory; + } + + void did_link() override + { + } + + void did_unlink() override + { + m_self = {}; + m_entries.clear(); + } + + usize entries() const override + { + return m_entries.size(); + } + + Result remove_entry(const char* name) override; + + Result> create_file(const char* name, mode_t mode) override; + Result> create_subdirectory(const char* name, mode_t mode) override; + + Result add_entry(SharedPtr inode, const char* name); + Result replace_entry(SharedPtr inode, const char* name); + + virtual ~RootInode() = default; + + private: + VFS::FileSystem* m_fs; + SharedPtr m_self; + Vector m_entries; + + friend class FileSystem; + }; +} diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 84703bfe..5e73a722 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -6,6 +6,7 @@ #include "config.h" #include "fs/InitRD.h" #include "fs/devices/DeviceRegistry.h" +#include "fs/devices/PTYMultiplexer.h" #include "fs/tmpfs/FileSystem.h" #include "memory/MemoryManager.h" #include "thread/Scheduler.h" @@ -94,6 +95,7 @@ extern "C" [[noreturn]] void _start() Thread::init(); Scheduler::init(); + PTYMultiplexer::init(); Scheduler::new_kernel_thread(init, "[kinit]"); diff --git a/kernel/src/sys/mount.cpp b/kernel/src/sys/mount.cpp index b01512e2..20b4a580 100644 --- a/kernel/src/sys/mount.cpp +++ b/kernel/src/sys/mount.cpp @@ -1,5 +1,6 @@ #include "Pledge.h" #include "fs/VFS.h" +#include "fs/devpts/FileSystem.h" #include "fs/ext2/FileSystem.h" #include "fs/tmpfs/FileSystem.h" #include "memory/MemoryManager.h" @@ -26,6 +27,8 @@ Result sys_mount(Registers*, SyscallArgs args) SharedPtr fs; if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create()); + else if (fstype.view() == "devpts") + fs = TRY(DevPTS::FileSystem::create()); else if (fstype.view() == "devfs") fs = TRY(DeviceRegistry::create_devfs_instance()); else if (fstype.view() == "ext2") diff --git a/libc/include/bits/termios.h b/libc/include/bits/termios.h index d1506915..5273c236 100644 --- a/libc/include/bits/termios.h +++ b/libc/include/bits/termios.h @@ -45,5 +45,6 @@ struct winsize #define TIOCSPGRP 2 #define TIOCGPGRP 3 #define TIOCGWINSZ 4 +#define TIOCGPTN 5 #endif -- 2.34.1 From 29a341d8f38337608bf0891bff7df955744a634c Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:47:43 +0200 Subject: [PATCH 087/103] init: Mount /dev/pts on startup --- apps/init.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/init.cpp b/apps/init.cpp index c0ee8ec6..f7f4015b 100644 --- a/apps/init.cpp +++ b/apps/init.cpp @@ -339,6 +339,13 @@ static void mount_shmfs() if (chmod("/dev/shm", 01777) < 0) exit(255); } +static void mount_devpts() +{ + if (mkdir("/dev/pts", 0755) < 0) exit(255); + + if (mount("/dev/pts", "devpts", "devpts") < 0) exit(255); +} + Result sysinit(StringView path) { if (getpid() != 1) @@ -359,6 +366,7 @@ Result sysinit(StringView path) mount_tmpfs(); mount_shmfs(); + mount_devpts(); umask(022); -- 2.34.1 From 75ea81bfbc64ef1ecc9e4bccb81925c788dc9445 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:47:57 +0200 Subject: [PATCH 088/103] libc: Add pseudoterminal-related functions --- libc/include/stdlib.h | 12 ++++++++++++ libc/src/stdlib.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h index b06e1861..35357d21 100644 --- a/libc/include/stdlib.h +++ b/libc/include/stdlib.h @@ -148,6 +148,18 @@ extern "C" /* Create a unique file from a template string whose last 6 bytes must be XXXXXX. */ int mkstemp(char* _template); + /* Create a new pseudoterminal pair. */ + int posix_openpt(int flags); + + /* Set the credentials of a pseudoterminal master. */ + int grantpt(int fd); + + /* Unlock a pseudoterminal master. */ + int unlockpt(int fd); + + /* Return the name of the slave associated with a pseudoterminal master. */ + char* ptsname(int fd); + #ifdef __cplusplus } #endif diff --git a/libc/src/stdlib.cpp b/libc/src/stdlib.cpp index b19b2a11..acbd7d9b 100644 --- a/libc/src/stdlib.cpp +++ b/libc/src/stdlib.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,8 +8,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -300,4 +303,31 @@ extern "C" { return strtod(str, nullptr); } + + int posix_openpt(int flags) + { + return open("/dev/ptmx", flags); + } + + int grantpt(int) + { + return 0; + } + + int unlockpt(int) + { + return 0; + } + + char* ptsname(int fd) + { + static char buffer[4096]; + + int index; + if (ioctl(fd, TIOCGPTN, &index) < 0) return nullptr; + + snprintf(buffer, sizeof(buffer), "/dev/pts/%d", index); + + return buffer; + } } -- 2.34.1 From 9b1e19ef72f96fab341d2c668aa34aa4b8de19bd Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 11:48:11 +0200 Subject: [PATCH 089/103] terminal: Use pseudoterminals and add keyboard support --- terminal/TerminalWidget.cpp | 175 +++++++++++++++++++++++++++++++----- terminal/TerminalWidget.h | 10 ++- terminal/main.cpp | 11 ++- 3 files changed, 167 insertions(+), 29 deletions(-) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index 6587fa0a..fcbe3687 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -1,9 +1,11 @@ #include "TerminalWidget.h" #include #include +#include #include #include #include +#include #include #include @@ -42,44 +44,164 @@ Result TerminalWidget::init(char* const* args) signal(SIGCHLD, sigchld_handler); - int infds[2]; - int outfds[2]; + int fd = posix_openpt(O_RDWR | O_CLOEXEC); + if (fd < 0) return err(errno); - int result = pipe(infds); - if (result < 0) return err(errno); - - result = pipe(outfds); - if (result < 0) return err(errno); + grantpt(fd); + unlockpt(fd); pid_t child = TRY(os::Process::fork()); if (child == 0) { - dup2(infds[0], STDIN_FILENO); - dup2(outfds[1], STDOUT_FILENO); - dup2(outfds[1], STDERR_FILENO); + int ptsfd = open(ptsname(fd), O_RDWR); - close(infds[0]); - close(infds[1]); - close(outfds[0]); - close(outfds[1]); + dup2(ptsfd, STDIN_FILENO); + dup2(ptsfd, STDOUT_FILENO); + dup2(ptsfd, STDERR_FILENO); + + setpgid(0, 0); + tcsetpgrp(ptsfd, getpid()); + + close(ptsfd); execv(args[0], args); _exit(127); } - close(infds[0]); - close(outfds[1]); + m_pty = fd; - m_write_fd = infds[1]; - m_read_fd = outfds[0]; - - fcntl(m_read_fd, F_SETFL, O_NONBLOCK); + fcntl(fd, F_SETFL, O_NONBLOCK); m_child_pid = child; return {}; } +Result TerminalWidget::handle_key_event(const ui::KeyEventRequest& request) +{ + if (!request.pressed) return ui::EventResult::DidNotHandle; + + query_termios(); + + bool is_special_character { false }; + + if (/*is_canonical()*/ true) + { + if (request.letter == m_settings.c_cc[VERASE]) + { + auto maybe_char = m_line_buffer.try_pop(); + if (maybe_char.has_value() && maybe_char.value()) + { + if ((m_settings.c_lflag & ECHO) && (m_settings.c_lflag & ECHOE)) + { + put_code_point(L'\b'); + if (_iscntrl(maybe_char.value())) put_code_point(L'\b'); + if (maybe_char.value() == '\t') + { + put_code_point(L'\b'); + put_code_point(L'\b'); + } + } + ui::App::the().main_window()->draw(); + return ui::EventResult::DidHandle; + } + + if ((m_settings.c_lflag & ECHOE)) return ui::EventResult::DidHandle; + else + is_special_character = true; + } + + if (request.letter == m_settings.c_cc[VEOF]) + { + write(m_pty, m_line_buffer.data(), m_line_buffer.size()); + m_line_buffer.clear(); + + // FIXME: tell the kernel that process may read without blocking. + is_special_character = true; + } + + if (m_settings.c_lflag & ISIG) + { + if (request.letter == m_settings.c_cc[VINTR]) + { + if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); + + // FIXME: Send SIGINT. + /*if (m_foreground_process_group.has_value()) + { + Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { + thread->send_signal(SIGINT); + return true; + }); + }*/ + is_special_character = true; + } + + if (request.letter == m_settings.c_cc[VQUIT]) + { + if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); + + // FIXME: Send SIGINT. + /*if (m_foreground_process_group.has_value()) + { + Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { + thread->send_signal(SIGQUIT); + return true; + }); + }*/ + is_special_character = true; + } + } + } + + if (!is_special_character) + { + if (/*is_canonical()*/ true) + { + m_line_buffer.try_append((u8)request.letter); + + if (request.letter == '\n') + { + write(m_pty, m_line_buffer.data(), m_line_buffer.size()); + m_line_buffer.clear(); + } + } + else + write(m_pty, &request.letter, 1); + } + + if (!(m_settings.c_lflag & ECHO)) return ui::EventResult::DidHandle; + + if (_iscntrl(request.letter)) + { + if (m_settings.c_lflag & ECHOCTL) + { + bool should_echo = true; + if (request.letter == '\n' || request.letter == '\t' || request.letter == '\0' || + request.letter == m_settings.c_cc[VEOF]) + should_echo = false; + + if (should_echo) + { + char caret_notation[3] = { '^', '\0', '\0' }; + if (request.letter == 0x7f) caret_notation[1] = '?'; + else + caret_notation[1] = request.letter + 0x40; + + for (int i = 0; i < 2; i++) { putchar(caret_notation[i]); } + } + else + putchar(request.letter); + } + } + else + putchar(request.letter); + + ui::App::the().main_window()->draw(); + + return ui::EventResult::DidHandle; +} + Result TerminalWidget::draw(ui::Canvas& canvas) { canvas.fill((u32*)m_terminal_canvas.ptr, m_terminal_canvas.stride); @@ -89,14 +211,14 @@ Result TerminalWidget::draw(ui::Canvas& canvas) Result TerminalWidget::process() { char buffer[BUFSIZ]; - ssize_t nread = read(m_read_fd, buffer, BUFSIZ); + ssize_t nread = read(m_pty, buffer, BUFSIZ); if (nread < 0) { if (errno == EAGAIN) return {}; return err(errno); } - os::eprintln("terminal: read %zd characters, processing...", nread); + query_termios(); for (ssize_t i = 0; i < nread; i++) TRY(putchar(buffer[i])); @@ -105,6 +227,11 @@ Result TerminalWidget::process() return {}; } +void TerminalWidget::query_termios() +{ + tcgetattr(m_pty, &m_settings); +} + void TerminalWidget::draw_glyph(wchar_t c, int x, int y) { auto subcanvas = m_terminal_canvas.subcanvas({ x, y, m_font->width(), m_font->height() }); @@ -114,7 +241,7 @@ void TerminalWidget::draw_glyph(wchar_t c, int x, int y) void TerminalWidget::erase_current_line() { - m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }); + m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }).fill(ui::BLACK); } void TerminalWidget::scroll() @@ -143,7 +270,7 @@ void TerminalWidget::next_char() void TerminalWidget::prev_char() { - m_y_position -= m_font->width(); + m_x_position -= m_font->width(); } void TerminalWidget::erase_current_char() diff --git a/terminal/TerminalWidget.h b/terminal/TerminalWidget.h index aaf26c5b..0c18bd74 100644 --- a/terminal/TerminalWidget.h +++ b/terminal/TerminalWidget.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,8 @@ class TerminalWidget : public ui::Widget public: Result init(char* const* args); + Result handle_key_event(const ui::KeyEventRequest& request) override; + Result draw(ui::Canvas& canvas) override; Result process(); @@ -20,10 +23,11 @@ class TerminalWidget : public ui::Widget private: ui::Canvas m_terminal_canvas; Vector m_line_buffer; - int m_write_fd; - int m_read_fd; + int m_pty; pid_t m_child_pid; + struct termios m_settings; + SharedPtr m_font; SharedPtr m_bold_font; @@ -41,6 +45,8 @@ class TerminalWidget : public ui::Widget ui::Color m_foreground_color { ui::WHITE }; ui::Color m_background_color { ui::BLACK }; + void query_termios(); + Utf8StateDecoder m_decoder; Option m_escape_parser; diff --git a/terminal/main.cpp b/terminal/main.cpp index 20986d35..96e1d698 100644 --- a/terminal/main.cpp +++ b/terminal/main.cpp @@ -1,6 +1,7 @@ #include "TerminalWidget.h" #include #include +#include Result luna_main(int argc, char** argv) { @@ -8,7 +9,7 @@ Result luna_main(int argc, char** argv) TRY(app.init(argc, argv)); app.set_nonblocking(); - auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 500, 300 })); + auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 })); app.set_main_window(window); window->set_background(ui::BLACK); window->set_title("Terminal"); @@ -16,12 +17,16 @@ Result luna_main(int argc, char** argv) TerminalWidget terminal; window->set_main_widget(terminal); - char* args[] = { "/bin/sh", "-i", nullptr }; + char* args[] = { "/bin/sh", nullptr }; TRY(terminal.init(args)); window->draw(); - while (app.process_events()) TRY(terminal.process()); + while (app.process_events()) + { + TRY(terminal.process()); + usleep(10000); + } terminal.quit(); -- 2.34.1 From dd3359b09b8fbb152b16fdcb0262a29dc89e6fea Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 13:12:26 +0200 Subject: [PATCH 090/103] libui: Properly request redraws from the server Before this, the call to update() was always skipped. --- libui/src/Window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index 441e47db..fc12467d 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -75,7 +75,7 @@ namespace ui Result Window::draw() { m_canvas.fill(m_background); - if (m_main_widget) return m_main_widget->draw(m_canvas); + if (m_main_widget) TRY(m_main_widget->draw(m_canvas)); update(); return {}; } -- 2.34.1 From 945dc6c73273a7afe68e55345e1170cfa0ee8445 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 13:12:39 +0200 Subject: [PATCH 091/103] terminal: Add cursor support --- terminal/TerminalWidget.cpp | 45 +++++++++++++++++++++++++++++++++---- terminal/TerminalWidget.h | 8 +++++-- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index fcbe3687..9709115b 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,14 @@ static constexpr auto BRIGHT_MAGENTA = ui::Color::from_u32(0xffff00ff); static constexpr auto BRIGHT_CYAN = ui::Color::from_u32(0xff00ffff); static constexpr auto BRIGHT_GRAY = ui::Color::from_u32(0xffffffff); +static long get_time_in_milliseconds() +{ + struct timespec ts; + check(clock_gettime(CLOCK_REALTIME, &ts) == 0); + + return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + static void sigchld_handler(int) { wait(NULL); @@ -74,6 +83,8 @@ Result TerminalWidget::init(char* const* args) m_child_pid = child; + m_last_cursor_tick = get_time_in_milliseconds(); + return {}; } @@ -214,19 +225,45 @@ Result TerminalWidget::process() ssize_t nread = read(m_pty, buffer, BUFSIZ); if (nread < 0) { - if (errno == EAGAIN) return {}; - return err(errno); + if (errno == EAGAIN) nread = 0; + else + return err(errno); } query_termios(); + bool should_update_cursor = tick_cursor(); + for (ssize_t i = 0; i < nread; i++) TRY(putchar(buffer[i])); - ui::App::the().main_window()->draw(); + if (should_update_cursor || nread > 0) ui::App::the().main_window()->draw(); return {}; } +bool TerminalWidget::tick_cursor() +{ + if (!m_cursor_enabled) return false; + + long now = get_time_in_milliseconds(); + long diff = now - m_last_cursor_tick; + m_last_cursor_tick = now; + + m_current_cursor_timeout -= (int)diff; + if (m_current_cursor_timeout <= 0) + { + m_current_cursor_timeout = CURSOR_TIMEOUT; + m_cursor_activated = !m_cursor_activated; + + if (m_cursor_activated) draw_cursor(); + else + erase_current_char(); + return true; + } + + return false; +} + void TerminalWidget::query_termios() { tcgetattr(m_pty, &m_settings); @@ -511,7 +548,7 @@ void TerminalWidget::put_code_point(wchar_t c) } // Erase the current cursor. - if (m_cursor_enabled) erase_current_char(); + if (m_cursor_activated) erase_current_char(); bool should_draw_cursor = m_cursor_enabled; diff --git a/terminal/TerminalWidget.h b/terminal/TerminalWidget.h index 0c18bd74..d29cb3ad 100644 --- a/terminal/TerminalWidget.h +++ b/terminal/TerminalWidget.h @@ -34,8 +34,10 @@ class TerminalWidget : public ui::Widget static constexpr int CURSOR_TIMEOUT = 500; int m_current_cursor_timeout = CURSOR_TIMEOUT; - bool m_cursor_activated = true; - bool m_cursor_enabled = false; + bool m_cursor_activated = false; + bool m_cursor_enabled = true; + + long m_last_cursor_tick; int m_x_position { 0 }; int m_y_position { 0 }; @@ -47,6 +49,8 @@ class TerminalWidget : public ui::Widget void query_termios(); + bool tick_cursor(); + Utf8StateDecoder m_decoder; Option m_escape_parser; -- 2.34.1 From 7f239310281e84057e66d866cae95d7b8ec43ac7 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 13:29:22 +0200 Subject: [PATCH 092/103] terminal: Draw directly onto the window canvas --- terminal/TerminalWidget.cpp | 13 +++++-------- terminal/TerminalWidget.h | 2 +- terminal/main.cpp | 5 ++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index 9709115b..e87b8e4d 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -46,10 +46,8 @@ Result TerminalWidget::init(char* const* args) m_font = ui::Font::default_font(); m_bold_font = ui::Font::default_bold_font(); - m_terminal_canvas = ui::Canvas { .width = m_rect.width, - .height = m_rect.height, - .stride = m_rect.width, - .ptr = (u8*)TRY(calloc_impl(m_rect.width, m_rect.height * sizeof(u32), false)) }; + m_terminal_canvas = ui::App::the().main_window()->canvas(); + m_terminal_canvas.fill(ui::BLACK); signal(SIGCHLD, sigchld_handler); @@ -213,13 +211,12 @@ Result TerminalWidget::handle_key_event(const ui::KeyEventReque return ui::EventResult::DidHandle; } -Result TerminalWidget::draw(ui::Canvas& canvas) +Result TerminalWidget::draw(ui::Canvas&) { - canvas.fill((u32*)m_terminal_canvas.ptr, m_terminal_canvas.stride); return {}; } -Result TerminalWidget::process() +Result TerminalWidget::process() { char buffer[BUFSIZ]; ssize_t nread = read(m_pty, buffer, BUFSIZ); @@ -238,7 +235,7 @@ Result TerminalWidget::process() if (should_update_cursor || nread > 0) ui::App::the().main_window()->draw(); - return {}; + return nread == 0; } bool TerminalWidget::tick_cursor() diff --git a/terminal/TerminalWidget.h b/terminal/TerminalWidget.h index d29cb3ad..694761d8 100644 --- a/terminal/TerminalWidget.h +++ b/terminal/TerminalWidget.h @@ -16,7 +16,7 @@ class TerminalWidget : public ui::Widget Result draw(ui::Canvas& canvas) override; - Result process(); + Result process(); void quit(); diff --git a/terminal/main.cpp b/terminal/main.cpp index 96e1d698..48e438b9 100644 --- a/terminal/main.cpp +++ b/terminal/main.cpp @@ -11,7 +11,6 @@ Result luna_main(int argc, char** argv) auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 })); app.set_main_window(window); - window->set_background(ui::BLACK); window->set_title("Terminal"); TerminalWidget terminal; @@ -24,8 +23,8 @@ Result luna_main(int argc, char** argv) while (app.process_events()) { - TRY(terminal.process()); - usleep(10000); + bool should_sleep = TRY(terminal.process()); + if (should_sleep) usleep(10000); } terminal.quit(); -- 2.34.1 From 7631b81681b683eb469da8c5cad4b7f6c14efafb Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 16 Sep 2023 13:29:42 +0200 Subject: [PATCH 093/103] libui: Allow not filling the window with a background color every time --- libui/include/ui/Window.h | 2 +- libui/src/Window.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index fd914b0a..3e121a8f 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -62,7 +62,7 @@ namespace ui int m_id; Canvas m_canvas; Widget* m_main_widget { nullptr }; - Color m_background { ui::BLACK }; + Option m_background {}; Option m_old_mouse_buttons; }; } diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index fc12467d..298a4499 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -74,7 +74,7 @@ namespace ui Result Window::draw() { - m_canvas.fill(m_background); + if (m_background.has_value()) m_canvas.fill(*m_background); if (m_main_widget) TRY(m_main_widget->draw(m_canvas)); update(); return {}; -- 2.34.1 From d93e9f6b4b02c25218ab2ee96087c1077209c3b7 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 18 Sep 2023 07:19:29 +0200 Subject: [PATCH 094/103] kernel: Fix sending signals to threads that are in a long syscall --- kernel/src/thread/Thread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/thread/Thread.cpp b/kernel/src/thread/Thread.cpp index 8907c917..ebac3c37 100644 --- a/kernel/src/thread/Thread.cpp +++ b/kernel/src/thread/Thread.cpp @@ -211,7 +211,7 @@ void Thread::send_signal(int signo) check(signo > 0 && signo <= NSIG); pending_signals.set(signo - 1, true); - if (state == ThreadState::Waiting || state == ThreadState::Sleeping) + if (state == ThreadState::Waiting || state == ThreadState::Sleeping || is_in_kernel(®s)) { interrupted = true; wake_up(); -- 2.34.1 From c5227d585c48e759e96114fc04ede50a9940271c Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 18 Sep 2023 07:19:53 +0200 Subject: [PATCH 095/103] kernel: Allow sending signals to process groups from userspace --- kernel/src/sys/signal.cpp | 65 ++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/kernel/src/sys/signal.cpp b/kernel/src/sys/signal.cpp index 779b151c..2d2d05c9 100644 --- a/kernel/src/sys/signal.cpp +++ b/kernel/src/sys/signal.cpp @@ -54,16 +54,65 @@ Result sys_kill(Registers*, SyscallArgs args) pid_t pid = (pid_t)args[0]; int signo = (int)args[1]; - // FIXME: Support this case. - if (pid <= 0) return err(ENOTSUP); + auto send_signal = [&](Thread* target) -> Result { + if (current->auth.euid != 0 && current->auth.euid != target->auth.euid && + current->auth.egid != target->auth.egid) + return err(EPERM); + if (target->is_kernel) return {}; + if (signo == 0) return {}; - auto* target = TRY(Result::from_option(Scheduler::find_by_pid(pid), ESRCH)); - if (current->auth.euid != 0 && current->auth.euid != target->auth.euid && current->auth.egid != target->auth.egid) - return err(EPERM); - if (target->is_kernel) return 0; - if (signo == 0) return 0; + target->send_signal(signo); - target->send_signal(signo); + return {}; + }; + + if (pid > 0) + { + auto* target = TRY(Result::from_option(Scheduler::find_by_pid(pid), ESRCH)); + TRY(send_signal(target)); + } + else if (pid == 0) + { + int errno = -1; + bool pgid_exists = false; + Scheduler::for_each_in_process_group(current->pgid, [&](Thread* target) { + pgid_exists = true; + auto rc = send_signal(target); + if (rc.has_error()) + { + errno = rc.error(); + return false; + } + return true; + }); + if (errno > 0) return err(errno); + if (!pgid_exists) return err(ESRCH); + } + else if (pid == -1) + { + for (auto* thread : g_threads) + { + // We ignore permission errors here. + if (thread != current && thread->id != 1) send_signal(thread); + } + } + else if (pid < -1) + { + int errno = -1; + bool pgid_exists = false; + Scheduler::for_each_in_process_group(-pid, [&](Thread* target) { + pgid_exists = true; + auto rc = send_signal(target); + if (rc.has_error()) + { + errno = rc.error(); + return false; + } + return true; + }); + if (errno > 0) return err(errno); + if (!pgid_exists) return err(ESRCH); + } return 0; } -- 2.34.1 From 3540033dd34c6af2fbcb428836bb1d3e777ddabf Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 18 Sep 2023 07:20:07 +0200 Subject: [PATCH 096/103] wind: Translate Ctrl-key presses correctly --- wind/Keyboard.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wind/Keyboard.cpp b/wind/Keyboard.cpp index e0941b49..ba95ce9e 100644 --- a/wind/Keyboard.cpp +++ b/wind/Keyboard.cpp @@ -288,9 +288,9 @@ namespace wind::Keyboard else letter = table[code]; if (_islower(letter)) letter = (char)_toupper(letter); - if (_isupper(letter)) letter = 0x40; - if (letter == '@') letter = 0x40; - if (letter > 'Z' && letter < '`') letter = 0x40; + if (_isupper(letter)) letter = letter - 0x40; + if (letter == '@') letter = letter - 0x40; + if (letter > 'Z' && letter < '`') letter = letter - 0x40; if (letter == '?') letter = 0x7f; request.letter = letter; return request; -- 2.34.1 From 0cb21c2e90956901ae85d7ba03656058d601e0d1 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 18 Sep 2023 07:21:06 +0200 Subject: [PATCH 097/103] terminal: Send signals on ^C and ^\ --- terminal/TerminalWidget.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index e87b8e4d..792938bb 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -135,14 +135,9 @@ Result TerminalWidget::handle_key_event(const ui::KeyEventReque { if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); - // FIXME: Send SIGINT. - /*if (m_foreground_process_group.has_value()) - { - Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { - thread->send_signal(SIGINT); - return true; - }); - }*/ + pid_t group = tcgetpgrp(m_pty); + TRY(os::Process::kill(-group, SIGINT)); + is_special_character = true; } @@ -150,14 +145,9 @@ Result TerminalWidget::handle_key_event(const ui::KeyEventReque { if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); - // FIXME: Send SIGINT. - /*if (m_foreground_process_group.has_value()) - { - Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { - thread->send_signal(SIGQUIT); - return true; - }); - }*/ + pid_t group = tcgetpgrp(m_pty); + TRY(os::Process::kill(-group, SIGQUIT)); + is_special_character = true; } } @@ -598,5 +588,5 @@ void TerminalWidget::put_code_point(wchar_t c) void TerminalWidget::quit() { - kill(m_child_pid, SIGHUP); + kill(-tcgetpgrp(m_pty), SIGHUP); } -- 2.34.1 From bc14b01bf8af8f6da9489fb237ec180df41b1f36 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 19:43:53 +0200 Subject: [PATCH 098/103] terminal: Fix certain keys being incorrectly inputted --- terminal/TerminalWidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index 792938bb..a55274f9 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -88,7 +88,11 @@ Result TerminalWidget::init(char* const* args) Result TerminalWidget::handle_key_event(const ui::KeyEventRequest& request) { + // Avoid handling "key released" events if (!request.pressed) return ui::EventResult::DidNotHandle; + // Non-printable key or key that has no special character (unlike Tab or Enter). We exit early to avoid inserting an + // invalid zero byte into the terminal input (this would also happen on Shift or Ctrl keypresses). + if (request.letter == '\0') return ui::EventResult::DidNotHandle; query_termios(); -- 2.34.1 From 9fd4fc7e91924ef11db7fd62df843ec916ad2ff9 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 19:45:01 +0200 Subject: [PATCH 099/103] wind+taskbar: Improve the dark color scheme --- apps/taskbar.cpp | 4 +++- wind/Window.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/taskbar.cpp b/apps/taskbar.cpp index 9283d6e6..e179f5fa 100644 --- a/apps/taskbar.cpp +++ b/apps/taskbar.cpp @@ -8,6 +8,8 @@ #include #include +static constexpr ui::Color TASKBAR_COLOR = ui::Color::from_rgb(83, 83, 83); + void sigchld_handler(int) { wait(nullptr); @@ -26,7 +28,7 @@ Result luna_main(int argc, char** argv) auto window = TRY(ui::Window::create(bar, false)); app.set_main_window(window); - window->set_background(ui::GRAY); + window->set_background(TASKBAR_COLOR); ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No); window->set_main_widget(layout); diff --git a/wind/Window.cpp b/wind/Window.cpp index 43e0e5ad..4bee2242 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -7,6 +7,8 @@ LinkedList g_windows; +static constexpr ui::Color TITLEBAR_COLOR = ui::Color::from_rgb(53, 53, 53); + void Window::draw(ui::Canvas& screen) { dirty = false; @@ -21,10 +23,10 @@ void Window::draw(ui::Canvas& screen) auto font = ui::Font::default_font(); auto titlebar_canvas = window.subcanvas(titlebar); - titlebar_canvas.fill(ui::GRAY); + titlebar_canvas.fill(TITLEBAR_COLOR); auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height }); - font->render(buffer, ui::BLACK, textarea); + font->render(buffer, ui::WHITE, textarea); static SharedPtr g_close_icon; -- 2.34.1 From b370a99aa6a3a42cd5b33007983c32a130166be7 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 19:45:19 +0200 Subject: [PATCH 100/103] libui: Allow specifying the color of Labels, and use that in about --- apps/about.cpp | 6 ++++-- libui/include/ui/Label.h | 3 ++- libui/src/Label.cpp | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/about.cpp b/apps/about.cpp index 89fb9c70..4bd95ef1 100644 --- a/apps/about.cpp +++ b/apps/about.cpp @@ -5,6 +5,8 @@ #include #include +static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89); + Result luna_main(int argc, char** argv) { ui::App app; @@ -14,7 +16,7 @@ Result luna_main(int argc, char** argv) app.set_main_window(window); window->set_title("About"); - window->set_background(ui::CYAN); + window->set_background(BACKGROUND_COLOR); utsname info; uname(&info); @@ -22,7 +24,7 @@ Result luna_main(int argc, char** argv) ui::VerticalLayout main_layout; window->set_main_widget(main_layout); - ui::Label title("About Luna", ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center, + ui::Label title("About Luna", ui::WHITE, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center, ui::Font::default_bold_font()); main_layout.add_widget(title); diff --git a/libui/include/ui/Label.h b/libui/include/ui/Label.h index b435e1c9..ca449335 100644 --- a/libui/include/ui/Label.h +++ b/libui/include/ui/Label.h @@ -22,7 +22,7 @@ namespace ui class Label final : public Widget { public: - Label(StringView text, VerticalAlignment valign = VerticalAlignment::Center, + Label(StringView text, ui::Color color = ui::WHITE, VerticalAlignment valign = VerticalAlignment::Center, HorizontalAlignment halign = HorizontalAlignment::Center, SharedPtr font = Font::default_font()); void set_text(StringView text) @@ -36,6 +36,7 @@ namespace ui StringView m_text; VerticalAlignment m_valign; HorizontalAlignment m_halign; + ui::Color m_color; SharedPtr m_font; }; } diff --git a/libui/src/Label.cpp b/libui/src/Label.cpp index 67226eda..71015c6e 100644 --- a/libui/src/Label.cpp +++ b/libui/src/Label.cpp @@ -12,8 +12,9 @@ namespace ui { - Label::Label(StringView text, VerticalAlignment valign, HorizontalAlignment halign, SharedPtr font) - : m_text(text), m_valign(valign), m_halign(halign), m_font(font) + Label::Label(StringView text, ui::Color color, VerticalAlignment valign, HorizontalAlignment halign, + SharedPtr font) + : m_text(text), m_valign(valign), m_halign(halign), m_color(color), m_font(font) { } @@ -30,7 +31,7 @@ namespace ui wchar_t buf[4096]; TRY(decoder.decode(buf, sizeof(buf))); - m_font->render(buf, ui::BLACK, subcanvas); + m_font->render(buf, m_color, subcanvas); return {}; } } -- 2.34.1 From b42497e05e0baaab99dcba4e71fee0df0ed79d18 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 19:49:13 +0200 Subject: [PATCH 101/103] kernel: Start clearing caches when free memory is lower than 1MiB This is done to avoid returning ENOMEM errors when cache memory can still be reclaimed. --- kernel/src/memory/MemoryManager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/src/memory/MemoryManager.cpp b/kernel/src/memory/MemoryManager.cpp index fc530b16..018c284d 100644 --- a/kernel/src/memory/MemoryManager.cpp +++ b/kernel/src/memory/MemoryManager.cpp @@ -157,6 +157,13 @@ namespace MemoryManager used_mem += ARCH_PAGE_SIZE; free_mem -= ARCH_PAGE_SIZE; + if (free_mem < 1024 * 1024) + { + // Less than 1 MiB of free memory! Let's start clearing caches... + kwarnln("Less than 1 MiB of free memory, clearing caches to try to gain extra memory"); + Scheduler::signal_oom_thread(); + } + return index * ARCH_PAGE_SIZE; } -- 2.34.1 From 52b04bd33bf450d325930b1fb511b6d16c622d50 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 19:58:26 +0200 Subject: [PATCH 102/103] kernel: Increase the OOM threshold to 4 MiB free --- kernel/src/memory/MemoryManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/src/memory/MemoryManager.cpp b/kernel/src/memory/MemoryManager.cpp index 018c284d..eb0a7ab1 100644 --- a/kernel/src/memory/MemoryManager.cpp +++ b/kernel/src/memory/MemoryManager.cpp @@ -157,10 +157,10 @@ namespace MemoryManager used_mem += ARCH_PAGE_SIZE; free_mem -= ARCH_PAGE_SIZE; - if (free_mem < 1024 * 1024) + if (free_mem < 4 * 1024 * 1024) { - // Less than 1 MiB of free memory! Let's start clearing caches... - kwarnln("Less than 1 MiB of free memory, clearing caches to try to gain extra memory"); + // Less than 4 MiB of free memory! Let's start clearing caches... + kwarnln("Less than 4 MiB of free memory, clearing caches to try to gain extra memory"); Scheduler::signal_oom_thread(); } -- 2.34.1 From b09226b8bad4ac3320e4b5be4c9139149d7b0469 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 20:17:11 +0200 Subject: [PATCH 103/103] gol: Use the windowing system --- apps/CMakeLists.txt | 1 + apps/gol.cpp | 117 +++++++++++++------------------------------- 2 files changed, 36 insertions(+), 82 deletions(-) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index da5a312f..18faa4f2 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -38,6 +38,7 @@ luna_app(sysfuzz.cpp sysfuzz) luna_app(cp.cpp cp) luna_app(kill.cpp kill) luna_app(gol.cpp gol) +target_link_libraries(gol PUBLIC ui) luna_app(touch.cpp touch) luna_app(free.cpp free) luna_app(about.cpp about) diff --git a/apps/gol.cpp b/apps/gol.cpp index 02399a7c..464fd31a 100644 --- a/apps/gol.cpp +++ b/apps/gol.cpp @@ -1,13 +1,12 @@ -#include #include #include #include #include #include #include -#include -#include #include +#include +#include #include struct Cell @@ -16,16 +15,12 @@ struct Cell bool new_state; }; -static int g_num_rows = 76; -static int g_num_columns = 102; - -static int g_fb_width; -static int g_fb_height; - -static int g_fd; +static int g_num_rows = 40; +static int g_num_columns = 60; static Cell* g_cells; -static char* g_fb; + +static ui::Window* g_window; static Result fill_cells() { @@ -47,31 +42,30 @@ static Cell& find_cell(int row, int column) return g_cells[row * g_num_columns + column]; } -static constexpr int BYTES_PER_PIXEL = 4; +static constexpr int BYTES_PER_PIXEL = sizeof(u32); +static constexpr ui::Color activated_cell_color = ui::CYAN; +static constexpr ui::Color deactivated_cell_color = ui::Color::from_rgb(40, 40, 40); static void draw_cells() { - const int CELL_WIDTH = g_fb_width / g_num_columns; - const int CELL_HEIGHT = g_fb_height / g_num_rows; + const int CELL_WIDTH = g_window->canvas().width / g_num_columns; + const int CELL_HEIGHT = g_window->canvas().height / g_num_rows; + + auto canvas = g_window->canvas(); for (int i = 0; i < g_num_rows; i++) { - for (int j = 0; j < g_num_columns; j++) { - char* buf = g_fb + (i * g_fb_width * CELL_HEIGHT * BYTES_PER_PIXEL); + auto subcanvas = canvas.subcanvas(ui::Rect { j * CELL_WIDTH, i * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT }); auto& cell = find_cell(i, j); - u8 color = cell.state ? 0xff : 0x00; - for (int k = 0; k < CELL_HEIGHT; k++) - { - memset(buf + (j * CELL_WIDTH * BYTES_PER_PIXEL), color, CELL_WIDTH * BYTES_PER_PIXEL); - buf += g_fb_width * BYTES_PER_PIXEL; - } + ui::Color color = cell.state ? activated_cell_color : deactivated_cell_color; + subcanvas.fill(color); } } - msync(g_fb, g_fb_height * g_fb_width * BYTES_PER_PIXEL, MS_SYNC); + g_window->update(); } static int find_neighbors(int row, int column) @@ -109,71 +103,30 @@ static void next_generation() Result luna_main(int argc, char** argv) { - u64 delay_between_iterations = 250; - u64 delay_at_end = 3000; - u64 num_iterations = 100; + ui::App app; + TRY(app.init(argc, argv)); + app.set_nonblocking(); - StringView columns; - StringView rows; - StringView delay; - StringView end_delay; - StringView iterations; - StringView seed; - - os::ArgumentParser parser; - parser.add_description("A framebuffer-based implementation for Conway's Game of Life."); - parser.add_system_program_info("gol"_sv); - parser.add_positional_argument(rows, "rows"_sv, "76"_sv); - parser.add_positional_argument(columns, "columns"_sv, "102"_sv); - parser.add_value_argument(delay, 'd', "delay"_sv, "the delay between generations (in ms)"); - parser.add_value_argument(end_delay, 'e', "end-delay"_sv, - "after finishing, how much to wait before returning to the shell (in ms)"); - parser.add_value_argument(iterations, 'i', "iterations"_sv, "how many generations to show (default: 100)"); - parser.add_value_argument(seed, 's', "seed"_sv, "the seed for the random number generator"); - parser.parse(argc, argv); - - g_num_columns = (int)TRY(columns.to_uint()); - g_num_rows = (int)TRY(rows.to_uint()); - if (!delay.is_empty()) delay_between_iterations = TRY(delay.to_uint()); - if (!end_delay.is_empty()) delay_at_end = TRY(end_delay.to_uint()); - if (!iterations.is_empty()) num_iterations = TRY(iterations.to_uint()); - if (!seed.is_empty()) srand((unsigned)TRY(seed.to_uint())); - else - srand((unsigned)time(NULL)); - - g_fd = open("/dev/fb0", O_RDWR); - if (g_fd < 0) - { - perror("gol: cannot open framebuffer for writing"); - return 1; - } - - g_fb_height = ioctl(g_fd, FB_GET_HEIGHT); - g_fb_width = ioctl(g_fd, FB_GET_WIDTH); + g_window = TRY(ui::Window::create(ui::Rect { 200, 200, 600, 400 })); + g_window->set_title("Game of Life"); + app.set_main_window(g_window); TRY(fill_cells()); - g_fb = - (char*)mmap(nullptr, g_fb_height * g_fb_width * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, 0); - if (g_fb == MAP_FAILED) + int counter = 0; + + while (app.process_events()) { - perror("gol: cannot map framebuffer into memory"); - return 1; + if (counter >= 10) + { + next_generation(); + draw_cells(); + counter = 0; + } + else + counter++; + usleep(10000); } - draw_cells(); - - while (num_iterations--) - { - usleep(delay_between_iterations * 1000); - next_generation(); - draw_cells(); - } - - usleep(delay_at_end * 1000); - - munmap(g_fb, g_fb_height * g_fb_width * BYTES_PER_PIXEL); - free(g_cells); - return 0; } -- 2.34.1