Compare commits

...

46 Commits

Author SHA1 Message Date
38f799ce39
libos: Remove some shared pointers and change them to owned/live on the stack
All checks were successful
continuous-integration/drone/pr Build is passing
2023-08-08 16:34:38 +02:00
fac7a68445
wind: Spawn a new client process after startup
Also, create the socket after dropping privileges.
2023-08-08 16:34:38 +02:00
fdd9f6103b
apps: Add gclient 2023-08-08 16:34:26 +02:00
6a74546a71
libos: Add os::LocalClient 2023-08-08 16:19:57 +02:00
b3cc53f7e9
libui: Change 'into' to 'onto' 2023-08-08 16:19:57 +02:00
361e57b17c
libui: Document ui::Font 2023-08-08 16:19:57 +02:00
dfee0981b8
libui+wind: Move some static variables inside functions 2023-08-08 16:19:57 +02:00
b0c905a33e
wind: Generate random windows on keypresses 2023-08-08 16:19:57 +02:00
4d1d4342ae
wind: Make sure windows have a minimum size to fit the titlebar 2023-08-08 16:19:56 +02:00
6404d01a5e
libui: Properly cut off the last drawn character if necessary 2023-08-08 16:19:56 +02:00
a510c58de0
libui: Add Rect::contains(Rect) 2023-08-08 16:19:56 +02:00
632252e1d1
libui: Render font characters properly with no spacing, matching the width calculations 2023-08-08 16:19:56 +02:00
db7dc09844
wind: Render an actual TGA mouse cursor 2023-08-08 16:19:56 +02:00
d142969eb0
wind: Add a close button to windows using a TGA icon 2023-08-08 16:19:55 +02:00
0e24e6a79a
libui: Add support for TGA image loading 2023-08-08 16:19:55 +02:00
d7fc3356e6
libui: Add an interface to fill a Canvas with an array of pixels 2023-08-08 16:19:55 +02:00
d1b31ab6aa
wind: Add window titlebars using ui::Font 2023-08-08 16:19:55 +02:00
e72a1a3697
libui: Add PSF font loading and rendering 2023-08-08 16:19:55 +02:00
26a61a6069
libui: Add Color::GRAY 2023-08-08 16:19:55 +02:00
f87952a615
libui: Rename Rect::absolute to normalized and add a new absolute function 2023-08-08 16:19:55 +02:00
b2037e978f
libluna: Add assignment operators to Buffer 2023-08-08 16:19:55 +02:00
78763b97f8
wind: Reorder drag sequence 2023-08-08 16:19:54 +02:00
5c3b5aae85
libui: Add Rect::relative 2023-08-08 16:19:54 +02:00
2261ffb31d
libui: Remove redundant statement 2023-08-08 16:19:54 +02:00
5343903e54
libui: Add getters for separate color values 2023-08-08 16:19:54 +02:00
ae7b712792
libui: Remove unnecessary stuff 2023-08-08 16:19:54 +02:00
6fdb245759
base: Remove startup items not necessary for GUI startup 2023-08-08 16:19:54 +02:00
0fe9b63f0e
libui+wind: (Draggable) windows 2023-08-08 16:19:54 +02:00
3dbe8418a1
wind: Create a local server object 2023-08-08 16:19:54 +02:00
c5900b7663
libos: Add a new LocalServer class for local domain sockets 2023-08-08 16:19:53 +02:00
0a06978fee
kernel: Support listening sockets in poll() 2023-08-08 16:19:53 +02:00
be99376307
base: Start wind on startup instead of the shell 2023-08-08 16:19:53 +02:00
a261603655
wind: Add a simple display server skeleton using libui
No client functionality yet, but it's a start.
2023-08-08 16:19:53 +02:00
a122ff133e
libui: Add a GUI and graphics library 2023-08-08 16:19:53 +02:00
adec709fe7
kernel: Fix negative movement in the PS/2 mouse driver 2023-08-08 16:19:52 +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
70 changed files with 2123 additions and 40 deletions

6
.gitignore vendored
View File

@ -4,7 +4,11 @@ build/
initrd/boot/moon
env-local.sh
initrd/bin/**
base/usr/**
base/usr/bin/**
base/usr/include/**
base/usr/lib/**
base/usr/share/pkgdb/**
!base/usr/share/fonts/*
.fakeroot
kernel/config.cmake
ports/out/

View File

@ -45,8 +45,10 @@ endif()
add_subdirectory(libluna)
add_subdirectory(libos)
add_subdirectory(libui)
add_subdirectory(libc)
add_subdirectory(kernel)
add_subdirectory(apps)
add_subdirectory(tests)
add_subdirectory(shell)
add_subdirectory(wind)

View File

@ -1,5 +1,5 @@
# 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 personal computers, written in C++. [![Build Status](https://drone.cloudapio.eu/api/badges/apio/Luna/status.svg)](https://drone.cloudapio.eu/apio/Luna)
## Another UNIX clone?
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone).
@ -8,16 +8,18 @@ A very basic POSIX-based operating system for personal computers, written in C++
- 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.
- Can [load ELF programs](kernel/src/binfmt/ELF.cpp), [shebang scripts](kernel/src/binfmt/Script.cpp) or [arbitrary binary formats](kernel/src/binfmt/BinaryFormat.h) (registered through kernel modules, which are not supported yet =D).
- 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.
- [System call](kernel/src/sys/) interface and [C Library](libc/), aiming to be almost POSIX-compatible.
- Support for [several third-party programs](ports/), including the [GNU binutils](ports/binutils/PACKAGE) suite of utilities.
- 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).
- Everything is [UTF-8](libluna/include/luna/Utf8.h).
- [UNIX local domain sockets](kernel/src/net/UnixSocket.cpp), allowing for local IPC.
- [POSIX shared memory](libc/include/sys/mman.h) support.
- 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).
- Return-oriented [error propagation](libluna/include/luna/Result.h), inspired by Rust and SerenityOS. No exceptions here :).
- 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/).
## Setup

View File

@ -46,3 +46,5 @@ luna_app(socket-test.cpp socket-test)
luna_app(socket-client.cpp socket-client)
luna_app(input.cpp input)
luna_app(shmem-test.cpp shmem-test)
luna_app(touch.cpp touch)
luna_app(gclient.cpp gclient)

20
apps/gclient.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <os/ArgumentParser.h>
#include <os/LocalClient.h>
Result<int> luna_main(int argc, char** argv)
{
StringView socket_path = "/tmp/wind.sock";
os::ArgumentParser parser;
parser.add_description("A graphical user interface client."_sv);
parser.add_system_program_info("gclient"_sv);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.parse(argc, argv);
auto client = TRY(os::LocalClient::connect(socket_path, false));
StringView message = "hello";
TRY(client->send((const u8*)message.chars(), message.length()));
return 0;
}

View File

@ -2,7 +2,7 @@
#include <os/FileSystem.h>
#include <os/Mode.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
static const char* file_type(mode_t mode)
{
@ -36,10 +36,20 @@ Result<int> luna_main(int argc, char** argv)
char buf[11];
os::format_mode(st.st_mode, buf);
char atime[256];
strftime(atime, sizeof(atime), "%Y-%m-%d %H:%M:%S", gmtime(&st.st_atim.tv_sec));
char mtime[256];
strftime(mtime, sizeof(mtime), "%Y-%m-%d %H:%M:%S", gmtime(&st.st_mtim.tv_sec));
char ctime[256];
strftime(ctime, sizeof(ctime), "%Y-%m-%d %H:%M:%S", gmtime(&st.st_ctim.tv_sec));
printf(" File: %s\n", path.chars());
printf(" Size: %zu (%s)\n", st.st_size, file_type(st.st_mode));
printf(" Inode: %lu Links: %lu\n", st.st_ino, st.st_nlink);
printf(" Mode: (%#o/%s) UID: %u GID: %u\n", st.st_mode & ~S_IFMT, buf, st.st_uid, st.st_gid);
printf("Access: %s.%.9ld\n", atime, st.st_atim.tv_nsec);
printf("Modify: %s.%.9ld\n", mtime, st.st_mtim.tv_nsec);
printf("Change: %s.%.9ld\n", ctime, st.st_ctim.tv_nsec);
return 0;
}

47
apps/touch.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <errno.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <sys/stat.h>
Result<int> luna_main(int argc, char** argv)
{
Vector<StringView> files;
bool only_atime { false };
bool only_mtime { false };
bool no_create { false };
bool no_dereference { false };
os::ArgumentParser parser;
parser.add_description("Update the access and modification times of files."_sv);
parser.add_system_program_info("touch"_sv);
parser.set_vector_argument(files, "files", true);
parser.add_switch_argument(only_atime, 'a', ""_sv, "change only the access time"_sv);
parser.add_switch_argument(no_create, 'c', "no-create"_sv, "do not create new files"_sv);
parser.add_switch_argument(no_dereference, 'h', "no-dereference"_sv, "do not follow symbolic links"_sv);
parser.add_switch_argument(only_mtime, 'm', ""_sv, "change only the modification time"_sv);
TRY(parser.parse(argc, argv));
if (only_atime && only_mtime)
{
os::eprintln("%s: only one of -a and -m can be specified.", argv[0]);
parser.short_usage(argv[0]);
}
struct timespec times[2] = {
{ .tv_sec = 0, .tv_nsec = only_mtime ? UTIME_OMIT : UTIME_NOW },
{ .tv_sec = 0, .tv_nsec = only_atime ? UTIME_OMIT : UTIME_NOW },
};
for (auto& filename : files)
{
SharedPtr<os::File> file;
if (no_create) file = TRY(os::File::open(filename, os::File::ReadOnly));
else
file = TRY(os::File::open_or_create(filename, os::File::ReadOnly));
if (futimens(file->fd(), times, no_dereference ? AT_SYMLINK_NOFOLLOW : 0) < 0) return err(errno);
}
return 0;
}

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,6 +0,0 @@
Name=listen
Description=Start a Unix domain socket test server.
Command=/usr/bin/socket-test
StandardOutput=/dev/uart0
StandardError=/dev/uart0
Restart=true

View File

@ -1,4 +1,6 @@
Name=login
Description=Start the command-line login program.
Command=/usr/bin/login
Description=Start the display server.
Command=/usr/bin/wind --user=selene
StandardOutput=/dev/uart0
StandardError=/dev/uart0
Restart=true

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

View File

@ -43,8 +43,8 @@ static void process_mouse_event(u8 data)
packet.buttons = 0;
u8 flags = g_mouse_packet[0];
if (flags & PS2_MOUSE_X_SIGN) packet.xdelta = -packet.xdelta;
if (flags & PS2_MOUSE_Y_SIGN) packet.ydelta = -packet.ydelta;
if (flags & PS2_MOUSE_X_SIGN) packet.xdelta = -(256 - packet.xdelta);
if (flags & PS2_MOUSE_Y_SIGN) packet.ydelta = -(256 - packet.ydelta);
if (flags & PS2_MOUSE_MIDDLE_BTN) packet.buttons |= moon::MouseButton::Middle;
if (flags & PS2_MOUSE_RIGHT_BTN) packet.buttons |= moon::MouseButton::Right;

View File

@ -57,6 +57,9 @@ namespace Ext2
inode->m_metadata.nlinks = inode->m_raw_inode.nlinks;
inode->m_metadata.uid = inode->m_raw_inode.uid;
inode->m_metadata.gid = inode->m_raw_inode.gid;
inode->m_metadata.atime = { .tv_sec = inode->m_raw_inode.atime, .tv_nsec = 0 };
inode->m_metadata.mtime = { .tv_sec = inode->m_raw_inode.mtime, .tv_nsec = 0 };
inode->m_metadata.ctime = { .tv_sec = inode->m_raw_inode.create_time, .tv_nsec = 0 };
#ifdef EXT2_DEBUG
kdbgln("ext2: Read inode %lu with mode %#x (%#x + %#o), size %lu", inum, inode->m_raw_inode.mode,

View File

@ -1,4 +1,5 @@
#include "fs/tmpfs/FileSystem.h"
#include "arch/Timer.h"
#include "fs/devices/DeviceRegistry.h"
#include "fs/tmpfs/Inode.h"
#include <luna/Alloc.h>
@ -26,6 +27,7 @@ namespace TmpFS
inode->set_fs(*this, {});
inode->set_inode_number(m_next_inode_number++, {});
inode->m_metadata.mode = mode;
inode->m_metadata.atime = inode->m_metadata.ctime = inode->m_metadata.mtime = *Timer::realtime_clock();
return (SharedPtr<VFS::Inode>)inode;
}
@ -35,6 +37,7 @@ namespace TmpFS
inode->set_fs(*this, {});
TRY(inode->set_link(link, {}));
inode->set_inode_number(m_next_inode_number++, {});
inode->m_metadata.atime = inode->m_metadata.ctime = inode->m_metadata.mtime = *Timer::realtime_clock();
return (SharedPtr<VFS::Inode>)inode;
}
@ -49,6 +52,7 @@ namespace TmpFS
inode->set_fs(*this, {});
inode->set_inode_number(m_next_inode_number++, {});
inode->m_metadata.mode = mode;
inode->m_metadata.atime = inode->m_metadata.ctime = inode->m_metadata.mtime = *Timer::realtime_clock();
return (SharedPtr<VFS::Inode>)inode;
}
@ -65,6 +69,8 @@ namespace TmpFS
// device ID atm.
inode->set_device_id(luna_dev_makedev(major, minor), {});
inode->m_metadata.mode = mode;
inode->m_metadata.atime = inode->m_metadata.ctime = inode->m_metadata.mtime = *Timer::realtime_clock();
inode->m_metadata.size = device->size();
return (SharedPtr<VFS::Inode>)inode;
}

View File

@ -45,6 +45,8 @@ namespace TmpFS
inode->did_link();
m_metadata.mtime = *Timer::realtime_clock();
return {};
}
@ -61,6 +63,8 @@ namespace TmpFS
inode->did_unlink();
m_metadata.mtime = *Timer::realtime_clock();
return {};
}
@ -114,6 +118,8 @@ namespace TmpFS
m_metadata.size = m_data_buffer.size();
m_metadata.mtime = *Timer::realtime_clock();
return length;
}
@ -127,6 +133,8 @@ namespace TmpFS
m_metadata.size = m_data_buffer.size();
m_metadata.mtime = *Timer::realtime_clock();
return {};
}

View File

@ -65,6 +65,10 @@ class Socket : public VFS::FileInode
m_metadata.nlinks--;
}
virtual bool can_accept_connections() const = 0;
virtual bool can_read_data() const = 0;
virtual ~Socket() = default;
protected:

View File

@ -17,6 +17,16 @@ class UnixSocket : public Socket
return (m_state == Connected || m_state == Reset) && !m_data.size();
}
bool can_read_data() const override
{
return (m_state == Connected || m_state == Reset) && m_data.size();
}
bool can_accept_connections() const override
{
return !m_listen_queue.is_empty();
}
Result<usize> send(const u8*, usize, int) override;
Result<usize> recv(u8*, usize, int) const override;

View File

@ -4,9 +4,11 @@
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/atfile.h>
#include <bits/fcntl.h>
#include <bits/open-flags.h>
#include <bits/seek.h>
#include <bits/utime.h>
#include <luna/SafeArithmetic.h>
#include <sys/types.h>
@ -262,3 +264,58 @@ Result<u64> sys_ftruncate(Registers*, SyscallArgs args)
return 0;
}
Result<u64> sys_utimensat(Registers*, SyscallArgs args)
{
int dirfd = (int)args[0];
auto path = TRY(MemoryManager::strdup_from_user(args[1]));
const auto* times = (const struct timespec*)args[2];
int flags = (int)args[3];
auto* current = Scheduler::current();
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW)));
struct timespec ktimes[2];
ktimes[0].tv_sec = ktimes[1].tv_sec = 0;
ktimes[0].tv_nsec = ktimes[1].tv_nsec = UTIME_NOW;
if (times && !MemoryManager::copy_from_user(times, ktimes, sizeof(ktimes))) return err(EFAULT);
// No permission checks are performed, since no actual modification is done, but the above checks are still
// performed.
if (ktimes[0].tv_nsec == UTIME_OMIT && ktimes[1].tv_nsec == UTIME_OMIT) return 0;
bool allow_write_access = ktimes[0].tv_nsec == UTIME_NOW && ktimes[1].tv_nsec == UTIME_NOW;
if (allow_write_access)
{
if (!VFS::can_write(inode, current->auth) && current->auth.euid != inode->metadata().uid &&
current->auth.euid != 0)
return err(EACCES);
}
else if (current->auth.euid != inode->metadata().uid && current->auth.euid != 0)
return err(EPERM);
auto metadata = inode->metadata();
if (ktimes[0].tv_nsec != UTIME_OMIT)
{
if (ktimes[0].tv_nsec == UTIME_NOW) metadata.atime = *Timer::realtime_clock();
else
{
if (ktimes[0].tv_nsec < 0 || ktimes[0].tv_nsec > 999'999'999) return err(EINVAL);
metadata.atime = ktimes[0];
}
}
if (ktimes[1].tv_nsec != UTIME_OMIT)
{
if (ktimes[1].tv_nsec == UTIME_NOW) metadata.mtime = *Timer::realtime_clock();
else
{
if (ktimes[1].tv_nsec < 0 || ktimes[1].tv_nsec > 999'999'999) return err(EINVAL);
metadata.mtime = ktimes[1];
}
}
TRY(inode->set_metadata(metadata));
return 0;
}

View File

@ -1,6 +1,7 @@
#include "Log.h"
#include "fs/VFS.h"
#include "memory/MemoryManager.h"
#include "net/Socket.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/poll.h>
@ -43,12 +44,27 @@ Result<u64> sys_poll(Registers*, SyscallArgs args)
auto& inode = inodes[i];
if (!inode) continue;
if (kfds[i].events & POLLIN && !inode->will_block_if_read())
if (kfds[i].events & POLLIN)
{
if (inode->type() == VFS::InodeType::Socket)
{
auto socket = (Socket*)inode.ptr();
if (socket->can_read_data() || socket->can_accept_connections())
{
fds_with_events++;
kfds[i].revents |= POLLIN;
}
}
else
{
if (!inode->will_block_if_read())
{
fds_with_events++;
kfds[i].revents |= POLLIN;
}
}
}
}
if (!fds_with_events && (timeout > 0 || infinite))
{

View File

@ -48,6 +48,9 @@ Result<u64> sys_fstatat(Registers*, SyscallArgs args)
kstat.st_size = metadata.size;
kstat.st_dev = inode->fs() ? inode->fs()->host_device_id() : 0;
kstat.st_rdev = metadata.devid;
kstat.st_atim = metadata.atime;
kstat.st_mtim = metadata.mtime;
kstat.st_ctim = metadata.ctime;
if (!MemoryManager::copy_to_user_typed(st, &kstat)) return err(EFAULT);

View File

@ -23,6 +23,8 @@ set(SOURCES
src/scanf.cpp
src/signal.cpp
src/termios.cpp
src/utime.cpp
src/strtod.cpp
src/sys/stat.cpp
src/sys/mman.cpp
src/sys/wait.cpp

View File

@ -3,6 +3,7 @@
#ifndef _BITS_STRUCT_STAT_H
#define _BITS_STRUCT_STAT_H
#include <bits/timespec.h>
#include <sys/types.h>
struct stat
@ -15,10 +16,13 @@ struct stat
gid_t st_gid;
off_t st_size;
dev_t st_rdev;
// FIXME: Actually fill these fields in.
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
struct timespec st_atim;
struct timespec st_mtim;
struct timespec st_ctim;
#define st_atime st_atim.tv_sec
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
#endif

View File

@ -0,0 +1,9 @@
/* bits/utime.h: Definitions for UTIME_NOW and UTIME_OMIT. */
#ifndef _BITS_UTIME_H
#define _BITS_UTIME_H
#define UTIME_NOW -1
#define UTIME_OMIT -2
#endif

View File

@ -84,9 +84,11 @@ extern "C"
/* Parse a decimal integer from a string. */
long long atoll(const char* s);
double atof(const char*);
/* Parse a floating-point number from a string. */
double atof(const char* str);
double strtod(const char*, char**);
/* Parse a floating-point number from a string. */
double strtod(const char* str, char** endptr);
/* Parse an integer of the specified base from a string, storing the first non-number character in endptr if
* nonnull. */

View File

@ -95,6 +95,9 @@ extern "C"
/* Compare two fixed-size null-terminated strings, ignoring case. */
int strncasecmp(const char* a, const char* b, size_t max);
/* Compare two null-terminated strings according to the current locale. */
int strcoll(const char* a, const char* b);
#ifdef __cplusplus
}
#endif

View File

@ -5,6 +5,8 @@
#include <bits/modes.h>
#include <bits/struct_stat.h>
#include <bits/timespec.h>
#include <bits/utime.h>
#include <sys/types.h>
#ifdef __cplusplus
@ -41,6 +43,12 @@ extern "C"
/* Change the process's file creation mask. */
mode_t umask(mode_t mask);
/* Change a file's access and modification timestamps, with nanosecond precision. */
int utimensat(int dirfd, const char* path, const struct timespec times[2], int flags);
/* Change a file's access and modification timestamps, with nanosecond precision. */
int futimens(int fd, const struct timespec times[2], int flags);
#ifdef __cplusplus
}
#endif

View File

@ -16,6 +16,15 @@ extern "C"
/* Get the current time of day. */
__deprecated int gettimeofday(struct timeval* tp, void* timezone);
/* Change a file's access and modification timestamps, with microsecond precision. */
int utimes(const char* path, const struct timeval buf[2]);
/* Change a file descriptor's access and modification timestamps, with microsecond precision. */
int futimes(int fd, const struct timeval buf[2]);
/* Change a symlink's access and modification timestamps, with microsecond precision. */
int lutimes(const char* path, const struct timeval buf[2]);
#ifdef __cplusplus
}
#endif

26
libc/include/utime.h Normal file
View File

@ -0,0 +1,26 @@
/* utime.h: The utime function. */
#ifndef _UTIME_H
#define _UTIME_H
#include <sys/types.h>
struct utimbuf
{
time_t actime;
time_t modtime;
};
#ifdef __cplusplus
extern "C"
{
#endif
/* Change a file's access and modification timestamps. */
int utime(const char* path, const struct utimbuf* buf);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -297,4 +297,9 @@ extern "C"
if (errno != EEXIST) return -1;
}
}
double atof(const char* str)
{
return strtod(str, nullptr);
}
}

View File

@ -9,6 +9,12 @@ extern "C"
extern "C++" const char* error_string(int);
int strcoll(const char* a, const char* b)
{
// In the POSIX or C locales strcoll() is equivalent to strcmp().
return strcmp(a, b);
}
char* strerror(int errnum)
{
return const_cast<char*>(error_string(errnum));

141
libc/src/strtod.cpp Normal file
View File

@ -0,0 +1,141 @@
/* vi:set ts=8 sts=4 sw=4: */
/*
* strtod implementation.
* author: Yasuhiro Matsumoto
* license: public domain
*
* source from https://gist.github.com/mattn/1890186
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char* skipwhite(const char* q)
{
const char* p = q;
while (isspace(*p)) ++p;
return p;
}
extern "C"
{
double strtod(const char* str, char** end)
{
double d = 0.0;
int sign;
int n = 0;
const char *p, *a;
a = p = str;
p = skipwhite(p);
/* decimal part */
sign = 1;
if (*p == '-')
{
sign = -1;
++p;
}
else if (*p == '+')
++p;
if (isdigit(*p))
{
d = (double)(*p++ - '0');
while (*p && isdigit(*p))
{
d = d * 10.0 + (double)(*p - '0');
++p;
++n;
}
a = p;
}
else if (*p != '.')
goto done;
d *= sign;
/* fraction part */
if (*p == '.')
{
double f = 0.0;
double base = 0.1;
++p;
if (isdigit(*p))
{
while (*p && isdigit(*p))
{
f += base * (*p - '0');
base /= 10.0;
++p;
++n;
}
}
d += f * sign;
a = p;
}
/* exponential part */
if ((*p == 'E') || (*p == 'e'))
{
int e = 0;
++p;
sign = 1;
if (*p == '-')
{
sign = -1;
++p;
}
else if (*p == '+')
++p;
if (isdigit(*p))
{
while (*p == '0') ++p;
if (*p == '\0') --p;
e = (int)(*p++ - '0');
while (*p && isdigit(*p))
{
e = e * 10 + (int)(*p - '0');
++p;
}
e *= sign;
}
else if (!isdigit(*(a - 1)))
{
a = str;
goto done;
}
else if (*p == 0)
goto done;
if (d == 2.2250738585072011 && e == -308)
{
d = 0.0;
a = p;
errno = ERANGE;
goto done;
}
if (d == 2.2250738585072012 && e <= -308)
{
d *= 1.0e-308;
a = p;
goto done;
}
d *= __builtin_powi(10.0, e);
a = p;
}
else if (p > str && !isdigit(*(p - 1)))
{
a = str;
goto done;
}
done:
if (end) *end = const_cast<char*>(a);
return d;
}
}

View File

@ -52,4 +52,15 @@ extern "C"
{
return (mode_t)syscall(SYS_umask, mask);
}
int utimensat(int dirfd, const char* path, const struct timespec* times, int flags)
{
long rc = syscall(SYS_utimensat, dirfd, path, times, flags);
__errno_return(rc, int);
}
int futimens(int fd, const struct timespec* times, int flags)
{
return utimensat(fd, "", times, flags | AT_EMPTY_PATH);
}
}

55
libc/src/utime.cpp Normal file
View File

@ -0,0 +1,55 @@
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <utime.h>
extern "C"
{
int utime(const char* path, const struct utimbuf* buf)
{
if (!buf) return utimensat(AT_FDCWD, path, nullptr, 0);
struct timespec times[2] = {
{ .tv_sec = buf->actime, .tv_nsec = 0 },
{ .tv_sec = buf->modtime, .tv_nsec = 0 },
};
return utimensat(AT_FDCWD, path, times, 0);
}
int utimes(const char* path, const struct timeval* buf)
{
if (!buf) return utimensat(AT_FDCWD, path, nullptr, 0);
struct timespec times[2] = {
{ .tv_sec = buf[0].tv_sec, .tv_nsec = buf[0].tv_usec * 1000 },
{ .tv_sec = buf[1].tv_sec, .tv_nsec = buf[1].tv_usec * 1000 },
};
return utimensat(AT_FDCWD, path, times, 0);
}
int futimes(int fd, const struct timeval* buf)
{
if (!buf) return utimensat(fd, "", nullptr, AT_EMPTY_PATH);
struct timespec times[2] = {
{ .tv_sec = buf[0].tv_sec, .tv_nsec = buf[0].tv_usec * 1000 },
{ .tv_sec = buf[1].tv_sec, .tv_nsec = buf[1].tv_usec * 1000 },
};
return utimensat(fd, "", times, AT_EMPTY_PATH);
}
int lutimes(const char* path, const struct timeval* buf)
{
if (!buf) return utimensat(AT_FDCWD, path, nullptr, AT_SYMLINK_NOFOLLOW);
struct timespec times[2] = {
{ .tv_sec = buf[0].tv_sec, .tv_nsec = buf[0].tv_usec * 1000 },
{ .tv_sec = buf[1].tv_sec, .tv_nsec = buf[1].tv_usec * 1000 },
};
return utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW);
}
}

View File

@ -11,6 +11,9 @@ class Buffer
Buffer(Buffer&& other);
Buffer(const Buffer& other) = delete; // For now.
Buffer& operator=(Buffer&&);
Buffer& operator=(const Buffer&) = delete;
static Result<Buffer> create_sized(usize size);
Result<void> try_resize(usize new_size);

View File

@ -16,7 +16,7 @@ template <typename T, usize Size> class CircularQueue
{
}
bool is_empty()
bool is_empty() const
{
return m_tail.load() == m_head.load();
}
@ -76,7 +76,7 @@ template <typename T> class DynamicCircularQueue
if (m_data) free_impl(m_data);
}
bool is_empty()
bool is_empty() const
{
return m_tail.load() == m_head.load();
}

View File

@ -8,7 +8,7 @@
_e(umount) _e(pstat) _e(getrusage) _e(symlinkat) _e(readlinkat) _e(umask) _e(linkat) _e(faccessat) \
_e(pivot_root) _e(sigreturn) _e(sigaction) _e(kill) _e(sigprocmask) _e(setpgid) _e(isatty) \
_e(getpgid) _e(socket) _e(bind) _e(connect) _e(listen) _e(accept) _e(poll) _e(msync) \
_e(truncate) _e(ftruncate)
_e(truncate) _e(ftruncate) _e(utimensat)
enum Syscalls
{

View File

@ -15,6 +15,16 @@ Buffer::Buffer(Buffer&& other) : m_data(other.data()), m_size(other.size())
other.m_data = nullptr;
}
Buffer& Buffer::operator=(Buffer&& other)
{
if (&other == this) return *this;
if (m_data) free_impl(m_data);
m_data = other.m_data;
m_size = other.m_size;
other.m_data = nullptr;
return *this;
}
Buffer::~Buffer()
{
if (m_data) free_impl(m_data);

View File

@ -13,6 +13,8 @@ set(SOURCES
src/Path.cpp
src/Mode.cpp
src/Prompt.cpp
src/LocalServer.cpp
src/LocalClient.cpp
)
add_library(os ${SOURCES})

View File

@ -0,0 +1,90 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/StringView.h>
namespace os
{
/**
* @brief A client used to connect to a local server socket.
*/
class LocalClient
{
public:
/**
* @brief Create a new client object and connect it to a local server.
*
* @param path The path of the server socket to connect to.
* @param blocking Whether the client should block if no data is available and recv() is called.
* @return Result<OwnedPtr<LocalClient>> An error, or a new client object.
*/
static Result<OwnedPtr<LocalClient>> connect(StringView path, bool blocking);
/**
* @brief Return the underlying socket file descriptor used by this object.
*
* @return int The file descriptor.
*/
int fd() const
{
return m_fd;
}
/**
* @brief Read arbitrary data from the server. The call will block if there is no data and this object has not
* been created as non-blocking.
*
* @param buf The buffer to read data into.
* @param length The maximum amount of bytes to read.
* @return Result<usize> An error, or the number of bytes read.
*/
Result<usize> recv(u8* buf, usize length);
/**
* @brief Read an object from the server. The call will block if there is no data and this object has not been
* created as non-blocking.
*
* @tparam T The type of the object.
* @param out A reference to the object to read data into.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> recv_typed(T& out)
{
TRY(recv((u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Send arbitrary data to the server.
*
* @param buf The buffer to send data from.
* @param length The amount of bytes to send.
* @return Result<usize> An error, or the number of bytes actually sent.
*/
Result<usize> send(const u8* buf, usize length);
/**
* @brief Send an object to the server.
*
* @tparam T The type of the object.
* @param out A reference to the object to send data from.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> send_typed(const T& out)
{
TRY(send((const u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Disconnect from the attached server.
*
* This will make any further reads on this connection return ECONNRESET, and will make this object invalid.
*/
void disconnect();
~LocalClient();
private:
int m_fd;
};
}

View File

@ -0,0 +1,133 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/Result.h>
#include <luna/StringView.h>
namespace os
{
/**
* @brief A local domain server, used to communicate between processes on the same machine.
*/
class LocalServer
{
public:
/**
* @brief Create a new server object and bind it to a local address.
*
* @param path The path to use for the server socket.
* @param blocking Whether the server should block if no connections are available when calling accept().
* @return Result<OwnedPtr<LocalServer>> An error, or a new server object.
*/
static Result<OwnedPtr<LocalServer>> create(StringView path, bool blocking);
/**
* @brief Activate the server and start listening for connections.
*
* @param backlog The number of unaccepted connections to keep.
* @return Result<void> Whether the operation succeded.
*/
Result<void> listen(int backlog);
/**
* @brief Return the underlying socket file descriptor used by this object.
*
* @return int The file descriptor.
*/
int fd() const
{
return m_fd;
}
/**
* @brief An interface to communicate with clients connected to a local server.
*/
class Client
{
public:
/**
* @brief Read arbitrary data from the client. The call will block if there is no data and the parent server
* object has not been created as non-blocking.
*
* @param buf The buffer to read data into.
* @param length The maximum amount of bytes to read.
* @return Result<usize> An error, or the number of bytes read.
*/
Result<usize> recv(u8* buf, usize length);
/**
* @brief Read an object from the client. The call will block if there is no data and the parent server
* object has not been created as non-blocking.
*
* @tparam T The type of the object.
* @param out A reference to the object to read data into.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> recv_typed(T& out)
{
TRY(recv((u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Send arbitrary data to the client.
*
* @param buf The buffer to send data from.
* @param length The amount of bytes to send.
* @return Result<usize> An error, or the number of bytes actually sent.
*/
Result<usize> send(const u8* buf, usize length);
/**
* @brief Send an object to the client.
*
* @tparam T The type of the object.
* @param out A reference to the object to send data from.
* @return Result<void> Whether the operation succeded.
*/
template <typename T> Result<void> send_typed(const T& out)
{
TRY(send((const u8*)&out, sizeof(T)));
return {};
}
/**
* @brief Disconnect from the attached client.
*
* This will make any further reads on the client return ECONNRESET, and will make this object invalid.
*/
void disconnect();
/**
* @brief Return the underlying socket file descriptor used by this object.
*
* @return int The file descriptor.
*/
int fd() const
{
return m_fd;
}
Client(Client&& other);
Client(int fd);
~Client();
private:
int m_fd;
};
/**
* @brief Accept a new incoming connection and return a handle to it. If there are no incoming connections,
* accept() either blocks until there is one (if the object was created with blocking=true), or returns EAGAIN
* (if the object was created with blocking=false).
*
* @return Result<Client> An error, or a handle to the new connection.
*/
Result<Client> accept();
~LocalServer();
private:
int m_fd;
bool m_blocking;
};
}

59
libos/src/LocalClient.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <errno.h>
#include <fcntl.h>
#include <os/LocalClient.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace os
{
Result<OwnedPtr<LocalClient>> LocalClient::connect(StringView path, bool blocking)
{
auto client = TRY(make_owned<LocalClient>());
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) return err(errno);
struct sockaddr_un un;
un.sun_family = AF_UNIX;
strncpy(un.sun_path, path.chars(), sizeof(un.sun_path));
if (::connect(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0)
{
close(sockfd);
return err(errno);
}
if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); }
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
client->m_fd = sockfd;
return client;
}
LocalClient::~LocalClient()
{
close(m_fd);
}
Result<usize> LocalClient::recv(u8* buf, usize length)
{
ssize_t nread = read(m_fd, buf, length);
if (nread < 0) return err(errno);
return nread;
}
Result<usize> LocalClient::send(const u8* buf, usize length)
{
ssize_t nwrite = write(m_fd, buf, length);
if (nwrite < 0) return err(errno);
return nwrite;
}
void LocalClient::disconnect()
{
close(m_fd);
m_fd = -1;
}
}

92
libos/src/LocalServer.cpp Normal file
View File

@ -0,0 +1,92 @@
#include <errno.h>
#include <fcntl.h>
#include <os/FileSystem.h>
#include <os/LocalServer.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace os
{
Result<OwnedPtr<LocalServer>> LocalServer::create(StringView path, bool blocking)
{
auto server = TRY(make_owned<LocalServer>());
(void)os::FileSystem::remove(path); // We explicitly ignore any error here, either it doesn't exist (which is
// fine), or it cannot be removed, which will make bind() fail later.
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) return err(errno);
struct sockaddr_un un;
un.sun_family = AF_UNIX;
strncpy(un.sun_path, path.chars(), sizeof(un.sun_path));
if (bind(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0)
{
close(sockfd);
return err(errno);
}
if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); }
server->m_blocking = blocking;
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
server->m_fd = sockfd;
return server;
}
Result<void> LocalServer::listen(int backlog)
{
if (::listen(m_fd, backlog) < 0) return err(errno);
return {};
}
Result<LocalServer::Client> LocalServer::accept()
{
int fd = ::accept(m_fd, nullptr, nullptr);
if (fd < 0) return err(errno);
if (!m_blocking) fcntl(fd, F_SETFL, O_NONBLOCK);
return Client { fd };
}
LocalServer::~LocalServer()
{
close(m_fd);
}
LocalServer::Client::Client(Client&& other) : m_fd(other.m_fd)
{
other.m_fd = -1;
}
LocalServer::Client::Client(int fd) : m_fd(fd)
{
}
LocalServer::Client::~Client()
{
if (m_fd >= 0) close(m_fd);
}
Result<usize> LocalServer::Client::recv(u8* buf, usize length)
{
ssize_t nread = read(m_fd, buf, length);
if (nread < 0) return err(errno);
return nread;
}
Result<usize> LocalServer::Client::send(const u8* buf, usize length)
{
ssize_t nwrite = write(m_fd, buf, length);
if (nwrite < 0) return err(errno);
return nwrite;
}
void LocalServer::Client::disconnect()
{
close(m_fd);
m_fd = -1;
}
}

17
libui/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
# The UI and graphics library for Luna.
file(GLOB HEADERS include/ui/*.h)
set(SOURCES
${HEADERS}
src/Canvas.cpp
src/Rect.cpp
src/Font.cpp
src/Image.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)

64
libui/include/ui/Canvas.h Normal file
View File

@ -0,0 +1,64 @@
#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);
};
};

104
libui/include/ui/Color.h Normal file
View File

@ -0,0 +1,104 @@
#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);
};

111
libui/include/ui/Font.h Normal file
View File

@ -0,0 +1,111 @@
#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 direct rendering of glyphs into a canvas.
*/
class Font : public Shareable
{
public:
/**
* @brief An enum used to select a font weight when loading a font.
*/
enum FontWeight
{
Regular,
Bold,
};
/**
* @brief Load a Font object from a font file.
*
* @param path The full path to the font file.
* @return Result<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;
};
};

71
libui/include/ui/Image.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.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;
};
}

13
libui/include/ui/Point.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
namespace ui
{
/**
* @brief A point in 2D space.
*/
struct Point
{
int x { 0 };
int y { 0 };
};
}

72
libui/include/ui/Rect.h Normal file
View File

@ -0,0 +1,72 @@
#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();
};
}

53
libui/src/Canvas.cpp Normal file
View File

@ -0,0 +1,53 @@
#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);
}
}
}

112
libui/src/Font.cpp Normal file
View File

@ -0,0 +1,112 @@
#include <luna/String.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(String::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;
}
}
}
}

24
libui/src/Image.cpp Normal file
View File

@ -0,0 +1,24 @@
#include <os/File.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;
}
}

53
libui/src/Rect.cpp Normal file
View File

@ -0,0 +1,53 @@
#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

@ -12,6 +12,7 @@ Try to keep this list in alphabetical order.
Name | Version | Description | URL
---|---|--- | ---
bc | 6.6.0 | An implementation of the POSIX bc calculator | https://github.com/gavinhoward/bc
binutils | 2.39 | The GNU suite of binary utilities | https://www.gnu.org/software/binutils
minitar | 1.7.5 | Tiny and easy-to-use C library to read/write tar archives | https://git.cloudapio.eu/apio/minitar
nasm | 2.16.01 | An assembler for the x86 CPU architecture | https://nasm.us

30
ports/binutils/PACKAGE Normal file
View File

@ -0,0 +1,30 @@
# Basic information
name="binutils"
version="2.39"
# Download options
format="tar"
url="https://ftp.gnu.org/gnu/binutils/binutils-$version.tar.gz"
output="binutils-$version.tar.gz"
sha256sum="d12ea6f239f1ffe3533ea11ad6e224ffcb89eb5d01bbea589e9158780fa11f10"
# Build instructions
default_build_make=true
do_patch()
{
patch -ui $portdir/binutils.patch -p 1 -d $srcdir/..
}
do_configure()
{
$srcdir/configure --host=$LUNA_ARCH-luna --disable-nls --disable-werror --enable-warn-rwx-segments=no --prefix=/usr --enable-gold=no --enable-ld=yes --enable-gprofng=no
}
do_install()
{
make install
cd $installdir/usr/bin
$LUNA_ARCH-luna-strip size objdump ar strings ranlib objcopy addr2line readelf elfedit nm strip c++filt as gprof ld.bfd ld
}

View File

@ -0,0 +1,117 @@
diff --color -rN -u binutils-2.39/bfd/config.bfd build/binutils-2.39/bfd/config.bfd
--- a/binutils-2.39/bfd/config.bfd 2022-01-22 13:14:07.000000000 +0100
+++ b/binutils-2.39/bfd/config.bfd 2022-10-01 22:12:16.914033792 +0200
@@ -651,6 +651,11 @@
targ_selvecs="iamcu_elf32_vec i386_pei_vec"
targ64_selvecs="x86_64_elf64_vec x86_64_elf32_vec x86_64_pe_vec x86_64_pei_vec l1om_elf64_vec k1om_elf64_vec"
;;
+ i[3-7]86-*-luna*)
+ targ_defvec=i386_elf32_vec
+ targ_selvecs=
+ targ64_selvecs=x86_64_elf64_vec
+ ;;
i[3-7]86-*-redox*)
targ_defvec=i386_elf32_vec
targ_selvecs=
@@ -706,6 +711,11 @@
targ_selvecs="i386_elf32_vec iamcu_elf32_vec x86_64_elf32_vec i386_pei_vec x86_64_pe_vec x86_64_pei_vec l1om_elf64_vec k1om_elf64_vec"
want64=true
;;
+ x86_64-*-luna*)
+ targ_defvec=x86_64_elf64_vec
+ targ_selvecs=i386_elf32_vec
+ want64=true
+ ;;
x86_64-*-mingw* | x86_64-*-pe | x86_64-*-pep | x86_64-*-cygwin)
targ_defvec=x86_64_pe_vec
targ_selvecs="x86_64_pe_vec x86_64_pei_vec x86_64_pe_big_vec x86_64_elf64_vec l1om_elf64_vec k1om_elf64_vec i386_pe_vec i386_pei_vec i386_elf32_vec iamcu_elf32_vec"
diff --color -rN -u binutils-2.39/gas/configure.tgt build/binutils-2.39/gas/configure.tgt
--- a/binutils-2.39/gas/configure.tgt 2022-01-22 13:14:08.000000000 +0100
+++ b/binutils-2.39/gas/configure.tgt 2022-10-01 22:12.39.115093972 +0200
@@ -238,6 +238,7 @@
x86_64*-linux-gnux32) arch=x86_64:32 ;;
esac ;;
i386-*-lynxos*) fmt=elf em=lynx ;;
+ i386-*-luna*) fmt=elf em=gnu ;;
i386-*-redox*) fmt=elf ;;
i386-*-solaris*) fmt=elf em=solaris ;;
i386-*-freebsd* \
diff --color -rN -u binutils-2.39/ld/configure.tgt build/binutils-2.39/ld/configure.tgt
--- a/binutils-2.39/ld/configure.tgt 2022-01-22 15:19:36.000000000 +0100
+++ b/binutils-2.39/ld/configure.tgt 2022-10-01 22:15:04.853571062 +0200
@@ -329,6 +329,11 @@
targ64_extra_emuls="elf_x86_64 elf32_x86_64 elf_l1om elf_k1om"
targ64_extra_libpath="elf_x86_64 elf32_x86_64"
;;
+i[3-7]86-*-luna*)
+ targ_emul=elf_i386_luna
+ targ_extra_emuls=elf_i386
+ targ64_extra_emuls="elf_x86_64_luna elf_x86_64"
+ ;;
i[3-7]86-*-redox*) targ_emul=elf_i386
targ_extra_emuls=elf_x86_64
;;
@@ -967,6 +972,10 @@
targ_extra_libpath="elf_i386 elf32_x86_64 elf_l1om elf_k1om"
tdir_elf_i386=`echo ${targ_alias} | sed -e 's/x86_64/i386/'`
;;
+x86_64-*-luna*)
+ targ_emul=elf_x86_64_luna
+ targ_extra_emuls="elf_i386_luna elf_x86_64 elf_i386"
+ ;;
x86_64-*-redox*) targ_emul=elf_x86_64
targ_extra_emuls=elf_i386
;;
diff --color -rN -u binutils-2.39/ld/emulparams/elf_i386_luna.sh build/binutils-2.39/ld/emulparams/elf_i386_luna.sh
--- a/dev/null 1970-01-01 01:00:00.000000000 +0100
+++ b/binutils-2.39/ld/emulparams/elf_i386_luna.sh 2022-10-01 21:52:12.394068335 +0200
@@ -0,0 +1,3 @@
+source_sh ${srcdir}/emulparams/elf_i386.sh
+TEXT_START_ADDR=0x08000000
+MAXPAGESIZE=0x1000
\ No newline at end of file
diff --color -rN -u binutils-2.39/ld/emulparams/elf_x86_64_luna.sh build/binutils-2.39/ld/emulparams/elf_x86_64_luna.sh
--- a/dev/null 1970-01-01 01:00:00.000000000 +0100
+++ b/binutils-2.39/ld/emulparams/elf_x86_64_luna.sh 2022-10-01 21:53:00.411200592 +0200
@@ -0,0 +1,2 @@
+source_sh ${srcdir}/emulparams/elf_x86_64.sh
+MAXPAGESIZE=0x1000
\ No newline at end of file
diff --color -rN -u binutils-2.39/ld/Makefile.am build/binutils-2.39/ld/Makefile.am
--- a/binutils-2.39/ld/Makefile.am 2022-01-22 13:14:09.000000000 +0100
+++ b/binutils-2.39/ld/Makefile.am 2022-10-01 22:18:02.660263017 +0200
@@ -278,6 +278,7 @@
eelf32xtensa.c \
eelf32z80.c \
eelf_i386.c \
+ eelf_i386_luna.c \
eelf_i386_be.c \
eelf_i386_fbsd.c \
eelf_i386_haiku.c \
@@ -464,6 +465,7 @@
eelf_x86_64_fbsd.c \
eelf_x86_64_haiku.c \
eelf_x86_64_sol2.c \
+ eelf_x86_64_luna.c \
ehppa64linux.c \
ei386pep.c \
emmo.c
diff --color -rN -u binutils-2.39/ld/Makefile.in build/binutils-2.39/ld/Makefile.in
--- a/binutils-2.39/ld/Makefile.in 2022-02-09 12:49:03.000000000 +0100
+++ b/binutils-2.39/ld/Makefile.in 2022-10-01 22:17:46.740196925 +0200
@@ -769,6 +769,7 @@
eelf32xtensa.c \
eelf32z80.c \
eelf_i386.c \
+ eelf_i386_luna.c \
eelf_i386_be.c \
eelf_i386_fbsd.c \
eelf_i386_haiku.c \
@@ -954,6 +955,7 @@
eelf_x86_64_fbsd.c \
eelf_x86_64_haiku.c \
eelf_x86_64_sol2.c \
+ eelf_x86_64_luna.c \
ehppa64linux.c \
ei386pep.c \
emmo.c

View File

@ -8,10 +8,12 @@ cd $LUNA_ROOT
mkdir -p $LUNA_BASE
mkdir -p $LUNA_BASE/usr/include
mkdir -p $LUNA_BASE/usr/include/luna
mkdir -p $LUNA_BASE/usr/include/ui
mkdir -p $LUNA_BASE/usr/include/os
mkdir -p $LUNA_BASE/usr/include/moon
cp --preserve=timestamps -RT libc/include/ $LUNA_BASE/usr/include
cp --preserve=timestamps -RT libluna/include/luna/ $LUNA_BASE/usr/include/luna
cp --preserve=timestamps -RT libui/include/ui/ $LUNA_BASE/usr/include/ui
cp --preserve=timestamps -RT libos/include/os/ $LUNA_BASE/usr/include/os
cp --preserve=timestamps -RT kernel/src/api/ $LUNA_BASE/usr/include/moon

View File

@ -7,6 +7,10 @@ cd $LUNA_ROOT
fakeroot -u -s $LUNA_ROOT/.fakeroot -- tools/install.sh
fakeroot -u -i $LUNA_ROOT/.fakeroot -- genext2fs -d base -B 4096 -b 8192 -L luna-rootfs -N 2048 build/ext2fs.bin
disk_space=$(du -s base | awk '{ print $1 }')
min_blocks=$(($disk_space / 4)) # This is just the blocks needed for all the files in the file system: this excludes space for the inode table, block group descriptors, etc.
blocks=$(($min_blocks + 1024)) # This is the actual number of blocks we're using.
fakeroot -u -i $LUNA_ROOT/.fakeroot -- genext2fs -d base -B 4096 -b $blocks -L luna-rootfs -N 1024 build/ext2fs.bin
mkbootimg luna.json Luna.iso

View File

@ -4,7 +4,7 @@ source $(dirname $0)/env.sh
cd $LUNA_ROOT
FOLDERS=(kernel libc libos libluna apps shell tests)
FOLDERS=(kernel libc libos libui libluna apps shell tests)
SOURCES=($(find ${FOLDERS[@]} -type f -name "*.cpp"))
SOURCES+=($(find ${FOLDERS[@]} -type f -name "*.h"))

16
wind/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
set(SOURCES
main.cpp
Screen.h
Screen.cpp
Mouse.h
Mouse.cpp
Window.h
Window.cpp
)
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)

77
wind/Mouse.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "Mouse.h"
#include <os/File.h>
#include <ui/Image.h>
static SharedPtr<ui::Image> g_mouse_cursor;
Mouse::Mouse(ui::Canvas& screen)
{
m_position.x = screen.width / 2;
m_position.y = screen.height / 2;
m_screen_rect = screen.rect();
g_mouse_cursor = ui::Image::load("/usr/share/cursors/default.tga").value_or({});
}
void Mouse::draw(ui::Canvas& screen)
{
if (!g_mouse_cursor) return;
auto canvas = screen.subcanvas(ui::Rect { m_position, g_mouse_cursor->width(), g_mouse_cursor->height() });
canvas.fill(g_mouse_cursor->pixels(), g_mouse_cursor->width());
}
void Mouse::update(const moon::MousePacket& packet)
{
m_position.x += packet.xdelta;
m_position.y -= packet.ydelta;
m_position = m_screen_rect.normalize(m_position);
if (m_dragging_window && !(packet.buttons & moon::MouseButton::Left))
{
os::println("Stopped drag: window at (%d,%d,%d,%d) with offset (%d,%d)", m_dragging_window->surface.pos.x,
m_dragging_window->surface.pos.y, m_dragging_window->surface.width,
m_dragging_window->surface.height, this->m_initial_drag_position.x,
this->m_initial_drag_position.y);
m_dragging_window = nullptr;
}
if (m_dragging_window)
{
m_dragging_window->surface.pos =
ui::Point { m_position.x - m_initial_drag_position.x, m_position.y - m_initial_drag_position.y };
m_dragging_window->surface = m_dragging_window->surface.normalized();
}
else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window)
{
// Iterate from the end of the list, since windows at the beginning are stacked at the bottom and windows at the
// top are at the end.
for (Window* window = g_windows.last().value_or(nullptr); window;
window = g_windows.previous(window).value_or(nullptr))
{
if (window->surface.absolute(window->close_button).contains(m_position))
{
// Close button pressed
g_windows.remove(window);
delete window;
break;
}
else if (window->surface.absolute(window->titlebar).contains(m_position))
{
m_dragging_window = window;
m_initial_drag_position = window->surface.relative(m_position);
os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x,
window->surface.pos.y, window->surface.width, window->surface.height,
m_initial_drag_position.x, m_initial_drag_position.y);
window->focus();
break;
}
else if (window->surface.absolute(window->contents).contains(m_position))
{
window->focus();
break; // We don't want to continue iterating, otherwise this would take into account windows whose
// titlebar is underneath another window's contents!
}
}
}
}

22
wind/Mouse.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "Screen.h"
#include "Window.h"
#include <moon/Mouse.h>
#include <ui/Canvas.h>
class Mouse
{
public:
Mouse(ui::Canvas& screen);
void update(const moon::MousePacket& packet);
void draw(ui::Canvas& screen);
private:
ui::Point m_position;
ui::Rect m_screen_rect;
Window* m_dragging_window = nullptr;
ui::Point m_initial_drag_position;
};

32
wind/Screen.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "Screen.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
Result<Screen> Screen::open()
{
int fd = ::open("/dev/fb0", O_RDWR);
if (fd < 0) return err(errno);
int width = ioctl(fd, FB_GET_WIDTH);
int height = ioctl(fd, FB_GET_HEIGHT);
void* p = mmap(nullptr, width * height * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
if (p == MAP_FAILED) { return err(errno); }
Screen screen;
screen.m_canvas = ui::Canvas::create((u8*)p, width, height);
screen.m_size = width * height * BYTES_PER_PIXEL;
return screen;
}
void Screen::sync()
{
msync(m_canvas.ptr, size(), MS_SYNC);
}

27
wind/Screen.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <ui/Canvas.h>
constexpr int BYTES_PER_PIXEL = 4;
class Screen
{
public:
static Result<Screen> open();
ui::Canvas& canvas()
{
return m_canvas;
}
int size() const
{
return m_size;
}
void sync();
private:
ui::Canvas m_canvas;
int m_size;
};

50
wind/Window.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "Window.h"
#include <luna/Utf8.h>
#include <os/File.h>
#include <ui/Font.h>
#include <ui/Image.h>
LinkedList<Window> g_windows;
void Window::draw(ui::Canvas& screen)
{
auto window = screen.subcanvas(surface);
window.subcanvas(contents).fill(color);
wchar_t buffer[4096];
Utf8StringDecoder decoder(name.chars());
decoder.decode(buffer, sizeof(buffer)).release_value();
auto font = ui::Font::default_font();
auto titlebar_canvas = window.subcanvas(titlebar);
titlebar_canvas.fill(ui::GRAY);
auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height });
font->render(buffer, ui::BLACK, textarea);
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_area = window.subcanvas(close_button);
close_area.fill(g_close_icon->pixels(), g_close_icon->width());
}
void Window::focus()
{
// Bring the window to the front of the list.
g_windows.remove(this);
g_windows.append(this);
}
Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), name(n)
{
auto font = ui::Font::default_font();
if (surface.width < 36) surface.width = 36;
if (surface.height < (font->height() + 20)) surface.height = font->height() + 20;
titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 };
close_button = ui::Rect { surface.width - 26, 10, 16, 16 };
contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) };
g_windows.append(this);
}

24
wind/Window.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <luna/LinkedList.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Color.h>
#include <ui/Rect.h>
struct Window : public LinkedListNode<Window>
{
ui::Rect surface;
ui::Rect titlebar;
ui::Rect close_button;
ui::Rect contents;
ui::Color color;
StringView name;
Window(ui::Rect, ui::Color, StringView);
void focus();
void draw(ui::Canvas& screen);
};
extern LinkedList<Window> g_windows;

131
wind/main.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "Mouse.h"
#include "Screen.h"
#include "Window.h"
#include <errno.h>
#include <moon/Keyboard.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/LocalServer.h>
#include <os/Process.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <time.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
{
srand((unsigned)time(NULL));
StringView socket_path = "/tmp/wind.sock";
StringView user;
os::ArgumentParser parser;
parser.add_description("The display server for Luna's graphical user interface."_sv);
parser.add_system_program_info("wind"_sv);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.add_value_argument(user, 'u', "user"_sv, "the user to run as"_sv);
parser.parse(argc, argv);
if (geteuid() != 0)
{
os::eprintln("error: wind must be run as root to initialize resources, run with --user=<USERNAME> to drop "
"privileges afterwards");
return 1;
}
auto mouse = TRY(os::File::open("/dev/mouse", os::File::ReadOnly));
mouse->set_buffer(os::File::NotBuffered);
mouse->set_close_on_exec();
auto keyboard = TRY(os::File::open("/dev/kbd", os::File::ReadOnly));
keyboard->set_buffer(os::File::NotBuffered);
keyboard->set_close_on_exec();
auto screen = TRY(Screen::open());
Mouse mouse_pointer { screen.canvas() };
ioctl(STDIN_FILENO, TTYSETGFX, 1);
setpgid(0, 0);
int fd = open("/dev/null", O_RDONLY);
if (fd >= 0)
{
dup2(fd, STDIN_FILENO);
close(fd);
}
clearenv();
if (!user.is_empty())
{
auto* pwd = getpwnam(user.chars());
if (pwd)
{
setgid(pwd->pw_gid);
setuid(pwd->pw_uid);
}
}
auto server = TRY(os::LocalServer::create(socket_path, false));
TRY(server->listen(20));
StringView args[] = { "/usr/bin/gclient"_sv };
TRY(os::Process::spawn("/usr/bin/gclient"_sv, Slice<StringView> { args, 1 }, false));
ui::Color background = ui::BLACK;
TRY(make<Window>(ui::Rect { 200, 200, 600, 400 }, ui::GREEN, "Calculator"_sv));
TRY(make<Window>(ui::Rect { 100, 100, 300, 200 }, ui::RED, "Settings"_sv));
TRY(make<Window>(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv));
Vector<os::LocalServer::Client> clients;
while (1)
{
screen.canvas().fill(background);
for (auto* window : g_windows) window->draw(screen.canvas());
mouse_pointer.draw(screen.canvas());
screen.sync();
struct pollfd fds[] = {
{ .fd = mouse->fd(), .events = POLLIN, .revents = 0 },
{ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 },
{ .fd = server->fd(), .events = POLLIN, .revents = 0 },
};
int rc = poll(fds, 3, 1000);
if (!rc) continue;
if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); }
if (fds[0].revents & POLLIN)
{
moon::MousePacket packet;
TRY(mouse->read_typed(packet));
mouse_pointer.update(packet);
}
if (fds[1].revents & POLLIN)
{
moon::KeyboardPacket packet;
TRY(keyboard->read_typed(packet));
if (!packet.released)
{
TRY(make<Window>(ui::Rect { rand() % screen.canvas().width, rand() % screen.canvas().height,
rand() % screen.canvas().width, rand() % screen.canvas().height },
ui::Color::from_rgb(static_cast<u8>(rand() % 256), static_cast<u8>(rand() % 256),
static_cast<u8>(rand() % 256)),
strerror(packet.key)));
}
}
if (fds[2].revents & POLLIN)
{
auto client = TRY(server->accept());
os::println("wind: New client connected!");
TRY(clients.try_append(move(client)));
}
}
}