Compare commits

...

87 Commits

Author SHA1 Message Date
8e1a0d0e13
libos+LICENSE: Update copyright year
Some checks failed
Build and test / build (push) Failing after 1m37s
Happy new year!
2025-01-06 16:10:00 +01:00
773cd576d1
tools: Print clang-format version
Some checks failed
Build and test / build (push) Failing after 1m41s
2024-12-23 23:12:57 +01:00
498c371547
tools: Show format violations
Some checks failed
Build and test / build (push) Failing after 1m40s
2024-12-23 23:10:11 +01:00
b0be170b41
CI: Automatically check formatting
Some checks failed
Build and test / build (push) Failing after 1m40s
2024-12-23 23:05:04 +01:00
5cb4b8b1fe
docs: Add clang-format dependency
All checks were successful
Build and test / build (push) Successful in 1m41s
2024-12-23 22:59:57 +01:00
d9713723c9
tools: Update sources list and run clang-format 2024-12-23 22:56:56 +01:00
c0cf952113
docs: Clarify the wording in dependencies.md
All checks were successful
Build and test / build (push) Successful in 1m31s
2024-12-22 19:27:11 +01:00
22766a6724
docs: Correct a few details in boot_process.md
All checks were successful
Build and test / build (push) Successful in 1m35s
2024-12-21 13:40:53 +01:00
abdaad5ea4
docs: Update boot_process.md
All checks were successful
Build and test / build (push) Successful in 1m39s
2024-12-21 10:30:33 +01:00
f116afd59d
docs: Fix relative source path
All checks were successful
Build and test / build (push) Successful in 1m30s
2024-12-19 21:17:27 +01:00
1b80111938
Fix a couple of Markdown links
All checks were successful
Build and test / build (push) Successful in 1m37s
2024-12-19 21:15:22 +01:00
f91800f5e1
docs: Add dependency information separately
All checks were successful
Build and test / build (push) Successful in 1m41s
2024-12-19 21:12:49 +01:00
6dcdc43dc2
gui+su+base: Store hashed passwords and use those to log in
All checks were successful
Build and test / build (push) Successful in 1m34s
Unsalted SHA256 passwords are still a long way from being secure, but at least we're not storing plaintext anymore.
2024-12-14 12:48:13 +01:00
00382421b2
libluna: Add move versions of value_or 2024-12-14 12:46:36 +01:00
5d5c85a022
gui/InputField: Calculate correct length for the returned StringView 2024-12-14 12:46:14 +01:00
48ee803e58
init: Avoid "No child processes" error spam
All checks were successful
Build and test / build (push) Successful in 1m41s
2024-12-14 12:19:13 +01:00
984200ca9a
loginui: Pledge unix 2024-12-14 12:18:59 +01:00
ac260d0397
wind+libui: Add "pledge" functionality to access special features for system programs
All checks were successful
Build and test / build (push) Successful in 1m42s
This segments privileges more, making it so that any app connecting to wsys.sock can't just always access every single advanced feature in wind if they don't need to.
Of course, apps have to restrict themselves, which is why only privileged apps have access to this feature in the first place.
Normal apps' pledges are all empty and can't be changed.

An example: taskbar uses the "ExtendedLayers" pledge to move its window to the background, but relinquishes it afterwards, and doesn't need any other advanced feature for now.

If a pledge-capable app tries to use a pledge-protected function without having pledged anything, it can't. Pledges are mandatory if you want to access certain functionality, unlike the kernel's pledges which make every syscall available if you don't use pledge().
2024-12-13 23:47:53 +01:00
fb3333a086
wind: Remove special window attributes and add different window layers.
All checks were successful
Build and test / build (push) Successful in 1m38s
Two layers are accessible to all apps: global and global_top (for popups and similar windows).
Three other layers are accessible to privileged clients (background, system and lock), for things that need to be on a different level than user apps, like the taskbar, system popups, menus and the lock screen.
2024-12-13 21:53:12 +01:00
ccef3e2069
ports: Add cbench to the ports list
All checks were successful
Build and test / build (push) Successful in 1m41s
This was missed when adding the original cbench port.
2024-12-13 21:39:47 +01:00
ad3cea7e78
gui: Rename "launch" to "execd"
All checks were successful
Build and test / build (push) Successful in 1m42s
2024-12-11 20:34:47 +01:00
865a913502
wind: Fix help message when unprivileged
wind --user=<NAME> is not supported anymore. We'll setuid() and setgid() to wind:wind on our own.
2024-12-11 20:30:40 +01:00
499bf6dd19
gui+system: Add pledges to loginui and startui
All checks were successful
Build and test / build (push) Successful in 1m46s
2024-12-11 19:56:40 +01:00
94e7dde8af
kernel/waitpid: fix a panic-causing extraneous exclamation mark
Big oof moment.

Thankfully kernel panics sometimes just give you the exact source of the problem :P
"-- KERNEL PANIC: Check failed at kernel/src/sys/waitpid.cpp:67, in sys_waitpid: !target->dead() --"
2024-12-11 19:56:24 +01:00
f38c9e68c1
wind: Remove unneeded pledges
wind doesn't spawn child processes anymore, startui does.
2024-12-11 19:45:04 +01:00
3b8aabce0f
kernel: Add debug.cmake include to config.cmake template
All checks were successful
Build and test / build (push) Successful in 1m32s
2024-12-11 19:28:22 +01:00
5f56e4b63a
kernel: Disable UBSAN in debug.cmake
All checks were successful
Build and test / build (push) Successful in 1m42s
UBSAN seems to bloat the kernel too much, so let's make debug.cmake actually usable for debugging by commenting it out.
2024-12-11 19:19:36 +01:00
24b886b0d1
kernel: Log each thread's instruction pointer when dumping scheduler stats 2024-12-11 19:18:25 +01:00
d8e4489079
kernel: Make Thread::ip() and sp() const-friendly 2024-12-11 19:17:04 +01:00
2868fd8122
kernel: Add a way to lookup specific threads 2024-12-11 19:16:45 +01:00
56a2b607b5
kernel: Fix some debug-only log strings after the process rework 2024-12-11 19:16:22 +01:00
ec6ceb4c8d
init: Fix infinite wait loop
All checks were successful
Build and test / build (push) Successful in 1m51s
Since waitpid always sends a SIGCHLD now, we'd handle a SIGCHLD after returning from waitpid, which would trigger another wait, looping forever and not actually handling the wait.
2024-12-11 19:13:12 +01:00
d05d6fad0b
kernel: Interrupt waitpid (even when SIGCHLD is pending) when other signals are also pending
All checks were successful
Build and test / build (push) Successful in 1m36s
This fixes init not receiving the kill signal when running tests.
2024-12-07 13:15:58 +01:00
42afef5ccb
kernel: Leave reaping to the reap thread
Some checks failed
Build and test / build (push) Has been cancelled
This seems to fix previous problems. Apparently reaping a thread somewhat corrupts/replaces the calling thread's address space.

I should've known there's a reason we do it in a separate kernel thread...
2024-12-07 13:05:38 +01:00
853a6d7b38
kernel/x86_64: Dump the process address space ranges on exception 2024-12-07 13:02:25 +01:00
8e30e0e19d
base: Revert loginui.conf change
Some checks failed
Build and test / build (push) Failing after 3h10m29s
Oops, was using this for loginui testing, it should be turned off by default.
2024-12-06 21:38:29 +01:00
dc766e1da7
kernel: Rework VFS access checking + add processes
Some checks failed
Build and test / build (push) Has been cancelled
VFS functions now accept a single Process* pointer instead of credentials and groups.
There is now a distinction between processes and threads
Now to fix all the bugs... waitpid crashes the process with an NX error...
2024-12-06 21:35:59 +01:00
6fc49a0be5
init: Let's not forget to unhook our notifiers
All checks were successful
Build and test / build (push) Successful in 1m42s
2024-12-04 22:54:01 +01:00
7761a8a41f
kernel+launch: Always send SIGCHLD when a child exits
All checks were successful
Build and test / build (push) Successful in 1m44s
POSIX says so. I thought it was only when the parent wasn't actively waiting, to signal it, but it turns out it's always sent.
2024-12-04 22:44:16 +01:00
0ca6c5f814
taskbar: Remove sigchld handler
We are not launching child processes from taskbar anymore, that job is left to /bin/launch.
2024-12-04 22:42:59 +01:00
3032415bc0
kernel: Move "push_mem_on_stack" and "pop_mem_from_stack" to MemoryManager
All checks were successful
Build and test / build (push) Successful in 1m52s
2024-11-23 20:03:04 +01:00
7b2977a036
kernel: Use a mutex to allocate new posix timers for a thread 2024-11-23 20:02:34 +01:00
9e65131452
editor: Avoid showing an error dialog when pressing Ctrl+S
All checks were successful
Build and test / build (push) Successful in 1m52s
2024-10-26 14:00:57 +02:00
d908ccea6b
wind: Move some stuff from String to RefString 2024-10-26 14:00:26 +02:00
e3613d1653
loginui+2048: Replace String with RefString 2024-10-26 13:43:20 +02:00
53f8a583dc
libluna+libos+libui: Move Action to libluna and make it usable in the kernel
Some checks failed
Build and test / build (push) Failing after 1m21s
This commit adds an error-propagating constructor for Action and Function, which makes them usable in the kernel.
2024-10-19 21:25:17 +02:00
c21fc2a297
ports: Add cbench port
Some checks failed
Build and test / build (push) Failing after 1m37s
2024-09-23 19:51:42 +02:00
fd26f40938
editor: Add "Save file as..." and error dialogs
Some checks failed
Build and test / build (push) Failing after 1m32s
2024-09-19 18:27:16 +02:00
fd2fe16538
libui: Add Dialog 2024-09-19 18:26:58 +02:00
38fcd8e3e1
libos: Stop timers in the destructor if needed
Fixes some bug in InputField causing a crash.
2024-09-19 18:26:42 +02:00
05bf792dbd
libui: Make InputField final
Some checks failed
Build and test / build (push) Failing after 1m31s
2024-09-18 21:48:15 +02:00
b95cfac3ec
libui: Fix crashes when closing non-main windows
Some checks failed
Build and test / build (push) Has been cancelled
This fix moves the actual closing of the window to after all the events are processed.
2024-09-18 21:47:31 +02:00
17a31e5ea9
libos: Add more constructor variants for Action/Function 2024-09-18 21:44:46 +02:00
1f0286c9c7
base: Add taskbar entry for the editor
All checks were successful
Build and test / build (push) Successful in 1m51s
2024-09-14 15:40:27 +02:00
ffd1c73b0f
base: Start a welcome message instead of a terminal on login
All checks were successful
Build and test / build (push) Successful in 1m44s
2024-09-14 15:31:42 +02:00
12ab71ee40
libos: Support comments in config files
All checks were successful
Build and test / build (push) Successful in 1m45s
2024-09-14 15:12:36 +02:00
4cf39c14a1
base+gui: Move autologin configuration to /etc/loginui.conf
All checks were successful
Build and test / build (push) Successful in 1m45s
2024-09-14 15:05:05 +02:00
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
224 changed files with 3679 additions and 1383 deletions

View File

@ -11,11 +11,13 @@ jobs:
- name: Download dependencies
run: |
apt update
apt install -y cmake ninja-build nasm genext2fs qemu-system build-essential wget git
apt install -y cmake ninja-build nasm genext2fs qemu-system build-essential wget git clang-format
- 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: Check formatting
run: tools/check-formatting.sh
- name: Build and run tests
run: tools/run-tests.sh

View File

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

View File

@ -1,6 +1,6 @@
BSD 2-Clause License
Copyright (c) 2022-2024, apio.
Copyright (c) 2022-2025, apio.
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -20,26 +20,30 @@ A simple POSIX-based operating system for 64-bit computers, written in C++.
## Screenshot
![Screenshot as of 0.6.0](docs/screenshots/screenshot-0.6.0.png)
## System requirements and dependencies
Read [docs/dependencies.md](docs/dependencies.md) for the full information. In short, all modern Unixes should work, provided the dependencies are available.
## Setup
To build and run Luna, you will need to build a [cross-compiler](https://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler) and cross-binutils for `x86_64-luna`.
For this, you should start by installing the [required dependencies](https://wiki.osdev.org/GCC_Cross_Compiler#Installing_Dependencies).
Then, run `tools/setup.sh` to build the toolchain.
There is a script provided for this. Run `tools/setup.sh` to build the toolchain.
Please beware that building GCC and Binutils can take some time, depending on your machine.
## Running
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`, `fakeroot` and `genext2fs`.
`tools/run.sh` is the script you should use in most cases. It will build changed files, install, make an ISO image, and run Luna in QEMU.
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` 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/loginui.conf](base/etc/loginui.conf) and change the line that says `Autologin=true` to `Autologin=false`.
## Prebuilt images
Prebuilt ISO images for every release version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).

View File

@ -1,54 +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)
luna_app(startui.cpp startui)
luna_app(launch.cpp launch)
luna_app(run.cpp run)

View File

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

5
base/etc/loginui.conf Normal file
View File

@ -0,0 +1,5 @@
# Configuration file for loginui.
# If this parameter is set to "true", loginui automatically spawns a UI session as the below user instead of prompting for a username and password.
Autologin=true
# The user to create a session for if "Autologin" is set to true (see above). If the username is invalid, loginui will behave as if "Autologin" was set to false.
AutologinUser=selene

View File

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

View File

@ -1,14 +1,26 @@
Welcome to the Luna operating system!
You are running on the default user account, selene.
If you are familiar with Unix-style operating systems (like Linux or *BSD), you should be able to use the Luna terminal without much problems.
If you are familiar with Unix-style operating systems (like Linux or *BSD),
you should be able to use the Luna terminal without much problems.
Following the traditional Unix filesystem structure, programs are installed in /usr/bin (/bin is a symlink to /usr/bin). The command `ls /bin` will show all commands available on your current Luna installation.
Following the traditional Unix filesystem structure,
programs are installed in /usr/bin (/bin is a symlink to /usr/bin).
The command `ls /bin` will show all commands available on
your current Luna installation.
Currently, because of driver limitations, the root file system is mounted read-only. Your home folder is writable, but volatile; it is created and populated on boot, and its contents will vanish after a reboot.
Currently, because of driver limitations,
the root file system is mounted read-only.
Your home folder is writable, but volatile; it is
created and populated on boot,
and its contents will vanish after a reboot.
The system is booted using the 'init' program. You can read its configuration files in the /etc/init directory to learn more about the boot process.
The system is booted using the 'init' program.
You can read its configuration files in the /etc/init directory to
learn more about the boot process.
Luna is free software, released under the BSD-2-Clause license. The license is included in the LICENSE file in your home directory.
Luna is free software, released under the BSD-2-Clause license.
The license is included in the LICENSE file in your home directory.
View the source code and read more about Luna at https://git.cloudapio.eu/apio/Luna.
View the source code and read more about Luna at
https://git.cloudapio.eu/apio/Luna.

View File

@ -1,3 +0,0 @@
Name=terminal
Description=Start the terminal.
Command=/usr/bin/terminal

3
base/etc/user/00-welcome Normal file
View File

@ -0,0 +1,3 @@
Name=welcome
Description=Show a welcome message for the user.
Command=/usr/bin/editor welcome

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -54,15 +54,15 @@ As soon as the scheduler switches to the `[kinit]` thread, it will never return
`[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.
- `[reap]`: To understand what this thread does, we must take a look at what happens when processes 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))_
_(Relevant files: [kernel/src/main.cpp](../kernel/src/main.cpp#L23), [kernel/src/thread/Scheduler.cpp](../kernel/src/thread/Scheduler.cpp#L231), [kernel/src/thread/Thread.cpp](../kernel/src/thread/Thread.cpp#L126), [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 a process calls the `_exit()` syscall, all its threads' states are set to "Dying". This tells the scheduler to avoid switching to them, and the process's parent is notified, by sending SIGCHLD and (optionally) unblocking a blocked `waitpid()` call. The process remains visible to the rest of the system, and if its parent does not wait for it, it will stay there as a "zombie process". Meanwhile, the `[reap]` thread runs and collects all the resources from each thread. The process object is still alive (in a "zombie" state), but its threads have been cleaned up.
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()`).
When the process's parent waits for it, it is marked for reaping (by setting its thread count to -1 (PROCESS_SHOULD_REAP)), and the `[reap]` thread runs.
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.
The `[reap]` thread then "reaps" all the dead processes' resources. It frees up their memory, file descriptors, and other resources. After reaping, the process 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.
@ -90,7 +90,7 @@ After the kernel stage of the boot process, the system looks like this:
```
## Stage 2: preinit
_Relevant files: [apps/preinit.cpp](../apps/preinit.cpp)_
_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.
@ -133,7 +133,7 @@ After the preinit stage of the boot process, the system looks like this:
```
## Stage 3: init
_Relevant files: [apps/init.cpp](../apps/init.cpp#L406)_
_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.
@ -149,7 +149,7 @@ 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.
`99-login`: This service starts a graphical session, by calling `/usr/bin/loginui`. This service will be restarted if necessary.
### File system and process layout
@ -172,24 +172,60 @@ After the init stage of the boot process, the system looks like this:
[x86_64-io] - PID 3
[reap] - PID 4
[oom] - PID 5
/usr/bin/startui - PID 13
/usr/bin/loginui - 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._
_Note: loginui 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, loginui ends up with PID 13._
## Stage 4: startui
_Relevant files: [apps/startui.cpp](../apps/startui.cpp), [wind/main.cpp](../wind/main.cpp)_
## Stage 4: loginui
_Relevant files: [gui/loginui.cpp](../gui/loginui.cpp), [gui/wind/main.cpp](../gui/wind/main.cpp)_
`/usr/bin/loginui`'s job is quite simple: it prompts the user to log in with their password, after which a graphical session is started.
_Note: On development builds, Autologin=true is added to /etc/loginui.conf which disables password prompting and executes startui directly._
First, loginui starts the display server, `/usr/bin/wind`, so that it can use its capabilities to show a graphical login prompt. It is started with permissions `root:root`, and later drops privileges to `wind:wind`.
After that, loginui prompts for a username and password, checks it against the hashed password stored in `/etc/shadow`, and finally executes `/usr/bin/startui` which does the actual heavy work of starting all the services needed for a UI session.
### File system and process layout
After the loginui 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
/usr/bin/wind - PID 14
```
## Stage 5: 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 display server itself, `/usr/bin/wind`. If not already started by loginui, `startui` makes sure it's running.
- The execution server (`/usr/bin/execd`), 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 (`/tmp/wsys.sock`, as opposed to the standard `/tmp/wind.sock`). This grants it the ability to use advanced wind features, such as placing the taskbar window behind all other windows.
- 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`.
Currently, `init --user` only does one thing: it opens up a text editor with a welcome message 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
@ -214,8 +250,7 @@ After the startui stage of the boot process, the system is fully started up and
[oom] - PID 5
/usr/bin/startui - PID 13
/usr/bin/wind - PID 14
/usr/bin/launch - PID 15
/usr/bin/execd - PID 15
/usr/bin/taskbar - PID 16
/usr/bin/init --user - PID 17
/usr/bin/terminal - PID 18
/bin/sh - PID 19
/usr/bin/editor welcome - PID 18

35
docs/dependencies.md Normal file
View File

@ -0,0 +1,35 @@
# Dependencies required to build and run Luna
## System requirements
Any modern UNIX-like system that supports all the tools listed below should work (Hopefully, that will include Luna itself in the future!).
I personally build and run Luna on an amd64 Fedora Linux 40 machine. CI runs on arm64 Ubuntu 22.04. Any other configurations are untested. Windows is not supported, although you can try using WSL if you really want to.
## Building a cross-compiler toolchain
For this, you should start by installing the [required dependencies](https://wiki.osdev.org/GCC_Cross_Compiler#Installing_Dependencies) for any OSdev cross-compiler build.
Also make sure you have the perl module `File::Compare` installed, it is required to build autoconf. On Fedora you can install it using the package manager by running `# dnf install perl-File-Compare`. If your distro doesn't have it, you might have to install it via `cpan`.
## Building the actual system
The build process needs some extra dependencies to run: `cmake`, `ninja`, `nasm`, `fakeroot` and `genext2fs`. On some distributions the `ninja` package is called `ninja-build` instead.
If you want to use `make` instead of `ninja`, create a file called `env-local.sh` in the project root and add the line `USE_MAKE=1`. In this case, ninja does not need to be installed.
## Running the built image in a virtual machine
The script provided by the project to run the system, `tools/run.sh`, assumes that QEMU is installed and uses that to run the image. Therefore, make sure your system has `qemu-system-x86_64` in the PATH. If it doesn't, install it using the method appropriate for your system, usually installing `qemu` or `qemu-system` from the package manager.
That being said, there's no requirement to use QEMU. If you want to use a different virtualization program, such as Oracle VirtualBox or VMWare, just use `tools/build-iso.sh` instead of `run.sh` and use the built `Luna.iso` in those programs.
## Formatting/linting
Please make sure you have `clang-format` installed. Additionally, if your editor does not support format-on-save or you do not have it configured, please run `tools/run-clang-format.sh` before committing, to make sure all code follows the same style conventions.
## Source dependencies
TLDR: Luna does not depend on any third-party library.
Every part of Luna is written from scratch and depends only on its own libraries and programs, with two small exceptions (included here for crediting and licensing purposes, but there is no need to download and build them separately):
The bootloader, BOOTBOOT. It is available at [gitlab.com/bztsrc/bootboot](https://gitlab.com/bztsrc/bootboot), under the MIT license. It is automatically pulled and built from source by `tools/setup.sh`.
[libc/src/strtod.cpp](../libc/src/strtod.cpp). Written by Yasuhiro Matsumoto, adapted from https://gist.github.com/mattn/1890186 and available under a public domain license.

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(execd.cpp execd)
luna_service(run.cpp run)
luna_service(loginui.cpp loginui)
target_link_libraries(loginui PRIVATE ui)

View File

@ -1,4 +1,4 @@
#include <luna/String.h>
#include <luna/RefString.h>
#include <luna/Utf8.h>
#include <stdlib.h>
#include <time.h>
@ -325,7 +325,7 @@ class GameWidget final : public ui::Widget
canvas.fill(colors[tile.color]);
auto fmt = TRY(String::format("%d"_sv, tile.number));
auto fmt = TRY(RefString::format("%d"_sv, tile.number));
auto font = ui::Font::default_bold_font();
auto rect = ui::align({ 0, 0, canvas.width, canvas.height },

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

@ -10,10 +10,12 @@
#include "EditorWidget.h"
#include <ctype.h>
#include <luna/PathParser.h>
#include <luna/RefString.h>
#include <luna/Utf8.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <ui/App.h>
#include <ui/Dialog.h>
EditorWidget::EditorWidget(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
{
@ -27,7 +29,8 @@ Result<void> EditorWidget::load_file(const os::Path& path)
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());
auto message = TRY(RefString::format("%s is not a regular file", path.name().chars()));
ui::Dialog::show_message("Error", message.view());
return {};
}
@ -41,9 +44,9 @@ Result<void> EditorWidget::load_file(const os::Path& path)
m_cursor = m_data.size();
m_path = path;
m_path = TRY(String::from_string_view(path.name()));
auto basename = TRY(PathParser::basename(m_path.name()));
auto basename = TRY(PathParser::basename(m_path.view()));
String title = TRY(String::format("Text Editor - %s"_sv, basename.chars()));
window()->set_title(title.view());
@ -126,15 +129,38 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
return ui::EventResult::DidHandle;
}
Result<void> EditorWidget::save_file_as()
{
ui::Dialog::show_input_dialog(
"Save file as...", "Please enter the path to save this file to:", [this](StringView path) {
m_path = String::from_string_view(path).release_value();
auto rc = save_file();
if (rc.has_error())
{
os::eprintln("Failed to save file %s: %s", m_path.chars(), rc.error_string());
ui::Dialog::show_message("Error", "Failed to save file");
}
else
{
auto basename = PathParser::basename(m_path.view()).release_value();
String title = String::format("Text Editor - %s"_sv, basename.chars()).release_value();
window()->set_title(title.view());
}
});
return {};
}
Result<void> EditorWidget::save_file()
{
if (m_path.is_empty_path())
if (m_path.is_empty())
{
os::eprintln("editor: no file to save buffer to!");
return err(ENOENT);
TRY(save_file_as());
return {};
}
auto file = TRY(os::File::open(m_path, os::File::WriteOnly));
auto file = TRY(os::File::open_or_create(m_path.view(), os::File::WriteOnly));
return file->write(m_data);
}

View File

@ -21,14 +21,15 @@ class EditorWidget : public ui::TextInput
Result<void> load_file(const os::Path& path);
Result<void> save_file();
Result<void> save_file_as();
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(ui::Canvas& canvas) override;
os::Path& path()
os::Path path()
{
return m_path;
return m_path.view();
}
private:
@ -41,7 +42,7 @@ class EditorWidget : public ui::TextInput
};
Vector<Line> m_lines;
os::Path m_path { AT_FDCWD };
String m_path;
Result<void> recalculate_lines();
void recalculate_cursor_position();

View File

@ -11,6 +11,7 @@
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <ui/App.h>
#include <ui/Dialog.h>
Result<int> luna_main(int argc, char** argv)
{
@ -32,15 +33,20 @@ Result<int> luna_main(int argc, char** argv)
auto* editor = TRY(make<EditorWidget>(ui::Font::default_font()));
window->set_main_widget(*editor);
if (!path.is_empty()) TRY(editor->load_file(path));
if (!path.is_empty()) 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());
if (result.has_error())
{
os::eprintln("Failed to save file %s: %s", editor->path().name().chars(), result.error_string());
ui::Dialog::show_message("Error", "Failed to save file");
}
}));
TRY(window->add_keyboard_shortcut({ moon::K_CH26, ui::Mod_Ctrl | ui::Mod_Shift }, true,
[&](ui::Shortcut) { editor->save_file_as(); }));
window->draw();
return app.run();

View File

@ -1,5 +1,6 @@
#include <luna/Sort.h>
#include <luna/StringBuilder.h>
#include <os/Config.h>
#include <os/Directory.h>
#include <os/File.h>
#include <os/FileSystem.h>
@ -18,12 +19,7 @@ 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 sighup_handler(int)
void sigquit_handler(int)
{
// Reload the taskbar by exec-ing the executable, resetting everything.
StringView args[] = { "/usr/bin/taskbar" };
@ -66,64 +62,31 @@ static Result<void> load_application_file(const os::Path& path)
{
os::println("[taskbar] reading app file: %s", path.name().chars());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
auto file = TRY(os::ConfigFile::open(path));
ApplicationFile app_file;
while (true)
app_file.name = TRY(String::from_string_view(file->read_string_or("Name", "")));
if (app_file.name.is_empty())
{
auto line = TRY(file->read_line());
if (line.is_empty()) break;
line.trim("\n");
if (line.is_empty()) continue;
auto parts = TRY(line.split_once('='));
if (parts.size() < 2 || parts[0].is_empty() || parts[1].is_empty())
{
os::println("[taskbar] file contains invalid line, aborting: '%s'", line.chars());
return {};
}
if (parts[0].view() == "Name")
{
app_file.name = move(parts[1]);
continue;
}
if (parts[0].view() == "Description")
{
// We let users specify this in the config file, but taskbar doesn't actually use it.
continue;
}
if (parts[0].view() == "Command")
{
app_file.command = move(parts[1]);
continue;
}
if (parts[0].view() == "Icon")
{
app_file.icon = move(parts[1]);
continue;
}
os::println("[taskbar] skipping unknown entry name %s", parts[0].chars());
}
if (app_file.icon.is_empty())
{
os::println("[taskbar] app file is missing 'Icon' entry, aborting!");
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)));
@ -149,11 +112,11 @@ Result<int> luna_main(int, char**)
{
ui::App app;
TRY(app.init("/tmp/wsys.sock"));
app.pledge(ui::Pledge::ExtendedLayers);
TRY(os::EventLoop::the().register_signal_handler(SIGCHLD, sigchld_handler));
TRY(os::EventLoop::the().register_signal_handler(SIGHUP, sighup_handler));
TRY(os::EventLoop::the().register_signal_handler(SIGQUIT, sigquit_handler));
launcher_client = TRY(os::IPC::Client::connect("/tmp/launch.sock", false));
launcher_client = TRY(os::IPC::Client::connect("/tmp/execd.sock", false));
ui::Rect screen = app.screen_rect();
@ -163,7 +126,8 @@ Result<int> luna_main(int, char**)
app.set_main_window(window);
window->set_background(TASKBAR_COLOR);
window->set_special_attributes(ui::UNFOCUSEABLE);
window->set_layer(ui::Layer::Background);
app.pledge(0);
ui::HorizontalLayout layout(ui::Margins { 0, 0, 0, 0 }, ui::AdjustHeight::Yes, ui::AdjustWidth::No);
window->set_main_widget(layout);

View File

@ -1,5 +1,5 @@
/**
* @file launch.cpp
* @file execd.cpp
* @author apio (cloudapio.eu)
* @brief Background process that handles detached launching of apps.
*
@ -10,6 +10,7 @@
#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>
@ -41,24 +42,24 @@ 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;
default: os::eprintln("execd: Invalid IPC message from client!"); return;
}
}
void sigchld_handler(int)
{
os::Process::wait(os::Process::ANY_CHILD, nullptr);
os::Process::wait(os::Process::ANY_CHILD, nullptr, WNOHANG);
}
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";
StringView socket_path = "/tmp/execd.sock";
os::ArgumentParser parser;
parser.add_description("Background process that handles detached launching of apps."_sv);
parser.add_system_program_info("launch"_sv);
parser.add_system_program_info("execd"_sv);
parser.parse(argc, argv);
signal(SIGCHLD, sigchld_handler);
@ -66,6 +67,9 @@ Result<int> luna_main(int argc, char** argv)
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 }));
@ -83,7 +87,7 @@ Result<int> luna_main(int argc, char** argv)
if (fds[0].revents & POLLIN)
{
auto client = TRY(server->accept());
os::println("launch: New client connected!");
os::println("execd: New client connected!");
TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 }));
auto connection = TRY(os::IPC::ClientConnection::adopt_connection(move(client)));
@ -95,7 +99,7 @@ Result<int> luna_main(int argc, char** argv)
if (fds[i + 1].revents & POLLIN) clients[i]->check_for_messages();
if (fds[i + 1].revents & POLLHUP)
{
os::println("launch: Client %zu disconnected", i);
os::println("execd: Client %zu disconnected", i);
fds.remove_at(i + 1);
auto client = clients.remove_at(i);
client->disconnect();

View File

@ -19,6 +19,7 @@ set(SOURCES
src/Label.cpp
src/InputField.cpp
src/TextInput.cpp
src/Dialog.cpp
)
add_library(ui ${SOURCES})

View File

@ -49,6 +49,8 @@ namespace ui
return m_main_window;
}
void pledge(i16 pledges);
Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>);
void unregister_window(Window* window, Badge<Window>);
@ -61,6 +63,7 @@ namespace ui
HashMap<int, OwnedPtr<Window>> m_windows;
bool m_should_close { false };
os::EventLoop m_loop;
Vector<int> m_window_clear_queue;
bool process_events();

View File

@ -8,7 +8,7 @@
*/
#pragma once
#include <os/Action.h>
#include <luna/Action.h>
#include <ui/Widget.h>
namespace ui
@ -19,7 +19,7 @@ namespace ui
Button(Rect rect);
void set_widget(Widget& widget);
void set_action(os::Action&& action);
void set_action(Action&& action);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
@ -32,6 +32,6 @@ namespace ui
bool m_hovered { false };
bool m_clicked { false };
Widget* m_child;
os::Action m_action;
Action m_action;
};
}

View File

@ -0,0 +1,22 @@
/**
* @file Window.h
* @author apio (cloudapio.eu)
* @brief UI window dialogs.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#pragma once
#include <luna/Action.h>
#include <ui/Window.h>
namespace ui
{
namespace Dialog
{
Result<void> show_message(StringView title, StringView message);
Result<void> show_input_dialog(StringView title, StringView message, Function<StringView> callback);
}
}

View File

@ -8,13 +8,13 @@
*/
#pragma once
#include <os/Action.h>
#include <luna/Action.h>
#include <ui/Font.h>
#include <ui/TextInput.h>
namespace ui
{
class InputField : public ui::TextInput
class InputField final : public ui::TextInput
{
public:
InputField(SharedPtr<ui::Font> font);
@ -23,9 +23,11 @@ namespace ui
Result<void> draw(ui::Canvas& canvas) override;
void clear();
StringView data();
void on_submit(os::Function<StringView>&& action)
void on_submit(Function<StringView>&& action)
{
m_on_submit_action = move(action);
m_has_on_submit_action = true;
@ -34,7 +36,7 @@ namespace ui
private:
SharedPtr<ui::Font> m_font;
os::Function<StringView> m_on_submit_action;
Function<StringView> m_on_submit_action;
bool m_has_on_submit_action { false };
};
}

View File

@ -65,7 +65,7 @@ namespace ui
void close();
void set_special_attributes(WindowAttributes attributes);
void set_layer(Layer layer);
Result<void> draw();
Result<ui::EventResult> handle_mouse_leave();
@ -73,13 +73,19 @@ namespace ui
Result<ui::EventResult> handle_mouse_buttons(ui::Point position, int buttons);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request);
Result<void> add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, os::Function<ui::Shortcut>&& action);
Result<void> add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, Function<ui::Shortcut>&& action);
int id() const
{
return m_id;
}
void on_close(Action&& action)
{
m_on_close_action = move(action);
m_has_on_close_action = true;
}
~Window();
private:
@ -93,10 +99,13 @@ namespace ui
Option<int> m_old_mouse_buttons;
bool m_decorated { false };
Action m_on_close_action;
bool m_has_on_close_action { false };
struct ShortcutAction
{
bool intercept;
os::Function<Shortcut> action;
Function<Shortcut> action;
};
HashMap<Shortcut, ShortcutAction> m_shortcuts;

View File

@ -25,7 +25,8 @@ namespace ui
CLOSE_WINDOW_ID,
GET_SCREEN_RECT_ID,
SET_TITLEBAR_HEIGHT_ID,
SET_SPECIAL_WINDOW_ATTRIBUTES_ID,
SET_WINDOW_LAYER_ID,
UPDATE_PLEDGE_REQUEST_ID,
};
struct CreateWindowRequest
@ -81,16 +82,32 @@ namespace ui
int height;
};
enum WindowAttributes : u8
enum Layer : u8
{
UNFOCUSEABLE = 1,
Background,
Global,
GlobalTop,
System,
Lock
};
struct SetSpecialWindowAttributesRequest
struct SetWindowLayer
{
static constexpr u8 ID = SET_SPECIAL_WINDOW_ATTRIBUTES_ID;
static constexpr u8 ID = SET_WINDOW_LAYER_ID;
int window;
WindowAttributes attributes;
Layer layer;
};
enum Pledge : i16
{
ExtendedLayers = 1,
};
struct UpdatePledgeRequest
{
static constexpr u8 ID = UPDATE_PLEDGE_REQUEST_ID;
i16 pledges;
};
}

View File

@ -75,7 +75,7 @@ namespace ui
void App::unregister_window(Window* window, Badge<Window>)
{
int id = window->id();
check(m_windows.try_remove(id));
m_window_clear_queue.try_append(id);
}
Window* App::find_window(int id)
@ -124,6 +124,22 @@ namespace ui
{
check(m_main_window);
m_client->check_for_messages().release_value();
for (int id : m_window_clear_queue)
{
check(m_windows.try_remove(id));
ui::CloseWindowRequest request;
request.window = id;
client().send_async(request);
}
m_window_clear_queue.clear_data();
return !m_should_close;
}
void App::pledge(i16 pledges)
{
ui::UpdatePledgeRequest request;
request.pledges = pledges;
client().send_async(request);
}
}

View File

@ -24,7 +24,7 @@ namespace ui
widget.set_parent(this);
}
void Button::set_action(os::Action&& action)
void Button::set_action(Action&& action)
{
m_action = move(action);
}

82
gui/libui/src/Dialog.cpp Normal file
View File

@ -0,0 +1,82 @@
/**
* @file Dialog.cpp
* @author apio (cloudapio.eu)
* @brief UI window dialogs.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <luna/Alloc.h>
#include <ui/App.h>
#include <ui/Dialog.h>
#include <ui/InputField.h>
#include <ui/Label.h>
#include <ui/Layout.h>
namespace ui::Dialog
{
Result<void> show_message(StringView title, StringView message)
{
auto rect = ui::App::the().main_window()->canvas().rect();
int text_length = (int)message.length() * ui::Font::default_font()->width();
int text_height = ui::Font::default_font()->height();
ui::Rect dialog_rect = { 0, 0, text_length + 20, text_height + 20 };
auto* dialog = TRY(ui::Window::create(
ui::align(rect, dialog_rect, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center)));
dialog->set_background(ui::GRAY);
dialog->set_title(title);
ui::Label* text = TRY(make<ui::Label>(message));
text->set_color(ui::BLACK);
dialog->set_main_widget(*text);
dialog->on_close([text] { delete text; });
dialog->draw();
return {};
}
Result<void> show_input_dialog(StringView title, StringView message, Function<StringView> callback)
{
auto rect = ui::App::the().main_window()->canvas().rect();
int text_length = (int)message.length() * ui::Font::default_font()->width();
int text_height = ui::Font::default_font()->height();
ui::Rect dialog_rect = { 0, 0, max(text_length + 20, 300), text_height * 2 + 30 };
auto* dialog = TRY(ui::Window::create(
ui::align(rect, dialog_rect, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center)));
dialog->set_background(ui::GRAY);
dialog->set_title(title);
ui::VerticalLayout* layout = TRY(make<ui::VerticalLayout>());
dialog->set_main_widget(*layout);
ui::Label* text = TRY(make<ui::Label>((message)));
text->set_color(ui::BLACK);
layout->add_widget(*text);
ui::InputField* input = TRY(make<ui::InputField>(ui::Font::default_font()));
input->on_submit([dialog, callback](StringView s) {
callback(s);
dialog->close();
});
layout->add_widget(*input);
dialog->on_close([layout, text, input] {
delete text;
delete input;
delete layout;
});
dialog->draw();
return {};
}
}

View File

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

View File

@ -17,6 +17,8 @@ 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)
@ -35,7 +37,7 @@ namespace ui
if (request.code == moon::K_RightArrow)
{
if (m_cursor < m_data.size()) m_cursor++;
if (m_cursor < (m_data.size() - 1)) m_cursor++;
else
return ui::EventResult::DidNotHandle;
update_cursor();
@ -67,9 +69,7 @@ namespace ui
if (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));
TRY(insert_character(request.letter));
m_cursor++;
@ -78,6 +78,14 @@ namespace ui
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();
@ -107,6 +115,7 @@ namespace ui
StringView InputField::data()
{
return StringView { (const char*)m_data.data(), m_data.size() };
if (m_data.size() < 2) return StringView {};
return StringView { (const char*)m_data.data(), m_data.size() - 1 };
}
}

View File

@ -84,6 +84,8 @@ namespace ui
Window::~Window()
{
if (m_canvas.ptr) munmap(m_canvas.ptr, ((usize)m_canvas.width) * ((usize)m_canvas.height) * 4);
if (m_has_on_close_action) m_on_close_action();
}
void Window::set_title(StringView title)
@ -108,20 +110,16 @@ namespace ui
{
App& app = App::the();
ui::CloseWindowRequest request;
request.window = m_id;
app.client().send_async(request);
if (this == app.main_window()) app.set_should_close(true);
app.unregister_window(this, {});
}
void Window::set_special_attributes(WindowAttributes attributes)
void Window::set_layer(Layer layer)
{
ui::SetSpecialWindowAttributesRequest request;
ui::SetWindowLayer request;
request.window = m_id;
request.attributes = attributes;
request.layer = layer;
App::the().client().send_async(request);
}
@ -226,8 +224,7 @@ namespace ui
return m_main_widget->handle_key_event(request);
}
Result<void> Window::add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept,
os::Function<ui::Shortcut>&& action)
Result<void> Window::add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, Function<ui::Shortcut>&& action)
{
TRY(m_shortcuts.try_set(shortcut, { intercept, move(action) }));

186
gui/loginui.cpp Normal file
View File

@ -0,0 +1,186 @@
/**
* @file loginui.cpp
* @author apio (cloudapio.eu)
* @brief Graphical login prompt.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <luna/RefString.h>
#include <luna/SHA.h>
#include <os/ArgumentParser.h>
#include <os/Config.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <os/IPC.h>
#include <os/Process.h>
#include <os/Security.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<String> hash_password(StringView& view)
{
SHA256 sha;
sha.append((const u8*)view.chars(), view.length());
auto digest = TRY(sha.digest());
return digest.to_string();
}
Result<int> luna_main(int argc, char** argv)
{
os::ArgumentParser parser;
parser.add_description("Login prompt for a graphical UI session.");
parser.add_system_program_info("loginui"_sv);
parser.parse(argc, argv);
if (geteuid() != 0)
{
os::eprintln("error: %s can only be started as root.", argv[0]);
return 1;
}
TRY(os::Security::pledge("stdio rpath wpath unix proc exec id", nullptr));
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;
}
auto config = TRY(os::ConfigFile::open("/etc/loginui.conf"));
if (config->read_boolean_or("Autologin", false))
{
StringView username = config->read_string_or("AutologinUser", "");
if (!username.is_empty())
{
auto flag = RefString::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:");
RefString title = RefString::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;
}
auto result = hash_password(data).release_value();
if (strcmp(result.chars(), passwd))
{
error.set_text("Incorrect password.");
input.clear();
return;
}
auto flag = RefString::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();
}

View File

@ -22,7 +22,7 @@ Result<int> luna_main(int argc, char** argv)
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));
OwnedPtr<os::IPC::Client> launcher_client = TRY(os::IPC::Client::connect("/tmp/execd.sock", false));
os::println("Requesting to start program '%s'...", program.chars());

View File

@ -11,6 +11,9 @@ set(SOURCES
Keyboard.cpp
Keyboard.h
Client.h
Client.cpp
Layer.cpp
Layer.h
)
add_executable(wind ${SOURCES})

52
gui/wind/Client.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "Client.h"
#include <os/File.h>
Client::Client(OwnedPtr<os::IPC::ClientConnection>&& client, i16 _pledges)
: conn(move(client)), windows(), pledges(_pledges)
{
conn->set_message_handler(wind::handle_ipc_message, this);
}
bool Client::update_pledges(i16 _pledges)
{
if (_pledges < 0)
{
os::eprintln("wind: Client trying to set an invalid pledge, disconnecting!");
should_be_disconnected = true;
return false;
}
if (pledges < 0)
{
pledges = _pledges;
return true;
}
if (_pledges & ~pledges)
{
os::eprintln("wind: Client trying to add pledges, disconnecting!");
should_be_disconnected = true;
return false;
}
pledges = _pledges;
return true;
}
bool Client::check_pledge(i16 pledge)
{
check(pledge > 0);
if (pledges < 0)
{
os::eprintln("wind: Client trying to use pledge-protected functions before pledging anything, disconnecting!");
should_be_disconnected = true;
return false;
}
if ((pledges & pledge) == pledge) return true;
os::eprintln("wind: Client trying to use a function they haven't pledged, disconnecting!");
should_be_disconnected = true;
return false;
}

21
gui/wind/Client.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "IPC.h"
#include "Window.h"
#include <os/IPC.h>
constexpr i16 HAS_NOT_YET_PLEDGED = -1;
constexpr i16 EMPTY_PLEDGE = 0;
struct Client
{
OwnedPtr<os::IPC::ClientConnection> conn;
Vector<Window*> windows;
const bool privileged { false };
bool should_be_disconnected { false };
i16 pledges = 0;
bool update_pledges(i16 pledges);
bool check_pledge(i16 pledge);
Client(OwnedPtr<os::IPC::ClientConnection>&& client, i16 pledges);
};

View File

@ -1,8 +1,9 @@
#include "IPC.h"
#include "Layer.h"
#include "Mouse.h"
#include "Screen.h"
#include <luna/Alignment.h>
#include <luna/String.h>
#include <luna/RefString.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
@ -35,9 +36,9 @@ static Result<void> handle_create_window_message(Client& client)
request.rect = request.rect.normalized();
auto name = TRY_OR_IPC_ERROR(String::from_cstring("Window"));
auto name = TRY_OR_IPC_ERROR(RefString::from_cstring("Window"));
auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn->fd(), time(NULL)));
auto shm_path = TRY_OR_IPC_ERROR(RefString::format("/wind-shm-%d-%lu"_sv, client.conn->fd(), time(NULL)));
auto* window = new (std::nothrow) Window(request.rect, move(name));
if (!window)
@ -47,7 +48,7 @@ static Result<void> handle_create_window_message(Client& client)
}
auto guard = make_scope_guard([window] {
g_windows.remove(window);
window->layer->windows.remove(window);
delete window;
});
@ -88,7 +89,7 @@ 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);
auto name = TRY(RefString::from_string(COPY_IPC_STRING(request.title)));
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
@ -120,7 +121,7 @@ static Result<void> handle_close_window_message(Client& client)
auto* window = client.windows[request.window];
client.windows[request.window] = nullptr;
g_windows.remove(window);
window->layer->windows.remove(window);
Mouse::the().window_did_close(window);
delete window;
@ -160,22 +161,48 @@ static Result<void> handle_set_titlebar_height_message(Client& client)
return {};
}
static Result<void> handle_set_special_window_attributes_message(Client& client)
static Result<void> handle_set_window_layer_message(Client& client)
{
ui::SetSpecialWindowAttributesRequest request;
ui::SetWindowLayer request;
if (!TRY(client.conn->read_message(request))) return {};
if (!client.privileged)
if (request.layer != ui::Layer::Global && request.layer != ui::Layer::GlobalTop)
{
os::eprintln(
"wind: Unprivileged client trying to call privileged request (SetSpecialWindowAttributes), disconnecting!");
if (!client.check_pledge(ui::Pledge::ExtendedLayers)) return {};
}
CHECK_WINDOW_ID(request, "SetWindowLayer");
auto* window = client.windows[request.window];
window->layer->windows.remove(window);
switch (request.layer)
{
case ui::Layer::Background: window->layer = &l_background; break;
case ui::Layer::Global: window->layer = &l_global; break;
case ui::Layer::GlobalTop: window->layer = &l_global_top; break;
case ui::Layer::System: window->layer = &l_system; break;
case ui::Layer::Lock: window->layer = &l_lock; break;
default: {
window->layer->windows.append(window);
os::eprintln("wind: Client trying to set window layer to an invalid layer, disconnecting!");
client.should_be_disconnected = true;
return {};
}
}
CHECK_WINDOW_ID(request, "SetSpecialWindowAttributes");
window->layer->windows.append(window);
client.windows[request.window]->attributes = request.attributes;
return {};
}
static Result<void> handle_update_pledge_request_message(Client& client)
{
ui::UpdatePledgeRequest request;
if (!TRY(client.conn->read_message(request))) return {};
client.update_pledges(request.pledges); // update_pledges does all the checking.
return {};
}
@ -194,7 +221,8 @@ namespace wind
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;
case ui::SET_WINDOW_LAYER_ID: handle_set_window_layer_message(client); break;
case ui::UPDATE_PLEDGE_REQUEST_ID: handle_update_pledge_request_message(client); break;
default: os::eprintln("wind: Invalid IPC message from client!"); return;
}
}

79
gui/wind/Layer.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "Layer.h"
#include "Client.h"
#include "Window.h"
Layer l_background;
Layer l_global;
Layer l_global_top;
Layer l_system;
Layer l_lock;
constexpr int NUM_LAYERS = 5;
static Layer* const layers_front_to_back[NUM_LAYERS] = { &l_lock, &l_system, &l_global_top, &l_global, &l_background };
static Layer* const layers_back_to_front[NUM_LAYERS] = { &l_background, &l_global, &l_global_top, &l_system, &l_lock };
Window* Layer::focused_window()
{
for (int i = 0; i < NUM_LAYERS; i++)
{
Layer* l = layers_front_to_back[i];
if (l->windows.last().has_value()) return l->windows.last().value();
}
return nullptr;
}
void Layer::draw_all_windows(ui::Canvas& canvas)
{
for (int i = 0; i < NUM_LAYERS; i++)
{
Layer* l = layers_back_to_front[i];
for (Window* w : l->windows) { w->draw(canvas); }
}
}
Window* Layer::propagate_mouse_event(ui::Point position, u8 buttons)
{
for (int i = 0; i < NUM_LAYERS; i++)
{
Layer* l = layers_front_to_back[i];
for (Window* window = l->windows.last().value_or(nullptr); window;
window = l->windows.previous(window).value_or(nullptr))
{
if (window->surface.contains(position))
{
ui::MouseEventRequest request;
request.window = window->id;
request.position = window->surface.relative(position);
request.buttons = buttons;
window->client->conn->send_async(request);
return window;
}
}
}
return nullptr;
}
Window* Layer::propagate_drag_event(ui::Point position)
{
for (int i = 0; i < NUM_LAYERS; i++)
{
Layer* l = layers_front_to_back[i];
for (Window* window = l->windows.last().value_or(nullptr); window;
window = l->windows.previous(window).value_or(nullptr))
{
if (window->surface.contains(position))
{
window->focus();
if (window->surface.absolute(window->titlebar).contains(position)) return window;
return nullptr;
}
}
}
return nullptr;
}

20
gui/wind/Layer.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "Window.h"
#include <luna/LinkedList.h>
#include <ui/Canvas.h>
struct Layer
{
LinkedList<Window> windows;
static Window* focused_window();
static void draw_all_windows(ui::Canvas& canvas);
static Window* propagate_mouse_event(ui::Point position, u8 buttons);
static Window* propagate_drag_event(ui::Point position);
};
extern Layer l_background;
extern Layer l_global;
extern Layer l_global_top;
extern Layer l_system;
extern Layer l_lock;

View File

@ -1,5 +1,6 @@
#include "Mouse.h"
#include "Client.h"
#include "Layer.h"
#include <os/File.h>
#include <os/IPC.h>
#include <ui/Image.h>
@ -57,45 +58,17 @@ void Mouse::update(const moon::MousePacket& packet)
else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window)
{
// Iterate from the end of the list, since windows at the beginning are stacked at the bottom and windows at the
// top are at the end.
for (Window* window = g_windows.last().value_or(nullptr); window;
window = g_windows.previous(window).value_or(nullptr))
if (auto* window = Layer::propagate_drag_event(m_position))
{
if (window->surface.contains(m_position))
{
if (!(window->attributes & ui::UNFOCUSEABLE)) window->focus();
if (window->surface.absolute(window->titlebar).contains(m_position))
{
m_dragging_window = window;
m_initial_drag_position = window->surface.relative(m_position);
os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x,
window->surface.pos.y, window->surface.width, window->surface.height,
m_initial_drag_position.x, m_initial_drag_position.y);
}
break;
}
m_dragging_window = window;
m_initial_drag_position = window->surface.relative(m_position);
os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x,
window->surface.pos.y, window->surface.width, window->surface.height, m_initial_drag_position.x,
m_initial_drag_position.y);
}
}
Window* new_active_window = nullptr;
for (Window* window = g_windows.last().value_or(nullptr); window;
window = g_windows.previous(window).value_or(nullptr))
{
if (window->surface.contains(m_position))
{
ui::MouseEventRequest request;
request.window = window->id;
request.position = window->surface.relative(m_position);
request.buttons = packet.buttons;
window->client->conn->send_async(request);
new_active_window = window;
break;
}
}
Window* new_active_window = Layer::propagate_mouse_event(m_position, packet.buttons);
if (m_active_window != new_active_window)
{

View File

@ -1,12 +1,11 @@
#include "Window.h"
#include "Layer.h"
#include <luna/Utf8.h>
#include <os/File.h>
#include <sys/mman.h>
#include <ui/Font.h>
#include <ui/Image.h>
LinkedList<Window> g_windows;
void Window::draw(ui::Canvas& screen)
{
dirty = false;
@ -18,15 +17,16 @@ void Window::draw(ui::Canvas& screen)
void Window::focus()
{
// Bring the window to the front of the list.
g_windows.remove(this);
g_windows.append(this);
layer->windows.remove(this);
layer->windows.append(this);
}
Window::Window(ui::Rect r, String&& n) : surface(r), name(move(n))
Window::Window(ui::Rect r, RefString&& n) : surface(r), name(move(n))
{
auto font = ui::Font::default_font();
titlebar = ui::Rect { 0, 0, 0, 0 };
g_windows.append(this);
l_global.windows.append(this);
layer = &l_global;
}
Window::~Window()

View File

@ -1,31 +1,30 @@
#pragma once
#include <luna/LinkedList.h>
#include <luna/String.h>
#include <luna/RefString.h>
#include <ui/Canvas.h>
#include <ui/Color.h>
#include <ui/Rect.h>
#include <ui/ipc/Server.h>
struct Client;
struct Layer;
struct Window : public LinkedListNode<Window>
{
ui::Rect surface;
ui::Rect titlebar;
u32* pixels;
String name;
String shm_path;
RefString name;
RefString shm_path;
bool dirty { false };
Client* client;
Layer* layer;
int id;
ui::WindowAttributes attributes { 0 };
Window(ui::Rect, String&&);
Window(ui::Rect, RefString&&);
~Window();
void focus();
void draw(ui::Canvas& screen);
};
extern LinkedList<Window> g_windows;

View File

@ -1,6 +1,6 @@
#define CLIENT_IMPLEMENTATION
#include "Client.h"
#include "Keyboard.h"
#include "Layer.h"
#include "Mouse.h"
#include "Screen.h"
#include "Window.h"
@ -8,6 +8,7 @@
#include <moon/Keyboard.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/IPC.h>
#include <os/LocalServer.h>
#include <os/Process.h>
#include <os/Security.h>
@ -22,42 +23,11 @@ static constexpr uid_t WIND_USER_ID = 2;
static constexpr gid_t WIND_GROUP_ID = 2;
static constexpr gid_t WSYS_GROUP_ID = 3;
static void debug(const Vector<OwnedPtr<Client>>& clients)
{
os::println("--- wind: DEBUG OUTPUT ---");
os::println("-- wind: Listing clients --");
for (const auto& client : clients)
{
os::println("Client with fd %d, owns %zu windows", client->conn->fd(), client->windows.size());
}
os::println("-- wind: Listing windows --");
for (const auto& window : g_windows)
{
os::println("Window of client (fd %d), id %d, %sdirty (\"%s\") (%d,%d,%d,%d)", window->client->conn->fd(),
window->id, window->dirty ? "" : "not ", window->name.chars(), window->surface.pos.x,
window->surface.pos.y, window->surface.width, window->surface.height);
}
os::println("-- wind: Listing processes --");
system("ps");
os::println("-- wind: Listing memory usage --");
system("free -h");
os::println("--- wind: END DEBUG OUTPUT ---");
}
Result<int> luna_main(int argc, char** argv)
{
srand((unsigned)time(NULL));
TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL));
TRY(os::Security::pledge("stdio rpath wpath cpath unix tty id", NULL));
StringView socket_path = "/tmp/wind.sock";
StringView system_socket_path = "/tmp/wsys.sock";
@ -72,8 +42,8 @@ Result<int> luna_main(int argc, char** argv)
if (geteuid() != 0)
{
os::eprintln("error: wind must be run as root to initialize resources, run with --user=<USERNAME> to drop "
"privileges afterwards");
os::eprintln("error: wind must be run as root to initialize resources, the server will drop "
"privileges automatically afterwards");
return 1;
}
@ -115,6 +85,9 @@ Result<int> luna_main(int argc, char** argv)
close(fd);
}
// We're ready now.
os::IPC::notify_parent();
ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10);
Vector<OwnedPtr<Client>> clients;
@ -124,12 +97,12 @@ Result<int> luna_main(int argc, char** argv)
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", NULL));
while (1)
{
screen.canvas().fill(background);
for (auto* window : g_windows) window->draw(screen.canvas());
Layer::draw_all_windows(screen.canvas());
mouse_pointer.draw(screen.canvas());
screen.sync();
@ -149,11 +122,9 @@ Result<int> luna_main(int argc, char** argv)
{
moon::KeyboardPacket packet;
TRY(keyboard->read_typed(packet));
if (!packet.released && packet.key == moon::K_Tab) debug(clients);
auto request = wind::Keyboard::decode_keyboard_event((moon::KeyCode)packet.key, packet.released);
if (g_windows.last().has_value())
if (auto* window = Layer::focused_window())
{
auto* window = g_windows.last().value();
request.window = window->id;
window->client->conn->send_async(request);
}
@ -166,7 +137,7 @@ Result<int> luna_main(int argc, char** argv)
auto connection = TRY(os::IPC::ClientConnection::adopt_connection(move(client)));
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(connection), false)));
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(connection), EMPTY_PLEDGE)));
TRY(clients.try_append(move(c)));
}
if (fds[3].revents & POLLIN)
@ -177,7 +148,7 @@ Result<int> luna_main(int argc, char** argv)
auto connection = TRY(os::IPC::ClientConnection::adopt_connection(move(client)));
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(connection), true)));
OwnedPtr<Client> c = TRY(adopt_owned_if_nonnull(new Client(move(connection), HAS_NOT_YET_PLEDGED)));
TRY(clients.try_append(move(c)));
}
for (usize i = 0; i < clients.size(); i++)
@ -194,7 +165,7 @@ Result<int> luna_main(int argc, char** argv)
{
if (window)
{
g_windows.remove(window);
window->layer->windows.remove(window);
mouse_pointer.window_did_close(window);
delete window;
}

View File

@ -19,3 +19,6 @@
# Uncomment the line below to make the kernel also calculate stack traces for userspace addresses on program crashes.
# This can aid in debugging, but makes the kernel more unstable as stack tracing will access arbitrary userspace memory.
# target_compile_definitions(moon PRIVATE MOON_ENABLE_USERSPACE_STACK_TRACES)
# Uncomment the line below to enable all kernel debug messages, and console logging.
# include(debug.cmake)

View File

@ -12,4 +12,4 @@ target_compile_definitions(moon PRIVATE DEVICE_REGISTRY_DEBUG)
target_compile_definitions(moon PRIVATE FORK_DEBUG)
target_compile_definitions(moon PRIVATE MOUNT_DEBUG)
target_compile_definitions(moon PRIVATE CACHE_DEBUG)
target_compile_options(moon PRIVATE -fsanitize=undefined)
#target_compile_options(moon PRIVATE -fsanitize=undefined)

View File

@ -77,8 +77,7 @@ static void log_text_console(LogLevel level, const char* format, va_list origin)
TextConsole::set_foreground(WHITE);
// NOTE: Same as above.
auto rc = cstyle_format(
format, [](char c, void*) -> Result<void> { return TextConsole::putchar(c); }, nullptr, ap);
auto rc = cstyle_format(format, [](char c, void*) -> Result<void> { return TextConsole::putchar(c); }, nullptr, ap);
if (rc.has_error()) { TextConsole::wprint(L"Invalid UTF-8 in log message"); }

View File

@ -1,6 +1,7 @@
#include "Pledge.h"
#include "Log.h"
#include "memory/MemoryManager.h"
#include "thread/Scheduler.h"
static const char* promise_names[] = {
#define __enumerate(promise) #promise,
@ -8,30 +9,34 @@ static const char* promise_names[] = {
#undef __enumerate
};
Result<void> check_pledge(Thread* thread, Promise promise)
Result<void> check_pledge(Process* process, Promise promise)
{
// Thread has not called pledge().
if (thread->promises < 0) return {};
if (process->promises < 0) return {};
int mask = (1 << (int)promise);
if ((thread->promises & mask) != mask)
if ((process->promises & mask) != mask)
{
kerrorln("Pledge violation in thread %d! Has not pledged %s", thread->id, promise_names[(int)promise]);
if (thread->promises & (1 << (int)Promise::p_error)) return err(ENOSYS);
kerrorln("Pledge violation in process %d! Has not pledged %s", process->id, promise_names[(int)promise]);
if (process->promises & (1 << (int)Promise::p_error)) return err(ENOSYS);
// Kill this thread with an uncatchable SIGABRT. For this, we reset the disposition of SIGABRT to the default
// (dump core). We could just kill the thread here and be done, but that discards anything on the current stack,
// which means that some destructors might not be called. Instead, leave the job to the next call of
// Thread::process_pending_signals().
thread->signal_handlers[SIGABRT - 1].sa_handler = SIG_DFL;
Scheduler::for_each_thread(process, [](Thread* thread) {
// Kill this thread with an uncatchable SIGABRT. For this, we reset the disposition of SIGABRT to the
// default (dump core). We could just kill the thread here and be done, but that discards anything on the
// current stack, which means that some destructors might not be called. Instead, leave the job to the next
// call of Thread::process_pending_signals().
thread->signal_handlers[SIGABRT - 1].sa_handler = SIG_DFL;
// Unblock SIGABRT.
thread->signal_mask.set(SIGABRT - 1, false);
// Unblock SIGABRT.
thread->signal_mask.set(SIGABRT - 1, false);
// If there are any other pending signals, they might be processed before SIGABRT. Avoid that by resetting the
// thread's pending signals.
thread->pending_signals.clear();
// If there are any other pending signals, they might be processed before SIGABRT. Avoid that by resetting
// the thread's pending signals.
thread->pending_signals.clear();
thread->send_signal(SIGABRT);
thread->send_signal(SIGABRT);
return true;
});
// This should never arrive to userspace, unless we're init and have ignored SIGABRT.
return err(ENOSYS);

View File

@ -14,6 +14,6 @@ enum class Promise
num_promises,
};
Result<void> check_pledge(Thread* thread, Promise promise);
Result<void> check_pledge(Process* process, Promise promise);
Result<int> parse_promises(u64 pledge);

View File

@ -91,6 +91,9 @@ void handle_cpu_exception(int signo, const char* err, Registers* regs)
auto* current = Scheduler::current();
if (current->check_stack_on_exception(regs->rsp)) return;
auto space = current->process->address_space.lock();
(*space)->debug_log();
current->send_signal(signo);
current->process_pending_signals(regs);
return;

View File

@ -8,7 +8,7 @@
static inline constexpr u32 make_pci_address(const PCI::Device::Address& address, u32 field)
{
return 0x80000000 | (address.bus << 16) | (address.slot << 11) | (address.function << 8) | ((field)&0xFC);
return 0x80000000 | (address.bus << 16) | (address.slot << 11) | (address.function << 8) | ((field) & 0xFC);
}
namespace PCI

View File

@ -15,7 +15,7 @@ void Thread::set_ip(u64 ip)
regs.rip = ip;
}
u64 Thread::ip()
u64 Thread::ip() const
{
return regs.rip;
}
@ -25,7 +25,7 @@ void Thread::set_sp(u64 sp)
regs.rsp = sp;
}
u64 Thread::sp()
u64 Thread::sp() const
{
return regs.rsp;
}
@ -71,31 +71,14 @@ void switch_context(Thread* old_thread, Thread* new_thread, Registers* regs)
memcpy(regs, &new_thread->regs, sizeof(Registers));
}
// FIXME: Move this function to a common location (also used in ThreadImage)
Result<u64> Thread::push_mem_on_stack(const u8* mem, usize size)
{
if ((regs.rsp - size) < stack.bottom()) return err(E2BIG);
if (!MemoryManager::validate_user_write((void*)(regs.rsp - size), size)) return err(EFAULT);
regs.rsp -= size;
memcpy((void*)regs.rsp, mem, size);
return regs.rsp;
return MemoryManager::push_mem_on_stack(mem, size, stack, regs.rsp);
}
Result<u64> Thread::pop_mem_from_stack(u8* mem, usize size)
{
if ((regs.rsp + size) > stack.top()) return err(E2BIG);
if (!MemoryManager::validate_user_read((void*)regs.rsp, size)) return err(EFAULT);
memcpy(mem, (void*)regs.rsp, size);
regs.rsp += size;
return regs.rsp;
return MemoryManager::pop_mem_from_stack(mem, size, stack, regs.rsp);
}
bool Thread::deliver_signal(int signo, Registers* current_regs)

View File

@ -7,6 +7,7 @@
#include "memory/MemoryManager.h"
#include "thread/Clock.h"
#include "thread/Scheduler.h"
#include <endian.h>
#include <luna/Alignment.h>
#include <luna/Buffer.h>
#include <luna/CType.h>
@ -517,9 +518,8 @@ namespace ATA
m_is_lba48 = true;
// FIXME: This assumes the host machine is little-endian.
u32 last_lba = __builtin_bswap32(reply.last_lba);
u32 sector_size = __builtin_bswap32(reply.sector_size);
u32 last_lba = be32toh(reply.last_lba);
u32 sector_size = be32toh(reply.sector_size);
m_block_count = last_lba + 1;
m_block_size = sector_size;

View File

@ -3,6 +3,7 @@
#include "fs/devices/BlockDevice.h"
#include "fs/devices/DeviceRegistry.h"
#include "lib/Mutex.h"
#include "thread/Thread.h"
#include <luna/Atomic.h>
#include <luna/SharedPtr.h>
#include <luna/StaticString.h>

View File

@ -17,7 +17,7 @@ Result<u64> ScriptLoader::load(AddressSpace* space)
{
u8 buf[256];
memset(buf, 0, sizeof(buf));
usize nread = TRY(m_inode->read(buf, 2, 255));
if (!nread) return err(ENOEXEC);
for (usize i = 0; i < nread; i++)
@ -35,11 +35,10 @@ Result<u64> ScriptLoader::load(AddressSpace* space)
if (!m_interpreter_cmdline.size()) return err(ENOEXEC);
auto& interpreter_path = m_interpreter_cmdline[0];
auto* current = Scheduler::current();
auto* current = Process::current();
auto interpreter = TRY(VFS::resolve_path(interpreter_path.chars(), current->auth, &current->extra_groups,
current->current_directory, true));
if (!VFS::can_execute(interpreter, current->auth, &current->extra_groups)) return err(EACCES);
auto interpreter = TRY(VFS::resolve_path(interpreter_path.chars(), current, current->current_directory, true));
if (!VFS::can_execute(interpreter, current)) return err(EACCES);
auto loader = TRY(BinaryFormat::create_loader(interpreter, m_recursion_level + 1));
u64 entry = TRY(loader->load(space));

View File

@ -20,7 +20,7 @@ void InitRD::initialize()
static Result<void> vfs_create_dir_if_not_exists(const char* path, mode_t mode)
{
auto rc = VFS::create_directory(path, mode & (mode_t)~S_IFMT, Credentials {}, nullptr);
auto rc = VFS::create_directory(path, mode & (mode_t)~S_IFMT, nullptr);
if (rc.has_error())
{
if (rc.error() == EEXIST) return {};
@ -37,8 +37,7 @@ Result<void> InitRD::populate_vfs()
{
if (entry.type == TarStream::EntryType::RegularFile)
{
auto file =
TRY(VFS::create_file(entry.name.chars(), entry.mode & (mode_t)~S_IFMT, Credentials {}, nullptr));
auto file = TRY(VFS::create_file(entry.name.chars(), entry.mode & (mode_t)~S_IFMT, nullptr));
file->write(entry.data(), 0, entry.size);
}
else if (entry.type == TarStream::EntryType::Directory)

View File

@ -8,7 +8,7 @@ Result<void> Pipe::create(SharedPtr<VFS::Inode>& rpipe, SharedPtr<VFS::Inode>& w
auto writer = TRY(make_shared<PipeWriter>());
auto reader = TRY(make_shared<PipeReader>());
auto auth = Scheduler::current()->auth;
auto auth = Process::current()->credentials();
pipe->m_writer = writer.ptr();
pipe->m_reader = reader.ptr();

View File

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

View File

@ -4,12 +4,15 @@
#include <luna/HashMap.h>
#include <luna/LinkedList.h>
class BlockDevice;
class StorageCache : public LinkedListNode<StorageCache>
{
public:
struct CacheEntry
{
Buffer buffer {};
bool dirty;
};
void lock()
@ -28,10 +31,11 @@ class StorageCache : public LinkedListNode<StorageCache>
static void clear_caches();
StorageCache();
StorageCache(BlockDevice* device);
~StorageCache();
private:
HashMap<u64, CacheEntry> m_cache_entries;
BlockDevice* m_device;
Mutex m_mutex;
};

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