Compare commits

...

579 Commits
v0.4.0 ... main

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
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
fd402083d7
kernel: Fix kernel panic when adding a timer before another one
This was caused because add_to_timer_queue() did not set active_clock when inserting a timer before the end, making disarm() a no-op.
Therefore, the clock would continue to use the timer after it had been freed.
2024-01-06 18:12:25 +01:00
4019cf90cf
kernel: Unify panic messages 2024-01-06 17:44:56 +01:00
1f2f676ea4
Update gitignore rules 2024-01-06 17:31:00 +01:00
075ed83764
Add about icon to git
Apparently, I had skipped this one, I really need to improve these
gitignore rules.
2024-01-06 17:23:14 +01:00
7f6863c093
apps: Use os::Timer instead of os::EventLoop::register_timer 2024-01-05 22:18:12 +01:00
a9c339939a
libos: Move timer handling to a separate class and use POSIX timers 2024-01-05 22:16:50 +01:00
3231a1296d
libc: Add support for POSIX timers 2024-01-05 22:15:06 +01:00
17b44a8ce6
kernel: Reenable stack tracing in kernel-mode exceptions
Stack tracing in user-mode exceptions was moved to when a signal kills the process, but this isn't done for kernel-mode exceptions so there was no stack trace anymore.
2024-01-05 22:14:31 +01:00
41c90aa436
kernel: Add POSIX timer support 2024-01-05 22:12:58 +01:00
f8cc093e17
tools: Add useful script to develop ports 2024-01-04 21:04:58 +01:00
64a941dc18
ports: Use --enable-initfini-array in gcc and binutils ports 2024-01-04 21:04:45 +01:00
2a85a7473a
kernel: Show stacktraces on all signal terminations 2024-01-04 21:04:24 +01:00
e34395915d
libc+tests+tools: Call global constructors in userspace code 2024-01-04 21:02:51 +01:00
d9899f1c3d
Update LICENSE 2024-01-04 11:24:48 +01:00
c24d0da5f0
ports/gcc: Fix --with-build-sysroot and enable asserts 2024-01-04 11:15:14 +01:00
1208d94b37
kernel: Add stack diagnostics to userspace fault reporting 2024-01-04 11:14:19 +01:00
f34dd56375
wind+libui: Implement client side decorations 2023-12-27 12:56:40 +01:00
5188def9e5
stat: Recognize sockets properly
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-04 20:42:31 +01:00
9b01b5a5db
kernel: Correctly register file times for more file systems and file types 2023-12-04 20:42:17 +01:00
b619f717c8
kernel+libc: Implement pause() and sigsuspend()
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-04 20:26:01 +01:00
92ab403687
terminal: Use forkpty()
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-25 12:18:48 +01:00
5bb4321134
libc: Add login_tty() and forkpty() 2023-11-25 12:18:25 +01:00
3a5924be64
kernel: Set the initial foreground process group when acquiring a controlling terminal 2023-11-25 12:18:04 +01:00
99dc819bca
terminal: Stop killing child process manually when exiting
All checks were successful
continuous-integration/drone/push Build is passing
This is already handled by the pty subsystem.
2023-11-22 21:35:26 +01:00
73a7d4f2a1
wind+libui: Run wind as a separate user
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-22 21:31:08 +01:00
efeab5699e
su: Allow disabled passwords 2023-11-22 21:31:08 +01:00
1005305d5a
su: Support supplementary groups 2023-11-22 21:31:08 +01:00
8a90db837b
kernel+libc: Add support for supplementary groups (2/2)
Adds system calls for setting and getting groups, along with libc wrappers.
2023-11-22 21:31:07 +01:00
3ad23eab21
kernel: Add support for supplementary groups (1/2)
Adds support for supplementary groups internally in the kernel.
No userspace support.
2023-11-22 18:49:40 +01:00
e97b61ef16
gol: Use EventLoop timers
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-16 22:02:31 +01:00
d1d53c6891
kernel: Remove signal debug messages 2023-11-16 22:02:17 +01:00
8a57d8a9b7
libos: Use setitimer() for millisecond precision 2023-11-16 21:58:45 +01:00
678121c3ed
kernel+libc: Add setitimer()
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-16 21:48:18 +01:00
c4d2847da1
kernel: Rework the entire time system to use modular clocks
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-15 23:50:04 +01:00
e28e1c682c
kernel: Tweak some timer code
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-04 10:47:41 +01:00
dfebdce689
kernel: Add a timer queue with more versatility than simple alarm()
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-03 19:53:34 +01:00
54ec441000
libluna: Add LinkedList::add_before() to mirror add_after()
Need this for later.
2023-11-03 19:52:36 +01:00
e1d5b7e7b4
kernel: Implement thread stopping and continuing
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-28 15:15:32 +02:00
69771cbd85
kernel: Show symbols correctly when at the beginning of a function
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-25 20:02:15 +02:00
4d5feb0f3b
libos: Add File::seek() and File::tell()
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-24 20:02:09 +02:00
cea1b030ff
kernel: Add locking to BinaryFormat and DeviceRegistry
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-23 22:48:04 +02:00
9c65dba412
kernel: Add a registry for file system implementations 2023-10-23 22:47:49 +02:00
ba4e807f8e
kernel: Fix off-by-one error in symbol lookup and add locking
This resulted in very weird backtraces.
2023-10-23 22:47:20 +02:00
b3cbbea9d6
kernel: Move file descriptors into their own separate file 2023-10-23 20:13:11 +02:00
8476ea0dc9
ports: Port gcc =D
All checks were successful
continuous-integration/drone/push Build is passing
The main compiler (cc1) crashes in some obscure null dereference that I'll have to investigate.

However, it compiles fine, and the preprocessor seems to work...
2023-10-15 13:13:11 +02:00
2134dcc5ec
libc: Add madvise stub 2023-10-15 13:09:56 +02:00
7db6e0163a
libc: Add unused WUNTRACED define for gcc 2023-10-15 13:09:46 +02:00
bd0fb8fe5b
libc: Add pclose definition
The function was already implemented, but no definition in the header...
2023-10-15 13:09:22 +02:00
15d5f00cd3
libc: Add ctermid
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-15 11:09:08 +02:00
56eb0c8130
su: Read password from /dev/tty instead of stdin
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-14 20:47:56 +02:00
c323a812a5
kernel+libc+terminal+wind: Add support for POSIX sessions
All checks were successful
continuous-integration/drone/push Build is passing
Fixes #42.
2023-10-14 20:41:34 +02:00
81131ad3a8
init: Handle SIGCHLD signals 2023-10-14 20:40:18 +02:00
5f0830cd41
kernel: Add /dev/tty
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-14 19:00:10 +02:00
9097400c32
wind: Return more errors to the client when creating windows
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-13 22:33:36 +02:00
3ca31770e7
initrd: Remove unused stray script file
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-13 22:12:18 +02:00
c75dbc0cbb
kernel: Lookup and print symbols for addresses in backtraces 2023-10-13 22:11:52 +02:00
37e046d766
libui: Make Label initialization step-by-step instead of setting everything in the constructor
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-11 22:56:14 +02:00
7812a4a44a
apps+libui: Make app.run() call window->draw() automatically
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-10 22:24:11 +02:00
e18ca9bfe0
libos: Document Action.h 2023-10-10 22:11:12 +02:00
d3a347e432
taskbar: Handle SIGCHLD as part of the event loop
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-09 22:14:34 +02:00
3e5bdc8c80
apps: Add clock
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-09 22:05:30 +02:00
0824ba7e23
libos: Add timers to event loops
Only second precision for now, as alarm() is used to control the timers. Hopefully setitimer() or timer_create() can be added to the kernel soon to benefit from more precision.
2023-10-09 22:00:15 +02:00
0b2a835336
libui+libos: Move Action to libos 2023-10-07 15:31:50 +02:00
945cfab3eb
libos: Add signal handling to event loops
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-07 14:26:35 +02:00
5892a6bf09
libos+libui: Add event loops
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-06 22:06:34 +02:00
f5aed95b8b
kernel: Fix inode link counting in tmpfs
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-04 20:58:40 +02:00
db2963d7bf
apps: Add 2048 prototype
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-28 21:38:42 +02:00
b3dc027ba0
taskbar: Add Game of Life to taskbar
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-27 19:03:49 +02:00
041d15a547
libui+taskbar: Make Buttons use Actions and clean up taskbar code 2023-09-27 18:52:17 +02:00
3d46e56386
libui: Add Margins to layouts 2023-09-27 18:51:54 +02:00
d4e834f734
libui: Add Actions
This allows components like Buttons to take in capturing lambdas
2023-09-27 18:51:38 +02:00
54afd7c2b0
libluna: Add OwnedPtr::leak() 2023-09-27 18:50:56 +02:00
9a9c7e577a
wind+libui+taskbar: Add various window types and never focus the taskbar
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-27 18:14:32 +02:00
f0844c9f69
cp: Support the -R flag and add better verbose messages
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-25 21:31:14 +02:00
eeb69c923c
kernel: Rename Scheduler::new_userspace_thread to clarify that it's only meant for init
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-25 19:34:02 +02:00
5626083aad
init: Add support for WorkingDirectory keys
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-25 19:26:05 +02:00
7ff5096083
kernel: Avoid page faults when reading from a slave pseudoterminal after the master is destroyed
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-23 12:57:03 +02:00
a47321a228
libc: Implement openpty()
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-22 23:02:33 +02:00
ffdcc843eb
kernel+terminal: Move pseudoterminal input processing to kernel-space
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-22 22:45:35 +02:00
441e04076a
kernel: Do not allow sending signals to dying threads
This was causing a kernel panic when closing the terminal by directly exiting the shell (either EOF or the exit command)
2023-09-22 22:40:24 +02:00
36fad85396
kernel+init+preinit+wind: Remove the kernel TTY and support only userspace terminals
All checks were successful
continuous-integration/drone/push Build is passing
Still allow printing text to the console, but without text input or ANSI escape fancy stuff.
2023-09-21 19:31:54 +02:00
b52f96ab64
kernel: Schedule cache clears only on certain checkpoints
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-21 19:14:13 +02:00
14f0c93175
kernel: Allow mapping the framebuffer even if its size is not page-aligned
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-20 22:41:48 +02:00
b4a9ea3857
terminal: Avoid doing too many redraws + support non-canonical mode
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-20 22:01:26 +02:00
9636b5d8da
Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-20 21:07:37 +02:00
36bd556406
all: Bump release version to 0.6.0 and start calling it "Andromeda"
All checks were successful
continuous-integration/drone/push Build is passing
I changed my mind, some alpha releases can have unique names now.
2023-09-20 20:56:59 +02:00
b09226b8ba
gol: Use the windowing system
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-09-20 20:17:11 +02:00
52b04bd33b
kernel: Increase the OOM threshold to 4 MiB free
All checks were successful
continuous-integration/drone/pr Build is passing
2023-09-20 19:58:26 +02:00
b42497e05e
kernel: Start clearing caches when free memory is lower than 1MiB
All checks were successful
continuous-integration/drone/pr Build is passing
This is done to avoid returning ENOMEM errors when cache memory can still be reclaimed.
2023-09-20 19:49:13 +02:00
b370a99aa6
libui: Allow specifying the color of Labels, and use that in about 2023-09-20 19:45:19 +02:00
9fd4fc7e91
wind+taskbar: Improve the dark color scheme 2023-09-20 19:45:01 +02:00
bc14b01bf8
terminal: Fix certain keys being incorrectly inputted 2023-09-20 19:43:53 +02:00
0cb21c2e90
terminal: Send signals on ^C and ^\
Some checks failed
continuous-integration/drone/pr Build is failing
2023-09-20 07:06:01 +02:00
3540033dd3
wind: Translate Ctrl-key presses correctly 2023-09-20 07:06:01 +02:00
c5227d585c
kernel: Allow sending signals to process groups from userspace 2023-09-20 07:06:01 +02:00
d93e9f6b4b
kernel: Fix sending signals to threads that are in a long syscall 2023-09-20 07:06:00 +02:00
7631b81681
libui: Allow not filling the window with a background color every time 2023-09-20 07:06:00 +02:00
7f23931028
terminal: Draw directly onto the window canvas 2023-09-20 07:06:00 +02:00
945dc6c732
terminal: Add cursor support 2023-09-20 07:06:00 +02:00
dd3359b09b
libui: Properly request redraws from the server
Before this, the call to update() was always skipped.
2023-09-20 07:06:00 +02:00
9b1e19ef72
terminal: Use pseudoterminals and add keyboard support 2023-09-20 07:06:00 +02:00
75ea81bfbc
libc: Add pseudoterminal-related functions 2023-09-20 07:06:00 +02:00
29a341d8f3
init: Mount /dev/pts on startup 2023-09-20 07:05:59 +02:00
a93626fc41
kernel: Add pseudoterminals and a /dev/pts filesystem 2023-09-20 07:05:59 +02:00
ab738772b9
wind: Stop tracking windows after they're closed 2023-09-20 07:05:59 +02:00
e2a1cb0d34
wind+libui: Add support for keyboard events 2023-09-20 07:05:59 +02:00
1b633212f6
apps: Remove gclient 2023-09-20 07:05:59 +02:00
5a49e97483
taskbar: Add a button to open terminal instead of gclient 2023-09-20 07:05:59 +02:00
a4b5e68e1b
kernel: Allow performing extra actions when opening an inode 2023-09-20 07:05:58 +02:00
835c39bc47
apps: Add basic terminal app 2023-09-20 07:05:58 +02:00
0e8183d2bb
shell: Allow running as interactive even if not running in a TTY 2023-09-20 07:05:58 +02:00
bb5d726fe8
libui: Add option to run event processing in a loop instead of in app.run() 2023-09-20 07:05:58 +02:00
08b56319c7
libui: Reduce redraw calls by doing them only when events are actually handled 2023-09-20 07:05:58 +02:00
a5790d0fb1
apps: Add about 2023-09-20 07:05:58 +02:00
4cf0fac16e
libui: Add a basic Label component 2023-09-20 07:05:58 +02:00
67eac983b5
libui: Clarify that Font is only used for low-level glyph rendering 2023-09-20 07:05:57 +02:00
2643f050eb
libui: Zero-initialize counter variables in Layout 2023-09-20 07:05:57 +02:00
669e2747a7
wind: Move more fallible operations before window creation 2023-09-20 07:05:57 +02:00
06f3affc71
wind: Make sure stdin is always a TTY 2023-09-20 07:05:57 +02:00
5db1c3722c
libui+wind+libos: Move shared memory handling code to os::SharedMemory 2023-09-20 07:05:57 +02:00
17248e4ccc
libui: Add default handlers for events in Widget 2023-09-20 07:05:57 +02:00
5908b07ee2
libui: Propagate Container events only if they are in the child widget's rect 2023-09-20 07:05:57 +02:00
a023811c26
libui+wind: Handle mouse leave events when the mouse leaves a window 2023-09-20 07:05:57 +02:00
5385b1c337
wind: Stop using the removed 'signal' pledge 2023-09-20 07:05:57 +02:00
5bd2b3d81d
libui: Install the built library into the system root 2023-09-20 07:05:56 +02:00
ad001b4ee7
wind: Show memory usage in debug output 2023-09-20 07:05:56 +02:00
88a202ba33
wind: Handle ftruncate() and mmap() errors properly 2023-09-20 07:05:56 +02:00
b656ceedfe
wind: Fix client references being out-of-date in windows when disconnecting other clients
Classic "keeping a pointer to an element inside a vector after the vector is updated" bug, ah yes.
2023-09-20 07:05:56 +02:00
d43d06604d
taskbar: Wait for terminated child windows 2023-09-20 07:05:56 +02:00
6375fb965a
wind: Add debug keybind 2023-09-20 07:05:56 +02:00
7e7f0a96f5
wind+libos+libui: Handle interrupted reads properly 2023-09-20 07:05:55 +02:00
8c4e9dff96
base: Actually add the start icon to source control 2023-09-20 07:05:55 +02:00
345cf5cae3
libui: Add Buttons 2023-09-20 07:05:55 +02:00
d6f63c0a5d
libui: Handle other mouse events 2023-09-20 07:05:55 +02:00
35c7011997
libui: Add aligned items using Containers, ImageWidget 2023-09-20 07:05:55 +02:00
f657ee9ba9
libui: Add VerticalLayout 2023-09-20 07:05:55 +02:00
5703faf50f
wind+libui+taskbar: Add GetScreenRect IPC, non-decorated windows, taskbar 2023-09-20 07:05:54 +02:00
4d068beaaf
libui: Actually fill window backgrounds with the correct color 2023-09-20 07:05:54 +02:00
819baa0cd5
libui: Add basic widget and layout system =D 2023-09-20 07:05:54 +02:00
69bb22095f
ui+wind: Send mouse move events through IPC 2023-09-20 07:05:54 +02:00
062b09e20c
wind+libui: Add protocol for window close requests 2023-09-20 07:05:54 +02:00
2328987d81
libos+libui+wind: Use uppercase for static struct IDs to avoid confusion with fields 2023-09-20 07:05:54 +02:00
820b1ae2ba
libui+gclient: Add basic OOP wrappers around the IPC protocol 2023-09-20 07:05:53 +02:00
0fb47d90a7
wind+gclient: Add SetWindowTitle and support shm buffers 2023-09-20 07:05:53 +02:00
0127068177
gclient: Create two example windows 2023-09-20 07:05:53 +02:00
d3dd257dc1
wind: Handle CreateWindow IPC messages 2023-09-20 07:05:53 +02:00
1eb00eabfa
libui: Add CreateWindow IPC message definitions 2023-09-20 07:05:53 +02:00
9125561cab
libos: Add basic IPC message framework 2023-09-20 07:05:53 +02:00
3d90d7f98e
kernel: Fix poll syscall 2023-09-20 07:05:52 +02:00
e931d11ae1
wind: Monitor data on client connections 2023-09-20 07:05:52 +02:00
6a35cad8d5
kernel: Add POLLHUP and store it when a polled socket's peer disconnects 2023-09-20 07:05:52 +02:00
5e6ce50c70
libui: Add copyright/author text 2023-09-20 07:05:52 +02:00
02b9dc579b
libos: Add copyright/author comments to LocalServer and LocalClient 2023-09-20 07:05:52 +02:00
774177ba1f
wind: Use init --user and pledge() 2023-09-20 07:05:52 +02:00
7d883fe33b
Update .gitignore 2023-09-20 07:05:52 +02:00
1c50d5133f
libos: Remove some shared pointers and change them to owned/live on the stack 2023-09-20 07:05:52 +02:00
6cf5fa3097
wind: Spawn a new client process after startup
Also, create the socket after dropping privileges.
2023-09-20 07:05:52 +02:00
60c3bcb3a9
apps: Add gclient 2023-09-20 07:05:51 +02:00
03096680ae
libos: Add os::LocalClient 2023-09-20 07:05:51 +02:00
639eb30c7b
libui: Change 'into' to 'onto' 2023-09-20 07:05:51 +02:00
0bb96985bf
libui: Document ui::Font 2023-09-20 07:05:51 +02:00
fbb66a9fc3
libui+wind: Move some static variables inside functions 2023-09-20 07:05:51 +02:00
3fefb74710
wind: Generate random windows on keypresses 2023-09-20 07:05:51 +02:00
11e0025a5b
wind: Make sure windows have a minimum size to fit the titlebar 2023-09-20 07:05:51 +02:00
98aaf1f7ff
libui: Properly cut off the last drawn character if necessary 2023-09-20 07:05:51 +02:00
c0ada40e2c
libui: Add Rect::contains(Rect) 2023-09-20 07:05:50 +02:00
35d2bd6931
libui: Render font characters properly with no spacing, matching the width calculations 2023-09-20 07:05:50 +02:00
25ad2b17aa
wind: Render an actual TGA mouse cursor 2023-09-20 07:05:50 +02:00
6d78fc64f9
wind: Add a close button to windows using a TGA icon 2023-09-20 07:05:50 +02:00
dc91d047de
libui: Add support for TGA image loading 2023-09-20 07:05:50 +02:00
916b19825d
libui: Add an interface to fill a Canvas with an array of pixels 2023-09-20 07:05:50 +02:00
f6ef79e759
wind: Add window titlebars using ui::Font 2023-09-20 07:05:50 +02:00
23f7210a87
libui: Add PSF font loading and rendering 2023-09-20 07:05:50 +02:00
c6c32f34f2
libui: Add Color::GRAY 2023-09-20 07:05:50 +02:00
16fa55899e
libui: Rename Rect::absolute to normalized and add a new absolute function 2023-09-20 07:05:50 +02:00
7ab0c6b72b
libluna: Add assignment operators to Buffer 2023-09-20 07:05:49 +02:00
277953065a
wind: Reorder drag sequence 2023-09-20 07:05:49 +02:00
2b3e9b778a
libui: Add Rect::relative 2023-09-20 07:05:49 +02:00
7441e396b3
libui: Remove redundant statement 2023-09-20 07:05:49 +02:00
07dc7064f8
libui: Add getters for separate color values 2023-09-20 07:05:49 +02:00
01da7be57c
libui: Remove unnecessary stuff 2023-09-20 07:05:49 +02:00
b6c85595be
base: Remove startup items not necessary for GUI startup 2023-09-20 07:05:49 +02:00
8859fc3d6a
libui+wind: (Draggable) windows 2023-09-20 07:05:49 +02:00
335911c287
wind: Create a local server object 2023-09-20 07:05:49 +02:00
b6fe96e364
libos: Add a new LocalServer class for local domain sockets 2023-09-20 07:05:49 +02:00
15192837c0
kernel: Support listening sockets in poll() 2023-09-20 07:05:48 +02:00
5b89fccb6a
base: Start wind on startup instead of the shell 2023-09-20 07:05:48 +02:00
36cc84c50d
wind: Add a simple display server skeleton using libui
No client functionality yet, but it's a start.
2023-09-20 07:05:48 +02:00
9df88bac3e
libui: Add a GUI and graphics library 2023-09-20 07:05:48 +02:00
4af337e92d
kernel: Improve the mutex system
Some checks are pending
continuous-integration/drone/push Build is running
2023-09-20 07:05:33 +02:00
3e896b0f62
kernel:Remove unused legacy variable from sys_mmap()
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-12 22:07:17 +02:00
66e3d71dbc
kernel/ATA: Fix sector number calculation for ATA drives
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-07 11:28:46 +02:00
6065b63801
kernel/ext2: Add support for files larger than 4MB
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-06 09:56:24 +02:00
e029679fba
kernel: Fix negative movement in the PS/2 mouse driver
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-05 10:46:05 +02:00
15f4f7c72f
apps: Remove pivot_root
All checks were successful
continuous-integration/drone/push Build is passing
This functionality is intended to be used only by preinit, so it doesn't make sense to make it a command-line utility.
2023-09-04 13:12:55 +02:00
0002c2314c
kernel: Handle tab properly in the terminal 2023-09-04 13:12:08 +02:00
27eacac19c
kernel: Add a blinking cursor to the terminal
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-04 11:44:35 +02:00
c5e11bb6cf
apps+base+libc: Use /usr/bin paths instead of /bin everywhere 2023-09-04 11:44:10 +02:00
3c9b2c49aa
init: Fix wrong log message 2023-09-04 11:43:36 +02:00
1528c772fd
kernel: Store the full command line of a process 2023-09-04 11:43:11 +02:00
39ba4c9087
ls: Add colors to output
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-02 20:01:10 +02:00
c524dc8d58
libluna+kernel: Basic ANSI escape sequences 2023-09-02 19:35:42 +02:00
e76a91d5d0
libc+libluna: Move the scanf implementation from libc to libluna 2023-09-02 15:48:58 +02:00
cb0d5cb6a1
rm: Add the -v flag
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-02 14:46:14 +02:00
06aa7725a1
libos: Stop using syscalls directly and proxy to libc 2023-09-02 14:27:04 +02:00
64f9e9dcde
libluna: Document Check.h
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-28 12:27:41 +02:00
9c912ddc51
libluna: Document Heap.h 2023-08-28 12:20:42 +02:00
419604a4d2
libluna: Document Buffer
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-28 11:10:04 +02:00
97037b06cb
libluna: Document Ignore.h and ImplPOSIX.cpp 2023-08-27 20:50:53 +02:00
c2f173f584
libc: Call __builtin_trap() in abort() if all else fails 2023-08-27 20:49:18 +02:00
a772d92e6f
libluna: Fix initial allocation for HashTable 2023-08-27 20:48:50 +02:00
6f3ed70363
kernel+libluna: Avoid scrubbing when the memory is going to be overwritten anyway
This is the case for objects with constructors and temporary memory which is filled afterwards.
2023-08-27 20:48:33 +02:00
d48142f163
libluna: Document HashMap and HashTable 2023-08-26 20:50:12 +02:00
cbea66c533
libos+libluna: Fix misspellings of "succeeded" 2023-08-26 20:49:57 +02:00
c6d817a0fd
libluna: Document Hash.h 2023-08-26 20:31:16 +02:00
55c362eecf
libluna: Document Format
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-26 12:59:22 +02:00
9fd8b10b3f
libluna: Document CString, CType and DebugLog 2023-08-26 12:43:44 +02:00
516d6bc65e
libluna: Document CircularQueue, CPath and CRC32 2023-08-26 12:33:12 +02:00
4becb2e427
init: Log requested exits
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-24 15:28:20 +02:00
3e174337ab
init: Hostname validation 2023-08-24 15:28:08 +02:00
0f377e7289
libluna: Fix off-by-one size calculation in Base64::decode_string()
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-24 12:06:01 +02:00
798a6d63aa
libluna: Document Bitset.h 2023-08-23 14:45:53 +02:00
3f1e6fc2ff
libluna+kernel: Move luna/Syscall.h to the kernel API directory
Some checks failed
continuous-integration/drone/push Build is failing
A kernel-specific header has nothing to do in libluna.
2023-08-23 14:35:21 +02:00
b8ae61b7c7
libluna: Document Bitmap
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-23 13:50:45 +02:00
1449e966ab
libluna: Document Base64.h 2023-08-23 13:34:14 +02:00
24f9dd22ec
libluna: Document Attributes.h and Badge.h 2023-08-23 13:24:28 +02:00
b8e70996c3
libluna: Document Atomic.h 2023-08-23 13:04:47 +02:00
6058a69182
libluna: Document Alloc.h 2023-08-23 11:08:18 +02:00
e247310ded
libluna: Document Alignment.h 2023-08-23 11:04:05 +02:00
a35ca0b367
libluna+kernel: Add Bitset and use it for signals
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-23 10:51:02 +02:00
d144a818d8
kernel: Completely prevent sending signals to kernel threads
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-22 15:25:05 +02:00
33c1a9c92b
init: Add a configurable service directory
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-22 15:23:06 +02:00
65834ff491
edit: Make it more user-friendly 2023-08-22 15:17:13 +02:00
5c2718545f
base+tools: Add a welcome file and store the license inside the OS 2023-08-22 15:08:07 +02:00
e5905a33e1
init: Stop using the removed 'signal' pledge
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-22 13:36:33 +02:00
993e94cc76
kernel: Remove the 'signal' promise and make signal functions require stdio instead
Some checks failed
continuous-integration/drone/push Build is failing
This follows OpenBSD and makes it so stdio is sufficient for an abort() call.
2023-08-22 13:34:30 +02:00
746834e2d9
kernel: Silently ignore TTY writes in graphical mode
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-22 13:31:31 +02:00
ce3542e2bd
apps+base: Remove many legacy test apps
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-22 13:29:52 +02:00
e72bc55c6f tools: Maybe, this finally works for CI...
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-22 10:19:17 +00:00
2abb43d709
kernel+libos: Call Vector::try_reserve where it is appropriate
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-22 11:54:00 +02:00
70a232cfcd
libluna: Make Vector grow exponentially 2023-08-22 11:53:42 +02:00
30ff704342
libluna+libos: Install built libraries into the system root
Some checks failed
continuous-integration/drone/push Build is failing
This is less important for libluna, as it is built into libc, but is needed to link programs compiled inside Luna with libos.
2023-08-21 14:06:32 +02:00
6e69d37d62
tools: Fix building ports from git repository
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-19 19:54:37 +02:00
f2a5c9ad3f
apps: Add free
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-17 20:15:39 +02:00
95a33c484e
kernel+libc: Add a memstat() syscall
We can finally show memory usage in userspace.

This could have been done using sysfs, but I'm lazy and don't want to implement that. Maybe in the next release?
2023-08-17 20:15:32 +02:00
4a654bf093
kernel: Handle OOMs better and without deadlocking
Use a separate task to do it.

Also fix a bug where the init thread would get no kernel stack ever since 5f698b477.
2023-08-17 20:14:33 +02:00
d43590e68c
kernel: Improve Your Disk IO performance by 500% with this One Trick!
Some checks failed
continuous-integration/drone/push Build is failing
The trick being caching lol.
2023-08-16 14:54:13 +02:00
c1f4997448
all: Start calling this release 0.5.0
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-16 09:57:38 +02:00
7b88b9cea3
kernel/ext2: Replace a manually allocated buffer with Buffer
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-16 09:15:29 +02:00
54cc80f649
kernel/ATA: Replace a manually allocated buffer with Buffer
This also lets us keep it empty unless it is used, in which case we resize it (if it wasn't already resized).
2023-08-16 09:15:00 +02:00
b8f81502b8
kernel: Fix some debug messages that are not shown by default 2023-08-16 09:03:25 +02:00
e8e05159c1
libluna+kernel: Make CRC32 a class 2023-08-15 19:27:09 +02:00
49a6c39c38
libc: Implement popen() and pclose() 2023-08-15 19:21:25 +02:00
706752d6b9
kernel: Return SIGPIPE/EPIPE when writing to a pipe with no more readers 2023-08-15 19:08:37 +02:00
1e68ac7312
Finally a working toolchain?
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-15 15:40:44 +02:00
6761b3fcaf
tools: Finally a proper autoconf build?
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 11:54:11 +02:00
71d1084be2 tools: Place autoconf in the correct path
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 09:34:36 +00:00
f65deb727a
tools: Fix filename in setup-autoconf.sh
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 11:27:47 +02:00
d07f592306
tools: Fix message printing in setup-autoconf.sh
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 11:26:18 +02:00
cad0bd8c48
tools: Build autoconf if needed and use it to patch libstdc++
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 11:18:53 +02:00
db3151d93b
init: Add some missing pledges
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 11:08:19 +02:00
ba3e32917e
init: Support starting services as a separate user
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-14 10:46:45 +02:00
cfb60fad25
init: Use pledge and support init --user 2023-08-14 10:46:28 +02:00
9954fc1658
libos: Add a pledge wrapper 2023-08-14 10:45:00 +02:00
a98df9e743
kernel: Return EACCES when trying to apply execpromises to a setuid program
Closes #41.
2023-08-14 09:50:52 +02:00
e2a77bb3da
kernel+libc: Add pledge support
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-12 21:38:25 +02:00
0ae409ae22
ports: Some enhancements in make-package.sh
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-11 18:25:07 +02:00
181b4c151b
tools: Build libstdc++ 2023-08-11 18:24:38 +02:00
0c64b6e040
libc: Add some stub network-related header files 2023-08-11 18:09:45 +02:00
fb3c31907d
fix 2023-08-11 18:09:28 +02:00
52064e0317
libc+kernel: Add alarm() and getpagesize() 2023-08-11 18:09:12 +02:00
ec3c1132d2
libc: Fix constness of some socket functions 2023-08-11 18:00:15 +02:00
5ea73197ad
libluna: Add a bunch more errno definitions 2023-08-11 17:59:41 +02:00
5a1adcb2a6
libc: Add putenv 2023-08-11 17:59:04 +02:00
c4f6191e24
libc: Implement some simple stuff needed for gcc 2023-08-08 22:06:11 +02:00
39e4fbd112
libc: Provide a bunch of math functions wrapped around compiler builtins 2023-08-08 20:38:38 +02:00
32fd6889b9
ports: Add pkg-config wrapper
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 20:28:11 +02:00
c6a5a81a7a
ports: Port required libraries to build GCC
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 19:51:45 +02:00
3f55a70f6e
ports: Auto-strip binaries, remove libtool .la files, add dependencies 2023-08-08 19:46:04 +02:00
b1e164f360
libc. Add basic wchar.h 2023-08-08 19:43:23 +02:00
ed8b210639
kernel: Detect some other cases of non-DMA support
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 18:23:13 +02:00
16590dbb02
ports: Avoid using two separate directories for port packages
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 17:43:27 +02:00
919c71ff85
README: More features
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 16:19:38 +02:00
1caa2c0888
Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 16:04:10 +02:00
8748364b7e
ports: Add a binutils port =D
All checks were successful
continuous-integration/drone/push Build is passing
I can't believe the fact that there is a working gas and ld on Luna. At least, for a hello world program anyway :)

objdump seems to have some problems with stack size in some cases, but apart from that, no crashes.

And that can easily be solved with either more stack preallocation or stack resizing in the kernel on page faults.
2023-08-08 16:00:31 +02:00
49662b6069
tools: Calculate the needed fs size dynamically 2023-08-08 15:43:20 +02:00
d96ff92461
libc: Add borrowed strtod implementation 2023-08-08 15:17:25 +02:00
bfb76b5625
kernel: Properly expose block device sizes with the new metadata API 2023-08-08 15:17:08 +02:00
8c13513bf4
libc: Add strcoll()
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 14:40:14 +02:00
37e9b25b62
apps: Add touch
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 14:34:58 +02:00
a92077d311
kernel+libc: Add all variants of utime
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 14:14:35 +02:00
1481a4736a
tmpfs: Update mtime on writes
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 13:36:25 +02:00
4195e7f206
kernel+libc+stat: Add support for file times
All checks were successful
continuous-integration/drone/push Build is passing
The modification time is not updated though.
2023-08-08 13:33:40 +02:00
159c05c064
libluna: Add max() and min()
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 12:39:03 +02:00
b63a8ff245
libluna: Move get_blocks_from_size to a new header and call it ceil_div instead
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 11:58:33 +02:00
917203ef11
kernel: Run the init function in a separate block to free everything before kernel_exit is called
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 10:44:18 +02:00
826be882a9
kernel: Interrupt syscalls before exiting because of a signal
Closes #40.
2023-08-08 10:41:46 +02:00
198935eb30
libc: Reset the read buffer even when read() returns an error
This fixes the same data being read multiple times if an error was returned
2023-08-08 10:39:15 +02:00
7c254e5e15
kernel: Properly check memory bounds in strdup_from_user()
All checks were successful
continuous-integration/drone/push Build is passing
Missed this one in 097353e779.
2023-08-08 10:27:19 +02:00
2e63b93e48
libos: Remove debug statements from Process::spawn()
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-07 22:53:37 +02:00
77ebdda2e0
libos: Add Process::spawn()
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-07 22:49:12 +02:00
097353e779
kernel: Properly check memory bounds while touching user memory
Before this patch, one byte of each page was being accessed without checking the page's permissions.
2023-08-07 22:49:00 +02:00
10c892d606
kernel: Allow null envp in execve()
As far as I know, this is not standard, but I'm doing this as a convenience for programs using exec() right after clearenv().
2023-08-07 22:48:21 +02:00
f45734c61d
kernel/ATA: Stop storing ATA::Drive in a separate shared pointer
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-07 19:22:57 +02:00
bc20e1a31b
kernel: Store FD_CLOEXEC in the file descriptor itself
All checks were successful
continuous-integration/drone/push Build is passing
Closes #39.
2023-08-03 17:47:18 +02:00
b01aa72f17
libc+init+shmem-test: Add POSIX shared memory objects
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-03 10:32:52 +02:00
d41fb85466
libluna/SharedPtr: Count references with separately created objects properly 2023-08-03 10:32:22 +02:00
5aa042a5f2
kernel: Add constructors to SharedMemory 2023-08-03 10:31:58 +02:00
f150425222
kernel: Create a new shared memory object if the old one was lost 2023-08-03 10:31:29 +02:00
842b212685
kernel: Set a SharedMemory object's prot value 2023-08-03 10:30:43 +02:00
641b65da0f
kernel: Take devices into account in SharedMemory::free() 2023-08-03 09:33:10 +02:00
bfcca3a220
gol: Use memory-mapped IO
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-03 09:26:35 +02:00
b5d146b492
kernel/mmap: Fix some parameters 2023-08-03 09:26:21 +02:00
b447c1a261
kernel: Set the shmem->inode field in query_shared_memory 2023-08-03 09:25:56 +02:00
f8e86b3b01
fix 2023-08-03 09:25:38 +02:00
d8f6af99b8
kernel: Let devices use shared memory 2023-08-03 09:25:23 +02:00
c5e24e478f
kernel+libc: Add truncate and ftruncate
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-03 08:47:37 +02:00
53d9f5c6fc
libc: Add msync wrapper
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 22:44:54 +02:00
84c1ac4cee
kernel: Add msync
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 22:39:07 +02:00
7293d47bf0
apps: Add shmem-test
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 22:20:05 +02:00
f66b0497cf
libc: Add support for mmap()'s new syscall format 2023-08-02 22:20:05 +02:00
2572695c8d
kernel: Support mapping shared memory using mmap() 2023-08-02 22:20:05 +02:00
9443551d71
kernel: Add shared memory 2023-08-02 22:20:05 +02:00
b4527786d4
kernel/MMU: Don't copy pages on clone and allow manipulating other page directories 2023-08-02 22:17:32 +02:00
3aaf1c5d84
apps: Rename mouse to input, read keyboard packets as well
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 17:20:26 +02:00
4794d0dfef
kernel: Add a keyboard device for graphical session 2023-08-02 17:20:13 +02:00
9c1e275f34
kernel: Make /dev/mouse read-write only for root 2023-08-02 17:19:37 +02:00
6593f9241b
libc: Add wrapper for poll() 2023-08-02 17:19:16 +02:00
df4227eab8
kernel: Make the poll() system call actually work 2023-08-02 17:18:38 +02:00
f8cb6e03df
kernel: Allow turning off the TTY by setting it to graphical mode
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 15:16:10 +02:00
207d901de8
kernel+libc: Add the poll() syscall
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 14:48:20 +02:00
df77fc8de8
libluna: Remove make_array() and destroy_array()
Placement new on arrays is a bit unreliable and could cause out-of-bounds data accesses.
2023-08-02 14:47:58 +02:00
b1fb6dee8a
login: Create a new process group to log in 2023-08-02 14:47:13 +02:00
aac8280e8a
libc+libos: Properly propagate errors through fgetc() and File::getchar()
This restores proper ^C behavior in the shell.
2023-08-02 14:46:47 +02:00
d0ceec6952
mouse: Show textual description of buttons
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 13:58:17 +02:00
7983d63b8e
libos: Add documentation for os::File::BufferingMode
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-02 13:15:51 +02:00
2198dedb96
apps: Add mouse
All checks were successful
continuous-integration/drone/push Build is passing
A simple mouse packet reader.
2023-08-02 11:56:12 +02:00
cd9219df52
tools: Install kernel API headers into /usr/include/moon 2023-08-02 11:56:00 +02:00
e8f3dd4cf9
kernel: Add a PS/2 mouse driver 2023-08-02 11:55:45 +02:00
6c26236167
libluna: Add CircularQueue::is_empty() 2023-08-02 11:55:08 +02:00
b17793134e
libos: Let users change the buffering mode of a File 2023-08-02 11:54:47 +02:00
dc35c42371
kernel/VFS: Rework the metadata system so that there is a single metadata struct
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-01 17:20:28 +02:00
4c87d72b44
kernel/binfmt: Add documentation + support script interpreters being scripts themselves
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-31 20:41:18 +02:00
d01087362e
libc: Set the close-on-exec flag for directories opened using opendir()
This is apparently mandated by POSIX.
2023-07-31 10:58:06 +02:00
3598dacbed
init: Remove support for 'Script' parameters
All checks were successful
continuous-integration/drone/push Build is passing
This can now be changed to good old 'Command', putting a shebang into the script,
thanks to the new shebang support in the kernel.
2023-07-30 18:47:38 +02:00
3638d3da46
kernel: Add binary format for shebang scripts 2023-07-30 18:46:19 +02:00
1c76675e40
kernel: Add a framework to add more executable formats, possibly from userspace
This lets us implement shebangs and possibly an interface similar to Linux's binfmt_misc.
2023-07-30 18:25:44 +02:00
6e269c6bc4
preinit: Search for init in several directories
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-30 13:08:53 +02:00
43fa128e29
Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-30 11:55:20 +02:00
8475a3aad9
socket-client: Send a user-provided message to the server
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-30 11:46:53 +02:00
187f0ff83e
kernel: Rename Inode::blocking() to Inode::will_block_if_read()
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-30 11:41:53 +02:00
0c873923e8
kernel: Make OpenFileDescription shareable
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-30 11:33:46 +02:00
63745565db
apps: Add socket client
Some checks failed
continuous-integration/drone/pr Build is failing
2023-07-30 11:33:06 +02:00
7b24a4d1c6
libc: Add wrappers for listen(), connect(), and accept() 2023-07-30 11:33:06 +02:00
8d3b3aaf05
libluna: Add a few more network-related errno codes 2023-07-30 11:33:06 +02:00
bb3127c212
kernel: Implement listen(), connect() and accept() 2023-07-30 11:33:06 +02:00
cca806f088
libluna: Add a variant of CircularQueue that dynamically allocates its buffer at runtime
This is needed to implement the backlog queue for listening sockets.
2023-07-30 11:33:06 +02:00
7a7ae086f5
kernel: Use a did_close callback for UnixSockets 2023-07-30 11:33:06 +02:00
fb08594a18
kernel: Separate FileDescriptors and OpenFileDescription
Also, add a did_close() callback for OpenFileDescriptions losing all their references.
2023-07-30 11:33:06 +02:00
9303c44269
apps: Add socket-test 2023-07-30 11:33:06 +02:00
a12b018b03
kernel+libc: Add basic Unix sockets (creation and binding) 2023-07-30 11:33:06 +02:00
c4e30c3029
kernel: Add functionality to allow external inodes (such as sockets) to acquire an inode number from a file system
This is only implemented in tmpfs.
2023-07-30 11:33:06 +02:00
c1d08b904e
kernel+libluna: Add Buffer::dequeue_data() 2023-07-30 11:33:05 +02:00
200bb6c240
kernel+libc+libos: Add inode type for sockets 2023-07-30 11:33:05 +02:00
6b0bc66fd2
libluna: Add new socket-related errno codes 2023-07-30 11:33:05 +02:00
4ed7ec5e93
libluna: Store SharedPtr's ref count in the object itself
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-30 11:32:46 +02:00
28cc4b2306
kernel: Add default action for SIGWINCH
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-27 14:00:30 +02:00
11df5a2ec3
kernel: Use pid_t internally for process IDs
All checks were successful
continuous-integration/drone/push Build is passing
This removes a bunch of casts between pid_t and u64, and makes more sense since pid_t is literally the data type for process IDs.
2023-07-26 21:32:00 +02:00
5aad7d3154
kernel/x86_64: Fail on invalid interrupts instead of calling FIXME_UNHANDLED_INTERRUPT
All checks were successful
continuous-integration/drone/push Build is passing
A FIXME is something that should be changed later,
this is a check for something that should never happen.
2023-07-25 18:23:33 +02:00
58fa297068
libluna: Wrap around when iterating through a HashTable's buckets array
All checks were successful
continuous-integration/drone/push Build is passing
Why am I so dumb?
2023-07-25 18:19:45 +02:00
28dd8194af
kernel: Include waited-for grandchildren in RUSAGE_CHILDREN's times
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-25 17:50:15 +02:00
b88da4811f
kernel: Remove debug message in readlink
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-25 17:30:56 +02:00
7f990b161b
libluna: Fix comparison of StringViews without null termination
All checks were successful
continuous-integration/drone/push Build is passing
This regressed in de7e58c274, and made value arguments pretty much unusable.

This really needs a test...
2023-07-25 17:25:18 +02:00
9bb3fed611
libluna: Use the right unsigned integer type for wcscmp()'s return type 2023-07-25 17:23:27 +02:00
105ed79f8f
kernel: Reenable userspace stack tracing, but hidden behind a config flag
All checks were successful
continuous-integration/drone/push Build is passing
Sometimes this is needed for userspace program debugging (such as ports),
but sometimes it can crash, so we leave it off by default.
2023-07-25 17:02:09 +02:00
905e71527e
libc: Actually implement sigsetjmp() and siglongjmp() 2023-07-24 19:39:22 +02:00
c6d91c89cd
stat: Change program description
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 19:17:36 +02:00
2e2b87b714
ports+tools: Add bc port
All checks were successful
continuous-integration/drone/push Build is passing
It segfaults when writing "about", but oh well...
2023-07-24 19:14:22 +02:00
b12f42cfe2
libc: Add SIGWINCH
Not used for anything at the moment, I guess if a user program wants to send it...

Required by bc.
2023-07-24 19:11:06 +02:00
c70790bf62
ports: Add some defaults for CMake projects as well
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 18:50:51 +02:00
a990cc145e
ports: Add initial documentation to the port system
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 18:29:44 +02:00
b0d7870ec6
ports: Add minitar (with examples!)
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 17:20:54 +02:00
9a00b29304
ports: Basic ports system + nasm port
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 17:07:49 +02:00
033aff4f6c
all: Error out on bidirectional characters (CVE-2021-42574)
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 15:06:48 +02:00
c5af1bcef9
libc: Fix typo in fopen description
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-24 11:57:47 +02:00
b3c478f19e
init: Make the log stream line-buffered
All checks were successful
continuous-integration/drone/push Build is passing
Looks like log output stopped showing after buffering was implemented, since the log was not flushed properly.
2023-07-24 11:54:49 +02:00
ad3f3bf4db
kernel: Log the release name and build date in the boot log 2023-07-24 11:48:01 +02:00
01359af288
tools: Make the Ext2 partition bigger (32M)
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-22 16:11:34 +02:00
490 changed files with 23738 additions and 3725 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,23 @@
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 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

12
.gitignore vendored
View File

@ -2,8 +2,18 @@ Luna.iso
toolchain/
build/
initrd/boot/moon
initrd/ksyms
env-local.sh
initrd/bin/**
base/usr/**
base/usr/*
!base/usr/share
base/usr/share/*
!base/usr/share/fonts
!base/usr/share/icons
!base/usr/share/applications
base/etc/skel/LICENSE
.fakeroot
kernel/config.cmake
ports/out/
ports/temp/
ports/dev/

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.4.0)
set(LUNA_RELEASE_NAME "Mercury") # Name for alpha releases
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)
@ -33,7 +33,7 @@ set(COMMON_FLAGS -Wall -Wextra -Werror -Wvla
-Wdisabled-optimization -Wformat=2 -Winit-self
-Wmissing-include-dirs -Wswitch-default -Wcast-qual
-Wundef -Wcast-align -Wwrite-strings -Wlogical-op
-Wredundant-decls -Wshadow -Wconversion
-Wredundant-decls -Wshadow -Wconversion -Wbidi-chars=any
-fno-asynchronous-unwind-tables -fno-omit-frame-pointer
-std=c++20 -fno-rtti -fno-exceptions)
@ -45,8 +45,10 @@ endif()
add_subdirectory(libluna)
add_subdirectory(libos)
add_subdirectory(gui)
add_subdirectory(libc)
add_subdirectory(kernel)
add_subdirectory(apps)
add_subdirectory(utils)
add_subdirectory(tests)
add_subdirectory(shell)
add_subdirectory(system)

View File

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

View File

@ -1,56 +1,56 @@
# Luna
A very basic POSIX-based operating system for personal computers, written in C++. [![Build Status](https://drone.cloudapio.eu/api/badges/apio/Luna/status.svg)](https://drone.cloudapio.eu/apio/Luna)
A simple POSIX-based operating system for 64-bit computers, written in C++.
## Another UNIX clone?
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone).
## Features
- x86_64-compatible lightweight [kernel](kernel/).
- Preemptive multitasking, with a round-robin [scheduler](kernel/src/thread/).
- [Virtual file system](kernel/src/fs/) with a simple [tmpfs](kernel/src/fs/tmpfs/) and read-only [ext2](kernel/src/fs/ext2/) support.
- Can [load ELF programs](kernel/src/thread/ELF.cpp) from the file system as userspace tasks.
- Boots from an [ext2](apps/preinit.cpp) root filesystem (a bit slow for now).
- [System call](kernel/src/sys/) interface and [C Library](libc/), aiming to be mostly POSIX-compatible.
- POSIX [signal](libc/src/signal.cpp) support.
- Designed to be [portable](kernel/src/arch), no need to be restricted to x86_64.
- Designed around [UTF-8](libluna/include/luna/Utf8.h).
- Lightweight 64-bit [kernel](kernel/). Compatible with the x86_64 architecture.
- Basic threads/processes, using a simple round-robin [scheduler](kernel/src/thread/).
- Read-only [ext2](kernel/src/fs/ext2/) filesystem.
- Can [load ELF executables](kernel/src/binfmt/ELF.cpp), [shebang scripts](kernel/src/binfmt/Script.cpp) or [arbitrary binary formats](kernel/src/binfmt/BinaryFormat.h) (registered through kernel modules, which are not supported yet =D).
- [C Library](libc/), aiming for POSIX compatibility, with many features such as local domain sockets, signals, and shared memory.
- Support for [several third-party programs](ports/), including the [GNU binutils](ports/binutils/PACKAGE) suite of utilities and the [GCC](ports/gcc/PACKAGE) compiler.
- Designed to be [portable](kernel/src/arch), so that additional architectures can be added in the future with relatively low effort.
- Everything text-related is designed around [UTF-8](libluna/include/luna/Utf8.h).
- Environment-agnostic [utility library](libluna/), which can be used in both kernel and userspace.
- Return-oriented [error propagation](libluna/include/luna/Result.h), inspired by Rust and SerenityOS.
- Build system uses [CMake](CMakeLists.txt).
- 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).
## 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` and `genext2fs`.
`tools/run.sh` is the script you should use in most cases. It will build changed files, install, make an ISO image, and run Luna in QEMU.
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` since `run.sh` does it for you.
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` manually since `run.sh` does it for you.
## Login UI
For development convenience, the system automatically starts a GUI session as the default user, without prompting for a password.
Despite this, Luna does have a login window built-in. If you'd like to try this feature out or start a GUI session as a different user, you'll need to edit [base/etc/loginui.conf](base/etc/loginui.conf) and change the line that says `Autologin=true` to `Autologin=false`.
## Prebuilt images
Prebuilt ISO images (numbered) for every version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
These images are built manually whenever a new release is created, and thus don't reflect the latest changes on the `main` branch.
Every hour, this server pulls the latest commits on `main` and builds an hourly ISO image. The ten most recent ones can be found in the [hourly](https://pub.cloudapio.eu/luna/hourly) directory, and [Luna-latest.iso](https://pub.cloudapio.eu/luna/Luna-latest.iso) should always be symlinked to the newest one.
These images do reflect the latest changes on the `main` branch, but are obviously less stable. Additionally, an hourly image will be skipped if building the latest commit of the project fails.
Prebuilt ISO images for every release version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
## Is there third-party software I can use on Luna?
There is no infrastructure for porting third-party software nor there are any patches in the repo for now, but some third-party programs can run on Luna, including [my own library](https://git.cloudapio.eu/apio/minitar).
Yes! A ports system is in place, and you can use the build scripts to add some ports to your image. More information in the [Ports](ports/README.md) page.
## License
Luna is open-source and free software under the [BSD-2 License](LICENSE).
Luna is open-source and free software under the [BSD-2-Clause License](LICENSE).

View File

@ -1,44 +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(ipc-test.cpp ipc-test)
luna_app(signal-test.cpp signal-test)
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(pivot_root.cpp pivot_root)
luna_app(cp.cpp cp)
luna_app(kill.cpp kill)
luna_app(gol.cpp gol)
luna_app(buffer-test.cpp buffer-test)

View File

@ -1,27 +0,0 @@
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
fprintf(stderr, "Writing incomplete line to stdout (_IOLBF=%d)...\n", stdout->_buf.mode);
fputs("hi!", stdout);
sleep(3);
putchar('\n');
fprintf(stderr, "Incomplete line should have been written.\n");
FILE* f = fopen("/dev/console", "w+");
assert(f);
assert(setvbuf(f, NULL, _IOFBF, 0) == 0);
fprintf(stderr, "Writing long text to file (_IOFBF=%d)...\n", f->_buf.mode);
fputs("Hello world!\nHow are you doing!\nThis is a test for many lines of buffering.\n", f);
sleep(3);
fflush(f);
fprintf(stderr, "Long text should have been written.\n");
fclose(f);
}

View File

@ -1,166 +0,0 @@
#include <alloca.h>
#include <assert.h>
#include <fcntl.h>
#include <luna/Heap.h>
#include <os/ArgumentParser.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
struct Cell
{
bool state;
bool new_state;
};
static int g_num_rows = 76;
static int g_num_columns = 102;
static int g_fb_width;
static int g_fb_height;
static int g_fd;
static Cell* g_cells;
static Result<void> fill_cells()
{
g_cells = (Cell*)TRY(calloc_impl(g_num_rows, g_num_columns * sizeof(Cell), false));
for (isize i = 0; i < (g_num_rows * g_num_columns); i++)
{
auto value = rand() % 2;
g_cells[i].state = g_cells[i].new_state = value;
}
return {};
}
static Cell& find_cell(int row, int column)
{
assert(row < g_num_rows);
assert(column < g_num_columns);
return g_cells[row * g_num_columns + column];
}
static constexpr int BYTES_PER_PIXEL = 4;
static char* g_buf;
static void draw_cells()
{
lseek(g_fd, 0, SEEK_SET);
const int CELL_WIDTH = g_fb_width / g_num_columns;
const int CELL_HEIGHT = g_fb_height / g_num_rows;
for (int i = 0; i < g_num_rows; i++)
{
memset(g_buf, 0, g_fb_width * BYTES_PER_PIXEL);
for (int j = 0; j < g_num_columns; j++)
{
auto& cell = find_cell(i, j);
if (cell.state) memset(g_buf + (j * CELL_WIDTH * BYTES_PER_PIXEL), 0xff, CELL_WIDTH * BYTES_PER_PIXEL);
}
for (int j = 0; j < CELL_HEIGHT; j++) { write(g_fd, g_buf, g_fb_width * BYTES_PER_PIXEL); }
}
}
static int find_neighbors(int row, int column)
{
int sum = 0;
if (row > 0 && column > 0) sum += find_cell(row - 1, column - 1).state;
if (row > 0) sum += find_cell(row - 1, column).state;
if (row > 0 && (column + 1) < g_num_columns) sum += find_cell(row - 1, column + 1).state;
if (column > 0) sum += find_cell(row, column - 1).state;
if ((column + 1) < g_num_columns) sum += find_cell(row, column + 1).state;
if ((row + 1) < g_num_rows && column > 0) sum += find_cell(row + 1, column - 1).state;
if ((row + 1) < g_num_rows) sum += find_cell(row + 1, column).state;
if ((row + 1) < g_num_rows && (column + 1) < g_num_columns) sum += find_cell(row + 1, column + 1).state;
return sum;
}
static void next_generation()
{
for (int i = 0; i < g_num_rows; i++)
{
for (int j = 0; j < g_num_columns; j++)
{
auto& cell = find_cell(i, j);
int neighbors = find_neighbors(i, j);
if (!cell.state && neighbors == 3) cell.new_state = true;
else if (cell.state && (neighbors < 2 || neighbors > 3))
cell.new_state = false;
}
}
for (isize i = 0; i < (g_num_rows * g_num_columns); i++) g_cells[i].state = g_cells[i].new_state;
}
Result<int> luna_main(int argc, char** argv)
{
u64 delay_between_iterations = 250;
u64 delay_at_end = 3000;
u64 num_iterations = 100;
StringView columns;
StringView rows;
StringView delay;
StringView end_delay;
StringView iterations;
StringView seed;
os::ArgumentParser parser;
parser.add_description("A framebuffer-based implementation for Conway's Game of Life.");
parser.add_system_program_info("gol"_sv);
parser.add_positional_argument(rows, "rows"_sv, "76"_sv);
parser.add_positional_argument(columns, "columns"_sv, "102"_sv);
parser.add_value_argument(delay, 'd', "delay"_sv, "the delay between generations (in ms)");
parser.add_value_argument(end_delay, 'e', "end-delay"_sv,
"after finishing, how much to wait before returning to the shell (in ms)");
parser.add_value_argument(iterations, 'i', "iterations"_sv, "how many generations to show (default: 100)");
parser.add_value_argument(seed, 's', "seed"_sv, "the seed for the random number generator");
parser.parse(argc, argv);
g_num_columns = (int)TRY(columns.to_uint());
g_num_rows = (int)TRY(rows.to_uint());
if (!delay.is_empty()) delay_between_iterations = TRY(delay.to_uint());
if (!end_delay.is_empty()) delay_at_end = TRY(end_delay.to_uint());
if (!iterations.is_empty()) num_iterations = TRY(iterations.to_uint());
if (!seed.is_empty()) srand((unsigned)TRY(seed.to_uint()));
else
srand((unsigned)time(NULL));
g_fd = open("/dev/fb0", O_WRONLY);
if (g_fd < 0)
{
perror("gol: cannot open framebuffer for writing");
return 1;
}
g_fb_height = ioctl(g_fd, FB_GET_HEIGHT);
g_fb_width = ioctl(g_fd, FB_GET_WIDTH);
TRY(fill_cells());
g_buf = (char*)TRY(calloc_impl(g_fb_width, BYTES_PER_PIXEL));
draw_cells();
while (num_iterations--)
{
usleep(delay_between_iterations * 1000);
next_generation();
draw_cells();
}
usleep(delay_at_end * 1000);
return 0;
}

View File

@ -1,373 +0,0 @@
#include <errno.h>
#include <fcntl.h>
#include <luna/PathParser.h>
#include <luna/Sort.h>
#include <luna/String.h>
#include <luna/Vector.h>
#include <os/Directory.h>
#include <os/File.h>
#include <os/Process.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <unistd.h>
FILE* g_init_log = nullptr;
// Request a successful exit from the system (for tests)
void sigterm_handler(int)
{
_exit(0);
}
// Request a failure exit from the system (for tests)
void sigquit_handler(int)
{
_exit(1);
}
struct Service
{
String name;
String command;
bool restart { false };
String environment;
String standard_output;
String standard_error;
String standard_input;
bool wait { false };
Option<pid_t> pid {};
};
Vector<Service> g_services;
static void do_log(const char* format, ...)
{
va_list ap;
va_start(ap, format);
if (g_init_log) vfprintf(g_init_log, format, ap);
va_end(ap);
}
static Result<void> service_child(const Service& service, SharedPtr<os::File> output, SharedPtr<os::File> error,
SharedPtr<os::File> input)
{
auto args = TRY(service.command.split(" \n"));
if (output) dup2(output->fd(), STDOUT_FILENO);
if (error) dup2(error->fd(), STDERR_FILENO);
if (input) dup2(input->fd(), STDIN_FILENO);
if (service.environment.is_empty()) { TRY(os::Process::exec(args[0].view(), args.slice(), false)); }
else
{
auto env = TRY(service.environment.split(",\n"));
TRY(os::Process::exec(args[0].view(), args.slice(), env.slice(), false));
}
return {};
}
static Result<void> try_start_service(Service& service)
{
SharedPtr<os::File> new_stdout = {};
SharedPtr<os::File> new_stderr = {};
SharedPtr<os::File> new_stdin = {};
if (!service.standard_output.is_empty())
{
new_stdout = TRY(os::File::open_or_create(service.standard_output.view(), os::File::Append, 0600));
new_stdout->set_close_on_exec();
}
if (!service.standard_error.is_empty())
{
new_stderr = TRY(os::File::open_or_create(service.standard_error.view(), os::File::Append, 0600));
new_stderr->set_close_on_exec();
}
if (!service.standard_input.is_empty())
{
new_stdin = TRY(os::File::open(service.standard_input.view(), os::File::ReadOnly));
new_stdin->set_close_on_exec();
}
pid_t pid = TRY(os::Process::fork());
if (pid == 0)
{
auto rc = service_child(service, new_stdout, new_stderr, new_stdin);
if (rc.has_error())
{
do_log("[child %d] failed to start service %s due to error: %s\n", getpid(), service.name.chars(),
rc.error_string());
}
fclose(g_init_log);
exit(127);
}
do_log("[init] created new child process %d for service %s\n", pid, service.name.chars());
if (service.wait)
{
do_log("[init] waiting for child process %d to finish\n", pid);
int status;
TRY(os::Process::wait(pid, &status));
do_log("[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
}
else
service.pid = pid;
return {};
}
static void start_service(Service& service)
{
auto rc = try_start_service(service);
if (rc.has_error())
{
do_log("[init] failed to start service %s due to error: %s\n", service.name.chars(), rc.error_string());
}
}
static Result<void> load_service(const os::Path& path)
{
do_log("[init] reading service file: %s\n", path.name().chars());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
Service service;
while (true)
{
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())
{
do_log("[init] file contains invalid line, aborting: '%s'\n", line.chars());
return {};
}
if (parts[0].view() == "Name")
{
service.name = move(parts[1]);
continue;
}
if (parts[0].view() == "Description")
{
// We let users specify this in the config file, but init doesn't actually use it.
continue;
}
if (parts[0].view() == "Command")
{
if (!service.command.is_empty())
{
do_log("[init] 'Command' cannot be specified after 'Script' has already been set! (%s)\n",
line.chars());
return {};
}
service.command = move(parts[1]);
continue;
}
if (parts[0].view() == "Script")
{
if (!service.command.is_empty())
{
do_log("[init] 'Script' cannot be specified after 'Command' has already been set! (%s)\n",
line.chars());
return {};
}
service.command = TRY(String::format("/bin/sh -- %s"_sv, parts[1].chars()));
continue;
}
if (parts[0].view() == "Restart")
{
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
{
service.restart = true;
continue;
}
service.restart = false;
continue;
}
if (parts[0].view() == "Environment")
{
service.environment = move(parts[1]);
continue;
}
if (parts[0].view() == "StandardOutput")
{
service.standard_output = move(parts[1]);
continue;
}
if (parts[0].view() == "StandardError")
{
service.standard_error = move(parts[1]);
continue;
}
if (parts[0].view() == "StandardInput")
{
service.standard_input = move(parts[1]);
continue;
}
if (parts[0].view() == "Wait")
{
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
{
service.wait = true;
continue;
}
service.wait = false;
continue;
}
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
}
if (service.name.is_empty())
{
do_log("[init] service file is missing 'Name' entry, aborting!\n");
return {};
}
if (service.command.is_empty())
{
do_log("[init] service file is missing 'Command' or 'Script' entry, aborting!\n");
return {};
}
do_log("[init] loaded service %s into memory\n", service.name.chars());
TRY(g_services.try_append(move(service)));
return {};
}
static Result<void> load_services()
{
auto dir = TRY(os::Directory::open("/etc/init"));
auto services = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));
sort(services.begin(), services.end(), String::compare);
for (const auto& entry : services) TRY(load_service({ dir->fd(), entry.view() }));
return {};
}
static Result<void> start_services()
{
TRY(load_services());
for (auto& service : g_services)
{
do_log("[init] starting service %s\n", service.name.chars());
start_service(service);
}
return {};
}
static Result<void> set_hostname()
{
auto file = TRY(os::File::open("/etc/hostname", os::File::ReadOnly));
auto hostname = TRY(file->read_line());
hostname.trim("\n");
if (sethostname(hostname.chars(), hostname.length()) < 0) return {};
do_log("[init] successfully set system hostname to '%s'\n", hostname.chars());
return {};
}
static void mount_tmpfs()
{
if (mount("/tmp", "tmpfs", "tmpfs") < 0) exit(255);
if (chmod("/tmp", 01777) < 0) exit(255);
}
int main()
{
if (getpid() != 1)
{
fprintf(stderr, "error: init not running as PID 1.\n");
return 1;
}
// Before this point, we don't even have an stdin, stdout and stderr. Set it up now so that child processes (and us)
// can print stuff.
stdin = fopen("/dev/console", "r");
stdout = fopen("/dev/console", "w");
stderr = fopen("/dev/console", "w");
mount_tmpfs();
umask(022);
g_init_log = fopen("/dev/uart0", "w+");
fcntl(fileno(g_init_log), F_SETFD, FD_CLOEXEC);
set_hostname();
if (signal(SIGTERM, sigterm_handler) == SIG_ERR) do_log("[init] failed to register handler for SIGTERM");
if (signal(SIGQUIT, sigquit_handler) == SIG_ERR) do_log("[init] failed to register handler for SIGQUIT");
start_services();
while (1)
{
int status;
auto rc = os::Process::wait(os::Process::ANY_CHILD, &status);
if (rc.has_error()) continue;
pid_t child = rc.release_value();
for (auto& service : g_services)
{
if (service.pid.has_value() && service.pid.value() == child)
{
if (WIFEXITED(status))
{
do_log("[init] service %s exited with status %d\n", service.name.chars(), WEXITSTATUS(status));
}
else
{
do_log("[init] service %s was terminated by signal %d\n", service.name.chars(), WTERMSIG(status));
}
if (service.restart)
{
do_log("[init] restarting service %s\n", service.name.chars());
start_service(service);
}
break;
}
}
}
}

View File

@ -1,45 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
int pfds[2];
if (pipe(pfds) < 0)
{
perror("pipe");
return 1;
}
pid_t child = fork();
if (child == 0)
{
close(pfds[1]);
char buffer[4096];
size_t nread = read(pfds[0], buffer, sizeof(buffer) - 1);
buffer[nread] = 0;
close(pfds[0]);
puts(buffer);
return 0;
}
else if (child == -1)
{
perror("fork");
return 1;
}
close(pfds[0]);
const char* string = "Hello from a child process who just received this message from its parent!";
write(pfds[1], string, strlen(string));
close(pfds[1]);
wait(NULL);
return 0;
}

View File

@ -1,19 +0,0 @@
#include <os/ArgumentParser.h>
#include <sys/syscall.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
{
StringView new_root;
StringView put_old;
os::ArgumentParser parser;
parser.add_description("Move the current root directory to another directory and replace it with another mount.");
parser.add_system_program_info("pivot_root"_sv);
parser.add_positional_argument(new_root, "new_root", true);
parser.add_positional_argument(put_old, "put_old", true);
parser.parse(argc, argv);
long rc = syscall(SYS_pivot_root, new_root.chars(), put_old.chars());
return Result<int>::from_syscall(rc);
}

View File

@ -1,22 +0,0 @@
#include <os/ArgumentParser.h>
#include <os/FileSystem.h>
Result<int> luna_main(int argc, char** argv)
{
StringView path;
bool recursive;
os::ArgumentParser parser;
parser.add_description("Remove a path from the file system."_sv);
parser.add_system_program_info("rm"_sv);
parser.add_positional_argument(path, "path"_sv, true);
parser.add_switch_argument(recursive, 'r', "recursive"_sv,
"remove a directory recursively (by default, rm removes only empty directories)"_sv);
parser.parse(argc, argv);
if (!recursive) TRY(os::FileSystem::remove(path));
else
TRY(os::FileSystem::remove_tree(path));
return 0;
}

View File

@ -1,23 +0,0 @@
#include <signal.h>
#include <stdio.h>
#include <string.h>
void handler(int)
{
puts("I caught a segfault!");
}
int main()
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGSEGV, &sa, NULL);
#pragma GCC diagnostic ignored "-Wnonnull"
char* str = nullptr;
memset(str, 0, 2);
return 0;
}

View File

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

View File

@ -1,4 +1,4 @@
Name=mount-home
Description=Mount the user's home directory on a writable filesystem.
Script=/etc/startup/mount-home.sh
Command=/etc/startup/mount-home.sh
Wait=true

View File

@ -1,4 +0,0 @@
Name=motd
Description=Show the message of the day to the user.
Command=/usr/bin/cat /etc/motd
Wait=true

View File

@ -1,4 +1,6 @@
Name=login
Description=Start the command-line login program.
Command=/usr/bin/login
Description=Start a graphical user session.
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,2 +1,3 @@
root:toor:0:0:Administrator:/:/bin/sh
selene:moon:1000:1000:User:/home/selene:/bin/sh
root:x:0:0:Administrator:/:/usr/bin/sh
wind:x:2:2:Window Manager:/:/usr/bin/init
selene:x:1000:1000:User:/home/selene:/usr/bin/sh

3
base/etc/shadow Normal file
View File

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

26
base/etc/skel/welcome Normal file
View File

@ -0,0 +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.
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.
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.
View the source code and read more about Luna at
https://git.cloudapio.eu/apio/Luna.

View File

@ -1,2 +1,10 @@
#!/bin/sh
# Create and populate a volatile home directory.
mount -t tmpfs tmpfs /home/selene
chown selene:selene /home/selene
cp /etc/skel/welcome /home/selene/
cp /etc/skel/LICENSE /home/selene/
chown selene:selene /home/selene/welcome
chown selene:selene /home/selene/LICENSE

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

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: 1004 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

256
docs/boot_process.md Normal file
View File

@ -0,0 +1,256 @@
# 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 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#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 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 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 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.
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/loginui`. 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/loginui - 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: 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`. 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 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
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/execd - PID 15
/usr/bin/taskbar - PID 16
/usr/bin/init --user - PID 17
/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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

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)

364
gui/apps/2048.cpp Normal file
View File

@ -0,0 +1,364 @@
#include <luna/RefString.h>
#include <luna/Utf8.h>
#include <stdlib.h>
#include <time.h>
#include <ui/Alignment.h>
#include <ui/App.h>
#include <ui/Font.h>
#include <ui/Layout.h>
static ui::Color colors[] = {
ui::Color::from_rgb(255, 255, 0), ui::Color::from_rgb(255, 230, 0), ui::Color::from_rgb(255, 210, 0),
ui::Color::from_rgb(255, 190, 0), ui::Color::from_rgb(255, 170, 0), ui::Color::from_rgb(255, 150, 0),
ui::Color::from_rgb(255, 130, 0), ui::Color::from_rgb(255, 110, 0), ui::Color::from_rgb(255, 90, 0),
ui::Color::from_rgb(255, 70, 0), ui::Color::from_rgb(255, 50, 0),
};
struct Tile
{
int number { 0 };
int color { 0 };
};
class GameWidget final : public ui::Widget
{
public:
static constexpr int MARGIN = 5;
Result<void> draw(ui::Canvas& canvas) override
{
int width = m_rect.width / 4;
int height = m_rect.height / 4;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
auto subcanvas = canvas.subcanvas(
ui::Rect { width * j + MARGIN, height * i + MARGIN, width - MARGIN, height - MARGIN });
int index = i * 4 + j;
TRY(draw_tile(index, subcanvas));
}
}
return {};
}
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override
{
if (!request.pressed) return ui::EventResult::DidNotHandle;
bool should_add_tile = false;
switch (request.code)
{
case moon::K_UpArrow: {
bool changed;
changed = move_up();
if (changed) should_add_tile = true;
join_up();
changed = move_up();
if (changed) should_add_tile = true;
}
break;
case moon::K_LeftArrow: {
bool changed;
changed = move_left();
if (changed) should_add_tile = true;
join_left();
changed = move_left();
if (changed) should_add_tile = true;
}
break;
case moon::K_DownArrow: {
bool changed;
changed = move_down();
if (changed) should_add_tile = true;
join_down();
changed = move_down();
if (changed) should_add_tile = true;
}
break;
case moon::K_RightArrow: {
bool changed;
changed = move_right();
if (changed) should_add_tile = true;
join_right();
changed = move_right();
if (changed) should_add_tile = true;
}
break;
case moon::K_Home: {
reset();
return ui::EventResult::DidHandle;
}
break;
default: return ui::EventResult::DidNotHandle;
}
if (should_add_tile) add_tile();
return ui::EventResult::DidHandle;
}
bool move_left()
{
Tile new_tiles[16];
bool changed = false;
for (int i = 0; i < 4; i++)
{
int pos = 0;
for (int j = 0; j < 4; j++)
{
if (tiles[i * 4 + j].number != 0)
{
new_tiles[i * 4 + pos] = tiles[i * 4 + j];
pos += 1;
changed = true;
}
}
}
memcpy(tiles, new_tiles, sizeof(tiles));
return changed;
}
void join_left()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 3; j++)
{
auto& from_tile = tiles[i * 4 + j];
auto& to_tile = tiles[i * 4 + j + 1];
if (from_tile.number != 0 && from_tile.number == to_tile.number)
{
from_tile.number *= 2;
from_tile.color += 1;
to_tile.number = 0;
}
}
}
}
bool move_right()
{
Tile new_tiles[16];
bool changed = false;
for (int i = 0; i < 4; i++)
{
int pos = 3;
for (int j = 0; j < 4; j++)
{
if (tiles[i * 4 + j].number != 0)
{
new_tiles[i * 4 + pos] = tiles[i * 4 + j];
pos -= 1;
changed = true;
}
}
}
memcpy(tiles, new_tiles, sizeof(tiles));
return changed;
}
void join_right()
{
for (int i = 0; i < 4; i++)
{
for (int j = 1; j < 4; j++)
{
auto& from_tile = tiles[i * 4 + j];
auto& to_tile = tiles[i * 4 + j - 1];
if (from_tile.number != 0 && from_tile.number == to_tile.number)
{
from_tile.number *= 2;
from_tile.color += 1;
to_tile.number = 0;
}
}
}
}
bool move_up()
{
Tile new_tiles[16];
bool changed = false;
for (int j = 0; j < 4; j++)
{
int pos = 0;
for (int i = 0; i < 4; i++)
{
if (tiles[i * 4 + j].number != 0)
{
new_tiles[pos * 4 + j] = tiles[i * 4 + j];
pos += 1;
changed = true;
}
}
}
memcpy(tiles, new_tiles, sizeof(tiles));
return changed;
}
void join_up()
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
auto& from_tile = tiles[i * 4 + j];
auto& to_tile = tiles[i * 4 + j + 4];
if (from_tile.number != 0 && from_tile.number == to_tile.number)
{
from_tile.number *= 2;
from_tile.color += 1;
to_tile.number = 0;
}
}
}
}
bool move_down()
{
Tile new_tiles[16];
bool changed = false;
for (int j = 0; j < 4; j++)
{
int pos = 3;
for (int i = 0; i < 4; i++)
{
if (tiles[i * 4 + j].number != 0)
{
new_tiles[pos * 4 + j] = tiles[i * 4 + j];
pos -= 1;
changed = true;
}
}
}
memcpy(tiles, new_tiles, sizeof(tiles));
return changed;
}
void join_down()
{
for (int i = 1; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
auto& from_tile = tiles[i * 4 + j];
auto& to_tile = tiles[i * 4 + j - 4];
if (from_tile.number != 0 && from_tile.number == to_tile.number)
{
from_tile.number *= 2;
from_tile.color += 1;
to_tile.number = 0;
}
}
}
}
void add_tile()
{
bool can_add_tile = false;
for (int i = 0; i < 16; i++)
{
if (tiles[i].number == 0)
{
can_add_tile = true;
break;
}
}
if (!can_add_tile)
{
reset();
return;
}
int start;
do {
start = rand() % 16;
} while (tiles[start].number != 0);
tiles[start].number = 2;
tiles[start].color = 0;
}
void reset()
{
for (int i = 0; i < 16; i++)
{
tiles[i].number = 0;
tiles[i].color = 0;
}
add_tile();
}
Tile tiles[16];
private:
Result<void> draw_tile(int index, ui::Canvas& canvas)
{
auto tile = tiles[index];
if (tile.number == 0)
{
canvas.fill(ui::GRAY);
return {};
}
canvas.fill(colors[tile.color]);
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 },
{ 0, 0, (int)fmt.length() * font->width(), font->height() },
ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
auto subcanvas = canvas.subcanvas(rect);
Utf8StringDecoder decoder(fmt.chars());
wchar_t buf[4096];
TRY(decoder.decode(buf, sizeof(buf)));
font->render(buf, ui::BLACK, subcanvas);
return {};
}
};
Result<int> luna_main(int, char**)
{
srand((unsigned)time(NULL));
ui::App app;
TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 400 }));
app.set_main_window(window);
window->set_background(ui::BLACK);
window->set_title("2048");
GameWidget game;
window->set_main_widget(game);
game.reset();
return app.run();
}

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)

47
gui/apps/about.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <luna/String.h>
#include <sys/utsname.h>
#include <ui/App.h>
#include <ui/Button.h>
#include <ui/Label.h>
#include <ui/Layout.h>
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89);
Result<int> luna_main(int, char**)
{
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("About");
window->set_background(BACKGROUND_COLOR);
utsname info;
uname(&info);
ui::VerticalLayout main_layout;
window->set_main_widget(main_layout);
ui::Label title("About Luna");
title.set_font(ui::Font::default_bold_font());
main_layout.add_widget(title);
ui::VerticalLayout version_info;
main_layout.add_widget(version_info);
ui::Label license("Licensed under the BSD-2-Clause license.");
main_layout.add_widget(license);
String os_release_text = TRY(String::format("OS release: %s"_sv, info.release));
ui::Label os_release(os_release_text.view());
version_info.add_widget(os_release);
String kernel_version_text = TRY(String::format("Kernel version: %s"_sv, info.version));
ui::Label kernel_version(kernel_version_text.view());
version_info.add_widget(kernel_version);
return app.run();
}

43
gui/apps/clock.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <os/Timer.h>
#include <time.h>
#include <ui/App.h>
#include <ui/Label.h>
ui::Label* g_label;
void update_time()
{
time_t t = time(NULL);
struct tm* tp = localtime(&t);
static char buf[2048];
strftime(buf, sizeof(buf), "%H:%M:%S", tp);
g_label->set_text(StringView { buf });
ui::App::the().main_window()->draw();
}
Result<int> luna_main(int, char**)
{
ui::App app;
TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 500, 400, 100, 50 }));
app.set_main_window(window);
window->set_title("Clock");
window->set_background(ui::GRAY);
g_label = TRY(make<ui::Label>("00:00:00"));
g_label->set_font(ui::Font::default_bold_font());
g_label->set_color(ui::BLACK);
window->set_main_widget(*g_label);
update_time();
auto timer = TRY(os::Timer::create_repeating(1000, update_time));
return app.run();
}

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,259 @@
/**
* @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/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)
{
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))
{
auto message = TRY(RefString::format("%s is not a regular file", path.name().chars()));
ui::Dialog::show_message("Error", message.view());
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 = TRY(String::from_string_view(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());
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_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())
{
TRY(save_file_as());
return {};
}
auto file = TRY(os::File::open_or_create(m_path.view(), 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,50 @@
/**
* @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<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()
{
return m_path.view();
}
private:
SharedPtr<ui::Font> m_font;
struct Line
{
usize begin;
usize end;
};
Vector<Line> m_lines;
String m_path;
Result<void> recalculate_lines();
void recalculate_cursor_position();
void recalculate_cursor_index();
};

53
gui/apps/editor/main.cpp Normal file
View File

@ -0,0 +1,53 @@
/**
* @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>
#include <ui/Dialog.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()) 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("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();
}

127
gui/apps/gol.cpp Normal file
View File

@ -0,0 +1,127 @@
#include <assert.h>
#include <fcntl.h>
#include <luna/Heap.h>
#include <os/ArgumentParser.h>
#include <os/Timer.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ui/App.h>
#include <ui/Window.h>
#include <unistd.h>
struct Cell
{
bool state;
bool new_state;
};
static int g_num_rows = 40;
static int g_num_columns = 60;
static Cell* g_cells;
static ui::Window* g_window;
static Result<void> fill_cells()
{
g_cells = (Cell*)TRY(calloc_impl(g_num_rows, g_num_columns * sizeof(Cell), false));
for (isize i = 0; i < (g_num_rows * g_num_columns); i++)
{
auto value = rand() % 2;
g_cells[i].state = g_cells[i].new_state = value;
}
return {};
}
static Cell& find_cell(int row, int column)
{
assert(row < g_num_rows);
assert(column < g_num_columns);
return g_cells[row * g_num_columns + column];
}
static constexpr int BYTES_PER_PIXEL = sizeof(u32);
static constexpr ui::Color activated_cell_color = ui::CYAN;
static constexpr ui::Color deactivated_cell_color = ui::Color::from_rgb(40, 40, 40);
static void draw_cells()
{
const int CELL_WIDTH = g_window->canvas().width / g_num_columns;
const int CELL_HEIGHT = g_window->canvas().height / g_num_rows;
auto canvas = g_window->canvas();
for (int i = 0; i < g_num_rows; i++)
{
for (int j = 0; j < g_num_columns; j++)
{
auto subcanvas = canvas.subcanvas(ui::Rect { j * CELL_WIDTH, i * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT });
auto& cell = find_cell(i, j);
ui::Color color = cell.state ? activated_cell_color : deactivated_cell_color;
subcanvas.fill(color);
}
}
g_window->update();
}
static int find_neighbors(int row, int column)
{
int sum = 0;
if (row > 0 && column > 0) sum += find_cell(row - 1, column - 1).state;
if (row > 0) sum += find_cell(row - 1, column).state;
if (row > 0 && (column + 1) < g_num_columns) sum += find_cell(row - 1, column + 1).state;
if (column > 0) sum += find_cell(row, column - 1).state;
if ((column + 1) < g_num_columns) sum += find_cell(row, column + 1).state;
if ((row + 1) < g_num_rows && column > 0) sum += find_cell(row + 1, column - 1).state;
if ((row + 1) < g_num_rows) sum += find_cell(row + 1, column).state;
if ((row + 1) < g_num_rows && (column + 1) < g_num_columns) sum += find_cell(row + 1, column + 1).state;
return sum;
}
static void next_generation()
{
for (int i = 0; i < g_num_rows; i++)
{
for (int j = 0; j < g_num_columns; j++)
{
auto& cell = find_cell(i, j);
int neighbors = find_neighbors(i, j);
if (!cell.state && neighbors == 3) cell.new_state = true;
else if (cell.state && (neighbors < 2 || neighbors > 3))
cell.new_state = false;
}
}
for (isize i = 0; i < (g_num_rows * g_num_columns); i++) g_cells[i].state = g_cells[i].new_state;
}
static void update()
{
next_generation();
draw_cells();
}
Result<int> luna_main(int, char**)
{
ui::App app;
TRY(app.init());
g_window = TRY(ui::Window::create(ui::Rect { 200, 200, 600, 400 }));
g_window->set_title("Game of Life");
app.set_main_window(g_window);
TRY(fill_cells());
update();
auto timer = TRY(os::Timer::create_repeating(100, update));
return app.run();
}

152
gui/apps/taskbar.cpp Normal file
View File

@ -0,0 +1,152 @@
#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 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"));
app.pledge(ui::Pledge::ExtendedLayers);
TRY(os::EventLoop::the().register_signal_handler(SIGQUIT, sigquit_handler));
launcher_client = TRY(os::IPC::Client::connect("/tmp/execd.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_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);
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

@ -0,0 +1,12 @@
set(SOURCES
main.cpp
TerminalWidget.h
TerminalWidget.cpp
)
add_executable(terminal ${SOURCES})
target_compile_options(terminal PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
add_dependencies(terminal libc)
target_include_directories(terminal PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(terminal PRIVATE os ui)
install(TARGETS terminal DESTINATION ${LUNA_BASE}/usr/bin)

View File

@ -0,0 +1,460 @@
#include "TerminalWidget.h"
#include <ctype.h>
#include <errno.h>
#include <luna/CType.h>
#include <os/File.h>
#include <os/Process.h>
#include <pty.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <time.h>
#include <ui/App.h>
#include <unistd.h>
static constexpr auto RED = ui::Color::from_u32(0xffcd0000);
static constexpr auto GREEN = ui::Color::from_u32(0xff00cd00);
static constexpr auto YELLOW = ui::Color::from_u32(0xffcdcd00);
static constexpr auto BLUE = ui::Color::from_u32(0xff0000ee);
static constexpr auto MAGENTA = ui::Color::from_u32(0xffcd00cd);
static constexpr auto CYAN = ui::Color::from_u32(0xff00cdcd);
static constexpr auto GRAY = ui::Color::from_u32(0xffe5e5e5);
static constexpr auto BRIGHT_BLACK = ui::Color::from_u32(0xff7f7f7f);
static constexpr auto BRIGHT_RED = ui::Color::from_u32(0xffff0000);
static constexpr auto BRIGHT_GREEN = ui::Color::from_u32(0xff00ff00);
static constexpr auto BRIGHT_YELLOW = ui::Color::from_u32(0xffffff00);
static constexpr auto BRIGHT_BLUE = ui::Color::from_u32(0xff5c5cff);
static constexpr auto BRIGHT_MAGENTA = ui::Color::from_u32(0xffff00ff);
static constexpr auto BRIGHT_CYAN = ui::Color::from_u32(0xff00ffff);
static constexpr auto BRIGHT_GRAY = ui::Color::from_u32(0xffffffff);
static void sigchld_handler(int)
{
wait(NULL);
ui::App::the().set_should_close(true);
}
Result<void> TerminalWidget::init(char* const* args)
{
m_font = ui::Font::default_font();
m_bold_font = ui::Font::default_bold_font();
m_terminal_canvas = window()->canvas();
m_terminal_canvas.fill(ui::BLACK);
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));
signal(SIGCHLD, sigchld_handler);
int master;
pid_t child = forkpty(&master, nullptr, nullptr, nullptr);
if (child < 0) return err(errno);
if (child == 0)
{
execv(args[0], args);
_exit(127);
}
m_pty = master;
os::EventLoop::the().register_fd_listener(m_pty, [this](int, int) { this->process(); });
m_child_pid = child;
return {};
}
Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventRequest& request)
{
// Avoid handling "key released" events
if (!request.pressed) return ui::EventResult::DidNotHandle;
// Non-printable key or key that has no special character (unlike Tab or Enter). We exit early to avoid inserting an
// invalid zero byte into the terminal input (this would also happen on Shift or Ctrl keypresses).
if (request.letter == '\0') return ui::EventResult::DidNotHandle;
write(m_pty, &request.letter, 1);
return ui::EventResult::DidHandle;
}
Result<void> TerminalWidget::draw(ui::Canvas&)
{
return {};
}
Result<void> TerminalWidget::process()
{
char buffer[BUFSIZ];
ssize_t nread = read(m_pty, buffer, BUFSIZ);
if (nread < 0)
{
if (errno == EAGAIN) nread = 0;
else
return err(errno);
}
ssize_t drawn = 0;
for (ssize_t i = 0; i < nread; i++)
{
bool did_draw = TRY(putchar(buffer[i]));
if (did_draw) drawn++;
}
if (drawn > 0) window()->draw();
return {};
}
void TerminalWidget::tick_cursor()
{
if (!m_cursor_enabled) return;
m_cursor_activated = !m_cursor_activated;
if (m_cursor_activated) draw_cursor();
else
erase_current_char();
window()->draw();
}
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)
{
auto subcanvas = m_terminal_canvas.subcanvas({ x, y, m_font->width(), m_font->height() });
subcanvas.fill(m_background_color);
(m_bold ? m_bold_font : m_font)->render(c, m_foreground_color, subcanvas);
}
void TerminalWidget::erase_current_line()
{
m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }).fill(ui::BLACK);
}
void TerminalWidget::scroll()
{
memcpy(m_terminal_canvas.ptr, m_terminal_canvas.ptr + (m_rect.width * sizeof(u32) * m_font->height()),
(m_rect.width * m_rect.height * sizeof(u32)) - (m_rect.width * sizeof(u32) * m_font->height()));
m_y_position -= m_font->height();
erase_current_line();
}
bool TerminalWidget::should_scroll()
{
return m_y_position >= m_rect.height;
}
void TerminalWidget::next_line()
{
m_x_position = 0;
m_y_position += m_font->height();
}
void TerminalWidget::next_char()
{
m_x_position += m_font->width();
}
void TerminalWidget::prev_char()
{
m_x_position -= m_font->width();
}
void TerminalWidget::erase_current_char()
{
m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::BLACK);
}
void TerminalWidget::draw_cursor()
{
m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::WHITE);
}
bool TerminalWidget::at_end_of_screen()
{
return (m_x_position + m_font->width()) > m_rect.width;
}
bool TerminalWidget::handle_escape_sequence(wchar_t c)
{
auto rc = m_escape_parser->advance(static_cast<u8>(c));
if (rc.has_error())
{
m_escape_parser = Option<EscapeSequenceParser> {};
return false;
}
if (!rc.value()) return true;
if (!m_escape_parser->valid())
{
m_escape_parser = Option<EscapeSequenceParser> {};
return false;
}
const auto& params = m_escape_parser->parameters();
switch (m_escape_parser->code())
{
case EscapeCode::CursorUp: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * m_font->height();
if (pixels > m_y_position) m_y_position = 0;
else
m_y_position -= pixels;
};
break;
case EscapeCode::CursorDown: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * m_font->height();
if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height();
else
m_y_position += pixels;
};
break;
case EscapeCode::CursorBack: {
int chars = params.size() ? params[0] : 1;
int pixels = chars * m_font->width();
if (pixels > m_x_position) m_x_position = 0;
else
m_x_position -= pixels;
};
break;
case EscapeCode::CursorForward: {
int chars = params.size() ? params[0] : 1;
int pixels = chars * m_font->width();
if (pixels + m_x_position >= m_rect.width) m_x_position = m_rect.width - m_font->width();
else
m_x_position += pixels;
};
break;
case EscapeCode::CursorNextLine: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * m_font->height();
if (pixels > m_y_position) m_y_position = 0;
else
m_y_position -= pixels;
m_x_position = 0;
};
break;
case EscapeCode::CursorPreviousLine: {
int lines = params.size() ? params[0] : 1;
int pixels = lines * m_font->height();
if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height();
else
m_y_position += pixels;
m_x_position = 0;
};
break;
case EscapeCode::CursorHorizontalAbsolute: {
int line = (params.size() ? params[0] : 1) - 1;
if (line < 0) break;
int position = line * m_font->height();
if (position >= m_rect.height) position = m_rect.height - m_font->height();
m_y_position = position;
};
break;
case EscapeCode::SetCursorPosition: {
int x = (params.size() ? params[0] : 1) - 1;
int y = (params.size() > 1 ? params[1] : 1) - 1;
if (x < 0 || y < 0) break;
int x_position = x * m_font->width();
if (x_position >= m_rect.width) x_position = m_rect.width - m_font->height();
m_x_position = x_position;
int y_position = y * m_font->height();
if (y_position >= m_rect.height) y_position = m_rect.height - m_font->height();
m_y_position = y_position;
};
break;
case EscapeCode::SelectGraphicRendition: {
if (!params.size())
{
m_foreground_color = ui::WHITE;
m_background_color = ui::BLACK;
m_bold = false;
break;
}
for (usize i = 0; i < params.size(); i++)
{
int arg = params[i];
switch (arg)
{
case 0: {
m_foreground_color = ui::BLACK;
m_background_color = ui::WHITE;
m_bold = false;
break;
}
case 1: {
m_bold = true;
break;
}
case 22: {
m_bold = false;
break;
}
case 30: {
m_foreground_color = m_bold ? BRIGHT_BLACK : ui::BLACK;
break;
}
case 31: {
m_foreground_color = m_bold ? BRIGHT_RED : RED;
break;
}
case 32: {
m_foreground_color = m_bold ? BRIGHT_GREEN : GREEN;
break;
}
case 33: {
m_foreground_color = m_bold ? BRIGHT_YELLOW : YELLOW;
break;
}
case 34: {
m_foreground_color = m_bold ? BRIGHT_BLUE : BLUE;
break;
}
case 35: {
m_foreground_color = m_bold ? BRIGHT_MAGENTA : MAGENTA;
break;
}
case 36: {
m_foreground_color = m_bold ? BRIGHT_CYAN : CYAN;
break;
}
case 37: {
m_foreground_color = m_bold ? BRIGHT_GRAY : GRAY;
break;
}
case 39: {
m_foreground_color = ui::WHITE;
break;
}
case 40: {
m_background_color = m_bold ? BRIGHT_BLACK : ui::BLACK;
break;
}
case 41: {
m_background_color = m_bold ? BRIGHT_RED : RED;
break;
}
case 42: {
m_background_color = m_bold ? BRIGHT_GREEN : GREEN;
break;
}
case 43: {
m_background_color = m_bold ? BRIGHT_YELLOW : YELLOW;
break;
}
case 44: {
m_background_color = m_bold ? BRIGHT_BLUE : BLUE;
break;
}
case 45: {
m_background_color = m_bold ? BRIGHT_MAGENTA : MAGENTA;
break;
}
case 46: {
m_background_color = m_bold ? BRIGHT_CYAN : CYAN;
break;
}
case 47: {
m_background_color = m_bold ? BRIGHT_GRAY : GRAY;
break;
}
case 49: {
m_background_color = ui::BLACK;
break;
}
default: break;
}
}
}
break;
default: break;
}
m_escape_parser = Option<EscapeSequenceParser> {};
return true;
}
Result<bool> TerminalWidget::putchar(char c)
{
auto guard = make_scope_guard([this] { m_decoder.reset(); });
bool is_ready = TRY(m_decoder.feed(c));
bool result = false;
if (is_ready) result = put_code_point(TRY(m_decoder.extract()));
guard.deactivate();
return result;
}
bool TerminalWidget::put_code_point(wchar_t c)
{
if (c > (wchar_t)255) c = (wchar_t)256;
if (m_escape_parser.has_value())
{
if (handle_escape_sequence(c)) return false;
}
// Erase the current cursor.
if (m_cursor_activated) erase_current_char();
bool should_draw_cursor = m_cursor_enabled;
bool did_draw = false;
switch (c)
{
case L'\n': {
next_line();
if (should_scroll()) scroll();
break;
}
case L'\t': {
for (int i = 0; i < 4; i++) { put_code_point(L' '); }
did_draw = true;
break;
}
case L'\r': m_x_position = 0; break;
case L'\b':
if (m_x_position != 0)
{
prev_char();
erase_current_char();
did_draw = true;
}
break;
case L'\x1b':
case L'\x9b':
case L'\x90':
case L'\x9d':
m_escape_parser = EscapeSequenceParser { (u8)c };
should_draw_cursor = false;
did_draw = true;
break;
default: {
if (iscntrl(c)) return false;
draw_glyph(c, m_x_position, m_y_position);
next_char();
if (at_end_of_screen())
{
next_line();
if (should_scroll()) scroll();
}
break;
}
}
if (should_draw_cursor)
{
m_cursor_timer->restart();
m_cursor_activated = true;
draw_cursor();
}
return did_draw;
}

View File

@ -0,0 +1,64 @@
#pragma once
#include <luna/EscapeSequence.h>
#include <luna/Utf8.h>
#include <luna/Vector.h>
#include <os/Timer.h>
#include <stdio.h>
#include <termios.h>
#include <ui/Font.h>
#include <ui/Widget.h>
class TerminalWidget : public ui::Widget
{
public:
Result<void> init(char* const* args);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(ui::Canvas& canvas) override;
private:
ui::Canvas m_terminal_canvas;
Vector<u8> m_line_buffer;
int m_pty;
pid_t m_child_pid;
struct termios m_settings;
SharedPtr<ui::Font> m_font;
SharedPtr<ui::Font> m_bold_font;
OwnedPtr<os::Timer> m_cursor_timer;
bool m_cursor_activated = false;
bool m_cursor_enabled = true;
long m_last_cursor_tick;
int m_x_position { 0 };
int m_y_position { 0 };
bool m_bold { false };
ui::Color m_foreground_color { ui::WHITE };
ui::Color m_background_color { ui::BLACK };
void tick_cursor();
Utf8StateDecoder m_decoder;
Option<EscapeSequenceParser> m_escape_parser;
void draw_glyph(wchar_t c, int x, int y);
void erase_current_line();
void scroll();
bool should_scroll();
void next_line();
void next_char();
void prev_char();
void erase_current_char();
void draw_cursor();
bool at_end_of_screen();
bool handle_escape_sequence(wchar_t c);
Result<bool> putchar(char c);
bool put_code_point(wchar_t c);
Result<void> process();
};

View File

@ -0,0 +1,24 @@
#include "TerminalWidget.h"
#include <os/ArgumentParser.h>
#include <ui/App.h>
#include <unistd.h>
Result<int> luna_main(int, char**)
{
ui::App app;
TRY(app.init());
auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 }));
app.set_main_window(window);
window->set_title("Terminal");
TerminalWidget terminal;
window->set_main_widget(terminal);
char* args[] = { "/bin/sh", nullptr };
TRY(terminal.init(args));
window->draw();
return app.run();
}

109
gui/execd.cpp Normal file
View File

@ -0,0 +1,109 @@
/**
* @file execd.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("execd: Invalid IPC message from client!"); return;
}
}
void sigchld_handler(int)
{
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/execd.sock";
os::ArgumentParser parser;
parser.add_description("Background process that handles detached launching of apps."_sv);
parser.add_system_program_info("execd"_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("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)));
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("execd: Client %zu disconnected", i);
fds.remove_at(i + 1);
auto client = clients.remove_at(i);
client->disconnect();
}
}
}
}

34
gui/libui/CMakeLists.txt Normal file
View File

@ -0,0 +1,34 @@
# The UI and graphics library for Luna.
file(GLOB HEADERS include/ui/*.h)
set(SOURCES
${HEADERS}
include/ui/ipc/Server.h
include/ui/ipc/Client.h
src/Canvas.cpp
src/Rect.cpp
src/Font.cpp
src/Image.cpp
src/App.cpp
src/Window.cpp
src/Layout.cpp
src/Alignment.cpp
src/Container.cpp
src/Button.cpp
src/Label.cpp
src/InputField.cpp
src/TextInput.cpp
src/Dialog.cpp
)
add_library(ui ${SOURCES})
target_compile_options(ui PRIVATE ${COMMON_FLAGS} -fno-threadsafe-statics)
target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/)
target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include)
target_link_libraries(ui PUBLIC os)
add_custom_command(
TARGET ui
COMMAND "${CMAKE_COMMAND}" -E copy ${CMAKE_CURRENT_BINARY_DIR}/libui.a ${LUNA_BASE}/usr/lib/libui.a
)

View File

@ -0,0 +1,30 @@
/**
* @file Alignment.h
* @author apio (cloudapio.eu)
* @brief UI component alignment.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Rect.h>
namespace ui
{
enum class VerticalAlignment
{
Top,
Center,
Bottom
};
enum class HorizontalAlignment
{
Left,
Center,
Right
};
Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign);
}

View File

@ -0,0 +1,76 @@
/**
* @file App.h
* @author apio (cloudapio.eu)
* @brief UI application event loop.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/HashMap.h>
#include <luna/StringView.h>
#include <os/EventLoop.h>
#include <os/IPC.h>
#include <ui/Window.h>
namespace ui
{
class App
{
public:
App();
~App();
Result<void> init(StringView socket_path = "/tmp/wind.sock");
Result<int> run();
Rect screen_rect();
os::IPC::Client& client()
{
return *m_client;
}
void set_should_close(bool b)
{
m_should_close = b;
if (b) m_loop.quit();
}
void set_main_window(Window* window)
{
check(!m_main_window);
m_main_window = window;
}
Window* main_window()
{
return m_main_window;
}
void pledge(i16 pledges);
Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>);
void unregister_window(Window* window, Badge<Window>);
static App& the();
private:
static App* s_app;
OwnedPtr<os::IPC::Client> m_client;
Window* m_main_window { nullptr };
HashMap<int, OwnedPtr<Window>> m_windows;
bool m_should_close { false };
os::EventLoop m_loop;
Vector<int> m_window_clear_queue;
bool process_events();
Window* find_window(int id);
Result<void> handle_ipc_event(os::IPC::Client&, u8 id, void*);
friend void handle_socket_event(int, int);
};
}

View File

@ -0,0 +1,37 @@
/**
* @file Button.h
* @author apio (cloudapio.eu)
* @brief A clickable component that triggers an action when pressed.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Action.h>
#include <ui/Widget.h>
namespace ui
{
class Button : public Widget
{
public:
Button(Rect rect);
void set_widget(Widget& widget);
void set_action(Action&& action);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(Canvas& canvas) override;
private:
bool m_hovered { false };
bool m_clicked { false };
Widget* m_child;
Action m_action;
};
}

View File

@ -0,0 +1,81 @@
/**
* @file Canvas.h
* @author apio (cloudapio.eu)
* @brief Drawable surfaces.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Result.h>
#include <luna/Types.h>
#include <ui/Color.h>
#include <ui/Point.h>
#include <ui/Rect.h>
namespace ui
{
/**
* @brief A drawable surface.
*/
struct Canvas
{
int width;
int height;
int stride;
u8* ptr;
/**
* @brief Create a new Canvas object.
*
* @param ptr The memory to use for the canvas. It must be of at least width * height * 4 bytes of length.
* @param width The width of the canvas.
* @param height The height of the canvas.
* @return Canvas The new Canvas object.
*/
static Canvas create(u8* ptr, int width, int height);
/**
* @brief Return a new Canvas that represents a subsection of the current one.
*
* @param rect The dimensions of the new canvas. If these exceed the bounds of the current canvas, they will be
* clamped.
* @return Canvas The new Canvas object.
*/
Canvas subcanvas(Rect rect);
/**
* @brief Return the dimensions of the current canvas.
*
* @return Rect This canvas's dimensions, as a Rect object.
*/
Rect rect()
{
return Rect { .pos = { 0, 0 }, .width = width, .height = height };
}
/**
* @brief Fill the entire canvas with one color.
*
* @param color The color to use.
*/
void fill(Color color);
/**
* @brief Fill the canvas with pixels.
*
* @param pixels The array of pixels (must be at least width*height).
* @param stride The number of pixels to skip to go to the next line.
*/
void fill(u32* pixels, int stride);
/**
* @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,113 @@
/**
* @file Color.h
* @author apio (cloudapio.eu)
* @brief RGBA colors.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Types.h>
namespace ui
{
/**
* @brief A 32-bit ARGB color.
*/
struct Color
{
union {
u32 raw;
u8 colors[4];
};
/**
* @brief Return the blue value of this color.
*
* @return constexpr u8 The blue value.
*/
constexpr u8 red() const
{
return colors[2];
}
/**
* @brief Return the green value of this color.
*
* @return constexpr u8 The green value.
*/
constexpr u8 green() const
{
return colors[1];
}
/**
* @brief Return the blue value of this color.
*
* @return constexpr u8 The blue value.
*/
constexpr u8 blue() const
{
return colors[0];
}
/**
* @brief Return the alpha value of this color.
*
* @return constexpr u8 The alpha value.
*/
constexpr u8 alpha() const
{
return colors[3];
}
/**
* @brief Construct a new color from a 32-bit ARGB integer.
*
* @param raw The integer representing the color.
* @return constexpr Color The new color.
*/
static constexpr Color from_u32(u32 raw)
{
return Color { .raw = raw };
}
/**
* @brief Construct a new color from its separate RGBA values (from 0 to 255).
*
* @param red The red value.
* @param green The green value.
* @param blue The blue value.
* @param alpha The alpha value.
* @return constexpr Color The new color.
*/
static constexpr Color from_rgba(u8 red, u8 green, u8 blue, u8 alpha)
{
return Color { .colors = { blue, green, red, alpha } };
}
/**
* @brief Construct a new color from its separate RGB values (from 0 to 255).
*
* @param red The red value.
* @param green The green value.
* @param blue The blue value.
* @return constexpr Color The new color.
*/
static constexpr Color from_rgb(u8 red, u8 green, u8 blue)
{
return from_rgba(red, green, blue, 0xff);
}
};
static constexpr Color WHITE = Color::from_rgb(0xff, 0xff, 0xff);
static constexpr Color BLACK = Color::from_rgb(0x00, 0x00, 0x00);
static constexpr Color GRAY = Color::from_rgb(0x80, 0x80, 0x80);
static constexpr Color BLUE = Color::from_rgb(0x00, 0x00, 0xff);
static constexpr Color GREEN = Color::from_rgb(0x00, 0xff, 0x00);
static constexpr Color RED = Color::from_rgb(0xff, 0x00, 0x00);
static constexpr Color CYAN = Color::from_rgb(0x00, 0xff, 0xff);
};

View File

@ -0,0 +1,35 @@
/**
* @file Container.h
* @author apio (cloudapio.eu)
* @brief A container widget to pad and align objects inside it.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Alignment.h>
#include <ui/Widget.h>
namespace ui
{
class Container : public Widget
{
public:
Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign);
void set_widget(Widget& widget);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(Canvas& canvas) override;
private:
Widget* m_widget;
VerticalAlignment m_valign;
HorizontalAlignment m_halign;
};
}

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

123
gui/libui/include/ui/Font.h Normal file
View File

@ -0,0 +1,123 @@
/**
* @file Font.h
* @author apio (cloudapio.eu)
* @brief PSF font loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.h>
#include <ui/Canvas.h>
#define PSF_FONT_MAGIC 0x864ab572
namespace ui
{
/**
* @brief A class holding PSF font data, used for low-level direct rendering of glyphs into a canvas.
*
* This class does not handle special characters such as tabs or newlines. For those, you should be using a more
* high-level component such as ui::Label instead.
*/
class Font : public Shareable
{
public:
/**
* @brief An enum used to select a font weight when loading a font.
*/
enum FontWeight
{
Regular,
Bold,
};
/**
* @brief Load a Font object from a font file.
*
* @param path The full path to the font file.
* @return Result<SharedPtr<Font>> An error, or the loaded Font object.
*/
static Result<SharedPtr<Font>> load(const os::Path& path);
/**
* @brief Load a system font by name.
*
* @param name The name of the font to load (the default system font is "Tamsyn").
* @param weight The weight of the font (regular or bold).
* @return Result<SharedPtr<Font>> An error, or the loaded Font object.
*/
static Result<SharedPtr<Font>> load_builtin(StringView name, FontWeight weight);
/**
* @brief Return a pointer to the system's default font.
*
* @return SharedPtr<Font> The default font.
*/
static SharedPtr<Font> default_font();
/**
* @brief Return a pointer to the system's default bold font.
*
* @return SharedPtr<Font> The default bold font.
*/
static SharedPtr<Font> default_bold_font();
/**
* @brief Render a single Unicode code point onto a canvas, using this font's glyphs.
*
* @param codepoint The code point to render.
* @param color The color to draw the code point in.
* @param canvas The canvas to use.
*/
void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas);
/**
* @brief Render a Unicode text string onto a canvas, using this font's glyphs.
*
* @param text The string to render (must be null-terminated).
* @param color The color to draw the code point in.
* @param canvas The canvas to use.
*/
void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas);
/**
* @brief Return the width of this font's glyphs.
*
* @return int The width.
*/
int width() const
{
return m_psf_header.width;
}
/**
* @brief Return the height of this font's glyphs.
*
* @return int The height.
*/
int height() const
{
return m_psf_header.height;
}
private:
struct PSFHeader
{
u32 magic;
u32 version; // zero
u32 headersize;
u32 flags; // 0 if there's no unicode table
u32 numglyph;
u32 bytesperglyph;
int height;
int width;
};
PSFHeader m_psf_header;
Buffer m_font_data;
};
};

View File

@ -0,0 +1,92 @@
/**
* @file Image.h
* @author apio (cloudapio.eu)
* @brief TGA image loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.h>
#include <ui/Widget.h>
namespace ui
{
/**
* @brief An image in the TGA file format.
*/
class Image : public Shareable
{
public:
/**
* @brief Load a new TGA image from a file.
*
* @param path The path to open.
* @return Result<SharedPtr<Image>> An error, or a new Image object.
*/
static Result<SharedPtr<Image>> load(const os::Path& path);
/**
* @brief Return the array of pixels contained in the image.
*
* @return u32* The array of pixels.
*/
u32* pixels()
{
return (u32*)m_image_data.data();
}
/**
* @brief Return the width of the image.
*
* @return u16 The width.
*/
u16 width()
{
return m_tga_header.w;
}
/**
* @brief Return the height of the image.
*
* @return u16 The height.
*/
u16 height()
{
return m_tga_header.h;
}
private:
struct [[gnu::packed]] TGAHeader
{
u8 idlen;
u8 colormap;
u8 encoding;
u16 cmaporig, cmaplen;
u8 cmapent;
u16 x;
u16 y;
u16 w;
u16 h;
u8 bpp;
u8 pixeltype;
};
TGAHeader m_tga_header;
Buffer m_image_data;
};
class ImageWidget final : public Widget
{
public:
static Result<OwnedPtr<ImageWidget>> load(const os::Path& path);
Result<void> draw(Canvas& canvas) override;
private:
SharedPtr<Image> m_image;
};
}

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 <luna/Action.h>
#include <ui/Font.h>
#include <ui/TextInput.h>
namespace ui
{
class InputField final : 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(Function<StringView>&& action)
{
m_on_submit_action = move(action);
m_has_on_submit_action = true;
}
private:
SharedPtr<ui::Font> m_font;
Function<StringView> m_on_submit_action;
bool m_has_on_submit_action { false };
};
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <moon/Keyboard.h>
namespace ui
{
enum Modifier
{
Mod_Shift = (1 << 0),
Mod_Alt = (1 << 1),
Mod_Super = (1 << 2),
Mod_AltGr = (1 << 3),
Mod_Ctrl = (1 << 4)
};
}

View File

@ -0,0 +1,57 @@
/**
* @file Label.h
* @author apio (cloudapio.eu)
* @brief A simple one-line text widget.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Alignment.h>
#include <ui/Font.h>
#include <ui/Widget.h>
namespace ui
{
/**
* @brief Displays one line of text.
*
* This component does not handle newlines.
*/
class Label final : public Widget
{
public:
Label(StringView text);
void set_alignment(VerticalAlignment valign, HorizontalAlignment halign)
{
m_valign = valign;
m_halign = halign;
}
void set_color(ui::Color color)
{
m_color = color;
}
void set_font(SharedPtr<ui::Font> font)
{
m_font = font;
}
void set_text(StringView text)
{
m_text = text;
}
Result<void> draw(Canvas& canvas) override;
private:
StringView m_text;
VerticalAlignment m_valign = VerticalAlignment::Center;
HorizontalAlignment m_halign = HorizontalAlignment::Center;
ui::Color m_color = ui::WHITE;
SharedPtr<Font> m_font;
};
}

View File

@ -0,0 +1,83 @@
/**
* @file Layout.h
* @author apio (cloudapio.eu)
* @brief Layout widgets to organize content.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Vector.h>
#include <ui/Widget.h>
namespace ui
{
enum class AdjustHeight
{
No,
Yes
};
enum class AdjustWidth
{
No,
Yes
};
struct Margins
{
int left;
int right;
int top;
int bottom;
};
class HorizontalLayout final : public Widget
{
public:
HorizontalLayout(Margins margins = Margins { 0, 0, 0, 0 }, AdjustHeight adjust_height = AdjustHeight::Yes,
AdjustWidth adjust_width = AdjustWidth::Yes);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(Canvas& canvas) override;
Result<void> add_widget(Widget& widget);
private:
Vector<Widget*> m_widgets;
Margins m_margins;
AdjustHeight m_adjust_height;
AdjustWidth m_adjust_width;
int m_used_width { 0 };
};
class VerticalLayout final : public Widget
{
public:
VerticalLayout(Margins margins = Margins { 0, 0, 0, 0 }, AdjustHeight adjust_height = AdjustHeight::Yes,
AdjustWidth adjust_width = AdjustWidth::Yes);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave() override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(Canvas& canvas) override;
Result<void> add_widget(Widget& widget);
private:
Vector<Widget*> m_widgets;
Margins m_margins;
AdjustHeight m_adjust_height;
AdjustWidth m_adjust_width;
int m_used_height { 0 };
};
}

View File

@ -0,0 +1,21 @@
/**
* @file Mouse.h
* @author apio (cloudapio.eu)
* @brief Mouse buttons.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <moon/Mouse.h>
namespace ui
{
enum MouseButtons
{
LEFT = moon::Left,
MIDDLE = moon::Middle,
RIGHT = moon::Right,
};
}

View File

@ -0,0 +1,22 @@
/**
* @file Point.h
* @author apio (cloudapio.eu)
* @brief 2D space points.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
namespace ui
{
/**
* @brief A point in 2D space.
*/
struct Point
{
int x { 0 };
int y { 0 };
};
}

View File

@ -0,0 +1,81 @@
/**
* @file Rect.h
* @author apio (cloudapio.eu)
* @brief A simple 2D rectangle representation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Point.h>
namespace ui
{
/**
* @brief A simple rectangle.
*/
struct Rect
{
Point pos;
int width;
int height;
/**
* @brief Check if a point is contained in this rectangle.
*
* @param point The point to check.
* @return true The point is contained inside the rectangle.
* @return false The point is not contained inside the rectangle.
*/
bool contains(Point point);
/**
* @brief Check if another rectangle is contained in this one.
*
* @param point The rectangle to check.
* @return true The other rectangle is contained inside this one.
* @return false The other rectangle is not contained inside this one.
*/
bool contains(Rect rect);
/**
* @brief Normalize a point to fit inside this rectangle.
*
* @param point The original point.
* @return Point The normalized point.
*/
Point normalize(Point point);
/**
* @brief Transform an absolute position to a position relative to this rectangle.
*
* @param pos The original absolute position.
* @return Point The position relative to this rectangle.
*/
Point relative(Point pos);
/**
* @brief Transform a position relative to this rectangle to an absolute position.
*
* @param pos The original relative position.
* @return Point The absolute position.
*/
Point absolute(Point pos);
/**
* @brief Transform another rectangle relative to this one to an absolute rectangle.
*
* @param rect The original relative rectangle.
* @return Point The absolute rectangle.
*/
Rect absolute(Rect rect);
/**
* @brief Return a copy of this rectangle with no negative values (normalized to 0).
*
* @return Rect The new rectangle.
*/
Rect normalized();
};
}

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

@ -0,0 +1,94 @@
/**
* @file Widget.h
* @author apio (cloudapio.eu)
* @brief Abstract widget class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Ignore.h>
#include <luna/Result.h>
#include <ui/Canvas.h>
#include <ui/Point.h>
#include <ui/Rect.h>
#include <ui/ipc/Client.h>
namespace ui
{
class Window;
enum class EventResult
{
DidHandle,
DidNotHandle,
};
class Widget
{
public:
virtual Result<EventResult> handle_mouse_move(Point position)
{
ignore(position);
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_mouse_down(Point position, int buttons)
{
ignore(position, buttons);
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_mouse_up(Point position, int buttons)
{
ignore(position, buttons);
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_mouse_leave()
{
return EventResult::DidNotHandle;
}
virtual Result<EventResult> handle_key_event(const ui::KeyEventRequest& request)
{
ignore(request);
return EventResult::DidNotHandle;
}
virtual Result<void> draw(Canvas& canvas);
void set_window(Window* window, Rect rect, Badge<Window>)
{
m_window = window;
m_rect = rect;
}
void set_parent(Widget* parent)
{
m_parent = parent;
m_window = parent->m_window;
}
Widget* parent()
{
return m_parent;
}
Window* window()
{
return m_window;
}
Rect& rect()
{
return m_rect;
}
protected:
Widget* m_parent { nullptr };
Window* m_window;
Rect m_rect { 0, 0, 50, 50 };
};
}

View File

@ -0,0 +1,115 @@
/**
* @file Window.h
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/String.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Mouse.h>
#include <ui/Rect.h>
#include <ui/Widget.h>
#include <ui/ipc/Server.h>
namespace ui
{
enum class WindowType : u8
{
Normal,
NotDecorated,
System,
};
struct [[gnu::packed]] Shortcut
{
moon::KeyCode key;
int modifiers;
bool operator==(const Shortcut& other) const
{
return key == other.key && modifiers == other.modifiers;
}
};
class Window
{
public:
static Result<Window*> create(Rect rect, WindowType type = WindowType::Normal);
void set_title(StringView title);
void set_background(Color color)
{
m_background = color;
}
void set_main_widget(Widget& widget)
{
check(!m_main_widget);
widget.set_window(this, m_window_canvas.rect(), {});
m_main_widget = &widget;
}
Canvas& canvas()
{
return m_window_canvas;
}
void update();
void close();
void set_layer(Layer layer);
Result<void> draw();
Result<ui::EventResult> handle_mouse_leave();
Result<ui::EventResult> handle_mouse_move(ui::Point position);
Result<ui::EventResult> handle_mouse_buttons(ui::Point position, int buttons);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request);
Result<void> add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, 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:
int m_id;
Canvas m_canvas;
Canvas m_titlebar_canvas;
Canvas m_window_canvas;
String m_name;
Widget* m_main_widget { nullptr };
Option<Color> m_background {};
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;
Function<Shortcut> action;
};
HashMap<Shortcut, ShortcutAction> m_shortcuts;
Result<void> draw_titlebar();
};
}

View File

@ -0,0 +1,72 @@
/**
* @file ipc/Client.h
* @author apio (cloudapio.eu)
* @brief IPC message definitions for UI messages sent to the client.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <os/IPC.h>
#include <ui/Key.h>
#include <ui/Point.h>
#include <ui/Rect.h>
namespace ui
{
enum ClientMessages : u8
{
IPC_ENUM_CLIENT(ui),
CREATE_WINDOW_RESPONSE_ID,
MOUSE_EVENT_REQUEST_ID,
MOUSE_LEAVE_REQUEST_ID,
GET_SCREEN_RECT_RESPONSE_ID,
KEY_EVENT_REQUEST_ID,
};
struct CreateWindowResponse
{
static constexpr u8 ID = CREATE_WINDOW_RESPONSE_ID;
int window;
IPC_STRING(shm_path);
};
struct MouseEventRequest
{
static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID;
int window;
Point position;
int buttons;
};
struct MouseLeaveRequest
{
static constexpr u8 ID = MOUSE_LEAVE_REQUEST_ID;
int window;
};
struct GetScreenRectResponse
{
static constexpr u8 ID = GET_SCREEN_RECT_RESPONSE_ID;
Rect rect;
};
struct KeyEventRequest
{
static constexpr u8 ID = KEY_EVENT_REQUEST_ID;
int window;
bool pressed;
char letter;
char key;
moon::KeyCode code;
int modifiers;
};
}

View File

@ -0,0 +1,113 @@
/**
* @file ipc/Server.h
* @author apio (cloudapio.eu)
* @brief IPC message definitions for UI messages sent to the server.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <os/IPC.h>
#include <ui/Color.h>
#include <ui/Rect.h>
#include <ui/ipc/Client.h>
namespace ui
{
enum ServerMessages : u8
{
IPC_ENUM_SERVER(ui),
CREATE_WINDOW_ID,
REMOVE_SHM_ID,
SET_WINDOW_TITLE_ID,
INVALIDATE_ID,
CLOSE_WINDOW_ID,
GET_SCREEN_RECT_ID,
SET_TITLEBAR_HEIGHT_ID,
SET_WINDOW_LAYER_ID,
UPDATE_PLEDGE_REQUEST_ID,
};
struct CreateWindowRequest
{
using ResponseType = CreateWindowResponse;
static constexpr u8 ID = CREATE_WINDOW_ID;
ui::Rect rect;
};
struct RemoveSharedMemoryRequest
{
static constexpr u8 ID = REMOVE_SHM_ID;
int window;
};
struct SetWindowTitleRequest
{
static constexpr u8 ID = SET_WINDOW_TITLE_ID;
int window;
IPC_STRING(title);
};
struct InvalidateRequest
{
static constexpr u8 ID = INVALIDATE_ID;
int window;
};
struct CloseWindowRequest
{
static constexpr u8 ID = CLOSE_WINDOW_ID;
int window;
};
struct GetScreenRectRequest
{
using ResponseType = GetScreenRectResponse;
static constexpr u8 ID = GET_SCREEN_RECT_ID;
int _shadow; // Unused.
};
struct SetTitlebarHeightRequest
{
static constexpr u8 ID = SET_TITLEBAR_HEIGHT_ID;
int window;
int height;
};
enum Layer : u8
{
Background,
Global,
GlobalTop,
System,
Lock
};
struct SetWindowLayer
{
static constexpr u8 ID = SET_WINDOW_LAYER_ID;
int window;
Layer layer;
};
enum Pledge : i16
{
ExtendedLayers = 1,
};
struct UpdatePledgeRequest
{
static constexpr u8 ID = UPDATE_PLEDGE_REQUEST_ID;
i16 pledges;
};
}

View File

@ -0,0 +1,40 @@
/**
* @file Alignment.cpp
* @author apio (cloudapio.eu)
* @brief UI component alignment.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Alignment.h>
namespace ui
{
Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign)
{
Rect result;
result.width = contained.width;
result.height = contained.height;
result.pos.y = container.pos.y;
result.pos.x = container.pos.x;
switch (valign)
{
case VerticalAlignment::Top: break;
case VerticalAlignment::Center: result.pos.y += (container.height - contained.height) / 2; break;
case VerticalAlignment::Bottom: result.pos.y += container.height - contained.height; break;
default: break;
}
switch (halign)
{
case HorizontalAlignment::Left: break;
case HorizontalAlignment::Center: result.pos.x += (container.width - contained.width) / 2; break;
case HorizontalAlignment::Right: result.pos.x += container.width - contained.width; break;
default: break;
}
return result;
}
}

145
gui/libui/src/App.cpp Normal file
View File

@ -0,0 +1,145 @@
/**
* @file App.cpp
* @author apio (cloudapio.eu)
* @brief UI application event loop.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/IPC.h>
#include <ui/App.h>
#include <ui/ipc/Client.h>
#include <ui/ipc/Server.h>
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 = this;
}
App::~App()
{
s_app = nullptr;
}
Result<void> App::init(StringView socket_path)
{
m_client = TRY(os::IPC::Client::connect(socket_path, false));
m_client->set_message_handler(
[this](os::IPC::Client& client, u8 id, void* arg) { this->handle_ipc_event(client, id, arg); }, nullptr);
TRY(m_loop.register_fd_listener(m_client->fd(), handle_socket_event));
return {};
}
Result<int> App::run()
{
TRY(m_main_window->draw());
return m_loop.run();
}
App& App::the()
{
check(s_app);
return *s_app;
}
Rect App::screen_rect()
{
ui::GetScreenRectRequest request {};
auto response = m_client->send_sync<ui::GetScreenRectResponse>(request).release_value();
return response.rect;
}
Result<void> App::register_window(OwnedPtr<Window>&& window, Badge<Window>)
{
int id = window->id();
check(TRY(m_windows.try_set(id, move(window))));
return {};
}
void App::unregister_window(Window* window, Badge<Window>)
{
int id = window->id();
m_window_clear_queue.try_append(id);
}
Window* App::find_window(int id)
{
auto* window = m_windows.try_get_ref(id);
check(window);
return window->ptr();
}
Result<void> App::handle_ipc_event(os::IPC::Client&, u8 id, void*)
{
switch (id)
{
case MOUSE_EVENT_REQUEST_ID: {
MouseEventRequest request;
if (!TRY(m_client->read_message(request))) return {};
auto* window = find_window(request.window);
auto move_result = window->handle_mouse_move(request.position).value_or(ui::EventResult::DidNotHandle);
auto button_result =
window->handle_mouse_buttons(request.position, request.buttons).value_or(ui::EventResult::DidNotHandle);
if (move_result == ui::EventResult::DidHandle || button_result == ui::EventResult::DidHandle)
window->draw();
return {};
}
case MOUSE_LEAVE_REQUEST_ID: {
MouseLeaveRequest request;
if (!TRY(m_client->read_message(request))) return {};
auto* window = find_window(request.window);
if (window->handle_mouse_leave().value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
window->draw();
return {};
}
case KEY_EVENT_REQUEST_ID: {
KeyEventRequest request;
if (!TRY(m_client->read_message(request))) return {};
auto* window = find_window(request.window);
if (window->handle_key_event(request).value_or(ui::EventResult::DidNotHandle) == ui::EventResult::DidHandle)
window->draw();
return {};
}
default: fail("Unexpected IPC request from server!");
}
}
bool App::process_events()
{
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);
}
}

73
gui/libui/src/Button.cpp Normal file
View File

@ -0,0 +1,73 @@
/**
* @file Button.cpp
* @author apio (cloudapio.eu)
* @brief A clickable component that triggers an action when pressed.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Button.h>
#include <ui/Mouse.h>
namespace ui
{
Button::Button(Rect rect)
{
m_rect = rect;
}
void Button::set_widget(Widget& widget)
{
widget.rect() = m_rect;
m_child = &widget;
widget.set_parent(this);
}
void Button::set_action(Action&& action)
{
m_action = move(action);
}
Result<EventResult> Button::handle_mouse_move(Point position)
{
m_hovered = true;
return m_child->handle_mouse_move(position);
}
Result<EventResult> Button::handle_mouse_leave()
{
m_hovered = m_clicked = false;
return m_child->handle_mouse_leave();
}
Result<EventResult> Button::handle_mouse_down(Point position, int buttons)
{
auto result = TRY(m_child->handle_mouse_down(position, buttons));
if (result == EventResult::DidNotHandle)
{
if (!m_clicked && (buttons == ui::MouseButtons::LEFT))
{
m_clicked = true;
m_action();
}
}
return EventResult::DidHandle;
}
Result<EventResult> Button::handle_mouse_up(Point position, int buttons)
{
if (buttons & ui::MouseButtons::LEFT) m_clicked = false;
return m_child->handle_mouse_up(position, buttons);
}
Result<EventResult> Button::handle_key_event(const ui::KeyEventRequest& request)
{
return m_child->handle_key_event(request);
}
Result<void> Button::draw(Canvas& canvas)
{
return m_child->draw(canvas);
}
}

75
gui/libui/src/Canvas.cpp Normal file
View File

@ -0,0 +1,75 @@
/**
* @file Canvas.cpp
* @author apio (cloudapio.eu)
* @brief Drawable surfaces.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/CString.h>
#include <ui/Canvas.h>
namespace ui
{
Canvas Canvas::create(u8* ptr, int width, int height)
{
return Canvas { .width = width, .height = height, .stride = width, .ptr = ptr };
}
Canvas Canvas::subcanvas(Rect rect)
{
if (rect.pos.x < 0) rect.pos.x = 0;
if (rect.pos.y < 0) rect.pos.y = 0;
if (rect.pos.x + rect.width > width) rect.width = width - rect.pos.x;
if (rect.pos.y + rect.height > height) rect.height = height - rect.pos.y;
u8* p = ptr + rect.pos.x * sizeof(Color) + (rect.pos.y * sizeof(Color) * stride);
return Canvas { .width = rect.width, .height = rect.height, .stride = stride, .ptr = p };
}
void Canvas::fill(Color color)
{
u8* p = ptr;
for (int i = 0; i < height; i++)
{
u32* colorp = (u32*)p;
for (int j = 0; j < width; j++)
{
*colorp = color.raw;
colorp++;
}
p += stride * sizeof(Color);
}
}
void Canvas::fill(u32* pixels, int _stride)
{
u8* p = ptr;
for (int i = 0; i < height; i++)
{
u32* colorp = (u32*)p;
for (int j = 0; j < width; j++)
{
u32 pix = pixels[j];
if (Color::from_u32(pix).alpha() == 0xff) *colorp = pix;
colorp++;
}
pixels += _stride;
p += stride * sizeof(Color);
}
}
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

@ -0,0 +1,63 @@
/**
* @file Container.cpp
* @author apio (cloudapio.eu)
* @brief A container widget to pad and align objects inside it.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Container.h>
namespace ui
{
Container::Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign)
: m_valign(valign), m_halign(halign)
{
m_rect = rect;
}
void Container::set_widget(Widget& widget)
{
m_widget = &widget;
widget.rect() = ui::align(m_rect, widget.rect(), m_valign, m_halign);
widget.set_parent(this);
}
Result<EventResult> Container::handle_mouse_move(Point position)
{
if (m_widget->rect().contains(position)) return m_widget->handle_mouse_move(position);
return ui::EventResult::DidNotHandle;
}
Result<EventResult> Container::handle_mouse_leave()
{
return m_widget->handle_mouse_leave();
}
Result<EventResult> Container::handle_mouse_down(Point position, int buttons)
{
if (m_widget->rect().contains(position)) return m_widget->handle_mouse_down(position, buttons);
return ui::EventResult::DidNotHandle;
}
Result<EventResult> Container::handle_mouse_up(Point position, int buttons)
{
if (m_widget->rect().contains(position)) return m_widget->handle_mouse_up(position, buttons);
return ui::EventResult::DidNotHandle;
}
Result<EventResult> Container::handle_key_event(const ui::KeyEventRequest& request)
{
return m_widget->handle_key_event(request);
}
Result<void> Container::draw(Canvas& canvas)
{
auto rect = ui::Rect { m_widget->rect().pos.x - m_rect.pos.x, m_widget->rect().pos.y - m_rect.pos.y,
m_widget->rect().width, m_widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
return m_widget->draw(subcanvas);
}
}

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

121
gui/libui/src/Font.cpp Normal file
View File

@ -0,0 +1,121 @@
/**
* @file Font.cpp
* @author apio (cloudapio.eu)
* @brief PSF font loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/RefString.h>
#include <os/File.h>
#include <ui/Font.h>
constexpr static int BYTES_PER_PIXEL = (int)sizeof(ui::Color);
namespace ui
{
Result<SharedPtr<Font>> Font::load(const os::Path& path)
{
auto font = TRY(make_shared<Font>());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
TRY(file->read_typed(font->m_psf_header));
if (font->m_psf_header.magic != PSF_FONT_MAGIC)
{
os::eprintln("ui::Font::load(%s) failed: font magic does not match PSF2 magic", path.name().chars());
return err(ENOTSUP);
}
if (font->m_psf_header.version != 0)
{
os::eprintln("ui::Font::load(%s) failed: font version is unsupported", path.name().chars());
return err(ENOTSUP);
}
if (font->m_psf_header.flags)
{
os::eprintln("ui::Font::load(%s) warning: font has a unicode table, which we're ignoring",
path.name().chars());
// todo(); // Font has a unicode table, oh no!
}
font->m_font_data = TRY(file->read_all()); // Read the rest of the file into the font data buffer.
return font;
}
Result<SharedPtr<Font>> Font::load_builtin(StringView name, FontWeight weight)
{
auto path = TRY(RefString::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(),
weight == FontWeight::Bold ? "Bold" : "Regular"));
return load(path.view());
}
SharedPtr<Font> Font::default_font()
{
static SharedPtr<ui::Font> s_default_font = {};
if (!s_default_font) s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value();
return s_default_font;
}
SharedPtr<Font> Font::default_bold_font()
{
static SharedPtr<ui::Font> s_default_bold_font = {};
if (!s_default_bold_font) s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value();
return s_default_bold_font;
}
void Font::render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas)
{
const wchar_t str[] = { codepoint, 0 };
render(str, color, canvas);
}
void Font::render(const wchar_t* text, ui::Color color, ui::Canvas& canvas)
{
usize len = wcslen(text);
int height = m_psf_header.height;
int width = m_psf_header.width;
int last_char_width = width;
if (canvas.width < (m_psf_header.width * static_cast<int>(len)))
{
len = (canvas.width / width) + 1;
last_char_width = canvas.width % width;
}
if (canvas.height < height) height = canvas.height;
const int bytes_per_line = (m_psf_header.width + 7) / 8;
for (usize i = 0; i < len; i++)
{
if (i + 1 == len) width = last_char_width;
wchar_t codepoint = text[i];
u8* glyph =
m_font_data.data() + (codepoint > 0 && codepoint < (wchar_t)m_psf_header.numglyph ? codepoint : 0) *
m_psf_header.bytesperglyph;
u32 offset = (u32)i * m_psf_header.width * BYTES_PER_PIXEL;
for (int y = 0; y < height; y++)
{
u32 line = offset;
int mask = 1 << (m_psf_header.width - 1);
for (int x = 0; x < width; x++)
{
if (*((u32*)glyph) & mask) *(u32*)(canvas.ptr + line) = color.raw;
mask >>= 1;
line += BYTES_PER_PIXEL;
}
glyph += bytes_per_line;
offset += canvas.stride * BYTES_PER_PIXEL;
}
}
}
}

48
gui/libui/src/Image.cpp Normal file
View File

@ -0,0 +1,48 @@
/**
* @file Image.cpp
* @author apio (cloudapio.eu)
* @brief TGA image loading and rendering.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/File.h>
#include <ui/Alignment.h>
#include <ui/Image.h>
namespace ui
{
Result<SharedPtr<Image>> Image::load(const os::Path& path)
{
auto image = TRY(make_shared<Image>());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
TRY(file->read_typed(image->m_tga_header));
if (image->m_tga_header.encoding != 2) todo();
if (image->m_tga_header.bpp != 32) todo();
Buffer image_id;
TRY(file->read(image_id, image->m_tga_header.idlen));
TRY(file->read(image->m_image_data,
image->m_tga_header.w * image->m_tga_header.h * (image->m_tga_header.bpp / 8)));
return image;
}
Result<OwnedPtr<ImageWidget>> ImageWidget::load(const os::Path& path)
{
auto widget = TRY(make_owned<ImageWidget>());
widget->m_image = TRY(Image::load(path));
widget->m_rect = { 0, 0, widget->m_image->width(), widget->m_image->height() };
return widget;
}
Result<void> ImageWidget::draw(Canvas& canvas)
{
canvas.subcanvas({ 0, 0, m_image->width(), m_image->height() }).fill(m_image->pixels(), m_image->width());
return {};
}
}

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() < 2) return StringView {};
return StringView { (const char*)m_data.data(), m_data.size() - 1 };
}
}

36
gui/libui/src/Label.cpp Normal file
View File

@ -0,0 +1,36 @@
/**
* @file Label.cpp
* @author apio (cloudapio.eu)
* @brief A simple one-line text widget.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/Utf8.h>
#include <ui/Label.h>
namespace ui
{
Label::Label(StringView text) : m_text(text)
{
m_font = ui::Font::default_font();
}
Result<void> Label::draw(Canvas& canvas)
{
ui::Rect contained;
contained.pos = { 0, 0 };
contained.width = static_cast<int>(m_text.length() * m_font->width());
contained.height = m_font->height();
auto subcanvas =
canvas.subcanvas(ui::align({ 0, 0, m_rect.width, m_rect.height }, contained, m_valign, m_halign));
Utf8StringDecoder decoder(m_text.chars());
wchar_t buf[4096];
TRY(decoder.decode(buf, sizeof(buf)));
m_font->render(buf, m_color, subcanvas);
return {};
}
}

236
gui/libui/src/Layout.cpp Normal file
View File

@ -0,0 +1,236 @@
/**
* @file Layout.cpp
* @author apio (cloudapio.eu)
* @brief Layout widgets to organize content.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <stdlib.h>
#include <ui/Layout.h>
namespace ui
{
HorizontalLayout::HorizontalLayout(Margins margins, AdjustHeight adjust_height, AdjustWidth adjust_width)
: m_margins(margins), m_adjust_height(adjust_height), m_adjust_width(adjust_width)
{
}
Result<EventResult> HorizontalLayout::handle_mouse_move(Point position)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position));
else
TRY(widget->handle_mouse_leave());
}
return result;
}
Result<EventResult> HorizontalLayout::handle_mouse_leave()
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
auto rc = TRY(widget->handle_mouse_leave());
if (rc == ui::EventResult::DidHandle) result = rc;
}
return result;
}
Result<EventResult> HorizontalLayout::handle_mouse_up(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> HorizontalLayout::handle_mouse_down(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> HorizontalLayout::handle_key_event(const ui::KeyEventRequest& request)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
auto rc = TRY(widget->handle_key_event(request));
if (rc == ui::EventResult::DidHandle) result = rc;
}
return result;
}
Result<void> HorizontalLayout::draw(Canvas& canvas)
{
for (auto widget : m_widgets)
{
ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
TRY(widget->draw(subcanvas));
}
return {};
}
Result<void> HorizontalLayout::add_widget(Widget& widget)
{
TRY(m_widgets.try_append(&widget));
if (m_adjust_width == AdjustWidth::No)
{
widget.rect().pos.x = m_rect.pos.x + m_used_width + m_margins.left;
m_used_width += m_margins.left + widget.rect().width + m_margins.right;
}
else
{
int used_width = 0;
div_t result = div(m_rect.width, (int)m_widgets.size());
for (auto w : m_widgets)
{
w->rect().pos.x = m_rect.pos.x + used_width + m_margins.left;
w->rect().width = result.quot - (m_margins.left + m_margins.right);
used_width += result.quot;
}
m_widgets[m_widgets.size() - 1]->rect().width += result.rem;
}
widget.rect().pos.y = m_rect.pos.y + m_margins.top;
if (m_adjust_height == AdjustHeight::Yes)
{
widget.rect().height = m_rect.height - (m_margins.top + m_margins.bottom);
}
widget.set_parent(this);
return {};
}
VerticalLayout::VerticalLayout(Margins margins, AdjustHeight adjust_height, AdjustWidth adjust_width)
: m_margins(margins), m_adjust_height(adjust_height), m_adjust_width(adjust_width)
{
}
Result<EventResult> VerticalLayout::handle_mouse_move(Point position)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position));
else
TRY(widget->handle_mouse_leave());
}
return result;
}
Result<EventResult> VerticalLayout::handle_mouse_leave()
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
auto rc = TRY(widget->handle_mouse_leave());
if (rc == ui::EventResult::DidHandle) result = rc;
}
return result;
}
Result<EventResult> VerticalLayout::handle_mouse_up(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> VerticalLayout::handle_mouse_down(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> VerticalLayout::handle_key_event(const ui::KeyEventRequest& request)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
auto rc = TRY(widget->handle_key_event(request));
if (rc == ui::EventResult::DidHandle) result = rc;
}
return result;
}
Result<void> VerticalLayout::draw(Canvas& canvas)
{
for (auto widget : m_widgets)
{
ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
TRY(widget->draw(subcanvas));
}
return {};
}
Result<void> VerticalLayout::add_widget(Widget& widget)
{
TRY(m_widgets.try_append(&widget));
if (m_adjust_height == AdjustHeight::No)
{
widget.rect().pos.y = m_rect.pos.y + m_used_height + m_margins.top;
m_used_height += m_margins.top + widget.rect().height + m_margins.bottom;
}
else
{
int used_height = 0;
div_t result = div(m_rect.height, (int)m_widgets.size());
for (auto w : m_widgets)
{
w->rect().pos.y = m_rect.pos.y + used_height + m_margins.top;
w->rect().height = result.quot - (m_margins.top + m_margins.bottom);
used_height += result.quot;
}
m_widgets[m_widgets.size() - 1]->rect().height += result.rem;
}
widget.rect().pos.x = m_rect.pos.x + m_margins.left;
if (m_adjust_width == AdjustWidth::Yes)
{
widget.rect().width = m_rect.width - (m_margins.left + m_margins.right);
}
widget.set_parent(this);
return {};
}
}

62
gui/libui/src/Rect.cpp Normal file
View File

@ -0,0 +1,62 @@
/**
* @file Rect.cpp
* @author apio (cloudapio.eu)
* @brief A simple 2D rectangle representation.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Rect.h>
namespace ui
{
bool Rect::contains(Point point)
{
return (point.x >= pos.x) && (point.y >= pos.y) && (point.x <= (pos.x + width)) &&
(point.y <= (pos.y + height));
}
bool Rect::contains(Rect rect)
{
if (!contains(rect.pos)) return false;
Point rel = relative(rect.pos);
if ((rel.x + rect.width) > width) return false;
if ((rel.y + rect.height) > height) return false;
return true;
}
Point Rect::normalize(Point point)
{
if (point.x < pos.x) point.x = pos.x;
if (point.y < pos.y) point.y = pos.y;
if (point.x > pos.x + width) point.x = pos.x + width;
if (point.y > pos.y + height) point.y = pos.y + height;
return point;
}
Point Rect::relative(Point point)
{
point = normalize(point);
point.x -= pos.x;
point.y -= pos.y;
return point;
}
Point Rect::absolute(Point point)
{
point.x += pos.x;
point.y += pos.y;
return point;
}
Rect Rect::absolute(Rect rect)
{
return Rect { absolute(rect.pos), rect.width, rect.height };
}
Rect Rect::normalized()
{
return Rect { ui::Point { pos.x < 0 ? 0 : pos.x, pos.y < 0 ? 0 : pos.y }, width, height };
}
};

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

233
gui/libui/src/Window.cpp Normal file
View File

@ -0,0 +1,233 @@
/**
* @file Window.cpp
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/String.h>
#include <luna/Utf8.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
#include <ui/App.h>
#include <ui/Font.h>
#include <ui/Image.h>
#include <ui/Window.h>
#include <ui/ipc/Server.h>
static int titlebar_height()
{
auto font = ui::Font::default_font();
return font->height() + 20;
}
namespace ui
{
Result<Window*> Window::create(Rect rect, WindowType type)
{
auto window = TRY(make_owned<Window>());
window->m_name = TRY(String::from_cstring("Window"));
if (type == ui::WindowType::Normal)
{
int height = titlebar_height();
rect.height += height; // Make sure we provide the full contents rect that was asked for.
rect.pos.y -= height; // Adjust it so the contents begin at the expected coordinates.
window->m_decorated = true;
}
rect = rect.normalized();
ui::CreateWindowRequest request;
request.rect = rect;
auto response = TRY(App::the().client().send_sync<ui::CreateWindowResponse>(request));
auto path = COPY_IPC_STRING(response.shm_path);
u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4, false));
Canvas canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels };
window->m_canvas = canvas;
window->m_id = response.window;
if (type == ui::WindowType::Normal)
{
int height = titlebar_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 });
ui::SetTitlebarHeightRequest titlebar_request;
titlebar_request.height = height;
titlebar_request.window = response.window;
App::the().client().send_async(titlebar_request);
}
else
{
window->m_titlebar_canvas = canvas.subcanvas(ui::Rect { 0, 0, 0, 0 });
window->m_window_canvas = canvas;
}
Window* p = window.ptr();
ui::RemoveSharedMemoryRequest shm_request;
shm_request.window = response.window;
App::the().client().send_async(shm_request);
App::the().register_window(move(window), {});
return p;
}
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)
{
ui::SetWindowTitleRequest request;
request.window = m_id;
SET_IPC_STRING(request.title, title.chars());
App::the().client().send_async(request);
m_name = String::from_string_view(title).release_value();
draw();
}
void Window::update()
{
ui::InvalidateRequest request;
request.window = m_id;
App::the().client().send_async(request);
}
void Window::close()
{
App& app = App::the();
if (this == app.main_window()) app.set_should_close(true);
app.unregister_window(this, {});
}
void Window::set_layer(Layer layer)
{
ui::SetWindowLayer request;
request.window = m_id;
request.layer = layer;
App::the().client().send_async(request);
}
Result<void> Window::draw()
{
if (m_background.has_value()) m_window_canvas.fill(*m_background);
if (m_decorated) TRY(draw_titlebar());
if (m_main_widget) TRY(m_main_widget->draw(m_window_canvas));
update();
return {};
}
static constexpr ui::Color TITLEBAR_COLOR = ui::Color::from_rgb(53, 53, 53);
// FIXME: Titlebars should be implemented as a separate widget group, to allow for customization and extensibility.
// Additionally, this very specific spaghetti code could be replaced with well-established UI components.
Result<void> Window::draw_titlebar()
{
wchar_t buffer[4096];
Utf8StringDecoder decoder(m_name.chars());
decoder.decode(buffer, sizeof(buffer)).release_value();
auto font = ui::Font::default_font();
m_titlebar_canvas.fill(TITLEBAR_COLOR);
auto textarea =
m_titlebar_canvas.subcanvas(ui::Rect { 10, 10, m_titlebar_canvas.width - 10, m_titlebar_canvas.height });
font->render(buffer, ui::WHITE, textarea);
static SharedPtr<ui::Image> g_close_icon;
if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").release_value();
auto close_rect = ui::Rect { m_titlebar_canvas.width - 26, 10, 16, 16 };
auto close_area = m_titlebar_canvas.subcanvas(close_rect);
close_area.fill(g_close_icon->pixels(), g_close_icon->width());
return {};
}
Result<ui::EventResult> Window::handle_mouse_leave()
{
if (!m_main_widget) return ui::EventResult::DidNotHandle;
return m_main_widget->handle_mouse_leave();
}
Result<ui::EventResult> Window::handle_mouse_move(ui::Point position)
{
if (!m_main_widget) return ui::EventResult::DidNotHandle;
return m_main_widget->handle_mouse_move(position);
}
Result<ui::EventResult> Window::handle_mouse_buttons(ui::Point position, int buttons)
{
auto result = ui::EventResult::DidNotHandle;
if (m_decorated && m_titlebar_canvas.rect().contains(position))
{
// Handle pressing the close button
auto close_rect = ui::Rect { m_titlebar_canvas.width - 26, 10, 16, 16 };
if (close_rect.contains(position) && (buttons & LEFT)) { close(); }
return ui::EventResult::DidNotHandle;
}
if (m_decorated) position.y -= m_titlebar_canvas.height;
if (!m_main_widget) return ui::EventResult::DidNotHandle;
if (buttons)
{
auto rc = TRY(m_main_widget->handle_mouse_down(position, buttons));
if (rc == ui::EventResult::DidHandle) result = rc;
}
if (m_old_mouse_buttons.has_value())
{
int old_buttons = m_old_mouse_buttons.value();
int diff = old_buttons & ~buttons;
if (diff)
{
auto rc = TRY(m_main_widget->handle_mouse_up(position, diff));
if (rc == ui::EventResult::DidHandle) result = rc;
}
}
m_old_mouse_buttons = buttons;
return result;
}
Result<ui::EventResult> Window::handle_key_event(const ui::KeyEventRequest& request)
{
if (request.pressed)
{
auto* shortcut = m_shortcuts.try_get_ref({ request.code, request.modifiers });
if (shortcut)
{
shortcut->action({ request.code, request.modifiers });
if (shortcut->intercept) return ui::EventResult::DidHandle;
}
}
if (!m_main_widget) return ui::EventResult::DidNotHandle;
return m_main_widget->handle_key_event(request);
}
Result<void> Window::add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, Function<ui::Shortcut>&& action)
{
TRY(m_shortcuts.try_set(shortcut, { intercept, move(action) }));
return {};
}
}

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

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/execd.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;
}

24
gui/wind/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
set(SOURCES
main.cpp
Screen.h
Screen.cpp
Mouse.h
Mouse.cpp
Window.h
Window.cpp
IPC.cpp
IPC.h
Keyboard.cpp
Keyboard.h
Client.h
Client.cpp
Layer.cpp
Layer.h
)
add_executable(wind ${SOURCES})
target_compile_options(wind PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings -fno-threadsafe-statics)
add_dependencies(wind libc)
target_include_directories(wind PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(wind PRIVATE os ui)
install(TARGETS wind DESTINATION ${LUNA_BASE}/usr/bin)

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

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