Compare commits
134 Commits
fd402083d7
...
bbe1eca711
Author | SHA1 | Date | |
---|---|---|---|
bbe1eca711 | |||
e7d361ca51 | |||
bb6759986e | |||
4cc8a44ec7 | |||
0a9578c1ec | |||
1fc2da4fb0 | |||
9c4f20790f | |||
0a143e8729 | |||
d4237d10a0 | |||
b24aa1821c | |||
7c0ff8c75a | |||
a11aa7a2d0 | |||
0abd9153ae | |||
abbfd5825f | |||
bfb45c7d4a | |||
d3fbddb191 | |||
0ab8efd405 | |||
2aefbdc4ee | |||
15dc71e8e1 | |||
140910763e | |||
829f455129 | |||
d10cb10404 | |||
c97876bba0 | |||
31c36b9b83 | |||
5fe0507ab1 | |||
e1c287a45b | |||
db2f91b1fb | |||
7345a952ca | |||
903dcfa52c | |||
2ce2d57eff | |||
907049c405 | |||
450ef2ce27 | |||
01dcb954e5 | |||
04649fce8a | |||
66983ce17c | |||
0a46cfc80c | |||
fb52c67f16 | |||
ab70a72434 | |||
1d0f18cab9 | |||
de6f5c38d8 | |||
e0ed4be0db | |||
6293aeea58 | |||
646a15d295 | |||
b59a787b9e | |||
e2ff0ad273 | |||
62cb53069c | |||
fe302f5967 | |||
69f3e28f2c | |||
2f56a52489 | |||
1176e64a7c | |||
e7780b04ee | |||
6ded7247e0 | |||
8b3755873b | |||
489d54c531 | |||
eb3af60497 | |||
aee100753d | |||
0be6a896bb | |||
7d738433ed | |||
701dc30221 | |||
6968961d5c | |||
d8914b3efa | |||
82985d691d | |||
ff10e5f3b2 | |||
71df91b4a0 | |||
7dc4b17d46 | |||
332976dde9 | |||
5b94217316 | |||
7205020bac | |||
898eb43360 | |||
a863b17746 | |||
5087b6db30 | |||
7d69ac56e2 | |||
f9b39c5ff3 | |||
d70effd1db | |||
59713279a0 | |||
6443ec77f8 | |||
86372a3893 | |||
3dc2c24ec5 | |||
06b8a41d2f | |||
eab44307f0 | |||
cdab3dea90 | |||
2780ee2ebc | |||
70c63572b2 | |||
fc37634a18 | |||
f05fea441c | |||
5975e58b4a | |||
d385e01796 | |||
4dc060e0b3 | |||
644614cdd8 | |||
1070c85922 | |||
8d5f598488 | |||
8efcf6d852 | |||
7165ff7683 | |||
0847a2cda0 | |||
42a7c7af5f | |||
49f84c9dda | |||
f5c0e724d5 | |||
ac3175cf26 | |||
a78620a7d2 | |||
fcd8c1d583 | |||
e1c2dfb9ba | |||
7fcc0659c8 | |||
3fc5f2b836 | |||
64965cd322 | |||
d0ad103e3d | |||
3728558b13 | |||
7cd6e9b12a | |||
e02dee1f41 | |||
ab2700ef5d | |||
bb18749d5b | |||
d4b368b078 | |||
7f2a65f6d6 | |||
02f8102d38 | |||
9bb66716a4 | |||
b9ccda132a | |||
b8470f753b | |||
909d0ed289 | |||
6bdf3169d2 | |||
75d0d12b71 | |||
a7ff298852 | |||
ca5b4de2d8 | |||
f8a39ffeec | |||
d440559d54 | |||
443d8957f3 | |||
ea14dab7d7 | |||
16223b2f53 | |||
9c36ef6e9e | |||
63f785563d | |||
1cd355a8e8 | |||
d4d748e153 | |||
cee677b1f7 | |||
e4c9211edc | |||
6bf8225f63 | |||
1223c6c20b |
25
.drone.yml
25
.drone.yml
@ -1,25 +0,0 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: test
|
||||
|
||||
platform:
|
||||
arch: arm64
|
||||
os: linux
|
||||
|
||||
steps:
|
||||
- name: build-and-test
|
||||
image: ubuntu
|
||||
commands:
|
||||
- apt update
|
||||
- apt install build-essential cmake ninja-build wget nasm genext2fs qemu-system git -y
|
||||
- wget https://pub.cloudapio.eu/luna/toolchains/ci-toolchain-arm64.tar.gz --quiet
|
||||
- tar xf ci-toolchain-arm64.tar.gz
|
||||
- rm ci-toolchain-arm64.tar.gz
|
||||
- tools/run-tests.sh
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
21
.gitea/workflows/build.yaml
Normal file
21
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
name: Build and test
|
||||
run-name: ${{ gitea.actor }} is testing and running the code
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v3
|
||||
- name: Download dependencies
|
||||
run: |
|
||||
apt update
|
||||
apt install -y cmake ninja-build nasm genext2fs qemu-system build-essential wget git
|
||||
- name: Set up the toolchain
|
||||
run: |
|
||||
wget https://pub.cloudapio.eu/luna/toolchains/ci-toolchain-arm64.tar.gz --quiet
|
||||
tar xf ci-toolchain-arm64.tar.gz
|
||||
rm ci-toolchain-arm64.tar.gz
|
||||
- name: Build and run tests
|
||||
run: tools/run-tests.sh
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ base/usr/*
|
||||
base/usr/share/*
|
||||
!base/usr/share/fonts
|
||||
!base/usr/share/icons
|
||||
!base/usr/share/applications
|
||||
base/etc/skel/LICENSE
|
||||
.fakeroot
|
||||
kernel/config.cmake
|
||||
|
@ -5,8 +5,8 @@ set(CMAKE_CXX_COMPILER_WORKS 1)
|
||||
|
||||
set(CMAKE_CROSSCOMPILING true)
|
||||
|
||||
project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.6.0)
|
||||
set(LUNA_RELEASE_NAME "Andromeda")
|
||||
project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.7.0)
|
||||
set(LUNA_RELEASE_NAME "Pulsar")
|
||||
|
||||
set(LUNA_ROOT ${CMAKE_CURRENT_LIST_DIR})
|
||||
set(LUNA_BASE ${CMAKE_CURRENT_LIST_DIR}/base)
|
||||
@ -45,11 +45,10 @@ endif()
|
||||
|
||||
add_subdirectory(libluna)
|
||||
add_subdirectory(libos)
|
||||
add_subdirectory(libui)
|
||||
add_subdirectory(gui)
|
||||
add_subdirectory(libc)
|
||||
add_subdirectory(kernel)
|
||||
add_subdirectory(apps)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(shell)
|
||||
add_subdirectory(wind)
|
||||
add_subdirectory(terminal)
|
||||
add_subdirectory(system)
|
||||
|
32
README.md
32
README.md
@ -1,18 +1,18 @@
|
||||
# Luna
|
||||
A simple POSIX-based operating system for personal computers, written in C++. [![Build Status](https://drone.cloudapio.eu/api/badges/apio/Luna/status.svg)](https://drone.cloudapio.eu/apio/Luna)
|
||||
A simple POSIX-based operating system for 64-bit computers, written in C++.
|
||||
|
||||
## Another UNIX clone?
|
||||
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone).
|
||||
|
||||
## Features
|
||||
- x86_64-compatible lightweight [kernel](kernel/).
|
||||
- Simple round-robin [scheduler](kernel/src/thread/).
|
||||
- Lightweight 64-bit [kernel](kernel/). Compatible with the x86_64 architecture.
|
||||
- Basic threads/processes, using a simple round-robin [scheduler](kernel/src/thread/).
|
||||
- Read-only [ext2](kernel/src/fs/ext2/) filesystem.
|
||||
- Can [load ELF programs](kernel/src/binfmt/ELF.cpp), [shebang scripts](kernel/src/binfmt/Script.cpp) or [arbitrary binary formats](kernel/src/binfmt/BinaryFormat.h) (registered through kernel modules, which are not supported yet =D).
|
||||
- Can [load ELF executables](kernel/src/binfmt/ELF.cpp), [shebang scripts](kernel/src/binfmt/Script.cpp) or [arbitrary binary formats](kernel/src/binfmt/BinaryFormat.h) (registered through kernel modules, which are not supported yet =D).
|
||||
- [C Library](libc/), aiming for POSIX compatibility, with many features such as local domain sockets, signals, and shared memory.
|
||||
- Support for [several third-party programs](ports/), including the [GNU binutils](ports/binutils/PACKAGE) suite of utilities.
|
||||
- Designed to be [portable](kernel/src/arch), no need to be restricted to x86_64.
|
||||
- Everything is designed around [UTF-8](libluna/include/luna/Utf8.h).
|
||||
- Support for [several third-party programs](ports/), including the [GNU binutils](ports/binutils/PACKAGE) suite of utilities and the [GCC](ports/gcc/PACKAGE) compiler.
|
||||
- Designed to be [portable](kernel/src/arch), so that additional architectures can be added in the future with relatively low effort.
|
||||
- Everything text-related is designed around [UTF-8](libluna/include/luna/Utf8.h).
|
||||
- Environment-agnostic [utility library](libluna/), which can be used in both kernel and userspace.
|
||||
- An extensive set of [standard Unix utilities](apps/), from [ls](apps/ls.cpp) to [uname](apps/uname.cpp) to [base64](apps/base64.cpp). Written in modern C++ and very small amounts of code, using Luna's practical [OS library](libos/).
|
||||
- A simple and efficient [windowing system](wind/), providing a lightweight GUI environment (still in development, not many GUI apps exist).
|
||||
@ -34,21 +34,21 @@ Please beware that building GCC and Binutils can take some time, depending on yo
|
||||
|
||||
To run Luna in a virtual machine, you should have [QEMU](https://www.qemu.org/) installed.
|
||||
|
||||
Additionally, the build process needs some extra dependencies to run: `cmake`, `ninja`, `nasm` and `genext2fs`.
|
||||
Additionally, the build process needs some extra dependencies to run: `cmake`, `ninja`, `nasm`, `fakeroot` and `genext2fs`.
|
||||
|
||||
`tools/run.sh` is the script you should use in most cases. It will build changed files, install, make an ISO image, and run Luna in QEMU.
|
||||
|
||||
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` since `run.sh` does it for you.
|
||||
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` manually since `run.sh` does it for you.
|
||||
|
||||
## Login UI
|
||||
|
||||
For development convenience, the system automatically starts a GUI session as the default user, without prompting for a password.
|
||||
|
||||
Despite this, Luna does have a login window built-in. If you'd like to try this feature out or start a GUI session as a different user, you'll need to edit [base/etc/init/99-login](base/etc/init/99-login) and change the line that says `Command=/usr/bin/loginui --autologin=selene` to `Command=/usr/bin/loginui`.
|
||||
|
||||
## Prebuilt images
|
||||
|
||||
Prebuilt ISO images (numbered) for every version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
|
||||
|
||||
These images are built manually whenever a new release is created, and thus don't reflect the latest changes on the `main` branch.
|
||||
|
||||
Every hour, this server pulls the latest commits on `main` and builds an hourly ISO image. The ten most recent ones can be found in the [hourly](https://pub.cloudapio.eu/luna/hourly) directory, and [Luna-latest.iso](https://pub.cloudapio.eu/luna/Luna-latest.iso) should always be symlinked to the newest one.
|
||||
|
||||
These images do reflect the latest changes on the `main` branch, but are obviously less stable. Additionally, an hourly image will be skipped if building the latest commit of the project fails.
|
||||
Prebuilt ISO images for every release version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
|
||||
|
||||
## Is there third-party software I can use on Luna?
|
||||
|
||||
|
@ -1,51 +0,0 @@
|
||||
function(luna_app SOURCE_FILE APP_NAME)
|
||||
add_executable(${APP_NAME} ${SOURCE_FILE})
|
||||
target_compile_options(${APP_NAME} PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||
add_dependencies(${APP_NAME} libc)
|
||||
target_include_directories(${APP_NAME} PRIVATE ${LUNA_BASE}/usr/include)
|
||||
target_link_libraries(${APP_NAME} PRIVATE os)
|
||||
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_BASE}/usr/bin)
|
||||
endfunction()
|
||||
|
||||
add_executable(preinit preinit.cpp)
|
||||
target_compile_options(preinit PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||
add_dependencies(preinit libc)
|
||||
target_include_directories(preinit PRIVATE ${LUNA_BASE}/usr/include)
|
||||
install(TARGETS preinit DESTINATION ${LUNA_ROOT}/initrd/bin)
|
||||
|
||||
luna_app(init.cpp init)
|
||||
luna_app(env.cpp env)
|
||||
luna_app(su.cpp su)
|
||||
luna_app(cat.cpp cat)
|
||||
luna_app(date.cpp date)
|
||||
luna_app(edit.cpp edit)
|
||||
luna_app(ls.cpp ls)
|
||||
luna_app(chown.cpp chown)
|
||||
luna_app(chmod.cpp chmod)
|
||||
luna_app(mkdir.cpp mkdir)
|
||||
luna_app(rm.cpp rm)
|
||||
luna_app(stat.cpp stat)
|
||||
luna_app(uname.cpp uname)
|
||||
luna_app(base64.cpp base64)
|
||||
luna_app(login.cpp login)
|
||||
luna_app(mount.cpp mount)
|
||||
luna_app(umount.cpp umount)
|
||||
luna_app(ps.cpp ps)
|
||||
luna_app(time.cpp time)
|
||||
luna_app(ln.cpp ln)
|
||||
luna_app(mktemp.cpp mktemp)
|
||||
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)
|
||||
target_link_libraries(about PUBLIC ui)
|
||||
luna_app(taskbar.cpp taskbar)
|
||||
target_link_libraries(taskbar PUBLIC ui)
|
||||
luna_app(2048.cpp 2048)
|
||||
target_link_libraries(2048 PUBLIC ui)
|
||||
luna_app(clock.cpp clock)
|
||||
target_link_libraries(clock PUBLIC ui)
|
@ -1,70 +0,0 @@
|
||||
#include <os/File.h>
|
||||
#include <os/Process.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ui/App.h>
|
||||
#include <ui/Button.h>
|
||||
#include <ui/Container.h>
|
||||
#include <ui/Image.h>
|
||||
#include <ui/Layout.h>
|
||||
|
||||
static constexpr ui::Color TASKBAR_COLOR = ui::Color::from_rgb(83, 83, 83);
|
||||
|
||||
void sigchld_handler(int)
|
||||
{
|
||||
wait(nullptr);
|
||||
}
|
||||
|
||||
Result<void> create_widget_group_for_app(ui::HorizontalLayout& layout, Slice<StringView> args, StringView icon)
|
||||
{
|
||||
auto* button = new (std::nothrow) ui::Button({ 0, 0, 50, 50 });
|
||||
if (!button) return err(ENOMEM);
|
||||
layout.add_widget(*button);
|
||||
|
||||
auto* container = new (std::nothrow)
|
||||
ui::Container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
|
||||
if (!container) return err(ENOMEM);
|
||||
button->set_widget(*container);
|
||||
button->set_action([=] { os::Process::spawn(args[0], args, false); });
|
||||
|
||||
auto image = TRY(ui::ImageWidget::load(icon));
|
||||
container->set_widget(*image);
|
||||
|
||||
image.leak();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
|
||||
TRY(os::EventLoop::the().register_signal_handler(SIGCHLD, sigchld_handler));
|
||||
|
||||
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, ui::WindowType::System));
|
||||
app.set_main_window(window);
|
||||
|
||||
window->set_background(TASKBAR_COLOR);
|
||||
|
||||
ui::HorizontalLayout layout(ui::Margins { 0, 0, 0, 0 }, ui::AdjustHeight::Yes, ui::AdjustWidth::No);
|
||||
window->set_main_widget(layout);
|
||||
|
||||
StringView terminal_command[] = { "/usr/bin/terminal" };
|
||||
TRY(create_widget_group_for_app(layout, { terminal_command, 1 }, "/usr/share/icons/32x32/app-terminal.tga"));
|
||||
|
||||
StringView about_command[] = { "/usr/bin/about" };
|
||||
TRY(create_widget_group_for_app(layout, { about_command, 1 }, "/usr/share/icons/32x32/app-about.tga"));
|
||||
|
||||
StringView gol_command[] = { "/usr/bin/gol" };
|
||||
TRY(create_widget_group_for_app(layout, { gol_command, 1 }, "/usr/share/icons/32x32/app-gol.tga"));
|
||||
|
||||
StringView clock_command[] = { "/usr/bin/clock" };
|
||||
TRY(create_widget_group_for_app(layout, { clock_command, 1 }, "/usr/share/icons/32x32/app-clock.tga"));
|
||||
|
||||
return app.run();
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
root:!:0:
|
||||
users:!:1:selene
|
||||
wind:!:2:selene
|
||||
wsys:!:3:
|
||||
selene:!:1000:
|
||||
|
@ -1,6 +1,6 @@
|
||||
Name=login
|
||||
Description=Start the display server.
|
||||
Command=/usr/bin/wind --user=selene
|
||||
Description=Start a graphical user session.
|
||||
Command=/usr/bin/loginui --autologin=selene
|
||||
StandardOutput=/dev/uart0
|
||||
StandardError=/dev/uart0
|
||||
Restart=true
|
||||
|
@ -1,3 +1,3 @@
|
||||
root:toor:0:0:Administrator:/:/usr/bin/sh
|
||||
wind:!:2:2:Window Manager:/:/usr/bin/init
|
||||
selene:moon:1000:1000:User:/home/selene:/usr/bin/sh
|
||||
root:x:0:0:Administrator:/:/usr/bin/sh
|
||||
wind:x:2:2:Window Manager:/:/usr/bin/init
|
||||
selene:x:1000:1000:User:/home/selene:/usr/bin/sh
|
||||
|
3
base/etc/shadow
Normal file
3
base/etc/shadow
Normal file
@ -0,0 +1,3 @@
|
||||
root:toor:0:0:99999:7:::
|
||||
wind:!:0:0:99999:7:::
|
||||
selene:moon:0:0:99999:7:::
|
@ -2,5 +2,9 @@
|
||||
# Create and populate a volatile home directory.
|
||||
mount -t tmpfs tmpfs /home/selene
|
||||
chown selene:selene /home/selene
|
||||
|
||||
cp /etc/skel/welcome /home/selene/
|
||||
cp /etc/skel/LICENSE /home/selene/
|
||||
|
||||
chown selene:selene /home/selene/welcome
|
||||
chown selene:selene /home/selene/LICENSE
|
||||
|
@ -1,5 +0,0 @@
|
||||
Name=taskbar
|
||||
Description=Start the taskbar.
|
||||
Command=/usr/bin/taskbar
|
||||
WorkingDirectory=/home/selene
|
||||
Restart=true
|
@ -1,4 +1,3 @@
|
||||
Name=terminal
|
||||
Description=Start the terminal.
|
||||
WorkingDirectory=/home/selene
|
||||
Command=/usr/bin/terminal
|
3
base/usr/share/applications/00-terminal
Normal file
3
base/usr/share/applications/00-terminal
Normal file
@ -0,0 +1,3 @@
|
||||
Name=terminal
|
||||
Icon=/usr/share/icons/32x32/app-terminal.tga
|
||||
Command=/usr/bin/terminal
|
3
base/usr/share/applications/01-about
Normal file
3
base/usr/share/applications/01-about
Normal file
@ -0,0 +1,3 @@
|
||||
Name=about
|
||||
Icon=/usr/share/icons/32x32/app-about.tga
|
||||
Command=/usr/bin/about
|
3
base/usr/share/applications/02-gol
Normal file
3
base/usr/share/applications/02-gol
Normal file
@ -0,0 +1,3 @@
|
||||
Name=gol
|
||||
Icon=/usr/share/icons/32x32/app-gol.tga
|
||||
Command=/usr/bin/gol
|
3
base/usr/share/applications/03-clock
Normal file
3
base/usr/share/applications/03-clock
Normal file
@ -0,0 +1,3 @@
|
||||
Name=clock
|
||||
Icon=/usr/share/icons/32x32/app-clock.tga
|
||||
Command=/usr/bin/clock
|
3
base/usr/share/applications/04-2048
Normal file
3
base/usr/share/applications/04-2048
Normal file
@ -0,0 +1,3 @@
|
||||
Name=2048
|
||||
Icon=/usr/share/icons/32x32/app-2048.tga
|
||||
Command=/usr/bin/2048
|
BIN
base/usr/share/icons/32x32/app-2048.tga
Normal file
BIN
base/usr/share/icons/32x32/app-2048.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
221
docs/boot_process.md
Normal file
221
docs/boot_process.md
Normal file
@ -0,0 +1,221 @@
|
||||
# The Luna boot process
|
||||
|
||||
## Stage 0: The Bootloader
|
||||
Luna uses the [BOOTBOOT](https://gitlab.com/bztsrc/bootboot) bootloader. _(For more information, read the [bootloader specification](https://gitlab.com/bztsrc/bootboot/-/blob/master/bootboot_spec_1st_ed.pdf).)_
|
||||
|
||||
This bootloader reads the initial ramdisk, which contains the following files:
|
||||
```
|
||||
/sys/config - copy of the configuration file for the bootloader
|
||||
/boot/moon - the kernel itself
|
||||
/bin/preinit - the first user program run in the boot process, before the root filesystem is mounted
|
||||
```
|
||||
|
||||
The bootloader loads the kernel in 64-bit mode into the higher half at address `0xffffffffffe02000`, with an appropriate stack already set up.
|
||||
|
||||
The first 16Gb of memory are identity-mapped at page 0.
|
||||
|
||||
It places a few other things into known addresses:
|
||||
```
|
||||
0xfffffffffc000000 - initial framebuffer
|
||||
0xffffffffffe00000 - bootloader information passed to the kernel
|
||||
0xffffffffffe01000 - kernel command line
|
||||
```
|
||||
|
||||
From here, the kernel takes over.
|
||||
|
||||
## Stage 1: The Kernel
|
||||
_Relevant files: [kernel/src/main.cpp](../kernel/src/main.cpp), [kernel/src/arch/x86_64/CPU.cpp](../kernel/src/arch/x86_64/CPU.cpp#L285)_
|
||||
|
||||
The kernel begins execution in the `_start()` function. This function initializes basic kernel functionality, such as time-keeping, memory management, graphics, and finally threading.
|
||||
|
||||
Once threading is set up and the scheduler is started, the kernel starts up a new kernel thread titled `[kinit]` to finish starting up other subsystems that assume they're running in a thread.
|
||||
|
||||
Before switching to `[kinit]`, `_start` does one more thing, it calls the `CPU::platform_finish_init()` function which is platform-specific. On x86_64, this function does the following things:
|
||||
|
||||
- Creates a new kernel thread: `[x86_64-io]`, which handles keyboard and mouse interrupts asynchronously
|
||||
- Starts receiving external interrupts
|
||||
- Initializes the mouse
|
||||
|
||||
As soon as the scheduler switches to the `[kinit]` thread, it will never return to `_start` (since it has no thread associated to it).
|
||||
|
||||
**IMPORTANT**: Although the `[kinit]` thread is the first thread to be started in the system, it has PID 2, not 1. The reason for this is that PID 1 is reserved for the userspace init process.
|
||||
|
||||
`[kinit]` does the following things, in order:
|
||||
|
||||
- Loads kernel debug symbols from the initial ramdisk
|
||||
- Creates the virtual file system and mounts the initial ramdisk on /
|
||||
- Initializes virtual device files such as `/dev/null` (the internal kernel representation of them, `/dev` is not mounted yet)
|
||||
- Loads `/bin/preinit` from the initial ramdisk as PID 1
|
||||
- Creates two more kernel threads, `[reap]` and `[oom]`
|
||||
- Scans for ATA hard disks and reads their partition tables
|
||||
- Finally, it sets PID 1's state to "Running" so that the scheduler can switch to it, and exits
|
||||
|
||||
### Kernel threads
|
||||
|
||||
`[kinit]` spawns two more kernel threads, `[reap]` and `[oom]`. While `[kinit]` exits before PID 1 is started, `[reap]` and `[oom]` are present throughout the lifetime of a Luna system, and can be seen in the output of `ps`. Let's take a look at what they do.
|
||||
|
||||
- `[reap]`: To understand what this thread does, we must take a look at what happens when threads exit on Luna.
|
||||
|
||||
_(Relevant files: [kernel/src/main.cpp](../kernel/src/main.cpp#L23), [kernel/src/thread/Scheduler.cpp](../kernel/src/thread/Scheduler.cpp#L205), [kernel/src/thread/Thread.cpp](../kernel/src/thread/Thread.cpp#L105), [kernel/src/sys/waitpid.cpp](../kernel/src/sys/waitpid.cpp#L84))_
|
||||
|
||||
When a thread calls the `_exit()` syscall, its state is set to "Exited". This tells the scheduler to avoid switching to it, and the thread's parent is notified, either by sending SIGCHLD or unblocking a blocked `waitpid()` call. The thread remains visible to the rest of the system, and if its parent does not wait for it, it will stay there as a "zombie thread".
|
||||
|
||||
When the thread's parent waits for it, its state is instead set to "Dying", and the `[reap]` thread runs. (Kernel threads skip all the "parent notifying" shenanigans and go straight to the "Dying" state when calling `kernel_exit()`).
|
||||
|
||||
The `[reap]` thread then "reaps" all the "Dying" threads' resources. It frees up the threads' memory, file descriptors, and unmaps the memory used for the kernel stack corresponding to that thread. After reaping, the thread is deleted, and no trace of it is left.
|
||||
|
||||
- `[oom]`: This thread handles Out-Of-Memory (OOM) situations. Whenever the kernel has 1/4 or 1/8 of the available physical memory left (thresholds may be tweaked in the future), or it has run out, it runs this thread.
|
||||
|
||||
The OOM thread then goes through all the disk caches and purges them all, hoping to reclaim as much memory as possible.
|
||||
|
||||
### File system and process layout
|
||||
|
||||
After the kernel stage of the boot process, the system looks like this:
|
||||
|
||||
#### File system
|
||||
```
|
||||
/ - initial ramdisk
|
||||
/sys/config - copy of the configuration file for the bootloader
|
||||
/boot/moon - the kernel itself
|
||||
/bin/preinit - the first user program run in the boot process
|
||||
```
|
||||
|
||||
#### Processes
|
||||
```
|
||||
/bin/preinit - PID 1
|
||||
[kinit] - PID 2 (Exited, soon to be reaped)
|
||||
[x86_64-io] - PID 3
|
||||
[reap] - PID 4
|
||||
[oom] - PID 5
|
||||
```
|
||||
|
||||
## Stage 2: preinit
|
||||
_Relevant files: [system/preinit.cpp](../system/preinit.cpp)_
|
||||
|
||||
Luna's userspace init process is split into two programs: `/bin/preinit`, which resides on the initial ramdisk, and `/usr/bin/init`, which resides on the root partition.
|
||||
|
||||
`/bin/preinit`'s job is to set up the file system in a "minimal known good" state for the actual `init` to run.
|
||||
|
||||
The "minimal known good" state includes:
|
||||
|
||||
- The ext2 root partition, which includes all the binaries in /usr
|
||||
- The /dev file system
|
||||
|
||||
`preinit` does the following things, in order:
|
||||
|
||||
- Mounts `/dev` to get access to disk device files
|
||||
- Mounts the root partition (`/dev/cd0p2`) on `/osroot`
|
||||
- Unmounts `/dev`
|
||||
- Uses the `pivot_root` system call to change the root file system to the one that was in `/osroot`, and mounts the old one on `/mnt` (previously `/osroot/mnt`)
|
||||
- Unmounts the initial ramdisk on `/mnt`
|
||||
- Mounts the `/dev` file system again on the new root partition
|
||||
- Executes `/usr/bin/init`
|
||||
|
||||
For now, much of `preinit`'s functionality is hard-coded, but as Luna supports more devices, it will become responsible for loading device drivers, discovering the root partition, and more...
|
||||
|
||||
### File system and process layout
|
||||
|
||||
After the preinit stage of the boot process, the system looks like this:
|
||||
|
||||
#### File system
|
||||
```
|
||||
/ - ext2 root partition
|
||||
/dev - device file system
|
||||
/usr, /etc, /home... - other directories contained in the root partition
|
||||
```
|
||||
|
||||
#### Processes
|
||||
```
|
||||
/usr/bin/init - PID 1
|
||||
[x86_64-io] - PID 3
|
||||
[reap] - PID 4
|
||||
[oom] - PID 5
|
||||
```
|
||||
|
||||
## Stage 3: init
|
||||
_Relevant files: [system/init.cpp](../system/init.cpp#L406)_
|
||||
|
||||
`/usr/bin/init` is the actual init system. It is in charge of starting user-defined services.
|
||||
|
||||
It does the following things:
|
||||
|
||||
- Mounts `/tmp`, `/dev/shm` and `/dev/pts`
|
||||
- Sets the system hostname by reading `/etc/hostname`
|
||||
- Reads configuration files from `/etc/init`
|
||||
- Starts services defined in `/etc/init`
|
||||
- Enters the init loop, waiting for child processes and restarting them if needed
|
||||
|
||||
Currently, there are two service files defined by default in `/etc/init`:
|
||||
|
||||
`00-home`: This service sets up a `tmpfs` on `/home/selene`, so that the home directory is writable.
|
||||
|
||||
`99-login`: This service starts a graphical session, by calling `/usr/bin/startui`. This service will be restarted if necessary.
|
||||
|
||||
### File system and process layout
|
||||
|
||||
After the init stage of the boot process, the system looks like this:
|
||||
|
||||
#### File system
|
||||
```
|
||||
/ - ext2 root partition
|
||||
/dev - device file system
|
||||
/dev/shm - POSIX shared memory file system
|
||||
/dev/pts - POSIX pseudoterminal file system
|
||||
/tmp - system temporary file directory
|
||||
/usr, /etc, /home... - other directories contained in the root partition
|
||||
/home/selene - temporary home directory
|
||||
```
|
||||
|
||||
#### Processes
|
||||
```
|
||||
/usr/bin/init - PID 1
|
||||
[x86_64-io] - PID 3
|
||||
[reap] - PID 4
|
||||
[oom] - PID 5
|
||||
/usr/bin/startui - PID 13
|
||||
```
|
||||
|
||||
_Note: startui is PID 13 because the `00-home` service is a shell script, which starts a few subprocesses. Since Luna does not allow for PID reuse right now, startui ends up with PID 13._
|
||||
|
||||
## Stage 4: startui
|
||||
_Relevant files: [system/startui.cpp](../system/startui.cpp), [gui/wind/main.cpp](../gui/wind/main.cpp)_
|
||||
|
||||
`/usr/bin/startui` starts a graphical user session.
|
||||
|
||||
A Luna graphical user session includes the following components:
|
||||
|
||||
- The display server itself, `/usr/bin/wind`. It is started with permissions `root:root`, and later drops privileges to `wind:wind`.
|
||||
- The launch server (`/usr/bih/launch`), which starts processes and keeps them alive on behalf of other processes. It is started with the standard permissions `selene:selene`.
|
||||
- The taskbar, `/usr/bin/taskbar`. It is started with the standard permissions `selene:selene`, plus an extra group `wsys` to be able to connect to a special display server socket.
|
||||
- The init process corresponding to that session (`/usr/bin/init --user`). This process does the same thing as `init` above (manages services), but runs with user privileges and reads configuration files from `/etc/user` instead (in the future this will be changed to a user-specific directory).
|
||||
|
||||
Currently, `init --user` only does one thing: it opens up a terminal window on startup. It can be configured to do whatever the user desires to do on startup, by placing the appropriate configuration files in `/etc/user`.
|
||||
|
||||
### File system and process layout
|
||||
|
||||
After the startui stage of the boot process, the system is fully started up and looks like this:
|
||||
|
||||
#### File system
|
||||
```
|
||||
/ - ext2 root partition
|
||||
/dev - device file system
|
||||
/dev/shm - POSIX shared memory file system
|
||||
/dev/pts - POSIX pseudoterminal file system
|
||||
/tmp - system temporary file directory
|
||||
/usr, /etc, /home... - other directories contained in the root partition
|
||||
/home/selene - temporary home directory
|
||||
```
|
||||
|
||||
#### Processes
|
||||
```
|
||||
/usr/bin/init - PID 1
|
||||
[x86_64-io] - PID 3
|
||||
[reap] - PID 4
|
||||
[oom] - PID 5
|
||||
/usr/bin/startui - PID 13
|
||||
/usr/bin/wind - PID 14
|
||||
/usr/bin/launch - PID 15
|
||||
/usr/bin/taskbar - PID 16
|
||||
/usr/bin/init --user - PID 17
|
||||
/usr/bin/terminal - PID 18
|
||||
/bin/sh - PID 19
|
17
gui/CMakeLists.txt
Normal file
17
gui/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
function(luna_service SOURCE_FILE APP_NAME)
|
||||
add_executable(${APP_NAME} ${SOURCE_FILE})
|
||||
target_compile_options(${APP_NAME} PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||
add_dependencies(${APP_NAME} libc)
|
||||
target_include_directories(${APP_NAME} PRIVATE ${LUNA_BASE}/usr/include)
|
||||
target_link_libraries(${APP_NAME} PRIVATE os)
|
||||
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_BASE}/usr/bin)
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(libui)
|
||||
add_subdirectory(wind)
|
||||
add_subdirectory(apps)
|
||||
|
||||
luna_service(launch.cpp launch)
|
||||
luna_service(run.cpp run)
|
||||
luna_service(loginui.cpp loginui)
|
||||
target_link_libraries(loginui PRIVATE ui)
|
@ -50,9 +50,9 @@ class GameWidget final : public ui::Widget
|
||||
|
||||
bool should_add_tile = false;
|
||||
|
||||
switch (request.key)
|
||||
switch (request.code)
|
||||
{
|
||||
case 'w': {
|
||||
case moon::K_UpArrow: {
|
||||
bool changed;
|
||||
changed = move_up();
|
||||
if (changed) should_add_tile = true;
|
||||
@ -61,7 +61,7 @@ class GameWidget final : public ui::Widget
|
||||
if (changed) should_add_tile = true;
|
||||
}
|
||||
break;
|
||||
case 'a': {
|
||||
case moon::K_LeftArrow: {
|
||||
bool changed;
|
||||
changed = move_left();
|
||||
if (changed) should_add_tile = true;
|
||||
@ -70,7 +70,7 @@ class GameWidget final : public ui::Widget
|
||||
if (changed) should_add_tile = true;
|
||||
}
|
||||
break;
|
||||
case 's': {
|
||||
case moon::K_DownArrow: {
|
||||
bool changed;
|
||||
changed = move_down();
|
||||
if (changed) should_add_tile = true;
|
||||
@ -79,7 +79,7 @@ class GameWidget final : public ui::Widget
|
||||
if (changed) should_add_tile = true;
|
||||
}
|
||||
break;
|
||||
case 'd': {
|
||||
case moon::K_RightArrow: {
|
||||
bool changed;
|
||||
changed = move_right();
|
||||
if (changed) should_add_tile = true;
|
||||
@ -88,7 +88,7 @@ class GameWidget final : public ui::Widget
|
||||
if (changed) should_add_tile = true;
|
||||
}
|
||||
break;
|
||||
case 'r': {
|
||||
case moon::K_Home: {
|
||||
reset();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
@ -343,12 +343,12 @@ class GameWidget final : public ui::Widget
|
||||
}
|
||||
};
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
Result<int> luna_main(int, char**)
|
||||
{
|
||||
srand((unsigned)time(NULL));
|
||||
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
TRY(app.init());
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 400 }));
|
||||
app.set_main_window(window);
|
17
gui/apps/CMakeLists.txt
Normal file
17
gui/apps/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
function(luna_app SOURCE_FILE APP_NAME)
|
||||
add_executable(${APP_NAME} ${SOURCE_FILE})
|
||||
target_compile_options(${APP_NAME} PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||
add_dependencies(${APP_NAME} libc)
|
||||
target_include_directories(${APP_NAME} PRIVATE ${LUNA_BASE}/usr/include)
|
||||
target_link_libraries(${APP_NAME} PRIVATE os ui)
|
||||
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_BASE}/usr/bin)
|
||||
endfunction()
|
||||
|
||||
luna_app(about.cpp about)
|
||||
luna_app(taskbar.cpp taskbar)
|
||||
luna_app(2048.cpp 2048)
|
||||
luna_app(clock.cpp clock)
|
||||
luna_app(gol.cpp gol)
|
||||
|
||||
add_subdirectory(editor)
|
||||
add_subdirectory(terminal)
|
@ -7,10 +7,10 @@
|
||||
|
||||
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89);
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
Result<int> luna_main(int, char**)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
TRY(app.init());
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 }));
|
||||
app.set_main_window(window);
|
@ -18,10 +18,10 @@ void update_time()
|
||||
ui::App::the().main_window()->draw();
|
||||
}
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
Result<int> luna_main(int, char**)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
TRY(app.init());
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 500, 400, 100, 50 }));
|
||||
app.set_main_window(window);
|
12
gui/apps/editor/CMakeLists.txt
Normal file
12
gui/apps/editor/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
EditorWidget.h
|
||||
EditorWidget.cpp
|
||||
)
|
||||
|
||||
add_executable(editor ${SOURCES})
|
||||
target_compile_options(editor PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||
add_dependencies(editor libc)
|
||||
target_include_directories(editor PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_link_libraries(editor PRIVATE os ui)
|
||||
install(TARGETS editor DESTINATION ${LUNA_BASE}/usr/bin)
|
233
gui/apps/editor/EditorWidget.cpp
Normal file
233
gui/apps/editor/EditorWidget.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
/**
|
||||
* @file EditorWidget.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Multiline text editing widget.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EditorWidget.h"
|
||||
#include <ctype.h>
|
||||
#include <luna/PathParser.h>
|
||||
#include <luna/Utf8.h>
|
||||
#include <os/File.h>
|
||||
#include <os/FileSystem.h>
|
||||
#include <ui/App.h>
|
||||
|
||||
EditorWidget::EditorWidget(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
|
||||
{
|
||||
recalculate_lines();
|
||||
}
|
||||
|
||||
Result<void> EditorWidget::load_file(const os::Path& path)
|
||||
{
|
||||
struct stat st;
|
||||
auto rc = os::FileSystem::stat(path, st, true);
|
||||
|
||||
if (!rc.has_error() && !S_ISREG(st.st_mode))
|
||||
{
|
||||
os::eprintln("editor: not loading %s as it is not a regular file", path.name().chars());
|
||||
return {};
|
||||
}
|
||||
|
||||
os::eprintln("Loading file: %s", path.name().chars());
|
||||
|
||||
auto file = TRY(os::File::open_or_create(path, os::File::ReadOnly));
|
||||
|
||||
m_data = TRY(file->read_all());
|
||||
|
||||
os::eprintln("Read %zu bytes.", m_data.size());
|
||||
|
||||
m_cursor = m_data.size();
|
||||
|
||||
m_path = path;
|
||||
|
||||
auto basename = TRY(PathParser::basename(m_path.name()));
|
||||
|
||||
String title = TRY(String::format("Text Editor - %s"_sv, basename.chars()));
|
||||
window()->set_title(title.view());
|
||||
|
||||
TRY(recalculate_lines());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest& request)
|
||||
{
|
||||
// Avoid handling "key released" events
|
||||
if (!request.pressed) return ui::EventResult::DidNotHandle;
|
||||
|
||||
if (request.code == moon::K_UpArrow)
|
||||
{
|
||||
if (m_cursor_position.y > 0) m_cursor_position.y--;
|
||||
else
|
||||
return ui::EventResult::DidNotHandle;
|
||||
recalculate_cursor_index();
|
||||
update_cursor();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.code == moon::K_DownArrow)
|
||||
{
|
||||
if (m_cursor_position.y + 1 < (int)m_lines.size()) m_cursor_position.y++;
|
||||
else
|
||||
return ui::EventResult::DidNotHandle;
|
||||
recalculate_cursor_index();
|
||||
update_cursor();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.code == moon::K_LeftArrow)
|
||||
{
|
||||
if (m_cursor > 0) m_cursor--;
|
||||
else
|
||||
return ui::EventResult::DidNotHandle;
|
||||
recalculate_cursor_position();
|
||||
update_cursor();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.code == moon::K_RightArrow)
|
||||
{
|
||||
if (m_cursor < m_data.size()) m_cursor++;
|
||||
else
|
||||
return ui::EventResult::DidNotHandle;
|
||||
recalculate_cursor_position();
|
||||
update_cursor();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.code == moon::K_Backspace)
|
||||
{
|
||||
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
|
||||
m_cursor--;
|
||||
|
||||
delete_current_character();
|
||||
|
||||
TRY(recalculate_lines());
|
||||
|
||||
update_cursor();
|
||||
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.letter != '\n' && iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
|
||||
|
||||
if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
|
||||
else
|
||||
TRY(insert_character(request.letter));
|
||||
|
||||
m_cursor++;
|
||||
TRY(recalculate_lines());
|
||||
|
||||
update_cursor();
|
||||
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
Result<void> EditorWidget::save_file()
|
||||
{
|
||||
if (m_path.is_empty_path())
|
||||
{
|
||||
os::eprintln("editor: no file to save buffer to!");
|
||||
return err(ENOENT);
|
||||
}
|
||||
|
||||
auto file = TRY(os::File::open(m_path, os::File::WriteOnly));
|
||||
return file->write(m_data);
|
||||
}
|
||||
|
||||
Result<void> EditorWidget::draw(ui::Canvas& canvas)
|
||||
{
|
||||
int visible_lines = canvas.height / m_font->height();
|
||||
int visible_columns = canvas.width / m_font->width();
|
||||
|
||||
if ((usize)visible_lines > m_lines.size()) visible_lines = static_cast<int>(m_lines.size());
|
||||
|
||||
for (int i = 0; i < visible_lines; i++)
|
||||
{
|
||||
auto line = m_lines[i];
|
||||
if (line.begin == line.end) continue;
|
||||
|
||||
auto slice = TRY(m_data.slice(line.begin, line.end - line.begin));
|
||||
auto string = TRY(
|
||||
String::from_string_view(StringView::from_fixed_size_cstring((const char*)slice, line.end - line.begin)));
|
||||
|
||||
Utf8StringDecoder decoder(string.chars());
|
||||
wchar_t buf[4096];
|
||||
decoder.decode(buf, sizeof(buf)).release_value();
|
||||
|
||||
int characters_to_render = (int)wcslen(buf);
|
||||
|
||||
for (int j = 0; j < visible_columns && j < characters_to_render; j++)
|
||||
{
|
||||
auto subcanvas =
|
||||
canvas.subcanvas({ j * m_font->width(), i * m_font->height(), m_font->width(), m_font->height() });
|
||||
m_font->render(buf[j], ui::WHITE, subcanvas);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the cursor
|
||||
if (m_cursor_position.x < visible_columns && m_cursor_position.y < visible_lines && m_cursor_activated)
|
||||
{
|
||||
canvas
|
||||
.subcanvas(
|
||||
{ m_cursor_position.x * m_font->width(), m_cursor_position.y * m_font->height(), 1, m_font->height() })
|
||||
.fill(ui::WHITE);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> EditorWidget::recalculate_lines()
|
||||
{
|
||||
m_lines.clear();
|
||||
|
||||
Line l;
|
||||
l.begin = 0;
|
||||
for (usize i = 0; i < m_data.size(); i++)
|
||||
{
|
||||
if (m_data.data()[i] == '\n')
|
||||
{
|
||||
l.end = i;
|
||||
TRY(m_lines.try_append(l));
|
||||
l.begin = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
l.end = m_data.size();
|
||||
TRY(m_lines.try_append(l));
|
||||
|
||||
recalculate_cursor_position();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void EditorWidget::recalculate_cursor_position()
|
||||
{
|
||||
if (m_cursor == 0) m_cursor_position = { 0, 0 };
|
||||
|
||||
for (int i = 0; i < (int)m_lines.size(); i++)
|
||||
{
|
||||
auto line = m_lines[i];
|
||||
if (m_cursor >= line.begin && m_cursor <= line.end)
|
||||
{
|
||||
m_cursor_position.x = (int)(m_cursor - line.begin);
|
||||
m_cursor_position.y = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
void EditorWidget::recalculate_cursor_index()
|
||||
{
|
||||
m_cursor = m_lines[m_cursor_position.y].begin + m_cursor_position.x;
|
||||
if (m_cursor > m_lines[m_cursor_position.y].end)
|
||||
{
|
||||
m_cursor = m_lines[m_cursor_position.y].end;
|
||||
recalculate_cursor_position();
|
||||
}
|
||||
}
|
49
gui/apps/editor/EditorWidget.h
Normal file
49
gui/apps/editor/EditorWidget.h
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @file EditorWidget.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Multiline text editing widget.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <luna/String.h>
|
||||
#include <os/Timer.h>
|
||||
#include <ui/Font.h>
|
||||
#include <ui/TextInput.h>
|
||||
#include <ui/Widget.h>
|
||||
|
||||
class EditorWidget : public ui::TextInput
|
||||
{
|
||||
public:
|
||||
EditorWidget(SharedPtr<ui::Font> font);
|
||||
|
||||
Result<void> load_file(const os::Path& path);
|
||||
|
||||
Result<void> save_file();
|
||||
|
||||
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
|
||||
|
||||
Result<void> draw(ui::Canvas& canvas) override;
|
||||
|
||||
os::Path& path()
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
private:
|
||||
SharedPtr<ui::Font> m_font;
|
||||
|
||||
struct Line
|
||||
{
|
||||
usize begin;
|
||||
usize end;
|
||||
};
|
||||
Vector<Line> m_lines;
|
||||
|
||||
os::Path m_path { AT_FDCWD };
|
||||
|
||||
Result<void> recalculate_lines();
|
||||
void recalculate_cursor_position();
|
||||
void recalculate_cursor_index();
|
||||
};
|
47
gui/apps/editor/main.cpp
Normal file
47
gui/apps/editor/main.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Graphical text editor.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EditorWidget.h"
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
#include <ui/App.h>
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
StringView path;
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("A graphical text editor"_sv);
|
||||
parser.add_system_program_info("editor"_sv);
|
||||
parser.add_positional_argument(path, "path", false);
|
||||
parser.parse(argc, argv);
|
||||
|
||||
ui::App app;
|
||||
TRY(app.init());
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 200, 300, 600, 600 }));
|
||||
window->set_background(ui::Color::from_rgb(40, 40, 40));
|
||||
window->set_title("Text Editor");
|
||||
app.set_main_window(window);
|
||||
|
||||
auto* editor = TRY(make<EditorWidget>(ui::Font::default_font()));
|
||||
window->set_main_widget(*editor);
|
||||
if (!path.is_empty()) TRY(editor->load_file(path));
|
||||
|
||||
TRY(window->add_keyboard_shortcut({ moon::K_CH26, ui::Mod_Ctrl }, true, [&](ui::Shortcut) {
|
||||
auto result = editor->save_file();
|
||||
if (result.has_error()) os::eprintln("editor: failed to save file: %s", result.error_string());
|
||||
else
|
||||
os::println("editor: buffer saved to %s successfully", editor->path().name().chars());
|
||||
}));
|
||||
|
||||
window->draw();
|
||||
|
||||
return app.run();
|
||||
}
|
@ -108,10 +108,10 @@ static void update()
|
||||
draw_cells();
|
||||
}
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
Result<int> luna_main(int, char**)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
TRY(app.init());
|
||||
|
||||
g_window = TRY(ui::Window::create(ui::Rect { 200, 200, 600, 400 }));
|
||||
g_window->set_title("Game of Life");
|
156
gui/apps/taskbar.cpp
Normal file
156
gui/apps/taskbar.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include <luna/Sort.h>
|
||||
#include <luna/StringBuilder.h>
|
||||
#include <os/Config.h>
|
||||
#include <os/Directory.h>
|
||||
#include <os/File.h>
|
||||
#include <os/FileSystem.h>
|
||||
#include <os/IPC.h>
|
||||
#include <os/Process.h>
|
||||
#include <os/ipc/Launcher.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ui/App.h>
|
||||
#include <ui/Button.h>
|
||||
#include <ui/Container.h>
|
||||
#include <ui/Image.h>
|
||||
#include <ui/Layout.h>
|
||||
|
||||
static constexpr ui::Color TASKBAR_COLOR = ui::Color::from_rgb(83, 83, 83);
|
||||
|
||||
static OwnedPtr<os::IPC::Client> launcher_client;
|
||||
|
||||
void sigchld_handler(int)
|
||||
{
|
||||
wait(nullptr);
|
||||
}
|
||||
|
||||
void sigquit_handler(int)
|
||||
{
|
||||
// Reload the taskbar by exec-ing the executable, resetting everything.
|
||||
StringView args[] = { "/usr/bin/taskbar" };
|
||||
os::Process::exec(args[0], { args, 1 });
|
||||
}
|
||||
|
||||
Result<void> create_widget_group_for_app(ui::HorizontalLayout& layout, StringView path, StringView icon)
|
||||
{
|
||||
auto* button = TRY(make<ui::Button>(ui::Rect { 0, 0, 50, 50 }));
|
||||
layout.add_widget(*button);
|
||||
|
||||
auto* container = TRY(
|
||||
make<ui::Container>(ui::Rect { 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center));
|
||||
button->set_widget(*container);
|
||||
button->set_action([=] {
|
||||
os::Launcher::LaunchDetachedRequest request;
|
||||
SET_IPC_STRING(request.command, path.chars());
|
||||
launcher_client->send_async(request);
|
||||
});
|
||||
|
||||
auto image = TRY(ui::ImageWidget::load(icon));
|
||||
container->set_widget(*image);
|
||||
|
||||
image.leak();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
struct ApplicationFile
|
||||
{
|
||||
String name;
|
||||
String command;
|
||||
String icon;
|
||||
};
|
||||
|
||||
Vector<ApplicationFile> s_app_files;
|
||||
|
||||
// Pretty much copied from init.cpp.
|
||||
static Result<void> load_application_file(const os::Path& path)
|
||||
{
|
||||
os::println("[taskbar] reading app file: %s", path.name().chars());
|
||||
|
||||
auto file = TRY(os::ConfigFile::open(path));
|
||||
|
||||
ApplicationFile app_file;
|
||||
|
||||
app_file.name = TRY(String::from_string_view(file->read_string_or("Name", "")));
|
||||
if (app_file.name.is_empty())
|
||||
{
|
||||
os::println("[taskbar] app file is missing 'Name' entry, aborting!");
|
||||
return {};
|
||||
}
|
||||
|
||||
app_file.command = TRY(String::from_string_view(file->read_string_or("Command", "")));
|
||||
if (app_file.command.is_empty())
|
||||
{
|
||||
os::println("[taskbar] app file is missing 'Command' entry, aborting!");
|
||||
return {};
|
||||
}
|
||||
|
||||
app_file.icon = TRY(String::from_string_view(file->read_string_or("Icon", "")));
|
||||
if (app_file.icon.is_empty())
|
||||
{
|
||||
os::println("[taskbar] app file is missing 'Icon' entry, aborting!");
|
||||
return {};
|
||||
}
|
||||
|
||||
os::println("[taskbar] loaded app %s into memory", app_file.name.chars());
|
||||
|
||||
TRY(s_app_files.try_append(move(app_file)));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> load_app_files_from_path(StringView path)
|
||||
{
|
||||
os::println("[taskbar] loading app files from %s", path.chars());
|
||||
|
||||
auto dir = TRY(os::Directory::open(path));
|
||||
|
||||
auto services = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));
|
||||
sort(services.begin(), services.end(), String::compare);
|
||||
|
||||
for (const auto& entry : services) TRY(load_application_file({ dir->fd(), entry.view() }));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<int> luna_main(int, char**)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init("/tmp/wsys.sock"));
|
||||
|
||||
TRY(os::EventLoop::the().register_signal_handler(SIGCHLD, sigchld_handler));
|
||||
TRY(os::EventLoop::the().register_signal_handler(SIGQUIT, sigquit_handler));
|
||||
|
||||
launcher_client = TRY(os::IPC::Client::connect("/tmp/launch.sock", false));
|
||||
|
||||
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, ui::WindowType::System));
|
||||
app.set_main_window(window);
|
||||
|
||||
window->set_background(TASKBAR_COLOR);
|
||||
window->set_special_attributes(ui::UNFOCUSEABLE);
|
||||
|
||||
ui::HorizontalLayout layout(ui::Margins { 0, 0, 0, 0 }, ui::AdjustHeight::Yes, ui::AdjustWidth::No);
|
||||
window->set_main_widget(layout);
|
||||
|
||||
load_app_files_from_path("/usr/share/applications/");
|
||||
|
||||
auto home = TRY(os::FileSystem::home_directory());
|
||||
|
||||
StringBuilder sb;
|
||||
TRY(sb.add(home.view()));
|
||||
TRY(sb.add("/.applications/"_sv));
|
||||
auto local_app_file_dir = TRY(sb.string());
|
||||
|
||||
load_app_files_from_path(local_app_file_dir.view());
|
||||
|
||||
for (const auto& app_file : s_app_files)
|
||||
{
|
||||
create_widget_group_for_app(layout, app_file.command.view(), app_file.icon.view());
|
||||
}
|
||||
|
||||
return app.run();
|
||||
}
|
@ -29,14 +29,6 @@ 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);
|
||||
@ -48,9 +40,11 @@ Result<void> TerminalWidget::init(char* const* args)
|
||||
m_font = ui::Font::default_font();
|
||||
m_bold_font = ui::Font::default_bold_font();
|
||||
|
||||
m_terminal_canvas = ui::App::the().main_window()->canvas();
|
||||
m_terminal_canvas = window()->canvas();
|
||||
m_terminal_canvas.fill(ui::BLACK);
|
||||
|
||||
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));
|
||||
|
||||
signal(SIGCHLD, sigchld_handler);
|
||||
|
||||
int master;
|
||||
@ -64,12 +58,10 @@ Result<void> TerminalWidget::init(char* const* args)
|
||||
|
||||
m_pty = master;
|
||||
|
||||
fcntl(master, F_SETFL, O_NONBLOCK);
|
||||
os::EventLoop::the().register_fd_listener(m_pty, [this](int, int) { this->process(); });
|
||||
|
||||
m_child_pid = child;
|
||||
|
||||
m_last_cursor_tick = get_time_in_milliseconds();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -91,7 +83,7 @@ Result<void> TerminalWidget::draw(ui::Canvas&)
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<bool> TerminalWidget::process()
|
||||
Result<void> TerminalWidget::process()
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
ssize_t nread = read(m_pty, buffer, BUFSIZ);
|
||||
@ -102,8 +94,6 @@ Result<bool> TerminalWidget::process()
|
||||
return err(errno);
|
||||
}
|
||||
|
||||
bool should_update_cursor = tick_cursor();
|
||||
|
||||
ssize_t drawn = 0;
|
||||
|
||||
for (ssize_t i = 0; i < nread; i++)
|
||||
@ -112,32 +102,22 @@ Result<bool> TerminalWidget::process()
|
||||
if (did_draw) drawn++;
|
||||
}
|
||||
|
||||
if (should_update_cursor || drawn > 0) ui::App::the().main_window()->draw();
|
||||
if (drawn > 0) window()->draw();
|
||||
|
||||
return nread == 0;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool TerminalWidget::tick_cursor()
|
||||
void TerminalWidget::tick_cursor()
|
||||
{
|
||||
if (!m_cursor_enabled) return false;
|
||||
if (!m_cursor_enabled) return;
|
||||
|
||||
long now = get_time_in_milliseconds();
|
||||
long diff = now - m_last_cursor_tick;
|
||||
m_last_cursor_tick = now;
|
||||
m_cursor_activated = !m_cursor_activated;
|
||||
|
||||
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();
|
||||
|
||||
if (m_cursor_activated) draw_cursor();
|
||||
else
|
||||
erase_current_char();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
window()->draw();
|
||||
}
|
||||
|
||||
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)
|
||||
@ -471,7 +451,7 @@ bool TerminalWidget::put_code_point(wchar_t c)
|
||||
|
||||
if (should_draw_cursor)
|
||||
{
|
||||
m_current_cursor_timeout = CURSOR_TIMEOUT;
|
||||
m_cursor_timer->restart();
|
||||
m_cursor_activated = true;
|
||||
draw_cursor();
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#include <luna/EscapeSequence.h>
|
||||
#include <luna/Utf8.h>
|
||||
#include <luna/Vector.h>
|
||||
#include <os/Timer.h>
|
||||
#include <stdio.h>
|
||||
#include <termios.h>
|
||||
#include <ui/Font.h>
|
||||
@ -16,8 +17,6 @@ class TerminalWidget : public ui::Widget
|
||||
|
||||
Result<void> draw(ui::Canvas& canvas) override;
|
||||
|
||||
Result<bool> process();
|
||||
|
||||
private:
|
||||
ui::Canvas m_terminal_canvas;
|
||||
Vector<u8> m_line_buffer;
|
||||
@ -29,9 +28,7 @@ class TerminalWidget : public ui::Widget
|
||||
SharedPtr<ui::Font> m_font;
|
||||
SharedPtr<ui::Font> m_bold_font;
|
||||
|
||||
static constexpr int CURSOR_TIMEOUT = 500;
|
||||
|
||||
int m_current_cursor_timeout = CURSOR_TIMEOUT;
|
||||
OwnedPtr<os::Timer> m_cursor_timer;
|
||||
bool m_cursor_activated = false;
|
||||
bool m_cursor_enabled = true;
|
||||
|
||||
@ -45,7 +42,7 @@ class TerminalWidget : public ui::Widget
|
||||
ui::Color m_foreground_color { ui::WHITE };
|
||||
ui::Color m_background_color { ui::BLACK };
|
||||
|
||||
bool tick_cursor();
|
||||
void tick_cursor();
|
||||
|
||||
Utf8StateDecoder m_decoder;
|
||||
Option<EscapeSequenceParser> m_escape_parser;
|
||||
@ -63,4 +60,5 @@ class TerminalWidget : public ui::Widget
|
||||
bool handle_escape_sequence(wchar_t c);
|
||||
Result<bool> putchar(char c);
|
||||
bool put_code_point(wchar_t c);
|
||||
Result<void> process();
|
||||
};
|
@ -3,10 +3,10 @@
|
||||
#include <ui/App.h>
|
||||
#include <unistd.h>
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
Result<int> luna_main(int, char**)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
TRY(app.init());
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 }));
|
||||
app.set_main_window(window);
|
||||
@ -20,11 +20,5 @@ Result<int> luna_main(int argc, char** argv)
|
||||
|
||||
window->draw();
|
||||
|
||||
while (app.process_events())
|
||||
{
|
||||
bool should_sleep = TRY(terminal.process());
|
||||
if (should_sleep) usleep(10000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return app.run();
|
||||
}
|
109
gui/launch.cpp
Normal file
109
gui/launch.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @file launch.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Background process that handles detached launching of apps.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
#include <os/IPC.h>
|
||||
#include <os/LocalServer.h>
|
||||
#include <os/Process.h>
|
||||
#include <os/Security.h>
|
||||
#include <os/ipc/Launcher.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
Result<void> handle_launch_detached_message(os::IPC::ClientConnection& client)
|
||||
{
|
||||
os::Launcher::LaunchDetachedRequest request;
|
||||
if (!TRY(client.read_message(request))) return {};
|
||||
|
||||
auto path = COPY_IPC_STRING(request.command);
|
||||
|
||||
StringView args[] = { path.view() };
|
||||
|
||||
os::Process::spawn(args[0], { args, 1 }, request.search_in_path);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void handle_ipc_message(os::IPC::ClientConnection& client, u8 id, void*)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case os::Launcher::LAUNCH_DETACHED_ID: handle_launch_detached_message(client); break;
|
||||
default: os::eprintln("launch: Invalid IPC message from client!"); return;
|
||||
}
|
||||
}
|
||||
|
||||
void sigchld_handler(int)
|
||||
{
|
||||
os::Process::wait(os::Process::ANY_CHILD, nullptr);
|
||||
}
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
TRY(os::Security::pledge("stdio wpath cpath unix proc exec", NULL));
|
||||
|
||||
StringView socket_path = "/tmp/launch.sock";
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("Background process that handles detached launching of apps."_sv);
|
||||
parser.add_system_program_info("launch"_sv);
|
||||
parser.parse(argc, argv);
|
||||
|
||||
signal(SIGCHLD, sigchld_handler);
|
||||
|
||||
auto server = TRY(os::LocalServer::create(socket_path, false));
|
||||
TRY(server->listen(20));
|
||||
|
||||
// We're ready now.
|
||||
os::IPC::notify_parent();
|
||||
|
||||
Vector<OwnedPtr<os::IPC::ClientConnection>> clients;
|
||||
Vector<struct pollfd> fds;
|
||||
TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 }));
|
||||
|
||||
TRY(os::Security::pledge("stdio unix proc exec", NULL));
|
||||
|
||||
while (1)
|
||||
{
|
||||
for (auto& pfd : fds) { pfd.revents = 0; }
|
||||
|
||||
int rc = poll(fds.data(), fds.size(), 1000);
|
||||
if (!rc) continue;
|
||||
if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); }
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
{
|
||||
auto client = TRY(server->accept());
|
||||
os::println("launch: New client connected!");
|
||||
TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 }));
|
||||
|
||||
auto connection = TRY(os::IPC::ClientConnection::adopt_connection(move(client)));
|
||||
connection->set_message_handler(handle_ipc_message, nullptr);
|
||||
TRY(clients.try_append(move(connection)));
|
||||
}
|
||||
for (usize i = 0; i < clients.size(); i++)
|
||||
{
|
||||
if (fds[i + 1].revents & POLLIN) clients[i]->check_for_messages();
|
||||
if (fds[i + 1].revents & POLLHUP)
|
||||
{
|
||||
os::println("launch: Client %zu disconnected", i);
|
||||
fds.remove_at(i + 1);
|
||||
auto client = clients.remove_at(i);
|
||||
client->disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ set(SOURCES
|
||||
src/Container.cpp
|
||||
src/Button.cpp
|
||||
src/Label.cpp
|
||||
src/InputField.cpp
|
||||
src/TextInput.cpp
|
||||
)
|
||||
|
||||
add_library(ui ${SOURCES})
|
@ -9,8 +9,9 @@
|
||||
|
||||
#pragma once
|
||||
#include <luna/HashMap.h>
|
||||
#include <luna/StringView.h>
|
||||
#include <os/EventLoop.h>
|
||||
#include <os/LocalClient.h>
|
||||
#include <os/IPC.h>
|
||||
#include <ui/Window.h>
|
||||
|
||||
namespace ui
|
||||
@ -21,12 +22,12 @@ namespace ui
|
||||
App();
|
||||
~App();
|
||||
|
||||
Result<void> init(int, char**);
|
||||
Result<void> init(StringView socket_path = "/tmp/wind.sock");
|
||||
Result<int> run();
|
||||
|
||||
Rect screen_rect();
|
||||
|
||||
os::LocalClient& client()
|
||||
os::IPC::Client& client()
|
||||
{
|
||||
return *m_client;
|
||||
}
|
||||
@ -51,20 +52,22 @@ namespace ui
|
||||
Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>);
|
||||
void unregister_window(Window* window, Badge<Window>);
|
||||
|
||||
Result<void> handle_ipc_event(u8 id);
|
||||
|
||||
bool process_events();
|
||||
|
||||
static App& the();
|
||||
|
||||
private:
|
||||
static App* s_app;
|
||||
OwnedPtr<os::LocalClient> m_client;
|
||||
OwnedPtr<os::IPC::Client> m_client;
|
||||
Window* m_main_window { nullptr };
|
||||
HashMap<int, OwnedPtr<Window>> m_windows;
|
||||
bool m_should_close { false };
|
||||
os::EventLoop m_loop;
|
||||
|
||||
bool process_events();
|
||||
|
||||
Window* find_window(int id);
|
||||
|
||||
Result<void> handle_ipc_event(os::IPC::Client&, u8 id, void*);
|
||||
|
||||
friend void handle_socket_event(int, int);
|
||||
};
|
||||
}
|
@ -69,5 +69,13 @@ namespace ui
|
||||
* @param stride The number of pixels to skip to go to the next line.
|
||||
*/
|
||||
void fill(u32* pixels, int stride);
|
||||
|
||||
/**
|
||||
* @brief Fill the canvas with pixels, without doing any extra processing.
|
||||
*
|
||||
* @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 copy(u32* pixels, int stride);
|
||||
};
|
||||
};
|
42
gui/libui/include/ui/InputField.h
Normal file
42
gui/libui/include/ui/InputField.h
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file InputField.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Single line text input widget.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <os/Action.h>
|
||||
#include <ui/Font.h>
|
||||
#include <ui/TextInput.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class InputField : public ui::TextInput
|
||||
{
|
||||
public:
|
||||
InputField(SharedPtr<ui::Font> font);
|
||||
|
||||
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
|
||||
|
||||
Result<void> draw(ui::Canvas& canvas) override;
|
||||
|
||||
void clear();
|
||||
|
||||
StringView data();
|
||||
|
||||
void on_submit(os::Function<StringView>&& action)
|
||||
{
|
||||
m_on_submit_action = move(action);
|
||||
m_has_on_submit_action = true;
|
||||
}
|
||||
|
||||
private:
|
||||
SharedPtr<ui::Font> m_font;
|
||||
|
||||
os::Function<StringView> m_on_submit_action;
|
||||
bool m_has_on_submit_action { false };
|
||||
};
|
||||
}
|
41
gui/libui/include/ui/TextInput.h
Normal file
41
gui/libui/include/ui/TextInput.h
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @file TextInput.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Base class for text inputs.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/Buffer.h>
|
||||
#include <os/Timer.h>
|
||||
#include <ui/Widget.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class TextInput : public Widget
|
||||
{
|
||||
public:
|
||||
TextInput();
|
||||
|
||||
virtual Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) = 0;
|
||||
|
||||
virtual Result<void> draw(ui::Canvas& canvas) = 0;
|
||||
|
||||
protected:
|
||||
Buffer m_data;
|
||||
|
||||
usize m_cursor { 0 };
|
||||
ui::Point m_cursor_position { 0, 0 };
|
||||
|
||||
OwnedPtr<os::Timer> m_cursor_timer;
|
||||
bool m_cursor_activated = true;
|
||||
|
||||
void tick_cursor();
|
||||
void update_cursor();
|
||||
|
||||
Result<void> delete_current_character();
|
||||
Result<void> insert_character(char c);
|
||||
};
|
||||
}
|
@ -19,6 +19,24 @@
|
||||
|
||||
namespace ui
|
||||
{
|
||||
enum class WindowType : u8
|
||||
{
|
||||
Normal,
|
||||
NotDecorated,
|
||||
System,
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] Shortcut
|
||||
{
|
||||
moon::KeyCode key;
|
||||
int modifiers;
|
||||
|
||||
bool operator==(const Shortcut& other) const
|
||||
{
|
||||
return key == other.key && modifiers == other.modifiers;
|
||||
}
|
||||
};
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
@ -47,12 +65,16 @@ namespace ui
|
||||
|
||||
void close();
|
||||
|
||||
void set_special_attributes(WindowAttributes attributes);
|
||||
|
||||
Result<void> draw();
|
||||
Result<ui::EventResult> handle_mouse_leave();
|
||||
Result<ui::EventResult> handle_mouse_move(ui::Point position);
|
||||
Result<ui::EventResult> handle_mouse_buttons(ui::Point position, int buttons);
|
||||
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request);
|
||||
|
||||
Result<void> add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, os::Function<ui::Shortcut>&& action);
|
||||
|
||||
int id() const
|
||||
{
|
||||
return m_id;
|
||||
@ -71,6 +93,14 @@ namespace ui
|
||||
Option<int> m_old_mouse_buttons;
|
||||
bool m_decorated { false };
|
||||
|
||||
struct ShortcutAction
|
||||
{
|
||||
bool intercept;
|
||||
os::Function<Shortcut> action;
|
||||
};
|
||||
|
||||
HashMap<Shortcut, ShortcutAction> m_shortcuts;
|
||||
|
||||
Result<void> draw_titlebar();
|
||||
};
|
||||
}
|
@ -19,7 +19,6 @@ namespace ui
|
||||
{
|
||||
IPC_ENUM_CLIENT(ui),
|
||||
CREATE_WINDOW_RESPONSE_ID,
|
||||
WINDOW_CLOSE_REQUEST_ID,
|
||||
MOUSE_EVENT_REQUEST_ID,
|
||||
MOUSE_LEAVE_REQUEST_ID,
|
||||
GET_SCREEN_RECT_RESPONSE_ID,
|
||||
@ -34,13 +33,6 @@ namespace ui
|
||||
IPC_STRING(shm_path);
|
||||
};
|
||||
|
||||
struct WindowCloseRequest
|
||||
{
|
||||
static constexpr u8 ID = WINDOW_CLOSE_REQUEST_ID;
|
||||
|
||||
int window;
|
||||
};
|
||||
|
||||
struct MouseEventRequest
|
||||
{
|
||||
static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID;
|
@ -24,14 +24,8 @@ namespace ui
|
||||
INVALIDATE_ID,
|
||||
CLOSE_WINDOW_ID,
|
||||
GET_SCREEN_RECT_ID,
|
||||
SET_TITLEBAR_RECT_ID,
|
||||
};
|
||||
|
||||
enum class WindowType : u8
|
||||
{
|
||||
Normal,
|
||||
NotDecorated,
|
||||
System,
|
||||
SET_TITLEBAR_HEIGHT_ID,
|
||||
SET_SPECIAL_WINDOW_ATTRIBUTES_ID,
|
||||
};
|
||||
|
||||
struct CreateWindowRequest
|
||||
@ -40,7 +34,6 @@ namespace ui
|
||||
static constexpr u8 ID = CREATE_WINDOW_ID;
|
||||
|
||||
ui::Rect rect;
|
||||
WindowType type;
|
||||
};
|
||||
|
||||
struct RemoveSharedMemoryRequest
|
||||
@ -80,11 +73,24 @@ namespace ui
|
||||
int _shadow; // Unused.
|
||||
};
|
||||
|
||||
struct SetTitlebarRectRequest
|
||||
struct SetTitlebarHeightRequest
|
||||
{
|
||||
static constexpr u8 ID = SET_TITLEBAR_RECT_ID;
|
||||
static constexpr u8 ID = SET_TITLEBAR_HEIGHT_ID;
|
||||
|
||||
int window;
|
||||
ui::Rect titlebar_rect;
|
||||
int height;
|
||||
};
|
||||
|
||||
enum WindowAttributes : u8
|
||||
{
|
||||
UNFOCUSEABLE = 1,
|
||||
};
|
||||
|
||||
struct SetSpecialWindowAttributesRequest
|
||||
{
|
||||
static constexpr u8 ID = SET_SPECIAL_WINDOW_ATTRIBUTES_ID;
|
||||
|
||||
int window;
|
||||
WindowAttributes attributes;
|
||||
};
|
||||
}
|
@ -14,19 +14,14 @@
|
||||
#include <ui/ipc/Client.h>
|
||||
#include <ui/ipc/Server.h>
|
||||
|
||||
Result<void> handle_ipc_client_event(os::LocalClient&, u8 id)
|
||||
{
|
||||
return ui::App::the().handle_ipc_event(id);
|
||||
}
|
||||
|
||||
void handle_socket_event(int, int status)
|
||||
{
|
||||
if (status & POLLHUP) ui::App::the().set_should_close(true);
|
||||
if (status & POLLIN) { ui::App::the().process_events(); }
|
||||
}
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void handle_socket_event(int, int status)
|
||||
{
|
||||
if (status & POLLHUP) ui::App::the().set_should_close(true);
|
||||
if (status & POLLIN) { ui::App::the().process_events(); }
|
||||
}
|
||||
|
||||
App* App::s_app { nullptr };
|
||||
|
||||
App::App()
|
||||
@ -39,18 +34,11 @@ namespace ui
|
||||
s_app = nullptr;
|
||||
}
|
||||
|
||||
Result<void> App::init(int argc, char** argv)
|
||||
Result<void> App::init(StringView socket_path)
|
||||
{
|
||||
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));
|
||||
fcntl(m_client->fd(), F_SETFL, O_NONBLOCK);
|
||||
m_client = TRY(os::IPC::Client::connect(socket_path, false));
|
||||
m_client->set_message_handler(
|
||||
[this](os::IPC::Client& client, u8 id, void* arg) { this->handle_ipc_event(client, id, arg); }, nullptr);
|
||||
|
||||
TRY(m_loop.register_fd_listener(m_client->fd(), handle_socket_event));
|
||||
|
||||
@ -73,7 +61,7 @@ namespace ui
|
||||
Rect App::screen_rect()
|
||||
{
|
||||
ui::GetScreenRectRequest request {};
|
||||
auto response = os::IPC::send_sync<ui::GetScreenRectResponse>(*m_client, request).release_value();
|
||||
auto response = m_client->send_sync<ui::GetScreenRectResponse>(request).release_value();
|
||||
return response.rect;
|
||||
}
|
||||
|
||||
@ -97,34 +85,13 @@ 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<void> App::handle_ipc_event(u8 id)
|
||||
Result<void> App::handle_ipc_event(os::IPC::Client&, u8 id, void*)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case WINDOW_CLOSE_REQUEST_ID: {
|
||||
WindowCloseRequest request;
|
||||
READ_MESSAGE(request);
|
||||
os::eprintln("ui: Window close request from server! Shall comply.");
|
||||
auto* window = find_window(request.window);
|
||||
window->close();
|
||||
return {};
|
||||
}
|
||||
case MOUSE_EVENT_REQUEST_ID: {
|
||||
MouseEventRequest request;
|
||||
READ_MESSAGE(request);
|
||||
if (!TRY(m_client->read_message(request))) return {};
|
||||
auto* window = find_window(request.window);
|
||||
auto move_result = window->handle_mouse_move(request.position).value_or(ui::EventResult::DidNotHandle);
|
||||
auto button_result =
|
||||
@ -135,7 +102,7 @@ namespace ui
|
||||
}
|
||||
case MOUSE_LEAVE_REQUEST_ID: {
|
||||
MouseLeaveRequest request;
|
||||
READ_MESSAGE(request);
|
||||
if (!TRY(m_client->read_message(request))) return {};
|
||||
auto* window = find_window(request.window);
|
||||
if (window->handle_mouse_leave().value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
|
||||
window->draw();
|
||||
@ -143,7 +110,7 @@ namespace ui
|
||||
}
|
||||
case KEY_EVENT_REQUEST_ID: {
|
||||
KeyEventRequest request;
|
||||
READ_MESSAGE(request);
|
||||
if (!TRY(m_client->read_message(request))) return {};
|
||||
auto* window = find_window(request.window);
|
||||
if (window->handle_key_event(request).value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
|
||||
window->draw();
|
||||
@ -156,7 +123,7 @@ namespace ui
|
||||
bool App::process_events()
|
||||
{
|
||||
check(m_main_window);
|
||||
os::IPC::check_for_messages(*m_client).release_value();
|
||||
m_client->check_for_messages().release_value();
|
||||
return !m_should_close;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <luna/CString.h>
|
||||
#include <ui/Canvas.h>
|
||||
|
||||
namespace ui
|
||||
@ -59,4 +60,16 @@ namespace ui
|
||||
p += stride * sizeof(Color);
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::copy(u32* pixels, int _stride)
|
||||
{
|
||||
u8* p = ptr;
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
u32* colorp = (u32*)p;
|
||||
memcpy(colorp, pixels, width * sizeof(u32));
|
||||
pixels += _stride;
|
||||
p += stride * sizeof(Color);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <luna/String.h>
|
||||
#include <luna/RefString.h>
|
||||
#include <os/File.h>
|
||||
#include <ui/Font.h>
|
||||
|
||||
@ -49,8 +49,8 @@ namespace ui
|
||||
|
||||
Result<SharedPtr<Font>> 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"));
|
||||
auto path = TRY(RefString::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(),
|
||||
weight == FontWeight::Bold ? "Bold" : "Regular"));
|
||||
|
||||
return load(path.view());
|
||||
}
|
121
gui/libui/src/InputField.cpp
Normal file
121
gui/libui/src/InputField.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @file InputField.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Single line text input widget.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <luna/String.h>
|
||||
#include <luna/StringView.h>
|
||||
#include <luna/Utf8.h>
|
||||
#include <ui/InputField.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
InputField::InputField(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
|
||||
{
|
||||
u8 zero = 0;
|
||||
m_data.append_data(&zero, 1);
|
||||
}
|
||||
|
||||
Result<ui::EventResult> InputField::handle_key_event(const ui::KeyEventRequest& request)
|
||||
{
|
||||
// Avoid handling "key released" events
|
||||
if (!request.pressed) return ui::EventResult::DidNotHandle;
|
||||
|
||||
if (request.code == moon::K_LeftArrow)
|
||||
{
|
||||
if (m_cursor > 0) m_cursor--;
|
||||
else
|
||||
return ui::EventResult::DidNotHandle;
|
||||
update_cursor();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.code == moon::K_RightArrow)
|
||||
{
|
||||
if (m_cursor < (m_data.size() - 1)) m_cursor++;
|
||||
else
|
||||
return ui::EventResult::DidNotHandle;
|
||||
update_cursor();
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.code == moon::K_Backspace)
|
||||
{
|
||||
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
|
||||
m_cursor--;
|
||||
|
||||
delete_current_character();
|
||||
|
||||
update_cursor();
|
||||
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
if (request.letter == '\n')
|
||||
{
|
||||
if (m_has_on_submit_action)
|
||||
{
|
||||
m_on_submit_action(data());
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
return ui::EventResult::DidNotHandle;
|
||||
}
|
||||
|
||||
if (iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
|
||||
|
||||
TRY(insert_character(request.letter));
|
||||
|
||||
m_cursor++;
|
||||
|
||||
update_cursor();
|
||||
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
void InputField::clear()
|
||||
{
|
||||
m_data.try_resize(0);
|
||||
u8 zero = 0;
|
||||
m_data.append_data(&zero, 1);
|
||||
m_cursor = 0;
|
||||
}
|
||||
|
||||
Result<void> InputField::draw(ui::Canvas& canvas)
|
||||
{
|
||||
int visible_characters = canvas.width / m_font->width();
|
||||
|
||||
auto string = data();
|
||||
|
||||
Utf8StringDecoder decoder(string.chars());
|
||||
wchar_t buf[4096];
|
||||
decoder.decode(buf, sizeof(buf)).release_value();
|
||||
|
||||
int characters_to_render = (int)wcslen(buf);
|
||||
|
||||
for (int j = 0; j < visible_characters && j < characters_to_render; j++)
|
||||
{
|
||||
auto subcanvas = canvas.subcanvas({ j * m_font->width(), 0, m_font->width(), m_font->height() });
|
||||
m_font->render(buf[j], ui::WHITE, subcanvas);
|
||||
}
|
||||
|
||||
// Draw the cursor
|
||||
if ((int)m_cursor < visible_characters && m_cursor_activated)
|
||||
{
|
||||
canvas.subcanvas({ (int)m_cursor * m_font->width(), 0, 1, m_font->height() }).fill(ui::WHITE);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
StringView InputField::data()
|
||||
{
|
||||
if (!m_data.size()) return StringView {};
|
||||
return StringView { (const char*)m_data.data(), m_data.size() };
|
||||
}
|
||||
}
|
52
gui/libui/src/TextInput.cpp
Normal file
52
gui/libui/src/TextInput.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @file TextInput.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Base class for text inputs.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ui/App.h>
|
||||
#include <ui/TextInput.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
TextInput::TextInput() : Widget()
|
||||
{
|
||||
m_cursor_timer = os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }).release_value();
|
||||
}
|
||||
|
||||
void TextInput::update_cursor()
|
||||
{
|
||||
m_cursor_timer->restart();
|
||||
m_cursor_activated = true;
|
||||
}
|
||||
|
||||
Result<void> TextInput::delete_current_character()
|
||||
{
|
||||
usize size = m_data.size() - m_cursor;
|
||||
u8* slice = TRY(m_data.slice(m_cursor, size));
|
||||
memmove(slice, slice + 1, size - 1);
|
||||
TRY(m_data.try_resize(m_data.size() - 1));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> TextInput::insert_character(char c)
|
||||
{
|
||||
usize size = m_data.size() - m_cursor;
|
||||
u8* slice = TRY(m_data.slice(m_cursor, size + 1));
|
||||
memmove(slice + 1, slice, size);
|
||||
*slice = (u8)c;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void TextInput::tick_cursor()
|
||||
{
|
||||
m_cursor_activated = !m_cursor_activated;
|
||||
|
||||
window()->draw();
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ namespace ui
|
||||
|
||||
ui::CreateWindowRequest request;
|
||||
request.rect = rect;
|
||||
auto response = TRY(os::IPC::send_sync<ui::CreateWindowResponse>(App::the().client(), request));
|
||||
auto response = TRY(App::the().client().send_sync<ui::CreateWindowResponse>(request));
|
||||
|
||||
auto path = COPY_IPC_STRING(response.shm_path);
|
||||
|
||||
@ -59,10 +59,10 @@ namespace ui
|
||||
window->m_titlebar_canvas = canvas.subcanvas(ui::Rect { 0, 0, canvas.width, height });
|
||||
window->m_window_canvas = canvas.subcanvas(ui::Rect { 0, height, canvas.width, canvas.height - height });
|
||||
|
||||
ui::SetTitlebarRectRequest titlebar_request;
|
||||
titlebar_request.titlebar_rect = window->m_titlebar_canvas.rect();
|
||||
ui::SetTitlebarHeightRequest titlebar_request;
|
||||
titlebar_request.height = height;
|
||||
titlebar_request.window = response.window;
|
||||
os::IPC::send_async(App::the().client(), titlebar_request);
|
||||
App::the().client().send_async(titlebar_request);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -74,7 +74,7 @@ namespace ui
|
||||
|
||||
ui::RemoveSharedMemoryRequest shm_request;
|
||||
shm_request.window = response.window;
|
||||
os::IPC::send_async(App::the().client(), shm_request);
|
||||
App::the().client().send_async(shm_request);
|
||||
|
||||
App::the().register_window(move(window), {});
|
||||
|
||||
@ -91,7 +91,7 @@ namespace ui
|
||||
ui::SetWindowTitleRequest request;
|
||||
request.window = m_id;
|
||||
SET_IPC_STRING(request.title, title.chars());
|
||||
os::IPC::send_async(App::the().client(), request);
|
||||
App::the().client().send_async(request);
|
||||
|
||||
m_name = String::from_string_view(title).release_value();
|
||||
draw();
|
||||
@ -101,7 +101,7 @@ namespace ui
|
||||
{
|
||||
ui::InvalidateRequest request;
|
||||
request.window = m_id;
|
||||
os::IPC::send_async(App::the().client(), request);
|
||||
App::the().client().send_async(request);
|
||||
}
|
||||
|
||||
void Window::close()
|
||||
@ -110,13 +110,21 @@ namespace ui
|
||||
|
||||
ui::CloseWindowRequest request;
|
||||
request.window = m_id;
|
||||
os::IPC::send_async(app.client(), request);
|
||||
app.client().send_async(request);
|
||||
|
||||
if (this == app.main_window()) app.set_should_close(true);
|
||||
|
||||
app.unregister_window(this, {});
|
||||
}
|
||||
|
||||
void Window::set_special_attributes(WindowAttributes attributes)
|
||||
{
|
||||
ui::SetSpecialWindowAttributesRequest request;
|
||||
request.window = m_id;
|
||||
request.attributes = attributes;
|
||||
App::the().client().send_async(request);
|
||||
}
|
||||
|
||||
Result<void> Window::draw()
|
||||
{
|
||||
if (m_background.has_value()) m_window_canvas.fill(*m_background);
|
||||
@ -204,7 +212,25 @@ namespace ui
|
||||
|
||||
Result<ui::EventResult> Window::handle_key_event(const ui::KeyEventRequest& request)
|
||||
{
|
||||
if (request.pressed)
|
||||
{
|
||||
auto* shortcut = m_shortcuts.try_get_ref({ request.code, request.modifiers });
|
||||
if (shortcut)
|
||||
{
|
||||
shortcut->action({ request.code, request.modifiers });
|
||||
if (shortcut->intercept) return ui::EventResult::DidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_main_widget) return ui::EventResult::DidNotHandle;
|
||||
return m_main_widget->handle_key_event(request);
|
||||
}
|
||||
|
||||
Result<void> Window::add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept,
|
||||
os::Function<ui::Shortcut>&& action)
|
||||
{
|
||||
TRY(m_shortcuts.try_set(shortcut, { intercept, move(action) }));
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
171
gui/loginui.cpp
Normal file
171
gui/loginui.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* @file loginui.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Graphical login prompt.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <luna/String.h>
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
#include <os/FileSystem.h>
|
||||
#include <os/IPC.h>
|
||||
#include <os/Process.h>
|
||||
#include <pwd.h>
|
||||
#include <shadow.h>
|
||||
#include <sys/stat.h>
|
||||
#include <ui/App.h>
|
||||
#include <ui/Button.h>
|
||||
#include <ui/InputField.h>
|
||||
#include <ui/Label.h>
|
||||
#include <ui/Layout.h>
|
||||
#include <unistd.h>
|
||||
|
||||
enum Stage
|
||||
{
|
||||
UsernameInput,
|
||||
PasswordInput,
|
||||
};
|
||||
|
||||
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89);
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
StringView username;
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("Login prompt for a graphical UI session.");
|
||||
parser.add_system_program_info("loginui"_sv);
|
||||
// FIXME: Make this a config option instead of a switch.
|
||||
// Also, calling "loginui --autologin=user" is functionally identical to calling "startui --user=user", the only
|
||||
// difference is that it makes the init config easier to change (only adding or removing the autologin flag, instead
|
||||
// of changing the program to use)
|
||||
parser.add_value_argument(username, ' ', "autologin", "login as a specific user without prompting");
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (geteuid() != 0)
|
||||
{
|
||||
os::eprintln("error: %s can only be started as root.", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
setsid();
|
||||
|
||||
bool success = os::IPC::Notifier::run_and_wait(
|
||||
[&] {
|
||||
StringView wind_command[] = { "/usr/bin/wind" };
|
||||
os::Process::spawn(wind_command[0], Slice<StringView>(wind_command, 1));
|
||||
},
|
||||
1000);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
os::eprintln("loginui: failed to start wind, timed out");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!username.is_empty())
|
||||
{
|
||||
auto flag = String::format("--user=%s"_sv, username.chars()).release_value();
|
||||
|
||||
StringView startui_command[] = { "/usr/bin/startui", flag.view() };
|
||||
os::Process::exec(startui_command[0], Slice<StringView>(startui_command, 2));
|
||||
unreachable();
|
||||
}
|
||||
|
||||
ui::App app;
|
||||
TRY(app.init());
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 }));
|
||||
app.set_main_window(window);
|
||||
|
||||
window->set_title("Log in");
|
||||
window->set_background(BACKGROUND_COLOR);
|
||||
|
||||
ui::VerticalLayout main_layout;
|
||||
window->set_main_widget(main_layout);
|
||||
|
||||
ui::Label label("Username:");
|
||||
main_layout.add_widget(label);
|
||||
|
||||
ui::InputField input(ui::Font::default_font());
|
||||
main_layout.add_widget(input);
|
||||
|
||||
ui::Label error("");
|
||||
error.set_font(ui::Font::default_bold_font());
|
||||
error.set_color(ui::RED);
|
||||
main_layout.add_widget(error);
|
||||
|
||||
Stage stage = Stage::UsernameInput;
|
||||
struct passwd* pw;
|
||||
|
||||
input.on_submit([&](StringView data) {
|
||||
error.set_text("");
|
||||
if (stage == Stage::UsernameInput)
|
||||
{
|
||||
struct passwd* entry = getpwnam(data.chars());
|
||||
if (!entry)
|
||||
{
|
||||
error.set_text("User not found.");
|
||||
input.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
pw = entry;
|
||||
stage = Stage::PasswordInput;
|
||||
label.set_text("Password:");
|
||||
|
||||
String title = String::format("Log in: %s"_sv, data.chars()).release_value();
|
||||
window->set_title(title.view());
|
||||
|
||||
input.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* passwd = pw->pw_passwd;
|
||||
|
||||
// If the user's password entry is 'x', read their password from the shadow file instead.
|
||||
if (!strcmp(pw->pw_passwd, "x"))
|
||||
{
|
||||
struct spwd* sp = getspnam(pw->pw_name);
|
||||
|
||||
if (!sp)
|
||||
{
|
||||
error.set_text("User not found in shadow file.");
|
||||
input.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
endspent();
|
||||
|
||||
passwd = sp->sp_pwdp;
|
||||
}
|
||||
|
||||
if (!strcmp(passwd, "!"))
|
||||
{
|
||||
error.set_text("User's password is disabled.");
|
||||
input.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(data.chars(), passwd))
|
||||
{
|
||||
error.set_text("Incorrect password.");
|
||||
input.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto flag = String::format("--user=%s"_sv, pw->pw_name).release_value();
|
||||
|
||||
StringView startui_command[] = { "/usr/bin/startui", flag.view() };
|
||||
os::Process::exec(startui_command[0], Slice<StringView>(startui_command, 2));
|
||||
unreachable();
|
||||
}
|
||||
});
|
||||
|
||||
return app.run();
|
||||
}
|
35
gui/run.cpp
Normal file
35
gui/run.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file run.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Tiny command-line utility to start a detached program in the current GUI session.
|
||||
*
|
||||
* @copyright Copyright (c) 2024, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
#include <os/LocalClient.h>
|
||||
#include <os/ipc/Launcher.h>
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
StringView program;
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("Start a detached program in the current GUI session."_sv);
|
||||
parser.add_system_program_info("run"_sv);
|
||||
parser.add_positional_argument(program, "program", true);
|
||||
parser.parse(argc, argv);
|
||||
|
||||
OwnedPtr<os::IPC::Client> launcher_client = TRY(os::IPC::Client::connect("/tmp/launch.sock", false));
|
||||
|
||||
os::println("Requesting to start program '%s'...", program.chars());
|
||||
|
||||
os::Launcher::LaunchDetachedRequest request;
|
||||
SET_IPC_STRING(request.command, program.chars());
|
||||
request.search_in_path = true;
|
||||
launcher_client->send_async(request);
|
||||
|
||||
return 0;
|
||||
}
|
22
gui/wind/Client.h
Normal file
22
gui/wind/Client.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "IPC.h"
|
||||
#include "Window.h"
|
||||
#include <os/IPC.h>
|
||||
|
||||
struct Client
|
||||
{
|
||||
OwnedPtr<os::IPC::ClientConnection> conn;
|
||||
Vector<Window*> windows;
|
||||
const bool privileged { false };
|
||||
bool should_be_disconnected { false };
|
||||
|
||||
Client(OwnedPtr<os::IPC::ClientConnection>&& client, bool priv)
|
||||
#ifdef CLIENT_IMPLEMENTATION
|
||||
: conn(move(client)), windows(), privileged(priv)
|
||||
{
|
||||
conn->set_message_handler(wind::handle_ipc_message, this);
|
||||
}
|
||||
#else
|
||||
;
|
||||
#endif
|
||||
};
|
201
gui/wind/IPC.cpp
Normal file
201
gui/wind/IPC.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
#include "IPC.h"
|
||||
#include "Mouse.h"
|
||||
#include "Screen.h"
|
||||
#include <luna/Alignment.h>
|
||||
#include <luna/String.h>
|
||||
#include <os/File.h>
|
||||
#include <os/SharedMemory.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
|
||||
#define TRY_OR_IPC_ERROR(expr) \
|
||||
({ \
|
||||
auto _expr_rc = (expr); \
|
||||
if (!_expr_rc.has_value()) \
|
||||
{ \
|
||||
client.conn->send_error(_expr_rc.error()); \
|
||||
return {}; \
|
||||
} \
|
||||
_expr_rc.release_value(); \
|
||||
})
|
||||
|
||||
#define CHECK_WINDOW_ID(request, context) \
|
||||
do { \
|
||||
if ((usize)request.window >= client.windows.size() || !client.windows[request.window]) \
|
||||
{ \
|
||||
os::eprintln("wind: Window id is invalid! (%s)", context); \
|
||||
return {}; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static Result<void> handle_create_window_message(Client& client)
|
||||
{
|
||||
ui::CreateWindowRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
request.rect = request.rect.normalized();
|
||||
|
||||
auto name = TRY_OR_IPC_ERROR(String::from_cstring("Window"));
|
||||
|
||||
auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn->fd(), time(NULL)));
|
||||
|
||||
auto* window = new (std::nothrow) Window(request.rect, move(name));
|
||||
if (!window)
|
||||
{
|
||||
client.conn->send_error(ENOMEM);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto guard = make_scope_guard([window] {
|
||||
g_windows.remove(window);
|
||||
delete window;
|
||||
});
|
||||
|
||||
window->pixels = (u32*)TRY_OR_IPC_ERROR(
|
||||
os::SharedMemory::create(shm_path.view(), window->surface.height * window->surface.width * 4));
|
||||
|
||||
TRY_OR_IPC_ERROR(client.windows.try_append(window));
|
||||
int id = static_cast<int>(client.windows.size() - 1);
|
||||
|
||||
// No more fallible operations, this operation is guaranteed to succeed now.
|
||||
guard.deactivate();
|
||||
|
||||
window->client = &client;
|
||||
window->id = id;
|
||||
|
||||
ui::CreateWindowResponse response;
|
||||
response.window = id;
|
||||
SET_IPC_STRING(response.shm_path, shm_path.chars());
|
||||
window->shm_path = move(shm_path);
|
||||
client.conn->send_async(response);
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_remove_shm_message(Client& client)
|
||||
{
|
||||
ui::RemoveSharedMemoryRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
CHECK_WINDOW_ID(request, "RemoveShm");
|
||||
|
||||
shm_unlink(client.windows[request.window]->shm_path.chars());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_set_window_title_message(Client& client)
|
||||
{
|
||||
ui::SetWindowTitleRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
auto name = COPY_IPC_STRING(request.title);
|
||||
|
||||
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
|
||||
|
||||
CHECK_WINDOW_ID(request, "SetWindowTitle");
|
||||
|
||||
client.windows[request.window]->name = move(name);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_invalidate_message(Client& client)
|
||||
{
|
||||
ui::InvalidateRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
CHECK_WINDOW_ID(request, "Invalidate");
|
||||
|
||||
client.windows[request.window]->dirty = true;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_close_window_message(Client& client)
|
||||
{
|
||||
ui::CloseWindowRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
CHECK_WINDOW_ID(request, "CloseWindow");
|
||||
|
||||
auto* window = client.windows[request.window];
|
||||
client.windows[request.window] = nullptr;
|
||||
g_windows.remove(window);
|
||||
Mouse::the().window_did_close(window);
|
||||
delete window;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_get_screen_rect_message(Client& client)
|
||||
{
|
||||
ui::GetScreenRectRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {}; // Kinda pointless, but required.
|
||||
|
||||
ui::GetScreenRectResponse response;
|
||||
response.rect = Screen::the().canvas().rect();
|
||||
client.conn->send_async(response);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_set_titlebar_height_message(Client& client)
|
||||
{
|
||||
ui::SetTitlebarHeightRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
if (request.height < 0) request.height = 0;
|
||||
|
||||
CHECK_WINDOW_ID(request, "SetTitlebarHeight");
|
||||
|
||||
auto* window = client.windows[request.window];
|
||||
|
||||
if (request.height > window->surface.height)
|
||||
{
|
||||
os::eprintln("wind: SetTitlebarHeight: titlebar height bigger than window!");
|
||||
return {};
|
||||
}
|
||||
|
||||
window->titlebar = ui::Rect { 0, 0, window->surface.width, request.height };
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_set_special_window_attributes_message(Client& client)
|
||||
{
|
||||
ui::SetSpecialWindowAttributesRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
if (!client.privileged)
|
||||
{
|
||||
os::eprintln(
|
||||
"wind: Unprivileged client trying to call privileged request (SetSpecialWindowAttributes), disconnecting!");
|
||||
client.should_be_disconnected = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
CHECK_WINDOW_ID(request, "SetSpecialWindowAttributes");
|
||||
|
||||
client.windows[request.window]->attributes = request.attributes;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
namespace wind
|
||||
{
|
||||
void handle_ipc_message(os::IPC::ClientConnection&, u8 id, void* c)
|
||||
{
|
||||
Client& client = *(Client*)c;
|
||||
switch (id)
|
||||
{
|
||||
case ui::CREATE_WINDOW_ID: handle_create_window_message(client); break;
|
||||
case ui::REMOVE_SHM_ID: handle_remove_shm_message(client); break;
|
||||
case ui::SET_WINDOW_TITLE_ID: handle_set_window_title_message(client); break;
|
||||
case ui::INVALIDATE_ID: handle_invalidate_message(client); break;
|
||||
case ui::CLOSE_WINDOW_ID: handle_close_window_message(client); break;
|
||||
case ui::GET_SCREEN_RECT_ID: handle_get_screen_rect_message(client); break;
|
||||
case ui::SET_TITLEBAR_HEIGHT_ID: handle_set_titlebar_height_message(client); break;
|
||||
case ui::SET_SPECIAL_WINDOW_ATTRIBUTES_ID: handle_set_special_window_attributes_message(client); break;
|
||||
default: os::eprintln("wind: Invalid IPC message from client!"); return;
|
||||
}
|
||||
}
|
||||
}
|
8
gui/wind/IPC.h
Normal file
8
gui/wind/IPC.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "Client.h"
|
||||
#include <ui/ipc/Server.h>
|
||||
|
||||
namespace wind
|
||||
{
|
||||
void handle_ipc_message(os::IPC::ClientConnection& connection, u8 id, void* c);
|
||||
}
|
@ -64,7 +64,7 @@ void Mouse::update(const moon::MousePacket& packet)
|
||||
{
|
||||
if (window->surface.contains(m_position))
|
||||
{
|
||||
window->focus();
|
||||
if (!(window->attributes & ui::UNFOCUSEABLE)) window->focus();
|
||||
|
||||
if (window->surface.absolute(window->titlebar).contains(m_position))
|
||||
{
|
||||
@ -91,7 +91,7 @@ void Mouse::update(const moon::MousePacket& packet)
|
||||
request.window = window->id;
|
||||
request.position = window->surface.relative(m_position);
|
||||
request.buttons = packet.buttons;
|
||||
os::IPC::send_async(window->client->conn, request);
|
||||
window->client->conn->send_async(request);
|
||||
new_active_window = window;
|
||||
break;
|
||||
}
|
||||
@ -103,7 +103,7 @@ void Mouse::update(const moon::MousePacket& packet)
|
||||
{
|
||||
ui::MouseLeaveRequest request;
|
||||
request.window = m_active_window->id;
|
||||
os::IPC::send_async(m_active_window->client->conn, request);
|
||||
m_active_window->client->conn->send_async(request);
|
||||
}
|
||||
m_active_window = new_active_window;
|
||||
}
|
@ -12,7 +12,7 @@ void Window::draw(ui::Canvas& screen)
|
||||
dirty = false;
|
||||
|
||||
auto window = screen.subcanvas(surface);
|
||||
window.fill(pixels, surface.width);
|
||||
window.copy(pixels, surface.width);
|
||||
}
|
||||
|
||||
void Window::focus()
|
@ -18,6 +18,7 @@ struct Window : public LinkedListNode<Window>
|
||||
bool dirty { false };
|
||||
Client* client;
|
||||
int id;
|
||||
ui::WindowAttributes attributes { 0 };
|
||||
|
||||
Window(ui::Rect, String&&);
|
||||
~Window();
|
@ -1,27 +1,28 @@
|
||||
#define CLIENT_IMPLEMENTATION
|
||||
#include "Client.h"
|
||||
#include "IPC.h"
|
||||
#include "Keyboard.h"
|
||||
#include "Mouse.h"
|
||||
#include "Screen.h"
|
||||
#include "Window.h"
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
#include <moon/Keyboard.h>
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
#include <os/IPC.h>
|
||||
#include <os/LocalServer.h>
|
||||
#include <os/Process.h>
|
||||
#include <os/Security.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static constexpr uid_t WIND_USER_ID = 2;
|
||||
static constexpr gid_t WIND_GROUP_ID = 2;
|
||||
static constexpr gid_t WSYS_GROUP_ID = 3;
|
||||
|
||||
static void debug(const Vector<OwnedPtr<Client>>& clients)
|
||||
{
|
||||
os::println("--- wind: DEBUG OUTPUT ---");
|
||||
@ -30,14 +31,14 @@ static void debug(const Vector<OwnedPtr<Client>>& 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 --");
|
||||
|
||||
for (const auto& window : g_windows)
|
||||
{
|
||||
os::println("Window of client (fd %d), id %d, %sdirty (\"%s\") (%d,%d,%d,%d)", window->client->conn.fd(),
|
||||
os::println("Window of client (fd %d), id %d, %sdirty (\"%s\") (%d,%d,%d,%d)", window->client->conn->fd(),
|
||||
window->id, window->dirty ? "" : "not ", window->name.chars(), window->surface.pos.x,
|
||||
window->surface.pos.y, window->surface.width, window->surface.height);
|
||||
}
|
||||
@ -53,31 +54,6 @@ static void debug(const Vector<OwnedPtr<Client>>& clients)
|
||||
os::println("--- wind: END DEBUG OUTPUT ---");
|
||||
}
|
||||
|
||||
Result<void> set_supplementary_groups(const char* name)
|
||||
{
|
||||
Vector<gid_t> extra_groups;
|
||||
|
||||
setgrent();
|
||||
group* grp;
|
||||
while ((grp = getgrent()))
|
||||
{
|
||||
for (char** user = grp->gr_mem; *user; user++)
|
||||
{
|
||||
if (!strcmp(*user, name))
|
||||
{
|
||||
os::println("Adding supplementary group: %d", grp->gr_gid);
|
||||
TRY(extra_groups.try_append(grp->gr_gid));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
endgrent();
|
||||
|
||||
if (setgroups(static_cast<int>(extra_groups.size()), extra_groups.data()) < 0) return err(errno);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
srand((unsigned)time(NULL));
|
||||
@ -85,13 +61,14 @@ Result<int> luna_main(int argc, char** argv)
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL));
|
||||
|
||||
StringView socket_path = "/tmp/wind.sock";
|
||||
StringView user;
|
||||
StringView system_socket_path = "/tmp/wsys.sock";
|
||||
|
||||
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.add_value_argument(system_socket_path, ' ', "system-socket"_sv,
|
||||
"the path for the system IPC socket, for privileged clients"_sv);
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (geteuid() != 0)
|
||||
@ -114,6 +91,24 @@ Result<int> luna_main(int argc, char** argv)
|
||||
|
||||
Mouse mouse_pointer { screen.canvas() };
|
||||
|
||||
umask(0002);
|
||||
|
||||
// Set permissions to wind:wsys temporarily, to create /tmp/wsys.sock with those privileges.
|
||||
setegid(WSYS_GROUP_ID);
|
||||
seteuid(WIND_USER_ID);
|
||||
|
||||
auto system_server = TRY(os::LocalServer::create(system_socket_path, false));
|
||||
TRY(system_server->listen(20));
|
||||
|
||||
seteuid(0);
|
||||
|
||||
// Opened all necessary files as root, drop privileges now.
|
||||
setgid(WIND_GROUP_ID);
|
||||
setuid(WIND_USER_ID);
|
||||
|
||||
auto server = TRY(os::LocalServer::create(socket_path, false));
|
||||
TRY(server->listen(20));
|
||||
|
||||
int fd = open("/dev/null", O_RDONLY);
|
||||
if (fd >= 0)
|
||||
{
|
||||
@ -121,44 +116,8 @@ Result<int> luna_main(int argc, char** argv)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
setegid(2);
|
||||
seteuid(2);
|
||||
|
||||
if (setsid() < 0) perror("setsid");
|
||||
|
||||
mode_t mask = umask(0002);
|
||||
|
||||
auto server = TRY(os::LocalServer::create(socket_path, false));
|
||||
TRY(server->listen(20));
|
||||
|
||||
umask(mask);
|
||||
|
||||
seteuid(0);
|
||||
|
||||
clearenv();
|
||||
|
||||
pid_t child = TRY(os::Process::fork());
|
||||
if (!child)
|
||||
{
|
||||
if (!user.is_empty())
|
||||
{
|
||||
auto* pwd = getpwnam(user.chars());
|
||||
if (pwd)
|
||||
{
|
||||
TRY(set_supplementary_groups(user.chars()));
|
||||
setgid(pwd->pw_gid);
|
||||
setuid(pwd->pw_uid);
|
||||
}
|
||||
}
|
||||
|
||||
StringView args[] = { "/usr/bin/init"_sv, "--user"_sv };
|
||||
TRY(os::Process::exec("/usr/bin/init"_sv, Slice<StringView> { args, 2 }, false));
|
||||
}
|
||||
|
||||
umask(0002);
|
||||
|
||||
setuid(2);
|
||||
setgid(2);
|
||||
// We're ready now.
|
||||
os::IPC::notify_parent();
|
||||
|
||||
ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10);
|
||||
|
||||
@ -167,6 +126,7 @@ Result<int> luna_main(int argc, char** argv)
|
||||
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(fds.try_append({ .fd = system_server->fd(), .events = POLLIN, .revents = 0 }));
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec", NULL));
|
||||
|
||||
@ -199,18 +159,41 @@ Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
auto* window = g_windows.last().value();
|
||||
request.window = window->id;
|
||||
os::IPC::send_async(window->client->conn, request);
|
||||
window->client->conn->send_async(request);
|
||||
}
|
||||
}
|
||||
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 }));
|
||||
|
||||
auto connection = TRY(os::IPC::ClientConnection::adopt_connection(move(client)));
|
||||
|
||||
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(connection), false)));
|
||||
TRY(clients.try_append(move(c)));
|
||||
}
|
||||
if (fds[3].revents & POLLIN)
|
||||
{
|
||||
auto client = TRY(system_server->accept());
|
||||
os::println("wind: New privileged client connected!");
|
||||
TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 }));
|
||||
|
||||
auto connection = TRY(os::IPC::ClientConnection::adopt_connection(move(client)));
|
||||
|
||||
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(connection), true)));
|
||||
TRY(clients.try_append(move(c)));
|
||||
}
|
||||
for (usize i = 0; i < clients.size(); i++)
|
||||
{
|
||||
if (fds[i + 3].revents & POLLIN) wind::handle_ipc(*clients[i]);
|
||||
if (fds[i + 3].revents & POLLHUP)
|
||||
if (fds[i + 4].revents & POLLIN) clients[i]->conn->check_for_messages();
|
||||
if (fds[i + 4].revents & POLLHUP) clients[i]->should_be_disconnected = true;
|
||||
if (clients[i]->should_be_disconnected)
|
||||
{
|
||||
os::println("wind: Client %d disconnected", i);
|
||||
fds.remove_at(i + 3);
|
||||
os::println("wind: Client %zu disconnected", i);
|
||||
fds.remove_at(i + 4);
|
||||
auto client = clients.remove_at(i);
|
||||
client->conn.disconnect();
|
||||
client->conn->disconnect();
|
||||
for (auto& window : client->windows)
|
||||
{
|
||||
if (window)
|
||||
@ -222,13 +205,5 @@ Result<int> luna_main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 }));
|
||||
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(client))));
|
||||
TRY(clients.try_append(move(c)));
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
#include <luna/Format.h>
|
||||
#include <luna/SourceLocation.h>
|
||||
#include <luna/Spinlock.h>
|
||||
#include <luna/StringBuilder.h>
|
||||
|
||||
static bool g_debug_enabled = true;
|
||||
static bool g_serial_enabled = true;
|
||||
@ -174,3 +175,23 @@ static bool g_check_already_failed = false;
|
||||
}
|
||||
CPU::efficient_halt();
|
||||
}
|
||||
|
||||
Result<void> hexdump(void* data, usize size)
|
||||
{
|
||||
StringBuilder sb;
|
||||
|
||||
u8* ptr = (u8*)data;
|
||||
|
||||
while (size)
|
||||
{
|
||||
TRY(sb.format("%#2x ", *ptr));
|
||||
ptr++;
|
||||
size--;
|
||||
}
|
||||
|
||||
auto message = TRY(sb.string());
|
||||
|
||||
kdbgln("hexdump: %s", message.chars());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ void set_text_console_initialized();
|
||||
#define kwarnln(...) log(LogLevel::Warn, __VA_ARGS__)
|
||||
#define kerrorln(...) log(LogLevel::Error, __VA_ARGS__)
|
||||
|
||||
Result<void> hexdump(void* data, usize size);
|
||||
|
||||
[[noreturn]] extern void __critical_error_handler(SourceLocation location, const char* expr, const char* failmsg,
|
||||
const char* errmsg);
|
||||
|
||||
|
@ -117,6 +117,30 @@ namespace moon
|
||||
K_CH46, // .
|
||||
K_CH47, // /
|
||||
K_CH48, // Space
|
||||
// Multimedia keys
|
||||
K_MediaPrev,
|
||||
K_MediaNext,
|
||||
K_MediaMute,
|
||||
K_MediaCalc,
|
||||
K_MediaPlay,
|
||||
K_MediaStop,
|
||||
K_MediaVolDown,
|
||||
K_MediaVolUp,
|
||||
// WWW keys
|
||||
K_WWWHome,
|
||||
K_WWWSearch,
|
||||
K_WWWFavorites,
|
||||
K_WWWRefresh,
|
||||
K_WWWStop,
|
||||
K_WWWForward,
|
||||
K_WWWBack,
|
||||
K_WWWMyComputer,
|
||||
K_WWWEmail,
|
||||
K_WWWSelect,
|
||||
// Power keys
|
||||
K_ACPIPower,
|
||||
K_ACPISleep,
|
||||
K_ACPIWake,
|
||||
// Unknown key
|
||||
K_Unknown,
|
||||
};
|
||||
|
@ -1,24 +1,16 @@
|
||||
#pragma once
|
||||
#include "api/Keyboard.h"
|
||||
#include <luna/Option.h>
|
||||
#include <luna/Vector.h>
|
||||
|
||||
namespace Keyboard
|
||||
{
|
||||
struct TTYKeyboardState
|
||||
{
|
||||
bool ignore_next { false };
|
||||
bool left_shift { false };
|
||||
bool right_shift { false };
|
||||
bool left_control { false };
|
||||
bool capslock { false };
|
||||
};
|
||||
|
||||
struct KeyboardState
|
||||
{
|
||||
bool ignore_next { false };
|
||||
Vector<u8> key_state;
|
||||
bool parsing_ext { false };
|
||||
bool parsing_pause { false };
|
||||
};
|
||||
|
||||
Option<char> decode_scancode_tty(u8 scancode, TTYKeyboardState& state);
|
||||
|
||||
Option<moon::KeyboardPacket> decode_scancode(u8 scancode, KeyboardState& state);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ namespace MMU
|
||||
NoExecute = 4,
|
||||
WriteThrough = 8,
|
||||
CacheDisable = 16,
|
||||
Global = 32,
|
||||
};
|
||||
|
||||
enum class UseHugePages
|
||||
|
@ -25,6 +25,13 @@ enable_nx:
|
||||
wrmsr
|
||||
ret
|
||||
|
||||
global enable_global_pages
|
||||
enable_global_pages:
|
||||
mov rax, cr4
|
||||
or ax, 1 << 7
|
||||
mov cr4, rax
|
||||
ret
|
||||
|
||||
global load_gdt
|
||||
load_gdt:
|
||||
cli
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
extern "C" void enable_sse();
|
||||
extern "C" void enable_write_protect();
|
||||
extern "C" void enable_global_pages();
|
||||
extern "C" void enable_nx();
|
||||
|
||||
extern void setup_gdt();
|
||||
@ -75,16 +76,6 @@ void decode_page_fault_error_code(u64 code)
|
||||
(code & PF_RESERVED) ? " | Reserved bits set" : "", (code & PF_NX_VIOLATION) ? " | NX violation" : "");
|
||||
}
|
||||
|
||||
static void check_stack(Thread* current, Registers* regs)
|
||||
{
|
||||
if (regs->rsp < current->stack.bottom() || regs->rsp >= current->stack.top())
|
||||
kerrorln("Abnormal stack (RSP outside the normal range, %.16lx-%.16lx)", current->stack.bottom(),
|
||||
current->stack.top());
|
||||
|
||||
if (regs->rsp >= (current->stack.bottom() - ARCH_PAGE_SIZE) && regs->rsp < current->stack.bottom())
|
||||
kerrorln("Likely stack overflow (CPU exception inside guard page)");
|
||||
}
|
||||
|
||||
void handle_cpu_exception(int signo, const char* err, Registers* regs)
|
||||
{
|
||||
if (err) kerrorln("Caught CPU exception: %s", err);
|
||||
@ -98,7 +89,7 @@ void handle_cpu_exception(int signo, const char* err, Registers* regs)
|
||||
if (!is_in_kernel(regs))
|
||||
{
|
||||
auto* current = Scheduler::current();
|
||||
check_stack(current, regs);
|
||||
if (current->check_stack_on_exception(regs->rsp)) return;
|
||||
|
||||
current->send_signal(signo);
|
||||
current->process_pending_signals(regs);
|
||||
@ -278,6 +269,7 @@ namespace CPU
|
||||
void platform_init()
|
||||
{
|
||||
enable_sse();
|
||||
enable_global_pages();
|
||||
// enable_write_protect();
|
||||
if (test_nx()) enable_nx();
|
||||
else
|
||||
|
@ -13,104 +13,9 @@ static bool is_key_released(u8& scancode)
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr u8 EXTENDED_KEY_CODE = 0xe0;
|
||||
constexpr u8 LEFT_SHIFT = 0x2a;
|
||||
constexpr u8 RIGHT_SHIFT = 0x36;
|
||||
constexpr u8 CAPS_LOCK = 0x3a;
|
||||
|
||||
constexpr u8 LEFT_CONTROL = 0x1D;
|
||||
constexpr u8 LEFT_ALT = 0x38;
|
||||
constexpr u8 F11 = 0x57;
|
||||
constexpr u8 F12 = 0x58;
|
||||
|
||||
static bool should_ignore_key(u8 scancode)
|
||||
{
|
||||
return (scancode > 0x3A && scancode < 0x47) || scancode == F11 || scancode == F12;
|
||||
}
|
||||
|
||||
constexpr char key_table[] = {
|
||||
'\0',
|
||||
'\1', // escape
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
|
||||
'\t', // tab
|
||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
|
||||
'\0', // left ctrl
|
||||
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
|
||||
'\0', // left shift
|
||||
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
|
||||
'\0', // right shift
|
||||
'*', // keypad *
|
||||
'\0', // left alt
|
||||
' ',
|
||||
'\0', // caps lock
|
||||
'\0', // f1
|
||||
'\0', // f2
|
||||
'\0', // f3
|
||||
'\0', // f4
|
||||
'\0', // f5
|
||||
'\0', // f6
|
||||
'\0', // f7
|
||||
'\0', // f8
|
||||
'\0', // f9
|
||||
'\0', // f10
|
||||
'\0', // num lock
|
||||
'\0', // scroll lock
|
||||
'7', // keypad 7
|
||||
'8', // keypad 8
|
||||
'9', // keypad 9
|
||||
'-', // keypad -
|
||||
'4', // keypad 4
|
||||
'5', // keypad 5
|
||||
'6', // keypad 6
|
||||
'+', // keypad +
|
||||
'1', // keypad 1
|
||||
'2', // keypad 2
|
||||
'3', // keypad 3
|
||||
'0', // keypad 0
|
||||
'.', // keypad .
|
||||
};
|
||||
|
||||
constexpr char shifted_key_table[] = {
|
||||
'\0',
|
||||
'\1', // escape
|
||||
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
|
||||
'\t', // tab
|
||||
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
|
||||
'\0', // left ctrl
|
||||
'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
|
||||
'\0', // left shift
|
||||
'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
|
||||
'\0', // right shift
|
||||
'*', // keypad *
|
||||
'\0', // left alt
|
||||
' ',
|
||||
'\0', // caps lock
|
||||
'\0', // f1
|
||||
'\0', // f2
|
||||
'\0', // f3
|
||||
'\0', // f4
|
||||
'\0', // f5
|
||||
'\0', // f6
|
||||
'\0', // f7
|
||||
'\0', // f8
|
||||
'\0', // f9
|
||||
'\0', // f10
|
||||
'\0', // num lock
|
||||
'\0', // scroll lock
|
||||
'7', // keypad 7
|
||||
'8', // keypad 8
|
||||
'9', // keypad 9
|
||||
'-', // keypad -
|
||||
'4', // keypad 4
|
||||
'5', // keypad 5
|
||||
'6', // keypad 6
|
||||
'+', // keypad +
|
||||
'1', // keypad 1
|
||||
'2', // keypad 2
|
||||
'3', // keypad 3
|
||||
'0', // keypad 0
|
||||
'.', // keypad .
|
||||
};
|
||||
constexpr static u8 print_screen_pressed[] = { 0xe0, 0x2a, 0xe0, 0x37 };
|
||||
constexpr static u8 print_screen_released[] = { 0xe0, 0xb7, 0xe0, 0xaa };
|
||||
constexpr static u8 pause[] = { 0xe1, 0x1d, 0x45, 0xe1, 0x9d, 0xc5 };
|
||||
|
||||
using namespace moon;
|
||||
|
||||
@ -176,95 +81,113 @@ constexpr KeyCode keycode_table[] = {
|
||||
K_F11, K_F12,
|
||||
};
|
||||
|
||||
static bool is_shifted(const Keyboard::TTYKeyboardState& state)
|
||||
{
|
||||
if (state.capslock) return !(state.left_shift || state.right_shift);
|
||||
return state.left_shift || state.right_shift;
|
||||
}
|
||||
constexpr KeyCode extended_keycode_table[] = {
|
||||
K_MediaPrev, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown,
|
||||
K_Unknown, K_Unknown, K_MediaNext, K_Unknown, K_Unknown, K_KeypadEnter, K_RightControl,
|
||||
K_Unknown, K_Unknown, K_MediaMute, K_MediaCalc, K_MediaPlay, K_Unknown, K_MediaStop,
|
||||
K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown,
|
||||
K_Unknown, K_Unknown, K_MediaVolDown, K_Unknown, K_MediaVolUp, K_Unknown, K_WWWHome,
|
||||
K_Unknown, K_Unknown, K_KeypadDiv, K_Unknown, K_Unknown, K_RightAlt, K_Unknown,
|
||||
K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown,
|
||||
K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Home,
|
||||
K_UpArrow, K_PageUp, K_Unknown, K_LeftArrow, K_Unknown, K_RightArrow, K_Unknown,
|
||||
K_End, K_DownArrow, K_PageDown, K_Insert, K_Delete, K_Unknown, K_Unknown,
|
||||
K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Super, K_Super,
|
||||
K_Menu, K_ACPIPower, K_ACPISleep, K_Unknown, K_Unknown, K_Unknown, K_ACPIWake,
|
||||
K_Unknown, K_WWWSearch, K_WWWFavorites, K_WWWRefresh, K_WWWStop, K_WWWForward, K_WWWBack,
|
||||
K_WWWMyComputer, K_WWWEmail, K_WWWSelect
|
||||
};
|
||||
|
||||
namespace Keyboard
|
||||
{
|
||||
Option<char> decode_scancode_tty(u8 scancode, TTYKeyboardState& state)
|
||||
{
|
||||
if (state.ignore_next)
|
||||
{
|
||||
state.ignore_next = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
// FIXME: Support extended scancodes.
|
||||
if (scancode == EXTENDED_KEY_CODE)
|
||||
{
|
||||
state.ignore_next = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool released = is_key_released(scancode);
|
||||
|
||||
if (scancode == LEFT_SHIFT)
|
||||
{
|
||||
state.left_shift = !released;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (scancode == RIGHT_SHIFT)
|
||||
{
|
||||
state.right_shift = !released;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (scancode == CAPS_LOCK)
|
||||
{
|
||||
if (!released) state.capslock = !state.capslock;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (scancode == LEFT_CONTROL)
|
||||
{
|
||||
if (released) state.left_control = false;
|
||||
else
|
||||
state.left_control = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (should_ignore_key(scancode)) return {};
|
||||
|
||||
if (released) return {};
|
||||
|
||||
if (state.left_control)
|
||||
{
|
||||
char key;
|
||||
if (is_shifted(state)) key = shifted_key_table[scancode];
|
||||
else
|
||||
key = key_table[scancode];
|
||||
if (_islower(key)) key = (char)_toupper(key);
|
||||
if (_isupper(key)) return key - 0x40;
|
||||
if (key == '@') return key - 0x40;
|
||||
if (key > 'Z' && key < '`') return key - 0x40;
|
||||
if (key == '?') return 0x7f;
|
||||
}
|
||||
|
||||
if (is_shifted(state)) return shifted_key_table[scancode];
|
||||
return key_table[scancode];
|
||||
}
|
||||
|
||||
Option<KeyboardPacket> decode_scancode(u8 scancode, KeyboardState& state)
|
||||
{
|
||||
if (state.ignore_next)
|
||||
if (state.parsing_pause)
|
||||
{
|
||||
state.ignore_next = false;
|
||||
if (state.key_state.size() < 6) state.key_state.try_append(scancode).release_value();
|
||||
|
||||
if (state.key_state.size() == 6)
|
||||
{
|
||||
state.parsing_pause = state.parsing_ext = false;
|
||||
if (!memcmp(state.key_state.data(), pause, 6))
|
||||
{
|
||||
state.key_state.clear_data();
|
||||
return KeyboardPacket { K_Pause, false };
|
||||
}
|
||||
|
||||
state.key_state.clear_data();
|
||||
return KeyboardPacket { K_Unknown, false };
|
||||
}
|
||||
}
|
||||
|
||||
if (state.parsing_ext)
|
||||
{
|
||||
if (scancode == 0xe0 && state.key_state.size() == 2)
|
||||
{
|
||||
state.key_state.try_append(scancode).release_value();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (state.key_state.size() == 3)
|
||||
{
|
||||
state.key_state.try_append(scancode).release_value();
|
||||
if (!memcmp(state.key_state.data(), print_screen_pressed, 4))
|
||||
{
|
||||
state.parsing_ext = false;
|
||||
state.key_state.clear_data();
|
||||
return KeyboardPacket { K_PrtScr, false };
|
||||
}
|
||||
|
||||
if (!memcmp(state.key_state.data(), print_screen_released, 4))
|
||||
{
|
||||
state.parsing_ext = false;
|
||||
state.key_state.clear_data();
|
||||
return KeyboardPacket { K_PrtScr, true };
|
||||
}
|
||||
|
||||
state.parsing_ext = false;
|
||||
state.key_state.clear_data();
|
||||
return KeyboardPacket { K_Unknown, false };
|
||||
}
|
||||
|
||||
if (scancode == 0x2a || scancode == 0xb7)
|
||||
{
|
||||
state.key_state.try_append(scancode).release_value();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool released = is_key_released(scancode);
|
||||
|
||||
KeyCode key = KeyCode::K_Unknown;
|
||||
|
||||
if (scancode <= 0x6d) key = extended_keycode_table[scancode - 0x10];
|
||||
|
||||
state.parsing_ext = false;
|
||||
state.key_state.clear_data();
|
||||
|
||||
return KeyboardPacket { key, released };
|
||||
}
|
||||
|
||||
if (scancode == 0xe0)
|
||||
{
|
||||
state.parsing_ext = true;
|
||||
state.key_state.try_append(scancode).release_value();
|
||||
return {};
|
||||
}
|
||||
|
||||
// FIXME: Support extended scancodes.
|
||||
if (scancode == EXTENDED_KEY_CODE)
|
||||
if (scancode == 0xe1)
|
||||
{
|
||||
state.ignore_next = true;
|
||||
state.parsing_pause = true;
|
||||
state.key_state.try_append(scancode).release_value();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool released = is_key_released(scancode);
|
||||
|
||||
return KeyboardPacket { keycode_table[scancode], released };
|
||||
KeyCode key = KeyCode::K_Unknown;
|
||||
|
||||
if (scancode <= 0x58) key = keycode_table[scancode];
|
||||
|
||||
return KeyboardPacket { key, released };
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ namespace MMU
|
||||
if (entry.no_execute) result |= Flags::NoExecute;
|
||||
if (entry.write_through) result |= Flags::WriteThrough;
|
||||
if (entry.cache_disabled) result |= Flags::CacheDisable;
|
||||
if (entry.global) result |= Flags::Global;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -181,6 +182,7 @@ namespace MMU
|
||||
entry.write_through = has_flag(flags, Flags::WriteThrough);
|
||||
entry.cache_disabled = has_flag(flags, Flags::CacheDisable);
|
||||
entry.no_execute = has_flag(flags, Flags::NoExecute);
|
||||
entry.global = has_flag(flags, Flags::Global);
|
||||
entry.set_address(phys);
|
||||
}
|
||||
|
||||
@ -249,6 +251,7 @@ namespace MMU
|
||||
l1.write_through = has_flag(flags, Flags::WriteThrough);
|
||||
l1.cache_disabled = has_flag(flags, Flags::CacheDisable);
|
||||
l1.no_execute = has_flag(flags, Flags::NoExecute);
|
||||
l1.global = has_flag(flags, Flags::Global);
|
||||
flush_page(virt);
|
||||
return {};
|
||||
}
|
||||
@ -291,7 +294,7 @@ namespace MMU
|
||||
|
||||
check(physical_memory_size % ARCH_HUGE_PAGE_SIZE == 0);
|
||||
MemoryManager::map_huge_frames_at(physical_memory_base, 0, physical_memory_size / ARCH_HUGE_PAGE_SIZE,
|
||||
MMU::ReadWrite | MMU::NoExecute);
|
||||
MMU::ReadWrite | MMU::NoExecute | MMU::Global);
|
||||
|
||||
g_physical_mapping_base = physical_memory_base;
|
||||
|
||||
|
@ -17,7 +17,7 @@ struct [[gnu::packed]] PageTableEntry
|
||||
bool accessed : 1;
|
||||
bool ignore0 : 1;
|
||||
bool larger_pages : 1;
|
||||
bool ignore1 : 1;
|
||||
bool global : 1;
|
||||
u8 available : 3;
|
||||
u64 address : 48;
|
||||
u8 available2 : 3;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "memory/MemoryManager.h"
|
||||
#include "thread/Clock.h"
|
||||
#include "thread/Scheduler.h"
|
||||
#include <endian.h>
|
||||
#include <luna/Alignment.h>
|
||||
#include <luna/Buffer.h>
|
||||
#include <luna/CType.h>
|
||||
@ -157,13 +158,27 @@ namespace ATA
|
||||
|
||||
void Channel::select(u8 drive)
|
||||
{
|
||||
if (drive == m_current_drive) return;
|
||||
|
||||
u8 value = (u8)(drive << 4) | 0xa0;
|
||||
if (value == m_current_select_value) return;
|
||||
|
||||
write_register(Register::DriveSelect, value);
|
||||
|
||||
delay_400ns();
|
||||
|
||||
m_current_select_value = value;
|
||||
m_current_drive = drive;
|
||||
}
|
||||
|
||||
void Channel::select(u8 base, u8 drive)
|
||||
{
|
||||
u8 value = (u8)(drive << 4) | base;
|
||||
if (value == m_current_select_value) return;
|
||||
|
||||
write_register(Register::DriveSelect, value);
|
||||
|
||||
delay_400ns();
|
||||
|
||||
m_current_select_value = value;
|
||||
m_current_drive = drive;
|
||||
}
|
||||
|
||||
@ -503,9 +518,8 @@ namespace ATA
|
||||
|
||||
m_is_lba48 = true;
|
||||
|
||||
// FIXME: This assumes the host machine is little-endian.
|
||||
u32 last_lba = __builtin_bswap32(reply.last_lba);
|
||||
u32 sector_size = __builtin_bswap32(reply.sector_size);
|
||||
u32 last_lba = be32toh(reply.last_lba);
|
||||
u32 sector_size = be32toh(reply.sector_size);
|
||||
|
||||
m_block_count = last_lba + 1;
|
||||
m_block_size = sector_size;
|
||||
@ -543,10 +557,61 @@ namespace ATA
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<void> Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size)
|
||||
void Drive::select_lba(u64 lba, usize count)
|
||||
{
|
||||
if (m_is_lba48)
|
||||
{
|
||||
m_channel->write_register(Register::SectorCount, (u8)((count >> 8) & 0xff));
|
||||
m_channel->write_register(Register::LBALow, (u8)((lba >> 24) & 0xff));
|
||||
m_channel->write_register(Register::LBAMiddle, (u8)((lba >> 32) & 0xff));
|
||||
m_channel->write_register(Register::LBAHigh, (u8)((lba >> 40) & 0xff));
|
||||
}
|
||||
m_channel->write_register(Register::SectorCount, (u8)(count & 0xff));
|
||||
m_channel->write_register(Register::LBALow, (u8)(lba & 0xff));
|
||||
m_channel->write_register(Register::LBAMiddle, (u8)((lba >> 8) & 0xff));
|
||||
m_channel->write_register(Register::LBAHigh, (u8)((lba >> 16) & 0xff));
|
||||
}
|
||||
|
||||
Result<void> Drive::read_pio_bytes(void* out, usize size)
|
||||
{
|
||||
u8* ptr = (u8*)out;
|
||||
|
||||
usize i = 0;
|
||||
|
||||
while (i < size)
|
||||
{
|
||||
TRY(m_channel->wait_until_ready());
|
||||
|
||||
usize byte_count;
|
||||
|
||||
if (m_is_atapi)
|
||||
{
|
||||
byte_count =
|
||||
m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle);
|
||||
}
|
||||
else
|
||||
byte_count = min(512ul, size - i);
|
||||
|
||||
usize word_count = byte_count / 2;
|
||||
|
||||
while (word_count--)
|
||||
{
|
||||
u16 value = m_channel->read_data();
|
||||
ptr[0] = (u8)(value & 0xff);
|
||||
ptr[1] = (u8)(value >> 8);
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
i += byte_count;
|
||||
|
||||
m_channel->delay_400ns();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size)
|
||||
{
|
||||
m_channel->select(m_drive_index);
|
||||
|
||||
// We use PIO here.
|
||||
@ -559,30 +624,11 @@ namespace ATA
|
||||
|
||||
m_channel->delay_400ns();
|
||||
|
||||
usize i = 0;
|
||||
|
||||
TRY(m_channel->wait_until_ready());
|
||||
|
||||
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
|
||||
|
||||
while (i < response_size)
|
||||
{
|
||||
TRY(m_channel->wait_until_ready());
|
||||
|
||||
usize byte_count =
|
||||
m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle);
|
||||
usize word_count = byte_count / 2;
|
||||
|
||||
while (word_count--)
|
||||
{
|
||||
u16 value = m_channel->read_data();
|
||||
ptr[0] = (u8)(value & 0xff);
|
||||
ptr[1] = (u8)(value >> 8);
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
i += byte_count;
|
||||
}
|
||||
TRY(read_pio_bytes(out, response_size));
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -704,6 +750,26 @@ namespace ATA
|
||||
return send_packet_atapi_pio(&read_packet, out, (u16)size);
|
||||
}
|
||||
|
||||
Result<void> Drive::ata_read_pio(u64 lba, void* out, usize size)
|
||||
{
|
||||
check(lba < m_block_count);
|
||||
check(size <= ARCH_PAGE_SIZE);
|
||||
|
||||
usize count = ceil_div(size, m_block_size);
|
||||
|
||||
m_channel->select(m_is_lba48 ? 0x40 : (0xe0 | ((lba >> 24) & 0xf)), m_drive_index);
|
||||
|
||||
select_lba(lba, count);
|
||||
|
||||
m_channel->write_register(Register::Command, m_is_lba48 ? CMD_ReadSectorsExt : CMD_ReadSectors);
|
||||
|
||||
m_channel->delay_400ns();
|
||||
|
||||
TRY(read_pio_bytes(out, size));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> Drive::read_lba(u64 lba, void* out, usize nblocks)
|
||||
{
|
||||
const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size;
|
||||
@ -719,7 +785,16 @@ namespace ATA
|
||||
return atapi_read_pio(lba, out, nblocks * m_block_size);
|
||||
}
|
||||
else
|
||||
todo();
|
||||
{
|
||||
while (nblocks > blocks_per_page)
|
||||
{
|
||||
TRY(ata_read_pio(lba, out, ARCH_PAGE_SIZE));
|
||||
lba += blocks_per_page;
|
||||
nblocks -= blocks_per_page;
|
||||
out = offset_ptr(out, ARCH_PAGE_SIZE);
|
||||
}
|
||||
return ata_read_pio(lba, out, nblocks * m_block_size);
|
||||
}
|
||||
}
|
||||
|
||||
void Drive::irq_handler()
|
||||
|
@ -54,6 +54,8 @@ namespace ATA
|
||||
|
||||
enum CommandRegister : u8
|
||||
{
|
||||
CMD_ReadSectors = 0x20,
|
||||
CMD_ReadSectorsExt = 0x24,
|
||||
CMD_Identify = 0xec,
|
||||
CMD_Packet = 0xa0,
|
||||
CMD_Identify_Packet = 0xa1
|
||||
@ -180,6 +182,11 @@ namespace ATA
|
||||
|
||||
Result<void> atapi_read_pio(u64 lba, void* out, usize size);
|
||||
|
||||
Result<void> ata_read_pio(u64 lba, void* out, usize size);
|
||||
|
||||
void select_lba(u64 lba, usize count);
|
||||
Result<void> read_pio_bytes(void* out, usize size);
|
||||
|
||||
Channel* m_channel;
|
||||
|
||||
u8 m_drive_index;
|
||||
@ -250,6 +257,7 @@ namespace ATA
|
||||
}
|
||||
|
||||
void select(u8 drive);
|
||||
void select(u8 base, u8 drive);
|
||||
|
||||
bool initialize();
|
||||
|
||||
@ -270,6 +278,7 @@ namespace ATA
|
||||
bool m_irq_called { false };
|
||||
|
||||
u8 m_current_drive = (u8)-1;
|
||||
u8 m_current_select_value = 0xff;
|
||||
|
||||
Option<Drive> m_drives[2];
|
||||
};
|
||||
|
@ -16,6 +16,8 @@ Result<bool> ScriptLoader::sniff()
|
||||
Result<u64> ScriptLoader::load(AddressSpace* space)
|
||||
{
|
||||
u8 buf[256];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
usize nread = TRY(m_inode->read(buf, 2, 255));
|
||||
if (!nread) return err(ENOEXEC);
|
||||
for (usize i = 0; i < nread; i++)
|
||||
|
@ -33,7 +33,7 @@ void StorageCache::clear()
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
StorageCache::StorageCache()
|
||||
StorageCache::StorageCache(BlockDevice* device) : m_device(device)
|
||||
{
|
||||
g_storage_caches.append(this);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user