Compare commits

...

134 Commits

Author SHA1 Message Date
bbe1eca711
utils: Add a hackish "logout" command
All checks were successful
Build and test / build (push) Successful in 1m45s
2024-09-07 17:40:39 +02:00
e7d361ca51
startui: Remove /tmp/launch.sock as root
All checks were successful
Build and test / build (push) Successful in 1m51s
Fixes #47.
This avoids permission errors.
2024-09-07 17:33:38 +02:00
bb6759986e
libos/LocalServer: Clean up socket file on exit
This doesn't work if the process is killed by an unhandled signal.
2024-09-07 17:33:01 +02:00
4cc8a44ec7
all: Update release to 0.7.0 "Pulsar"
All checks were successful
Build and test / build (push) Successful in 1m57s
2024-09-07 17:17:08 +02:00
0a9578c1ec
libui/Font: Use RefString
All checks were successful
Build and test / build (push) Successful in 1m54s
2024-09-07 16:52:48 +02:00
1fc2da4fb0
taskbar: Use os::ConfigFile instead of manually parsing app files 2024-09-07 16:52:31 +02:00
9c4f20790f
CI: Undo artifact stuff
All checks were successful
Build and test / build (push) Successful in 1m55s
Doesn't seem to be really supported on Gitea for now.
2024-09-07 16:35:59 +02:00
0a143e8729
CI: Use different artifact provider
Some checks failed
Build and test / build (push) Failing after 1m52s
2024-09-01 14:17:22 +02:00
d4237d10a0
CI: Fix actions syntax
Some checks failed
Build and test / build (push) Failing after 2m6s
2024-09-01 14:12:45 +02:00
b24aa1821c
CI: Upload built ISOs
Some checks failed
Build and test / build (push) Failing after 0s
2024-09-01 14:11:54 +02:00
7c0ff8c75a
libos+init: Add a standard API for config file access
All checks were successful
Build and test / build (push) Successful in 1m50s
2024-09-01 12:40:37 +02:00
a11aa7a2d0
libluna: Add a reference-counted immutable string type 2024-09-01 12:40:20 +02:00
0abd9153ae
tools+libluna: Make new and delete weak to avoid conflicts with libstdc++
Wasn't causing problems earlier, but when trying to rebuild the toolchain, it failed because of this.
2024-09-01 12:39:55 +02:00
abbfd5825f
kernel: Make StorageCache have a reference to its parent BlockDevice
All checks were successful
Build and test / build (push) Successful in 2m10s
This will make it easier to implement sync later on.
2024-08-09 18:52:56 +02:00
bfb45c7d4a
gui: Add a login UI and support the os::IPC::Notifier API
All checks were successful
Build and test / build (push) Successful in 3m2s
2024-07-31 19:50:20 +02:00
d3fbddb191
taskbar: Use SIGQUIT to restart
Whenever a GUI session ends, SIGHUP is sent to all GUI processes. Previously, taskbar would catch this signal and wrongly restart.

Now, taskbar correctly dies alongside everyone else.
2024-07-31 19:46:02 +02:00
0ab8efd405
libos+init: Add a Notifier API to know when child processes have finished initialization
init now supports the "WaitUntilReady" key, which will wait until the child calls os::IPC::notify_parent().
2024-07-31 19:43:09 +02:00
2aefbdc4ee
libos: Fix Action 2024-07-31 19:32:47 +02:00
15dc71e8e1
libui/InputField: Fix a few bugs and add a clear() method 2024-07-31 19:32:22 +02:00
140910763e
all: Reorder directory structure
All checks were successful
Build and test / build (push) Successful in 1m56s
Why are command-line utilities stored in "apps"?
And why are apps like "editor" or "terminal" top-level directories?
Command-line utilities now go in "utils".
GUI stuff now goes in "gui".
This includes: libui -> gui/libui, wind -> gui/wind, GUI apps -> gui/apps, editor&terminal -> gui/apps...
System services go in "system".
2024-07-21 13:24:46 +02:00
829f455129
apps: Add arch
All checks were successful
Build and test / build (push) Successful in 2m16s
2024-07-21 13:07:51 +02:00
d10cb10404
libluna/SHA: Reuse the m_message buffer to avoid duplicating the data to hash
All checks were successful
Build and test / build (push) Successful in 2m14s
This change is almost insignificant in most cases, but it avoids using 4GB of memory to hash a 2GB file.
2024-07-21 12:52:12 +02:00
c97876bba0
kernel/ATA: Avoid assuming endianness 2024-07-21 12:51:06 +02:00
31c36b9b83
tests: Add tests for SHA256
All checks were successful
Build and test / build (push) Successful in 2m22s
2024-07-20 16:34:31 +02:00
5fe0507ab1
libc+libluna: Add endianness-dependent functions and use them in SHA256
All checks were successful
Build and test / build (push) Successful in 2m18s
2024-07-20 16:26:06 +02:00
e1c287a45b
libluna: Return a new Digest structure instead of a Buffer from SHA256
All checks were successful
Build and test / build (push) Successful in 2m30s
2024-07-20 16:12:43 +02:00
db2f91b1fb
libluna+apps: Add a SHA256 hash implementation
All checks were successful
Build and test / build (push) Successful in 2m26s
2024-07-20 15:50:59 +02:00
7345a952ca
libluna: Implement hash table iteration
All checks were successful
Build and test / build (push) Successful in 1m47s
2024-07-02 20:51:28 +02:00
903dcfa52c
libc: Partially implement freopen() when a null pathname is provided
All checks were successful
Build and test / build (push) Successful in 2m1s
The specification says "It is implementation-defined which changes of mode are permitted (if any)", so we can comply by not permitting any mode changes, at least for now.
2024-07-02 12:44:43 +02:00
2ce2d57eff
kernel: Prevent kernel threads from calling exit_and_signal_parent()
All checks were successful
Build and test / build (push) Successful in 1m53s
Kernel threads are supposed to use kernel_exit() instead, so it makes no sense to have an extra branch for them.
2024-06-23 22:53:30 +02:00
907049c405
kernel: Signal the reap thread when a kernel thread exits via exit_and_signal_parent()
All checks were successful
Build and test / build (push) Successful in 1m54s
This shouldn't happen, but just in case.
2024-05-02 10:58:34 +02:00
450ef2ce27
docs: Add boot_process.md 2024-05-02 10:57:46 +02:00
01dcb954e5
cp: Show an error message when attempting to copy a directory into a file
All checks were successful
Build and test / build (push) Successful in 2m12s
2024-05-01 18:55:34 +02:00
04649fce8a
base: Change ownership of skeleton files when copying them to the home folder 2024-05-01 18:55:18 +02:00
66983ce17c
base: Correct description for the login service 2024-05-01 18:54:53 +02:00
0a46cfc80c
libui: Remove the server->client message "WindowCloseRequest"
Some checks are pending
Build and test / build (push) Waiting to run
This is no longer used as the client manages its own close buttons.
2024-05-01 18:19:34 +02:00
fb52c67f16
kernel/x86_64: Map kernel-side pages as global to avoid TLB flushes
Some checks are pending
Build and test / build (push) Waiting to run
Fixes #34.
2024-05-01 18:14:43 +02:00
ab70a72434
kernel: Fix extra qualification in Thread.h
Some checks failed
Build and test / build (push) Failing after 12m15s
Forgot to build after writing the previous commit, but CI caught it anyway.
2024-05-01 10:54:59 +02:00
1d0f18cab9
kernel: Move stack checking and expansion into an architecture-independent file
Some checks failed
Build and test / build (push) Failing after 1m45s
2024-05-01 10:52:08 +02:00
de6f5c38d8
kernel: Try to grow the stack on stack overflows, up to a maximum of 8MB
All checks were successful
Build and test / build (push) Successful in 1m49s
This helps keep GCC happy when compiling slightly complex programs :)
2024-04-28 16:27:18 +02:00
e0ed4be0db
libos: Change the year in the default ArgumentParser copyright message
Some checks failed
Build and test / build (push) Failing after 12m48s
Almost four months into 2024, I finally remembered that I had to change this.
2024-04-20 17:24:26 +02:00
6293aeea58
shell: Split code into multiple files, add the "echo" builtin, and add support for a .shellrc file
All checks were successful
Build and test / build (push) Successful in 1m46s
2024-04-20 17:17:31 +02:00
646a15d295
Update README
All checks were successful
Build and test / build (push) Successful in 1m38s
2024-04-20 15:57:01 +02:00
b59a787b9e
kernel: Properly initialize the shebang read buffer with zeros
Some checks failed
Build and test / build (push) Failing after 11m23s
Before this patch, a shebang line that was too long could have left the buffer without a null terminator, allowing some other stack contents to pass into the m_interpreter_cmdline.
2024-04-18 21:55:16 +02:00
e2ff0ad273
libc: Propagate errors correctly in shadow functions
All checks were successful
Build and test / build (push) Successful in 2m58s
2024-04-18 21:25:36 +02:00
62cb53069c
libc: Close all pipe file descriptors in popen() after dup2() is called 2024-04-18 21:25:13 +02:00
fe302f5967
libc: Clean up stdio.h a bit 2024-04-18 21:24:34 +02:00
69f3e28f2c
editor: Use window->add_keyboard_shortcut to handle Ctrl+S
Some checks failed
Build and test / build (push) Failing after 13m5s
2024-04-15 19:34:09 +02:00
2f56a52489
libui: Add support for keyboard shortcuts natively 2024-04-15 19:33:32 +02:00
1176e64a7c editor: Fix creation of new files
All checks were successful
Build and test / build (push) Successful in 1m44s
The editor is supposed to create files if they don't exist, however before this commit stat() would fail and exit load_file() before we even got to File::open_or_create().
2024-04-15 17:06:04 +00:00
e7780b04ee editor: Display only the basename of the current file in the window title 2024-04-15 17:06:04 +00:00
6ded7247e0 terminal: Use widget->window() instead of the App's main window 2024-04-15 17:06:04 +00:00
8b3755873b editor: Use TextInput as a base class 2024-04-15 17:06:04 +00:00
489d54c531 libui: Add a TextInput base class to handle most input fields and add an InputField class for single-line inputs 2024-04-15 17:06:04 +00:00
eb3af60497 editor: Refuse to load non-regular file types 2024-04-15 17:06:04 +00:00
aee100753d editor: Remove insert mode and use the arrow keys to navigate, plus Ctrl+S to save 2024-04-15 17:06:04 +00:00
0be6a896bb editor: Add basic loading and saving 2024-04-15 17:06:04 +00:00
7d738433ed editor: Add a basic text editor 2024-04-15 17:06:04 +00:00
701dc30221
base+su+libc: Add support for a shadow file and use it by default
All checks were successful
Build and test / build (push) Successful in 1m39s
2024-04-10 22:37:36 +02:00
6968961d5c
tools: Use the correct architecture in make-package.sh
Some checks are pending
Build and test / build (push) Waiting to run
2024-04-10 21:53:20 +02:00
d8914b3efa
base: Add taskbar entry for 2048
All checks were successful
Build and test / build (push) Successful in 1m32s
2024-04-10 21:44:04 +02:00
82985d691d
taskbar: Make taskbar entries configurable 2024-04-10 21:42:21 +02:00
ff10e5f3b2
Remove the build status badge
Some checks failed
Build and test / build (push) Failing after 14m33s
It links to drone, which we have phased out in favor of Gitea Actions.
2024-04-01 18:42:34 +02:00
71df91b4a0
libc: Add a _POSIX_VERSION define
All checks were successful
Build and test / build (push) Successful in 1m39s
2024-04-01 14:18:46 +02:00
7dc4b17d46
libc: Implement strtoll and strtoull as functions 2024-04-01 14:18:34 +02:00
332976dde9
tools: Make luna-pkg-config executable 2024-04-01 14:18:15 +02:00
5b94217316
ports: Add doomgeneric port
All checks were successful
Build and test / build (push) Successful in 1m34s
2024-03-31 13:40:43 +02:00
7205020bac
ports: Add libwind port 2024-03-31 13:40:01 +02:00
898eb43360
tools: Add new files to diff as well 2024-03-31 13:38:56 +02:00
a863b17746
wind+libui: Ignore alpha bits in the window buffer 2024-03-31 13:38:39 +02:00
5087b6db30
wind: Show which IPC function was called with an invalid window id 2024-03-31 13:38:08 +02:00
7d69ac56e2
apps+libos+shell+wind: Correct a bunch of format strings
All checks were successful
Build and test / build (push) Successful in 1m41s
2024-03-29 14:42:38 +01:00
f9b39c5ff3
libos: Add ways to format output to a File 2024-03-29 14:41:45 +01:00
d70effd1db
libos: Clarify requirements for File::write 2024-03-29 14:25:14 +01:00
59713279a0
kernel: Add a hexdump() method to log binary data for debugging
All checks were successful
Build and test / build (push) Successful in 1m33s
2024-03-29 12:12:56 +01:00
6443ec77f8
kernel/ATA: Add support for regular ATA drives (non-ATAPI)
Don't know why this took so long to figure out, I just had to pass the right value to select().
2024-03-29 12:11:39 +01:00
86372a3893
Update README.md 2024-03-28 22:37:16 +01:00
3dc2c24ec5
apps: Add run
Some checks failed
Build and test / build (push) Failing after 1m43s
This utility lets you run a process detached from the shell, using the magic of the launch server.
2024-03-20 19:58:45 +01:00
06b8a41d2f
launch: Add support for PATH searching 2024-03-20 19:57:43 +01:00
eab44307f0
run-tests: Avoid starting a shell to spawn test processes
All checks were successful
Build and test / build (push) Successful in 2m9s
2024-03-14 12:37:59 +01:00
cdab3dea90
libluna: Assert some unreachable conditions
All checks were successful
Build and test / build (push) Successful in 1m48s
2024-03-07 23:10:35 +01:00
2780ee2ebc
ports: Build gcc without --enable-checking
All checks were successful
Build and test / build (push) Successful in 1m54s
Now that gcc works, we don't need to bloat the binary with asserts.
2024-03-07 22:33:17 +01:00
70c63572b2
2048: Use the arrow keys and the Home key as input
All checks were successful
Build and test / build (push) Successful in 1m53s
2024-03-06 20:34:13 +01:00
fc37634a18
kernel: Add much-needed support for extended keyboard scancodes 2024-03-06 20:33:54 +01:00
f05fea441c
libluna: Add LinkedList tags
All checks were successful
Build and test / build (push) Successful in 1m51s
2024-03-03 14:52:23 +01:00
5975e58b4a
libluna: Add typename to some stuff in TypeTraits
All checks were successful
Build and test / build (push) Successful in 1m52s
G++ on Luna was complaining about this.
2024-02-11 19:26:40 +01:00
d385e01796
ports: Fix mpfr download url
All checks were successful
Build and test / build (push) Successful in 1m48s
2024-02-11 18:26:46 +01:00
4dc060e0b3
libluna: Fix String::from_string_view construction for inline strings
All checks were successful
Build and test / build (push) Successful in 1m41s
Before, this method failed to add a null terminator if the source string did not have one, which was possible.
2024-02-11 17:09:37 +01:00
644614cdd8
libluna: Fix memmove when dest > src
Really? A crucial component of the libc was broken? No wonder some ports did not work very well...
2024-02-11 17:08:36 +01:00
1070c85922
CI: Don't install fakeroot as a dependency
All checks were successful
Build and test / build (push) Successful in 1m43s
We install as root directly.
2024-02-11 13:45:20 +01:00
8d5f598488
tools: Allow building as root for CI
All checks were successful
Build and test / build (push) Successful in 1m45s
2024-02-11 13:39:51 +01:00
8efcf6d852
Run apt as sudo
Some checks failed
Build and test / build (push) Has been cancelled
2024-02-10 11:55:34 +01:00
7165ff7683
Add fakeroot dependency
Some checks failed
Build and test / build (push) Failing after 6s
2024-02-10 11:50:01 +01:00
0847a2cda0
ports: Update README.md
Some checks failed
Build and test / build (push) Failing after 2m25s
This changes a few occurrences of "if using CMake" to the more correct "if the port uses CMake"
2024-02-10 11:37:52 +01:00
42a7c7af5f
Check glibc version
Some checks failed
Build and test / build (push) Failing after 2m1s
2024-02-06 22:46:05 +01:00
49f84c9dda
Set ubuntu version to latest
Some checks failed
Build and test / build (push) Failing after 2m3s
2024-02-06 22:43:28 +01:00
f5c0e724d5
Update ubuntu version to 22.04
Some checks failed
Build and test / build (push) Failing after 2m0s
2024-02-06 22:40:38 +01:00
ac3175cf26
Remove old drone.yml
Some checks failed
Build and test / build (push) Has been cancelled
Now that we use Gitea Actions, this is no longer needed.
2024-02-06 22:38:27 +01:00
a78620a7d2
Update workflow
Some checks failed
Build and test / build (push) Failing after 2m25s
2024-02-06 21:32:27 +01:00
fcd8c1d583
Update workflow
Some checks failed
Build and test / build (push) Failing after 2m20s
2024-02-04 14:40:07 +01:00
e1c2dfb9ba
Update workflow
Some checks failed
Build and test / build (push) Failing after 9s
2024-02-04 14:39:31 +01:00
7fcc0659c8
Update workflow
Some checks failed
Build and test / build (push) Failing after 7s
2024-02-04 14:39:07 +01:00
3fc5f2b836
Update workflow
Some checks failed
Build and test / build (push) Failing after 0s
2024-02-04 14:38:34 +01:00
64965cd322
Update workflow 2024-02-04 14:38:01 +01:00
d0ad103e3d
Update workflow
Some checks failed
Build and test / build (push) Failing after 0s
2024-02-04 14:36:48 +01:00
3728558b13
Update workflow
Some checks failed
Build and test / build (push) Failing after 9s
2024-02-04 14:33:30 +01:00
7cd6e9b12a
Update workflow
Some checks failed
Build and test / build (push) Failing after 7s
2024-02-04 14:30:30 +01:00
e02dee1f41
Update workflow
Some checks failed
Build and test / build (push) Failing after 50s
2024-02-04 14:27:20 +01:00
ab2700ef5d
Update workflow
Some checks failed
Build and test / build (push) Failing after 2s
2024-02-04 14:25:59 +01:00
bb18749d5b
Update build workflow 2024-02-04 14:24:22 +01:00
d4b368b078
Update build workflow 2024-02-04 14:18:51 +01:00
7f2a65f6d6
Add basic actions file
Some checks failed
Build and test / build (push) Has been cancelled
2024-02-04 14:16:01 +01:00
02f8102d38
wind+libui+taskbar: Add a request for setting special window attributes
This lets the taskbar window stay unfocused even when it's clicked.
2024-02-04 13:35:50 +01:00
9bb66716a4
libui: Move WindowType from ipc/Server.h to Window.h as it is no longer used in IPC 2024-02-04 13:16:50 +01:00
b9ccda132a
wind+libui: Rename SetTitlebarRect to SetTitlebarHeight
We only need the height to be customizable.
2024-02-04 13:13:21 +01:00
b8470f753b
startui: Move socket file checking around a bit 2024-02-03 19:35:40 +01:00
909d0ed289
libos+wind+apps: Make IPC code object-oriented and add functionality for properly receiving messages
This functionality previously had to be repeated across all server programs using the IPC API.
2024-02-03 19:16:39 +01:00
6bdf3169d2
kernel: Handle aborted connections instead of crashing 2024-02-03 19:15:19 +01:00
75d0d12b71
apps: Add a background launcher service
This service is used only by taskbar, for now, to launch apps with regular privileges instead of inheriting the special group 'wsys'.
2024-02-01 21:58:44 +01:00
a7ff298852
wind: Add a second unix socket for privileged clients which need to be in a different group 2024-02-01 21:57:40 +01:00
ca5b4de2d8
libui+apps: Change ui::App::init() to take the socket path directly instead of command-line arguments 2024-02-01 21:15:31 +01:00
f8a39ffeec
startui: Change mismatched parameter name 2024-01-31 22:45:11 +01:00
d440559d54
startui: Fix typo 2024-01-31 22:43:40 +01:00
443d8957f3
apps: Add startui
This service starts a complete UI session (wind, components, and init --user), all with their respective privileges.

This lets us move that responsibility away from wind and let it be only a window manager.
2024-01-31 22:42:24 +01:00
ea14dab7d7
wind: Stop using magic numbers for user and group IDs 2024-01-30 20:26:54 +01:00
16223b2f53
init: Add a (user) prefix to logs when not running as system init 2024-01-19 21:21:07 +01:00
9c36ef6e9e
terminal: Stop setting the pty to nonblocking mode when it's not needed 2024-01-19 21:18:01 +01:00
63f785563d
terminal: Use proper lambda functions when registering a fd listener for the pseudoterminal 2024-01-19 21:11:03 +01:00
1cd355a8e8
libos: Add Function<Args...> and use that to make EventLoop callbacks more versatile 2024-01-18 20:52:36 +01:00
d4d748e153
terminal: Remove m_cursor_timer->restart() from tick_cursor()
Since the timer was created as a repeating timer, it is already restarted after the function.
2024-01-13 16:26:37 +01:00
cee677b1f7
libui: Make App::process_events() private
Previously this was public as some applications (mainly terminal) needed to run some code in-between events, but this is no longer needed, due to the EventLoop providing these services (timers and file descriptor notifiers)
2024-01-08 19:13:47 +01:00
e4c9211edc
terminal: Use os::Timer and EventLoop::register_fd_listener 2024-01-08 19:04:04 +01:00
6bf8225f63
libos: Add Timer::reset, restart and stop 2024-01-08 19:01:59 +01:00
1223c6c20b
libos: Add FIXME to EventLoop 2024-01-08 19:01:39 +01:00
209 changed files with 5262 additions and 1469 deletions

View File

@ -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

View 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
View File

@ -10,6 +10,7 @@ base/usr/*
base/usr/share/* base/usr/share/*
!base/usr/share/fonts !base/usr/share/fonts
!base/usr/share/icons !base/usr/share/icons
!base/usr/share/applications
base/etc/skel/LICENSE base/etc/skel/LICENSE
.fakeroot .fakeroot
kernel/config.cmake kernel/config.cmake

View File

@ -5,8 +5,8 @@ set(CMAKE_CXX_COMPILER_WORKS 1)
set(CMAKE_CROSSCOMPILING true) set(CMAKE_CROSSCOMPILING true)
project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.6.0) project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.7.0)
set(LUNA_RELEASE_NAME "Andromeda") set(LUNA_RELEASE_NAME "Pulsar")
set(LUNA_ROOT ${CMAKE_CURRENT_LIST_DIR}) set(LUNA_ROOT ${CMAKE_CURRENT_LIST_DIR})
set(LUNA_BASE ${CMAKE_CURRENT_LIST_DIR}/base) set(LUNA_BASE ${CMAKE_CURRENT_LIST_DIR}/base)
@ -45,11 +45,10 @@ endif()
add_subdirectory(libluna) add_subdirectory(libluna)
add_subdirectory(libos) add_subdirectory(libos)
add_subdirectory(libui) add_subdirectory(gui)
add_subdirectory(libc) add_subdirectory(libc)
add_subdirectory(kernel) add_subdirectory(kernel)
add_subdirectory(apps) add_subdirectory(utils)
add_subdirectory(tests) add_subdirectory(tests)
add_subdirectory(shell) add_subdirectory(shell)
add_subdirectory(wind) add_subdirectory(system)
add_subdirectory(terminal)

View File

@ -1,18 +1,18 @@
# Luna # 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? ## Another UNIX clone?
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone). [Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone).
## Features ## Features
- x86_64-compatible lightweight [kernel](kernel/). - Lightweight 64-bit [kernel](kernel/). Compatible with the x86_64 architecture.
- Simple round-robin [scheduler](kernel/src/thread/). - Basic threads/processes, using a simple round-robin [scheduler](kernel/src/thread/).
- Read-only [ext2](kernel/src/fs/ext2/) filesystem. - 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. - [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. - 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), no need to be restricted to x86_64. - Designed to be [portable](kernel/src/arch), so that additional architectures can be added in the future with relatively low effort.
- Everything is designed around [UTF-8](libluna/include/luna/Utf8.h). - 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. - 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/). - 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). - 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. 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. `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 images
Prebuilt ISO images (numbered) for every version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases). Prebuilt ISO images for every release 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.
## Is there third-party software I can use on Luna? ## Is there third-party software I can use on Luna?

View File

@ -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)

View File

@ -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();
}

View File

@ -1,4 +1,5 @@
root:!:0: root:!:0:
users:!:1:selene users:!:1:selene
wind:!:2:selene wind:!:2:selene
wsys:!:3:
selene:!:1000: selene:!:1000:

View File

@ -1,6 +1,6 @@
Name=login Name=login
Description=Start the display server. Description=Start a graphical user session.
Command=/usr/bin/wind --user=selene Command=/usr/bin/loginui --autologin=selene
StandardOutput=/dev/uart0 StandardOutput=/dev/uart0
StandardError=/dev/uart0 StandardError=/dev/uart0
Restart=true Restart=true

View File

@ -1,3 +1,3 @@
root:toor:0:0:Administrator:/:/usr/bin/sh root:x:0:0:Administrator:/:/usr/bin/sh
wind:!:2:2:Window Manager:/:/usr/bin/init wind:x:2:2:Window Manager:/:/usr/bin/init
selene:moon:1000:1000:User:/home/selene:/usr/bin/sh selene:x:1000:1000:User:/home/selene:/usr/bin/sh

3
base/etc/shadow Normal file
View File

@ -0,0 +1,3 @@
root:toor:0:0:99999:7:::
wind:!:0:0:99999:7:::
selene:moon:0:0:99999:7:::

View File

@ -2,5 +2,9 @@
# Create and populate a volatile home directory. # Create and populate a volatile home directory.
mount -t tmpfs tmpfs /home/selene mount -t tmpfs tmpfs /home/selene
chown selene:selene /home/selene chown selene:selene /home/selene
cp /etc/skel/welcome /home/selene/ cp /etc/skel/welcome /home/selene/
cp /etc/skel/LICENSE /home/selene/ cp /etc/skel/LICENSE /home/selene/
chown selene:selene /home/selene/welcome
chown selene:selene /home/selene/LICENSE

View File

@ -1,5 +0,0 @@
Name=taskbar
Description=Start the taskbar.
Command=/usr/bin/taskbar
WorkingDirectory=/home/selene
Restart=true

View File

@ -1,4 +1,3 @@
Name=terminal Name=terminal
Description=Start the terminal. Description=Start the terminal.
WorkingDirectory=/home/selene
Command=/usr/bin/terminal Command=/usr/bin/terminal

View File

@ -0,0 +1,3 @@
Name=terminal
Icon=/usr/share/icons/32x32/app-terminal.tga
Command=/usr/bin/terminal

View File

@ -0,0 +1,3 @@
Name=about
Icon=/usr/share/icons/32x32/app-about.tga
Command=/usr/bin/about

View File

@ -0,0 +1,3 @@
Name=gol
Icon=/usr/share/icons/32x32/app-gol.tga
Command=/usr/bin/gol

View File

@ -0,0 +1,3 @@
Name=clock
Icon=/usr/share/icons/32x32/app-clock.tga
Command=/usr/bin/clock

View File

@ -0,0 +1,3 @@
Name=2048
Icon=/usr/share/icons/32x32/app-2048.tga
Command=/usr/bin/2048

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

221
docs/boot_process.md Normal file
View 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
View 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)

View File

@ -50,9 +50,9 @@ class GameWidget final : public ui::Widget
bool should_add_tile = false; bool should_add_tile = false;
switch (request.key) switch (request.code)
{ {
case 'w': { case moon::K_UpArrow: {
bool changed; bool changed;
changed = move_up(); changed = move_up();
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
@ -61,7 +61,7 @@ class GameWidget final : public ui::Widget
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
} }
break; break;
case 'a': { case moon::K_LeftArrow: {
bool changed; bool changed;
changed = move_left(); changed = move_left();
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
@ -70,7 +70,7 @@ class GameWidget final : public ui::Widget
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
} }
break; break;
case 's': { case moon::K_DownArrow: {
bool changed; bool changed;
changed = move_down(); changed = move_down();
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
@ -79,7 +79,7 @@ class GameWidget final : public ui::Widget
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
} }
break; break;
case 'd': { case moon::K_RightArrow: {
bool changed; bool changed;
changed = move_right(); changed = move_right();
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
@ -88,7 +88,7 @@ class GameWidget final : public ui::Widget
if (changed) should_add_tile = true; if (changed) should_add_tile = true;
} }
break; break;
case 'r': { case moon::K_Home: {
reset(); reset();
return ui::EventResult::DidHandle; 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)); srand((unsigned)time(NULL));
ui::App app; ui::App app;
TRY(app.init(argc, argv)); TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 400 })); auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 400 }));
app.set_main_window(window); app.set_main_window(window);

17
gui/apps/CMakeLists.txt Normal file
View 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)

View File

@ -7,10 +7,10 @@
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89); 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; ui::App app;
TRY(app.init(argc, argv)); TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 })); auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 }));
app.set_main_window(window); app.set_main_window(window);

View File

@ -18,10 +18,10 @@ void update_time()
ui::App::the().main_window()->draw(); ui::App::the().main_window()->draw();
} }
Result<int> luna_main(int argc, char** argv) Result<int> luna_main(int, char**)
{ {
ui::App app; ui::App app;
TRY(app.init(argc, argv)); TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 500, 400, 100, 50 })); auto* window = TRY(ui::Window::create(ui::Rect { 500, 400, 100, 50 }));
app.set_main_window(window); app.set_main_window(window);

View 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)

View 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();
}
}

View 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
View 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();
}

View File

@ -108,10 +108,10 @@ static void update()
draw_cells(); draw_cells();
} }
Result<int> luna_main(int argc, char** argv) Result<int> luna_main(int, char**)
{ {
ui::App app; 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 = TRY(ui::Window::create(ui::Rect { 200, 200, 600, 400 }));
g_window->set_title("Game of Life"); g_window->set_title("Game of Life");

156
gui/apps/taskbar.cpp Normal file
View 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();
}

View File

@ -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_CYAN = ui::Color::from_u32(0xff00ffff);
static constexpr auto BRIGHT_GRAY = ui::Color::from_u32(0xffffffff); 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) static void sigchld_handler(int)
{ {
wait(NULL); wait(NULL);
@ -48,9 +40,11 @@ Result<void> TerminalWidget::init(char* const* args)
m_font = ui::Font::default_font(); m_font = ui::Font::default_font();
m_bold_font = ui::Font::default_bold_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_terminal_canvas.fill(ui::BLACK);
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));
signal(SIGCHLD, sigchld_handler); signal(SIGCHLD, sigchld_handler);
int master; int master;
@ -64,12 +58,10 @@ Result<void> TerminalWidget::init(char* const* args)
m_pty = master; 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_child_pid = child;
m_last_cursor_tick = get_time_in_milliseconds();
return {}; return {};
} }
@ -91,7 +83,7 @@ Result<void> TerminalWidget::draw(ui::Canvas&)
return {}; return {};
} }
Result<bool> TerminalWidget::process() Result<void> TerminalWidget::process()
{ {
char buffer[BUFSIZ]; char buffer[BUFSIZ];
ssize_t nread = read(m_pty, buffer, BUFSIZ); ssize_t nread = read(m_pty, buffer, BUFSIZ);
@ -102,8 +94,6 @@ Result<bool> TerminalWidget::process()
return err(errno); return err(errno);
} }
bool should_update_cursor = tick_cursor();
ssize_t drawn = 0; ssize_t drawn = 0;
for (ssize_t i = 0; i < nread; i++) for (ssize_t i = 0; i < nread; i++)
@ -112,32 +102,22 @@ Result<bool> TerminalWidget::process()
if (did_draw) drawn++; 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(); m_cursor_activated = !m_cursor_activated;
long diff = now - m_last_cursor_tick;
m_last_cursor_tick = now;
m_current_cursor_timeout -= (int)diff; if (m_cursor_activated) draw_cursor();
if (m_current_cursor_timeout <= 0) else
{ erase_current_char();
m_current_cursor_timeout = CURSOR_TIMEOUT;
m_cursor_activated = !m_cursor_activated;
if (m_cursor_activated) draw_cursor(); window()->draw();
else
erase_current_char();
return true;
}
return false;
} }
void TerminalWidget::draw_glyph(wchar_t c, int x, int y) 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) if (should_draw_cursor)
{ {
m_current_cursor_timeout = CURSOR_TIMEOUT; m_cursor_timer->restart();
m_cursor_activated = true; m_cursor_activated = true;
draw_cursor(); draw_cursor();
} }

View File

@ -2,6 +2,7 @@
#include <luna/EscapeSequence.h> #include <luna/EscapeSequence.h>
#include <luna/Utf8.h> #include <luna/Utf8.h>
#include <luna/Vector.h> #include <luna/Vector.h>
#include <os/Timer.h>
#include <stdio.h> #include <stdio.h>
#include <termios.h> #include <termios.h>
#include <ui/Font.h> #include <ui/Font.h>
@ -16,8 +17,6 @@ class TerminalWidget : public ui::Widget
Result<void> draw(ui::Canvas& canvas) override; Result<void> draw(ui::Canvas& canvas) override;
Result<bool> process();
private: private:
ui::Canvas m_terminal_canvas; ui::Canvas m_terminal_canvas;
Vector<u8> m_line_buffer; Vector<u8> m_line_buffer;
@ -29,9 +28,7 @@ class TerminalWidget : public ui::Widget
SharedPtr<ui::Font> m_font; SharedPtr<ui::Font> m_font;
SharedPtr<ui::Font> m_bold_font; SharedPtr<ui::Font> m_bold_font;
static constexpr int CURSOR_TIMEOUT = 500; OwnedPtr<os::Timer> m_cursor_timer;
int m_current_cursor_timeout = CURSOR_TIMEOUT;
bool m_cursor_activated = false; bool m_cursor_activated = false;
bool m_cursor_enabled = true; bool m_cursor_enabled = true;
@ -45,7 +42,7 @@ class TerminalWidget : public ui::Widget
ui::Color m_foreground_color { ui::WHITE }; ui::Color m_foreground_color { ui::WHITE };
ui::Color m_background_color { ui::BLACK }; ui::Color m_background_color { ui::BLACK };
bool tick_cursor(); void tick_cursor();
Utf8StateDecoder m_decoder; Utf8StateDecoder m_decoder;
Option<EscapeSequenceParser> m_escape_parser; Option<EscapeSequenceParser> m_escape_parser;
@ -63,4 +60,5 @@ class TerminalWidget : public ui::Widget
bool handle_escape_sequence(wchar_t c); bool handle_escape_sequence(wchar_t c);
Result<bool> putchar(char c); Result<bool> putchar(char c);
bool put_code_point(wchar_t c); bool put_code_point(wchar_t c);
Result<void> process();
}; };

View File

@ -3,10 +3,10 @@
#include <ui/App.h> #include <ui/App.h>
#include <unistd.h> #include <unistd.h>
Result<int> luna_main(int argc, char** argv) Result<int> luna_main(int, char**)
{ {
ui::App app; ui::App app;
TRY(app.init(argc, argv)); TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 })); auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 }));
app.set_main_window(window); app.set_main_window(window);
@ -20,11 +20,5 @@ Result<int> luna_main(int argc, char** argv)
window->draw(); window->draw();
while (app.process_events()) return app.run();
{
bool should_sleep = TRY(terminal.process());
if (should_sleep) usleep(10000);
}
return 0;
} }

109
gui/launch.cpp Normal file
View 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();
}
}
}
}

View File

@ -17,6 +17,8 @@ set(SOURCES
src/Container.cpp src/Container.cpp
src/Button.cpp src/Button.cpp
src/Label.cpp src/Label.cpp
src/InputField.cpp
src/TextInput.cpp
) )
add_library(ui ${SOURCES}) add_library(ui ${SOURCES})

View File

@ -9,8 +9,9 @@
#pragma once #pragma once
#include <luna/HashMap.h> #include <luna/HashMap.h>
#include <luna/StringView.h>
#include <os/EventLoop.h> #include <os/EventLoop.h>
#include <os/LocalClient.h> #include <os/IPC.h>
#include <ui/Window.h> #include <ui/Window.h>
namespace ui namespace ui
@ -21,12 +22,12 @@ namespace ui
App(); App();
~App(); ~App();
Result<void> init(int, char**); Result<void> init(StringView socket_path = "/tmp/wind.sock");
Result<int> run(); Result<int> run();
Rect screen_rect(); Rect screen_rect();
os::LocalClient& client() os::IPC::Client& client()
{ {
return *m_client; return *m_client;
} }
@ -51,20 +52,22 @@ namespace ui
Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>); Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>);
void unregister_window(Window* window, Badge<Window>); void unregister_window(Window* window, Badge<Window>);
Result<void> handle_ipc_event(u8 id);
bool process_events();
static App& the(); static App& the();
private: private:
static App* s_app; static App* s_app;
OwnedPtr<os::LocalClient> m_client; OwnedPtr<os::IPC::Client> m_client;
Window* m_main_window { nullptr }; Window* m_main_window { nullptr };
HashMap<int, OwnedPtr<Window>> m_windows; HashMap<int, OwnedPtr<Window>> m_windows;
bool m_should_close { false }; bool m_should_close { false };
os::EventLoop m_loop; os::EventLoop m_loop;
bool process_events();
Window* find_window(int id); Window* find_window(int id);
Result<void> handle_ipc_event(os::IPC::Client&, u8 id, void*);
friend void handle_socket_event(int, int);
}; };
} }

View File

@ -69,5 +69,13 @@ namespace ui
* @param stride The number of pixels to skip to go to the next line. * @param stride The number of pixels to skip to go to the next line.
*/ */
void fill(u32* pixels, int stride); 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);
}; };
}; };

View 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 };
};
}

View 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);
};
}

View File

@ -19,6 +19,24 @@
namespace ui 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 class Window
{ {
public: public:
@ -47,12 +65,16 @@ namespace ui
void close(); void close();
void set_special_attributes(WindowAttributes attributes);
Result<void> draw(); Result<void> draw();
Result<ui::EventResult> handle_mouse_leave(); Result<ui::EventResult> handle_mouse_leave();
Result<ui::EventResult> handle_mouse_move(ui::Point position); Result<ui::EventResult> handle_mouse_move(ui::Point position);
Result<ui::EventResult> handle_mouse_buttons(ui::Point position, int buttons); Result<ui::EventResult> handle_mouse_buttons(ui::Point position, int buttons);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request); 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 int id() const
{ {
return m_id; return m_id;
@ -71,6 +93,14 @@ namespace ui
Option<int> m_old_mouse_buttons; Option<int> m_old_mouse_buttons;
bool m_decorated { false }; bool m_decorated { false };
struct ShortcutAction
{
bool intercept;
os::Function<Shortcut> action;
};
HashMap<Shortcut, ShortcutAction> m_shortcuts;
Result<void> draw_titlebar(); Result<void> draw_titlebar();
}; };
} }

View File

@ -19,7 +19,6 @@ namespace ui
{ {
IPC_ENUM_CLIENT(ui), IPC_ENUM_CLIENT(ui),
CREATE_WINDOW_RESPONSE_ID, CREATE_WINDOW_RESPONSE_ID,
WINDOW_CLOSE_REQUEST_ID,
MOUSE_EVENT_REQUEST_ID, MOUSE_EVENT_REQUEST_ID,
MOUSE_LEAVE_REQUEST_ID, MOUSE_LEAVE_REQUEST_ID,
GET_SCREEN_RECT_RESPONSE_ID, GET_SCREEN_RECT_RESPONSE_ID,
@ -34,13 +33,6 @@ namespace ui
IPC_STRING(shm_path); IPC_STRING(shm_path);
}; };
struct WindowCloseRequest
{
static constexpr u8 ID = WINDOW_CLOSE_REQUEST_ID;
int window;
};
struct MouseEventRequest struct MouseEventRequest
{ {
static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID; static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID;

View File

@ -24,14 +24,8 @@ namespace ui
INVALIDATE_ID, INVALIDATE_ID,
CLOSE_WINDOW_ID, CLOSE_WINDOW_ID,
GET_SCREEN_RECT_ID, GET_SCREEN_RECT_ID,
SET_TITLEBAR_RECT_ID, SET_TITLEBAR_HEIGHT_ID,
}; SET_SPECIAL_WINDOW_ATTRIBUTES_ID,
enum class WindowType : u8
{
Normal,
NotDecorated,
System,
}; };
struct CreateWindowRequest struct CreateWindowRequest
@ -40,7 +34,6 @@ namespace ui
static constexpr u8 ID = CREATE_WINDOW_ID; static constexpr u8 ID = CREATE_WINDOW_ID;
ui::Rect rect; ui::Rect rect;
WindowType type;
}; };
struct RemoveSharedMemoryRequest struct RemoveSharedMemoryRequest
@ -80,11 +73,24 @@ namespace ui
int _shadow; // Unused. 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; 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;
}; };
} }

View File

@ -14,19 +14,14 @@
#include <ui/ipc/Client.h> #include <ui/ipc/Client.h>
#include <ui/ipc/Server.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 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::s_app { nullptr };
App::App() App::App()
@ -39,18 +34,11 @@ namespace ui
s_app = nullptr; s_app = nullptr;
} }
Result<void> App::init(int argc, char** argv) Result<void> App::init(StringView socket_path)
{ {
StringView socket_path = "/tmp/wind.sock"; m_client = TRY(os::IPC::Client::connect(socket_path, false));
m_client->set_message_handler(
os::ArgumentParser parser; [this](os::IPC::Client& client, u8 id, void* arg) { this->handle_ipc_event(client, id, arg); }, nullptr);
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);
TRY(m_loop.register_fd_listener(m_client->fd(), handle_socket_event)); TRY(m_loop.register_fd_listener(m_client->fd(), handle_socket_event));
@ -73,7 +61,7 @@ namespace ui
Rect App::screen_rect() Rect App::screen_rect()
{ {
ui::GetScreenRectRequest request {}; 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; return response.rect;
} }
@ -97,34 +85,13 @@ namespace ui
return window->ptr(); return window->ptr();
} }
#define READ_MESSAGE(request) \ Result<void> App::handle_ipc_event(os::IPC::Client&, u8 id, void*)
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)
{ {
switch (id) 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: { case MOUSE_EVENT_REQUEST_ID: {
MouseEventRequest request; MouseEventRequest request;
READ_MESSAGE(request); if (!TRY(m_client->read_message(request))) return {};
auto* window = find_window(request.window); auto* window = find_window(request.window);
auto move_result = window->handle_mouse_move(request.position).value_or(ui::EventResult::DidNotHandle); auto move_result = window->handle_mouse_move(request.position).value_or(ui::EventResult::DidNotHandle);
auto button_result = auto button_result =
@ -135,7 +102,7 @@ namespace ui
} }
case MOUSE_LEAVE_REQUEST_ID: { case MOUSE_LEAVE_REQUEST_ID: {
MouseLeaveRequest request; MouseLeaveRequest request;
READ_MESSAGE(request); if (!TRY(m_client->read_message(request))) return {};
auto* window = find_window(request.window); auto* window = find_window(request.window);
if (window->handle_mouse_leave().value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle) if (window->handle_mouse_leave().value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
window->draw(); window->draw();
@ -143,7 +110,7 @@ namespace ui
} }
case KEY_EVENT_REQUEST_ID: { case KEY_EVENT_REQUEST_ID: {
KeyEventRequest request; KeyEventRequest request;
READ_MESSAGE(request); if (!TRY(m_client->read_message(request))) return {};
auto* window = find_window(request.window); auto* window = find_window(request.window);
if (window->handle_key_event(request).value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle) if (window->handle_key_event(request).value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
window->draw(); window->draw();
@ -156,7 +123,7 @@ namespace ui
bool App::process_events() bool App::process_events()
{ {
check(m_main_window); check(m_main_window);
os::IPC::check_for_messages(*m_client).release_value(); m_client->check_for_messages().release_value();
return !m_should_close; return !m_should_close;
} }
} }

View File

@ -7,6 +7,7 @@
* *
*/ */
#include <luna/CString.h>
#include <ui/Canvas.h> #include <ui/Canvas.h>
namespace ui namespace ui
@ -59,4 +60,16 @@ namespace ui
p += stride * sizeof(Color); 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);
}
}
} }

View File

@ -7,7 +7,7 @@
* *
*/ */
#include <luna/String.h> #include <luna/RefString.h>
#include <os/File.h> #include <os/File.h>
#include <ui/Font.h> #include <ui/Font.h>
@ -49,8 +49,8 @@ namespace ui
Result<SharedPtr<Font>> Font::load_builtin(StringView name, FontWeight weight) Result<SharedPtr<Font>> Font::load_builtin(StringView name, FontWeight weight)
{ {
auto path = TRY(String::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(), auto path = TRY(RefString::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(),
weight == FontWeight::Bold ? "Bold" : "Regular")); weight == FontWeight::Bold ? "Bold" : "Regular"));
return load(path.view()); return load(path.view());
} }

View 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() };
}
}

View 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();
}
}

View File

@ -43,7 +43,7 @@ namespace ui
ui::CreateWindowRequest request; ui::CreateWindowRequest request;
request.rect = rect; 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); 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_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 }); window->m_window_canvas = canvas.subcanvas(ui::Rect { 0, height, canvas.width, canvas.height - height });
ui::SetTitlebarRectRequest titlebar_request; ui::SetTitlebarHeightRequest titlebar_request;
titlebar_request.titlebar_rect = window->m_titlebar_canvas.rect(); titlebar_request.height = height;
titlebar_request.window = response.window; titlebar_request.window = response.window;
os::IPC::send_async(App::the().client(), titlebar_request); App::the().client().send_async(titlebar_request);
} }
else else
{ {
@ -74,7 +74,7 @@ namespace ui
ui::RemoveSharedMemoryRequest shm_request; ui::RemoveSharedMemoryRequest shm_request;
shm_request.window = response.window; 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), {}); App::the().register_window(move(window), {});
@ -91,7 +91,7 @@ namespace ui
ui::SetWindowTitleRequest request; ui::SetWindowTitleRequest request;
request.window = m_id; request.window = m_id;
SET_IPC_STRING(request.title, title.chars()); 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(); m_name = String::from_string_view(title).release_value();
draw(); draw();
@ -101,7 +101,7 @@ namespace ui
{ {
ui::InvalidateRequest request; ui::InvalidateRequest request;
request.window = m_id; request.window = m_id;
os::IPC::send_async(App::the().client(), request); App::the().client().send_async(request);
} }
void Window::close() void Window::close()
@ -110,13 +110,21 @@ namespace ui
ui::CloseWindowRequest request; ui::CloseWindowRequest request;
request.window = m_id; 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); if (this == app.main_window()) app.set_should_close(true);
app.unregister_window(this, {}); 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() Result<void> Window::draw()
{ {
if (m_background.has_value()) m_window_canvas.fill(*m_background); 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) 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; if (!m_main_widget) return ui::EventResult::DidNotHandle;
return m_main_widget->handle_key_event(request); 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
View 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
View 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
View 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
View 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
View 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);
}

View File

@ -64,7 +64,7 @@ void Mouse::update(const moon::MousePacket& packet)
{ {
if (window->surface.contains(m_position)) if (window->surface.contains(m_position))
{ {
window->focus(); if (!(window->attributes & ui::UNFOCUSEABLE)) window->focus();
if (window->surface.absolute(window->titlebar).contains(m_position)) 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.window = window->id;
request.position = window->surface.relative(m_position); request.position = window->surface.relative(m_position);
request.buttons = packet.buttons; request.buttons = packet.buttons;
os::IPC::send_async(window->client->conn, request); window->client->conn->send_async(request);
new_active_window = window; new_active_window = window;
break; break;
} }
@ -103,7 +103,7 @@ void Mouse::update(const moon::MousePacket& packet)
{ {
ui::MouseLeaveRequest request; ui::MouseLeaveRequest request;
request.window = m_active_window->id; 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; m_active_window = new_active_window;
} }

View File

@ -12,7 +12,7 @@ void Window::draw(ui::Canvas& screen)
dirty = false; dirty = false;
auto window = screen.subcanvas(surface); auto window = screen.subcanvas(surface);
window.fill(pixels, surface.width); window.copy(pixels, surface.width);
} }
void Window::focus() void Window::focus()

View File

@ -18,6 +18,7 @@ struct Window : public LinkedListNode<Window>
bool dirty { false }; bool dirty { false };
Client* client; Client* client;
int id; int id;
ui::WindowAttributes attributes { 0 };
Window(ui::Rect, String&&); Window(ui::Rect, String&&);
~Window(); ~Window();

View File

@ -1,27 +1,28 @@
#define CLIENT_IMPLEMENTATION #define CLIENT_IMPLEMENTATION
#include "Client.h" #include "Client.h"
#include "IPC.h"
#include "Keyboard.h" #include "Keyboard.h"
#include "Mouse.h" #include "Mouse.h"
#include "Screen.h" #include "Screen.h"
#include "Window.h" #include "Window.h"
#include <errno.h> #include <errno.h>
#include <grp.h>
#include <moon/Keyboard.h> #include <moon/Keyboard.h>
#include <os/ArgumentParser.h> #include <os/ArgumentParser.h>
#include <os/File.h> #include <os/File.h>
#include <os/IPC.h>
#include <os/LocalServer.h> #include <os/LocalServer.h>
#include <os/Process.h> #include <os/Process.h>
#include <os/Security.h> #include <os/Security.h>
#include <pwd.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h> #include <sys/poll.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h> #include <time.h>
#include <unistd.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) static void debug(const Vector<OwnedPtr<Client>>& clients)
{ {
os::println("--- wind: DEBUG OUTPUT ---"); os::println("--- wind: DEBUG OUTPUT ---");
@ -30,14 +31,14 @@ static void debug(const Vector<OwnedPtr<Client>>& clients)
for (const auto& 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 --"); os::println("-- wind: Listing windows --");
for (const auto& window : g_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->id, window->dirty ? "" : "not ", window->name.chars(), window->surface.pos.x,
window->surface.pos.y, window->surface.width, window->surface.height); 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 ---"); 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) Result<int> luna_main(int argc, char** argv)
{ {
srand((unsigned)time(NULL)); 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)); TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL));
StringView socket_path = "/tmp/wind.sock"; StringView socket_path = "/tmp/wind.sock";
StringView user; StringView system_socket_path = "/tmp/wsys.sock";
os::ArgumentParser parser; os::ArgumentParser parser;
parser.add_description("The display server for Luna's graphical user interface."_sv); parser.add_description("The display server for Luna's graphical user interface."_sv);
parser.add_system_program_info("wind"_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(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); parser.parse(argc, argv);
if (geteuid() != 0) if (geteuid() != 0)
@ -114,6 +91,24 @@ Result<int> luna_main(int argc, char** argv)
Mouse mouse_pointer { screen.canvas() }; 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); int fd = open("/dev/null", O_RDONLY);
if (fd >= 0) if (fd >= 0)
{ {
@ -121,44 +116,8 @@ Result<int> luna_main(int argc, char** argv)
close(fd); close(fd);
} }
setegid(2); // We're ready now.
seteuid(2); os::IPC::notify_parent();
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);
ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10); 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 = mouse->fd(), .events = POLLIN, .revents = 0 }));
TRY(fds.try_append({ .fd = keyboard->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 = 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)); 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(); auto* window = g_windows.last().value();
request.window = window->id; 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++) for (usize i = 0; i < clients.size(); i++)
{ {
if (fds[i + 3].revents & POLLIN) wind::handle_ipc(*clients[i]); if (fds[i + 4].revents & POLLIN) clients[i]->conn->check_for_messages();
if (fds[i + 3].revents & POLLHUP) 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); os::println("wind: Client %zu disconnected", i);
fds.remove_at(i + 3); fds.remove_at(i + 4);
auto client = clients.remove_at(i); auto client = clients.remove_at(i);
client->conn.disconnect(); client->conn->disconnect();
for (auto& window : client->windows) for (auto& window : client->windows)
{ {
if (window) 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)));
}
} }
} }

View File

@ -6,6 +6,7 @@
#include <luna/Format.h> #include <luna/Format.h>
#include <luna/SourceLocation.h> #include <luna/SourceLocation.h>
#include <luna/Spinlock.h> #include <luna/Spinlock.h>
#include <luna/StringBuilder.h>
static bool g_debug_enabled = true; static bool g_debug_enabled = true;
static bool g_serial_enabled = true; static bool g_serial_enabled = true;
@ -174,3 +175,23 @@ static bool g_check_already_failed = false;
} }
CPU::efficient_halt(); 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 {};
}

View File

@ -26,6 +26,8 @@ void set_text_console_initialized();
#define kwarnln(...) log(LogLevel::Warn, __VA_ARGS__) #define kwarnln(...) log(LogLevel::Warn, __VA_ARGS__)
#define kerrorln(...) log(LogLevel::Error, __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, [[noreturn]] extern void __critical_error_handler(SourceLocation location, const char* expr, const char* failmsg,
const char* errmsg); const char* errmsg);

View File

@ -117,6 +117,30 @@ namespace moon
K_CH46, // . K_CH46, // .
K_CH47, // / K_CH47, // /
K_CH48, // Space 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 // Unknown key
K_Unknown, K_Unknown,
}; };

View File

@ -1,24 +1,16 @@
#pragma once #pragma once
#include "api/Keyboard.h" #include "api/Keyboard.h"
#include <luna/Option.h> #include <luna/Option.h>
#include <luna/Vector.h>
namespace Keyboard 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 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); Option<moon::KeyboardPacket> decode_scancode(u8 scancode, KeyboardState& state);
} }

View File

@ -19,6 +19,7 @@ namespace MMU
NoExecute = 4, NoExecute = 4,
WriteThrough = 8, WriteThrough = 8,
CacheDisable = 16, CacheDisable = 16,
Global = 32,
}; };
enum class UseHugePages enum class UseHugePages

View File

@ -25,6 +25,13 @@ enable_nx:
wrmsr wrmsr
ret ret
global enable_global_pages
enable_global_pages:
mov rax, cr4
or ax, 1 << 7
mov cr4, rax
ret
global load_gdt global load_gdt
load_gdt: load_gdt:
cli cli

View File

@ -22,6 +22,7 @@
extern "C" void enable_sse(); extern "C" void enable_sse();
extern "C" void enable_write_protect(); extern "C" void enable_write_protect();
extern "C" void enable_global_pages();
extern "C" void enable_nx(); extern "C" void enable_nx();
extern void setup_gdt(); 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" : ""); (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) void handle_cpu_exception(int signo, const char* err, Registers* regs)
{ {
if (err) kerrorln("Caught CPU exception: %s", err); 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)) if (!is_in_kernel(regs))
{ {
auto* current = Scheduler::current(); auto* current = Scheduler::current();
check_stack(current, regs); if (current->check_stack_on_exception(regs->rsp)) return;
current->send_signal(signo); current->send_signal(signo);
current->process_pending_signals(regs); current->process_pending_signals(regs);
@ -278,6 +269,7 @@ namespace CPU
void platform_init() void platform_init()
{ {
enable_sse(); enable_sse();
enable_global_pages();
// enable_write_protect(); // enable_write_protect();
if (test_nx()) enable_nx(); if (test_nx()) enable_nx();
else else

View File

@ -13,104 +13,9 @@ static bool is_key_released(u8& scancode)
return false; return false;
} }
constexpr u8 EXTENDED_KEY_CODE = 0xe0; constexpr static u8 print_screen_pressed[] = { 0xe0, 0x2a, 0xe0, 0x37 };
constexpr u8 LEFT_SHIFT = 0x2a; constexpr static u8 print_screen_released[] = { 0xe0, 0xb7, 0xe0, 0xaa };
constexpr u8 RIGHT_SHIFT = 0x36; constexpr static u8 pause[] = { 0xe1, 0x1d, 0x45, 0xe1, 0x9d, 0xc5 };
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 .
};
using namespace moon; using namespace moon;
@ -176,95 +81,113 @@ constexpr KeyCode keycode_table[] = {
K_F11, K_F12, K_F11, K_F12,
}; };
static bool is_shifted(const Keyboard::TTYKeyboardState& state) constexpr KeyCode extended_keycode_table[] = {
{ K_MediaPrev, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown, K_Unknown,
if (state.capslock) return !(state.left_shift || state.right_shift); K_Unknown, K_Unknown, K_MediaNext, K_Unknown, K_Unknown, K_KeypadEnter, K_RightControl,
return state.left_shift || state.right_shift; 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 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) 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 {}; return {};
} }
// FIXME: Support extended scancodes. if (scancode == 0xe1)
if (scancode == EXTENDED_KEY_CODE)
{ {
state.ignore_next = true; state.parsing_pause = true;
state.key_state.try_append(scancode).release_value();
return {}; return {};
} }
bool released = is_key_released(scancode); 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 };
} }
} }

View File

@ -107,6 +107,7 @@ namespace MMU
if (entry.no_execute) result |= Flags::NoExecute; if (entry.no_execute) result |= Flags::NoExecute;
if (entry.write_through) result |= Flags::WriteThrough; if (entry.write_through) result |= Flags::WriteThrough;
if (entry.cache_disabled) result |= Flags::CacheDisable; if (entry.cache_disabled) result |= Flags::CacheDisable;
if (entry.global) result |= Flags::Global;
return result; return result;
} }
@ -181,6 +182,7 @@ namespace MMU
entry.write_through = has_flag(flags, Flags::WriteThrough); entry.write_through = has_flag(flags, Flags::WriteThrough);
entry.cache_disabled = has_flag(flags, Flags::CacheDisable); entry.cache_disabled = has_flag(flags, Flags::CacheDisable);
entry.no_execute = has_flag(flags, Flags::NoExecute); entry.no_execute = has_flag(flags, Flags::NoExecute);
entry.global = has_flag(flags, Flags::Global);
entry.set_address(phys); entry.set_address(phys);
} }
@ -249,6 +251,7 @@ namespace MMU
l1.write_through = has_flag(flags, Flags::WriteThrough); l1.write_through = has_flag(flags, Flags::WriteThrough);
l1.cache_disabled = has_flag(flags, Flags::CacheDisable); l1.cache_disabled = has_flag(flags, Flags::CacheDisable);
l1.no_execute = has_flag(flags, Flags::NoExecute); l1.no_execute = has_flag(flags, Flags::NoExecute);
l1.global = has_flag(flags, Flags::Global);
flush_page(virt); flush_page(virt);
return {}; return {};
} }
@ -291,7 +294,7 @@ namespace MMU
check(physical_memory_size % ARCH_HUGE_PAGE_SIZE == 0); 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, 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; g_physical_mapping_base = physical_memory_base;

View File

@ -17,7 +17,7 @@ struct [[gnu::packed]] PageTableEntry
bool accessed : 1; bool accessed : 1;
bool ignore0 : 1; bool ignore0 : 1;
bool larger_pages : 1; bool larger_pages : 1;
bool ignore1 : 1; bool global : 1;
u8 available : 3; u8 available : 3;
u64 address : 48; u64 address : 48;
u8 available2 : 3; u8 available2 : 3;

View File

@ -7,6 +7,7 @@
#include "memory/MemoryManager.h" #include "memory/MemoryManager.h"
#include "thread/Clock.h" #include "thread/Clock.h"
#include "thread/Scheduler.h" #include "thread/Scheduler.h"
#include <endian.h>
#include <luna/Alignment.h> #include <luna/Alignment.h>
#include <luna/Buffer.h> #include <luna/Buffer.h>
#include <luna/CType.h> #include <luna/CType.h>
@ -157,13 +158,27 @@ namespace ATA
void Channel::select(u8 drive) void Channel::select(u8 drive)
{ {
if (drive == m_current_drive) return;
u8 value = (u8)(drive << 4) | 0xa0; u8 value = (u8)(drive << 4) | 0xa0;
if (value == m_current_select_value) return;
write_register(Register::DriveSelect, value); write_register(Register::DriveSelect, value);
delay_400ns(); 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; m_current_drive = drive;
} }
@ -503,9 +518,8 @@ namespace ATA
m_is_lba48 = true; m_is_lba48 = true;
// FIXME: This assumes the host machine is little-endian. u32 last_lba = be32toh(reply.last_lba);
u32 last_lba = __builtin_bswap32(reply.last_lba); u32 sector_size = be32toh(reply.sector_size);
u32 sector_size = __builtin_bswap32(reply.sector_size);
m_block_count = last_lba + 1; m_block_count = last_lba + 1;
m_block_size = sector_size; m_block_size = sector_size;
@ -543,10 +557,61 @@ namespace ATA
return true; 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; 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); m_channel->select(m_drive_index);
// We use PIO here. // We use PIO here.
@ -559,30 +624,11 @@ namespace ATA
m_channel->delay_400ns(); m_channel->delay_400ns();
usize i = 0;
TRY(m_channel->wait_until_ready()); TRY(m_channel->wait_until_ready());
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]); for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
while (i < response_size) TRY(read_pio_bytes(out, 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;
}
return {}; return {};
} }
@ -704,6 +750,26 @@ namespace ATA
return send_packet_atapi_pio(&read_packet, out, (u16)size); 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) Result<void> Drive::read_lba(u64 lba, void* out, usize nblocks)
{ {
const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size; 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); return atapi_read_pio(lba, out, nblocks * m_block_size);
} }
else 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() void Drive::irq_handler()

View File

@ -54,6 +54,8 @@ namespace ATA
enum CommandRegister : u8 enum CommandRegister : u8
{ {
CMD_ReadSectors = 0x20,
CMD_ReadSectorsExt = 0x24,
CMD_Identify = 0xec, CMD_Identify = 0xec,
CMD_Packet = 0xa0, CMD_Packet = 0xa0,
CMD_Identify_Packet = 0xa1 CMD_Identify_Packet = 0xa1
@ -180,6 +182,11 @@ namespace ATA
Result<void> atapi_read_pio(u64 lba, void* out, usize size); 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; Channel* m_channel;
u8 m_drive_index; u8 m_drive_index;
@ -250,6 +257,7 @@ namespace ATA
} }
void select(u8 drive); void select(u8 drive);
void select(u8 base, u8 drive);
bool initialize(); bool initialize();
@ -270,6 +278,7 @@ namespace ATA
bool m_irq_called { false }; bool m_irq_called { false };
u8 m_current_drive = (u8)-1; u8 m_current_drive = (u8)-1;
u8 m_current_select_value = 0xff;
Option<Drive> m_drives[2]; Option<Drive> m_drives[2];
}; };

View File

@ -16,6 +16,8 @@ Result<bool> ScriptLoader::sniff()
Result<u64> ScriptLoader::load(AddressSpace* space) Result<u64> ScriptLoader::load(AddressSpace* space)
{ {
u8 buf[256]; u8 buf[256];
memset(buf, 0, sizeof(buf));
usize nread = TRY(m_inode->read(buf, 2, 255)); usize nread = TRY(m_inode->read(buf, 2, 255));
if (!nread) return err(ENOEXEC); if (!nread) return err(ENOEXEC);
for (usize i = 0; i < nread; i++) for (usize i = 0; i < nread; i++)

View File

@ -33,7 +33,7 @@ void StorageCache::clear()
m_mutex.unlock(); m_mutex.unlock();
} }
StorageCache::StorageCache() StorageCache::StorageCache(BlockDevice* device) : m_device(device)
{ {
g_storage_caches.append(this); g_storage_caches.append(this);
} }

Some files were not shown because too many files have changed in this diff Show More