Compare commits
46 Commits
1bd6cf048b
...
38f799ce39
Author | SHA1 | Date | |
---|---|---|---|
38f799ce39 | |||
fac7a68445 | |||
fdd9f6103b | |||
6a74546a71 | |||
b3cc53f7e9 | |||
361e57b17c | |||
dfee0981b8 | |||
b0c905a33e | |||
4d1d4342ae | |||
6404d01a5e | |||
a510c58de0 | |||
632252e1d1 | |||
db7dc09844 | |||
d142969eb0 | |||
0e24e6a79a | |||
d7fc3356e6 | |||
d1b31ab6aa | |||
e72a1a3697 | |||
26a61a6069 | |||
f87952a615 | |||
b2037e978f | |||
78763b97f8 | |||
5c3b5aae85 | |||
2261ffb31d | |||
5343903e54 | |||
ae7b712792 | |||
6fdb245759 | |||
0fe9b63f0e | |||
3dbe8418a1 | |||
c5900b7663 | |||
0a06978fee | |||
be99376307 | |||
a261603655 | |||
a122ff133e | |||
adec709fe7 | |||
919c71ff85 | |||
1caa2c0888 | |||
8748364b7e | |||
49662b6069 | |||
d96ff92461 | |||
bfb76b5625 | |||
8c13513bf4 | |||
37e9b25b62 | |||
a92077d311 | |||
1481a4736a | |||
4195e7f206 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -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/
|
||||
|
@ -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)
|
||||
|
14
README.md
14
README.md
@ -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
|
||||
|
||||
|
@ -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
20
apps/gclient.cpp
Normal 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;
|
||||
}
|
@ -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
47
apps/touch.cpp
Normal 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;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
Name=motd
|
||||
Description=Show the message of the day to the user.
|
||||
Command=/usr/bin/cat /etc/motd
|
||||
Wait=true
|
@ -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
|
@ -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
|
||||
|
BIN
base/usr/share/cursors/default.tga
Normal file
BIN
base/usr/share/cursors/default.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 1004 B |
BIN
base/usr/share/fonts/Tamsyn-Bold.psf
Normal file
BIN
base/usr/share/fonts/Tamsyn-Bold.psf
Normal file
Binary file not shown.
BIN
base/usr/share/fonts/Tamsyn-Regular.psf
Normal file
BIN
base/usr/share/fonts/Tamsyn-Regular.psf
Normal file
Binary file not shown.
BIN
base/usr/share/icons/16x16/app-close.tga
Normal file
BIN
base/usr/share/icons/16x16/app-close.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
9
libc/include/bits/utime.h
Normal file
9
libc/include/bits/utime.h
Normal 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
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
26
libc/include/utime.h
Normal 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
|
@ -297,4 +297,9 @@ extern "C"
|
||||
if (errno != EEXIST) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
double atof(const char* str)
|
||||
{
|
||||
return strtod(str, nullptr);
|
||||
}
|
||||
}
|
||||
|
@ -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
141
libc/src/strtod.cpp
Normal 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;
|
||||
}
|
||||
}
|
@ -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
55
libc/src/utime.cpp
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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})
|
||||
|
90
libos/include/os/LocalClient.h
Normal file
90
libos/include/os/LocalClient.h
Normal 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;
|
||||
};
|
||||
}
|
133
libos/include/os/LocalServer.h
Normal file
133
libos/include/os/LocalServer.h
Normal 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
59
libos/src/LocalClient.cpp
Normal 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
92
libos/src/LocalServer.cpp
Normal 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
17
libui/CMakeLists.txt
Normal 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
64
libui/include/ui/Canvas.h
Normal 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
104
libui/include/ui/Color.h
Normal 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
111
libui/include/ui/Font.h
Normal 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
71
libui/include/ui/Image.h
Normal 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
13
libui/include/ui/Point.h
Normal 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
72
libui/include/ui/Rect.h
Normal 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
53
libui/src/Canvas.cpp
Normal 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
112
libui/src/Font.cpp
Normal 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
24
libui/src/Image.cpp
Normal 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
53
libui/src/Rect.cpp
Normal 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 };
|
||||
}
|
||||
};
|
@ -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
30
ports/binutils/PACKAGE
Normal 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
|
||||
}
|
117
ports/binutils/binutils.patch
Normal file
117
ports/binutils/binutils.patch
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
16
wind/CMakeLists.txt
Normal 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
77
wind/Mouse.cpp
Normal 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
22
wind/Mouse.h
Normal 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
32
wind/Screen.cpp
Normal 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
27
wind/Screen.h
Normal 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
50
wind/Window.cpp
Normal 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
24
wind/Window.h
Normal 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
131
wind/main.cpp
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user