diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9864e13..8a728fb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,3 +27,5 @@ A clear and concise description of what the bug is. What did you expect to happe **5. Additional Context:** Add any other context about the problem here (e.g., "This only happens when my mouse is moving"). + +**6. Please add tags to your issue to help with organization.** \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 39e9279..c9bb6c7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -21,3 +21,5 @@ Let us know if this is just an idea you'd like to see, or if you plan on submitt **5. Additional Context:** Add any other context, mockup screenshots, or links to technical documentation (e.g., OSDev Wiki links) here. + +**6. Please add tags to your issue to help with organization.** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e077795..d7e81c2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -15,6 +15,7 @@ Notes: --- ## Documentation +- [ ] Code contains appropriate comments (REQUIRED for medium to large PR's.) - [ ] Documentation updated if needed diff --git a/README.md b/README.md index f1ed5f7..01948ab 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,77 @@ +## ๐Ÿ“š Documentation + +| Guide | Description | +|-------|-------------| +| [Documentation Index](docs/README.md) | Start here! | +| [Architecture Overview](docs/architecture/README.md) | Deep dive into the kernel | +| [Building and Running](docs/build/usage.md) | Set up your build environment | +| [AppDev SDK](docs/appdev/custom_apps.md) | Build your own apps for BoredOS | + +--- + +## Contributors + +
+ BoredOS Logo + +

A modern x86_64 hobbyist operating system built from the ground up.

+ + [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + ![Platform: x86_64](https://img.shields.io/badge/Platform-x86_64-lightgrey) + ![Status: Active](https://img.shields.io/badge/Status-Active-brightgreen) + ![GitHub all releases](https://img.shields.io/github/downloads/boreddevnl/BoredOS/total?color=brightgreen) + +
+ + [Docs](docs/README.md) ยท [Build & Run](docs/build/usage.md) ยท [AppDev SDK](docs/appdev/sdk_reference.md) ยท [Discord](https://discord.gg/J2BxWaFAgY) ยท [Support](https://buymeacoffee.com/boreddevhq) + +
+ +--- + +![Screenshot](branding/screenshot.jpg) + +> [!NOTE] +> The screenshot above may represent a previous build and is subject to change as the UI evolves. + +--- + +## Features + +### Kernel and Architecture +- **Long Mode Architecture** โ€” Native x86_64 implementation utilizing 64-bit address space and registers +- **Symmetric Multi-Processing** โ€” Scalable multi-core support with IPI-based scheduling and synchronization +- **Advanced Memory Management** โ€” Custom slab allocator with object pooling and efficient physical/virtual page mapping +- **Hybrid VFS Layer** โ€” Unified filesystem interface supporting FAT32, TAR, ProcFS, and SysFS +- **Preemptive Multitasking** โ€” Prioritized process scheduling with full context isolation +- **Hardware Abstraction** โ€” Comprehensive driver support for PCI, AHCI, PS/2, and ACPI + +### Graphical Desktop Environment +- **BoredWM** โ€” High-performance window manager featuring window stacking, focus management, and drag-and-drop interactions +- **Typography Engine** โ€” Integrated font manager with TrueType (TTF) support and efficient glyph caching +- **Rich Media Subsystem** โ€” Native hardware-independent decoding for PNG, JPEG, GIF, BMP, and TGA formats +- **LibWidget Toolkit** โ€” Native UI component library for rapid application development + +### Networking Stack +- **TCP/IP Integration** โ€” Full lwIP-based network stack featuring DHCP, DNS, and Berkeley-style sockets +- **Network Services** โ€” Integrated support for basic web browsing and real-time network telemetry + +### Application Ecosystem +| Category | Applications | +|----------|--------------| +| Productivity | Text Editor, Markdown Viewer, BoredWord Processor, Web Browser, Calculator | +| Development | TCC (Tiny C Compiler), Lua| +| System | Explorer (File Manager), Task Manager, System Monitor, Graphing Utility | +| Games | doomgeneric, Minesweeper, 2048, Snake | + + +--- + + + ## ๐Ÿ“š Documentation | Guide | Description | @@ -99,6 +170,8 @@
Artwork + +
@@ -109,10 +182,24 @@

- Artwork + Naplon74

- Contributor + Artwork + + +
+ pixelyblah +

+ Artwork + + + +
+ qwroffc +

+ Artwork + @@ -147,3 +234,35 @@ Distributed under the **GNU General Public License v3**. See [`LICENSE`](LICENSE > [!IMPORTANT] > You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the [`NOTICE`](NOTICE) file for more details. + +--- + +## โ˜• Support the Journey + +If you find BoredOS interesting or useful, consider fueling development with a coffee! + + + Buy Me A Coffee + + +--- + +## History + +**BoredOS** is the successor to **[BrewKernel](https://github.com/boreddevnl/brewkernel)**, a project started in 2023. BrewKernel served as the foundational learning ground but has since been officially deprecated and archived โ€” it no longer receives updates, bug fixes, or pull request reviews. + +BoredOS is a complete architectural reboot, applying years of lessons learned to build a cleaner, more modular, and more capable system. + +> [!IMPORTANT] +> Please direct all issues, discussions, and contributions to this repository. Legacy BrewKernel code is preserved for historical purposes only and is not compatible with BoredOS. + +--- + +## License + +**Copyright (C) 2023โ€“2026 boreddevnl** + +Distributed under the **GNU General Public License v3**. See [`LICENSE`](LICENSE) for details. + +> [!IMPORTANT] +> You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the [`NOTICE`](NOTICE) file for more details. diff --git a/docs/build/toolchain.md b/docs/build/toolchain.md index d3e6c79..adfb5c4 100644 --- a/docs/build/toolchain.md +++ b/docs/build/toolchain.md @@ -2,6 +2,13 @@ BoredOS is built cross-compiled from a host system (such as macOS or Linux) to target the generic `x86_64-elf` platform. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Building the Cross-Compiler on Linux](#building-the-cross-compiler-on-linux) +- [Installing the Toolchain on Windows](#installing-the-toolchain-on-windows) + ## Prerequisites To build BoredOS, you need the following tools: @@ -65,3 +72,123 @@ x86_64-elf-gcc --version ``` > **Note**: Building the cross-compiler can take 20-30 minutes depending on system performance. This is a one-time setup cost. + +## Installing the Toolchain on Windows +### Recommended Environment: MSYS2 + +On Windows, the recommended way to build BoredOS is using **MSYS2**. +MSYS2 provides a Unix-like environment with the `pacman` package manager, making it easy to install the required development tools. + +--- + +## 1. Install MSYS2 + +Download and install MSYS2 from the official website: + +- https://www.msys2.org/ + +After installation, launch the **MSYS2 UCRT64** terminal. + +--- + +## 2. Update MSYS2 + +Before installing packages, fully update the environment: + +```bash +pacman -Syu +``` + +You may be asked to close the terminal after the first update. + +If so: + +1. Close the MSYS2 window +2. Reopen **MSYS2 UCRT64** +3. Run the update command again: + +```bash +pacman -Syu +``` + +Repeat until no further updates are available. + +--- + +## 3. Install Required Packages + +Install the required development tools: + +```bash +pacman -S make nasm xorriso git +``` + +--- + +## 4. Install QEMU for Windows + +Download the Windows version of QEMU from: + +- https://qemu.weilnetz.de/w64/ + +Install QEMU normally and make sure the installation directory is added to your Windows `PATH`. +Note that if it breaks when building, you need too add `qemu-img` to your `PATH`: +`export PATH="/c/Program Files/qemu:$PATH"` + +You can verify the installation with: + +```bash +qemu-system-x86_64 --version +``` + +--- + +## 5. Install the x86_64 ELF Cross Toolchain + +Download the prebuilt `x86_64-elf` toolchain for Windows: + +- https://github.com/lordmilko/i686-elf-tools/releases/download/15.2.0/x86_64-elf-tools-windows.zip + +Extract the archive somewhere convenient. + +--- + +## 6. Add the Toolchain to PATH + +Inside the **MSYS2 UCRT64** terminal, add the toolchain binaries to your `PATH`: + +```bash +export PATH="/c/Users/your/path/to/the/binaries/x86_64-elf-tools-windows/bin:$PATH" +``` + +To make this permanent, add the line to your `~/.bashrc` file: + +```bash +echo 'export PATH="/c/Users/your/path/to/the/binaries/x86_64-elf-tools-windows/bin:$PATH"' >> ~/.bashrc +``` + +Then reload the shell: + +```bash +source ~/.bashrc +``` + +--- + +## 7. Verify the Installation + +Verify that the cross compiler is available: + +```bash +x86_64-elf-gcc --version +``` + +You should also verify NASM and QEMU: + +```bash +nasm -v +qemu-system-x86_64 --version +``` + +If all commands work, the development environment is correctly configured. + diff --git a/docs/usage/commands/du.md b/docs/usage/commands/du.md new file mode 100644 index 0000000..f48dfcd --- /dev/null +++ b/docs/usage/commands/du.md @@ -0,0 +1,90 @@ +# du + +`du` (disk usage) reports the disk space used by files and directories. + +## Usage + +```sh +du [OPTIONS]... [FILE]... +``` + +## Description + +By default, `du` prints human-readable sizes for each file and directory it encounters, starting from the current directory (`.`) if no path is given. + +## Options + +| Option | Description | +| :--- | :--- | +| `-s, --summarize` | Show only a total for each argument, suppressing per-entry output. | +| `-a, --all` | Write counts for all files, not just directories. | +| `-d, --max-depth=N` | Stop at depth N; show only entries at or above depth N. | +| `-c, --total` | Print a grand total after all arguments have been processed. | +| `-b, --bytes` | Print sizes in exact bytes instead of human-readable units. | +| `-H, --human-readable` | Accepted for compatibility; human-readable is the default. | +| `--help` | Display usage information and exit. | + +## Output Format + +Each line shows a size followed by the path: + +``` +SIZE PATH +``` + +Sizes are formatted as `B`, `KB`, `MB`, or `GB` by default, with one decimal place when appropriate (e.g., `1.5 GB`). The `-b` option overrides this to show exact byte counts. + +## Examples + +Show disk usage for the current directory: + +```sh +du +``` + +Show disk usage for a specific path: + +```sh +du /bin +``` + +Show only totals per argument (`-s`): + +```sh +du -s /bin /home +``` + +Show all files and directories recursively (`-a`): + +```sh +du -a /bin +``` + +Limit output to depth 1 (`-d`): + +```sh +du -d 1 / +``` + +Print a grand total after processing (`-c`): + +```sh +du -c /bin /home +``` + +Show exact byte counts (`-b`): + +```sh +du -b /bin +``` + +## How It Works + +`du` uses `sys_get_file_info()` to read file sizes and `sys_list()` to enumerate directory contents recursively. The command skips the synthetic `.` and `..` entries and continues processing remaining paths if one path is inaccessible, printing an error for the failed path. + +The size reported is the **apparent file size** (the logical size stored in the directory entry), not the allocated disk blocks. This is consistent with how BoredOS reports file sizes through the filesystem API. + +## Exit Status + +- `0`: Success +- `1`: One or more paths could not be accessed or listed \ No newline at end of file diff --git a/docs/usage/terminal.md b/docs/usage/terminal.md index 4b806c2..3ee2b96 100644 --- a/docs/usage/terminal.md +++ b/docs/usage/terminal.md @@ -54,6 +54,7 @@ Below are some of the most used commands available in `/bin`: | `mkdir` | Create a new directory. | | `man` | View the manual for a specific command (e.g., `man ls`). | | `lsblk` | List block devices and partitions with size, type, filesystem, label, and flags. | +| `du` | Report disk usage for files and directories, recursively. | | `sysfetch` | Display system and hardware information. | diff --git a/src/fs/fat32.c b/src/fs/fat32.c index daaee95..9994e52 100644 --- a/src/fs/fat32.c +++ b/src/fs/fat32.c @@ -1661,6 +1661,22 @@ static uint32_t vfs_fat_get_size(void *file_handle) { return ((FAT32_FileHandle*)file_handle)->size; } +static int vfs_ramfs_statfs(void *fs_private, vfs_statfs_t *stat) { + (void)fs_private; + uint64_t rflags = spinlock_acquire_irqsave(&ramfs_lock); + + stat->total_blocks = MAX_CLUSTERS; + uint64_t free_count = 0; + for (int i = 0; i < MAX_CLUSTERS; i++) { + if (fat_table[i] == 0) free_count++; + } + stat->free_blocks = free_count; + stat->block_size = FAT32_CLUSTER_SIZE; + + spinlock_release_irqrestore(&ramfs_lock, rflags); + return 0; +} + static struct vfs_fs_ops ramfs_ops = { .open = vfs_ramfs_open, .close = vfs_ramfs_close, @@ -1676,7 +1692,8 @@ static struct vfs_fs_ops ramfs_ops = { .is_dir = vfs_ramfs_is_dir, .get_info = vfs_ramfs_get_info, .get_position = vfs_fat_get_position, - .get_size = vfs_fat_get_size + .get_size = vfs_fat_get_size, + .statfs = vfs_ramfs_statfs }; struct vfs_fs_ops* fat32_get_ramfs_ops(void) { @@ -1858,6 +1875,45 @@ static int vfs_realfs_get_info(void *fs_private, const char *rel_path, vfs_diren return -1; } +static int vfs_realfs_statfs(void *fs_private, vfs_statfs_t *stat) { + FAT32_Volume *vol = (FAT32_Volume*)fs_private; + uint64_t rflags = spinlock_acquire_irqsave(&vol->lock); + + stat->total_blocks = vol->total_sectors / vol->sectors_per_cluster; + stat->block_size = vol->sectors_per_cluster * 512; + + // Instead of scanning the entire FAT which can be slow, + // we estimate or count a subset, but let's do a fast count. + uint64_t free_count = 0; + uint32_t fat_entries = (vol->fat_size * 512) / 4; + uint32_t current = 2; + + uint8_t *fat_buf = (uint8_t*)kmalloc(512); + if (fat_buf) { + uint32_t cached_sector = 0xFFFFFFFF; + while (current < fat_entries) { + uint32_t sector = vol->fat_begin_lba + (current * 4) / 512; + uint32_t offset = (current * 4) % 512; + + if (sector != cached_sector) { + if (vol->disk->read_sector(vol->disk, sector, fat_buf) != 0) break; + cached_sector = sector; + } + + uint32_t val = *(uint32_t*)&fat_buf[offset]; + if ((val & 0x0FFFFFFF) == 0) free_count++; + + current++; + } + kfree(fat_buf); + } + + stat->free_blocks = free_count; + + spinlock_release_irqrestore(&vol->lock, rflags); + return 0; +} + static struct vfs_fs_ops realfs_ops = { .open = vfs_realfs_open, .close = vfs_realfs_close, @@ -1873,7 +1929,8 @@ static struct vfs_fs_ops realfs_ops = { .is_dir = vfs_realfs_is_dir, .get_info = vfs_realfs_get_info, .get_position = vfs_fat_get_position, - .get_size = vfs_fat_get_size + .get_size = vfs_fat_get_size, + .statfs = vfs_realfs_statfs }; struct vfs_fs_ops* fat32_get_realfs_ops(void) { diff --git a/src/fs/procfs.c b/src/fs/procfs.c index e2b1659..b829c1c 100644 --- a/src/fs/procfs.c +++ b/src/fs/procfs.c @@ -467,6 +467,14 @@ bool procfs_is_dir(void *fs_private, const char *path) { return false; } +static int procfs_statfs(void *fs_private, vfs_statfs_t *stat) { + (void)fs_private; + stat->total_blocks = 0; + stat->free_blocks = 0; + stat->block_size = 512; + return 0; +} + vfs_fs_ops_t procfs_ops = { .open = procfs_open, .close = procfs_close, @@ -474,7 +482,8 @@ vfs_fs_ops_t procfs_ops = { .write = procfs_write, .readdir = procfs_readdir, .exists = procfs_exists, - .is_dir = procfs_is_dir + .is_dir = procfs_is_dir, + .statfs = procfs_statfs }; vfs_fs_ops_t* procfs_get_ops(void) { diff --git a/src/fs/sysfs.c b/src/fs/sysfs.c index 6f45a83..70c4031 100644 --- a/src/fs/sysfs.c +++ b/src/fs/sysfs.c @@ -166,6 +166,14 @@ static bool sysfs_is_dir(void *fs_private, const char *path) { return sysfs_exists(fs_private, path); } +static int sysfs_statfs(void *fs_private, vfs_statfs_t *stat) { + (void)fs_private; + stat->total_blocks = 0; + stat->free_blocks = 0; + stat->block_size = 512; + return 0; +} + vfs_fs_ops_t sysfs_ops = { .open = sysfs_open, .close = sysfs_close, @@ -173,7 +181,8 @@ vfs_fs_ops_t sysfs_ops = { .write = sysfs_write, .readdir = sysfs_readdir, .exists = sysfs_exists, - .is_dir = sysfs_is_dir + .is_dir = sysfs_is_dir, + .statfs = sysfs_statfs }; vfs_fs_ops_t* sysfs_get_ops(void) { diff --git a/src/fs/vfs.c b/src/fs/vfs.c index b053b4a..8f88a02 100644 --- a/src/fs/vfs.c +++ b/src/fs/vfs.c @@ -710,6 +710,26 @@ bool vfs_is_directory(const char *path) { return mount->ops->is_dir(mount->fs_private, rel_path); } +int vfs_statfs(const char *path, vfs_statfs_t *stat) { + if (!path || !stat) return -1; + + char normalized[VFS_MAX_PATH]; + vfs_normalize_path("/", path, normalized); + + const char *rel_path = NULL; + vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path); + if (!mount) return -1; + + if (mount->ops->statfs) { + return mount->ops->statfs(mount->fs_private, stat); + } + + stat->total_blocks = 0; + stat->free_blocks = 0; + stat->block_size = 512; + return 0; +} + int vfs_get_info(const char *path, vfs_dirent_t *info) { if (!path || !info) return -1; diff --git a/src/fs/vfs.h b/src/fs/vfs.h index 06ddf1d..834ac21 100644 --- a/src/fs/vfs.h +++ b/src/fs/vfs.h @@ -13,6 +13,13 @@ #define VFS_MAX_MOUNTS 16 #define VFS_MAX_OPEN_FILES 64 +// statfs structure +typedef struct { + uint64_t total_blocks; + uint64_t free_blocks; + uint64_t block_size; +} vfs_statfs_t; + // Forward declarations typedef struct vfs_mount vfs_mount_t; typedef struct vfs_file vfs_file_t; @@ -47,6 +54,7 @@ typedef struct vfs_fs_ops { bool (*exists)(void *fs_private, const char *rel_path); bool (*is_dir)(void *fs_private, const char *rel_path); int (*get_info)(void *fs_private, const char *rel_path, vfs_dirent_t *info); + int (*statfs)(void *fs_private, vfs_statfs_t *stat); // Handle info (for backward compat with syscall position/size queries) uint32_t (*get_position)(void *file_handle); @@ -99,6 +107,7 @@ bool vfs_rename(const char *old_path, const char *new_path); bool vfs_exists(const char *path); bool vfs_is_directory(const char *path); int vfs_get_info(const char *path, vfs_dirent_t *info); +int vfs_statfs(const char *path, vfs_statfs_t *stat); // Mount enumeration int vfs_get_mount_count(void); diff --git a/src/images/wallpapers/The-Cat-Of-Destiny.png b/src/images/wallpapers/The-Cat-Of-Destiny.png new file mode 100644 index 0000000..6f6fc5b Binary files /dev/null and b/src/images/wallpapers/The-Cat-Of-Destiny.png differ diff --git a/src/images/wallpapers/Web-Of-Connectivity.png b/src/images/wallpapers/Web-Of-Connectivity.png new file mode 100644 index 0000000..9daa50e Binary files /dev/null and b/src/images/wallpapers/Web-Of-Connectivity.png differ diff --git a/src/images/wallpapers/adrian.jpg b/src/images/wallpapers/adrian.jpg new file mode 100644 index 0000000..9e70e73 Binary files /dev/null and b/src/images/wallpapers/adrian.jpg differ diff --git a/src/images/wallpapers/flowerdark.jpg b/src/images/wallpapers/flowerdark.jpg new file mode 100644 index 0000000..3e6665f Binary files /dev/null and b/src/images/wallpapers/flowerdark.jpg differ diff --git a/src/images/wallpapers/flowerlight.jpg b/src/images/wallpapers/flowerlight.jpg new file mode 100644 index 0000000..cdb6c44 Binary files /dev/null and b/src/images/wallpapers/flowerlight.jpg differ diff --git a/src/images/wallpapers/mountain.jpg b/src/images/wallpapers/mountain.jpg deleted file mode 100644 index e419a63..0000000 Binary files a/src/images/wallpapers/mountain.jpg and /dev/null differ diff --git a/src/images/wallpapers/orbital.png b/src/images/wallpapers/orbital.png new file mode 100644 index 0000000..9355e07 Binary files /dev/null and b/src/images/wallpapers/orbital.png differ diff --git a/src/images/wallpapers/squiggly.png b/src/images/wallpapers/squiggly.png new file mode 100644 index 0000000..968a4f9 Binary files /dev/null and b/src/images/wallpapers/squiggly.png differ diff --git a/src/images/wallpapers/valley.jpg b/src/images/wallpapers/valley.jpg deleted file mode 100644 index 31b20ae..0000000 Binary files a/src/images/wallpapers/valley.jpg and /dev/null differ diff --git a/src/input/keyboard.c b/src/input/keyboard.c index a4533e6..6a501ae 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #include "keyboard.h" #include "keymap.h" #include "../core/io.h" diff --git a/src/input/keyboard.h b/src/input/keyboard.h index 88a005c..41f8e8c 100644 --- a/src/input/keyboard.h +++ b/src/input/keyboard.h @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #ifndef KEYBOARD_H #define KEYBOARD_H diff --git a/src/input/keycodes.h b/src/input/keycodes.h index 2969c5f..80ecd76 100644 --- a/src/input/keycodes.h +++ b/src/input/keycodes.h @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #ifndef KEYCODES_H #define KEYCODES_H diff --git a/src/input/keymap.c b/src/input/keymap.c index 505c946..cf7c07b 100644 --- a/src/input/keymap.c +++ b/src/input/keymap.c @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #include "keymap.h" #define DEAD_NORMAL 0x01 diff --git a/src/input/keymap.h b/src/input/keymap.h index 809be99..e5bd705 100644 --- a/src/input/keymap.h +++ b/src/input/keymap.h @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #ifndef KEYMAP_H #define KEYMAP_H diff --git a/src/sys/process.c b/src/sys/process.c index 01fde11..a0fe9aa 100644 --- a/src/sys/process.c +++ b/src/sys/process.c @@ -691,7 +691,9 @@ void process_kill_by_tty(int tty_id) { if (tty_id < 0) return; for (int i = 0; i < MAX_PROCESSES; i++) { if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0 && processes[i].tty_id == tty_id) { - process_terminate(&processes[i]); + if (!processes[i].exited && !processes[i].kill_pending) { + process_terminate(&processes[i]); + } } } } @@ -735,6 +737,7 @@ void process_terminate(process_t *to_delete) { void process_terminate_with_status(process_t *to_delete, int status) { if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return; + if (to_delete->exited || to_delete->kill_pending) return; uint32_t cpu_count = smp_cpu_count(); for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) { diff --git a/src/sys/syscall.c b/src/sys/syscall.c index f519bbb..fe53cab 100644 --- a/src/sys/syscall.c +++ b/src/sys/syscall.c @@ -1416,26 +1416,61 @@ static uint64_t fs_cmd_chdir(const syscall_args_t *args) { } return -1; } -#define FS_CMD_TABLE_SIZE 19 +static uint64_t fs_cmd_statfs(const syscall_args_t *args) { + const char *path = (const char *)args->arg2; + vfs_statfs_t *stat = (vfs_statfs_t *)args->arg3; + if (!path || !stat) return -1; + return vfs_statfs(path, stat) == 0 ? 0 : -1; +} + +static uint64_t fs_cmd_mount_count(const syscall_args_t *args) { + (void)args; + return (uint64_t)vfs_get_mount_count(); +} + +typedef struct { + char path[256]; + char device[32]; + char fs_type[16]; +} syscall_mount_info_t; + +static uint64_t fs_cmd_mount_info(const syscall_args_t *args) { + int index = (int)args->arg2; + syscall_mount_info_t *info = (syscall_mount_info_t *)args->arg3; + if (!info) return -1; + + vfs_mount_t *m = vfs_get_mount(index); + if (!m) return -1; + + strcpy(info->path, m->path); + strcpy(info->device, m->device); + strcpy(info->fs_type, m->fs_type); + return 0; +} + +#define FS_CMD_TABLE_SIZE 22 static const syscall_handler_fn fs_cmd_table[FS_CMD_TABLE_SIZE] = { - [FS_CMD_OPEN] = fs_cmd_open, // 1 - [FS_CMD_READ] = fs_cmd_read, // 2 - [FS_CMD_WRITE] = fs_cmd_write, // 3 - [FS_CMD_CLOSE] = fs_cmd_close, // 4 - [FS_CMD_SEEK] = fs_cmd_seek, // 5 - [FS_CMD_TELL] = fs_cmd_tell, // 6 - [FS_CMD_LIST] = fs_cmd_list, // 7 - [FS_CMD_DELETE] = fs_cmd_delete, // 8 - [FS_CMD_SIZE] = fs_cmd_size, // 9 - [FS_CMD_MKDIR] = fs_cmd_mkdir, // 10 - [FS_CMD_EXISTS] = fs_cmd_exists, // 11 - [FS_CMD_GETCWD] = fs_cmd_getcwd, // 12 - [FS_CMD_CHDIR] = fs_cmd_chdir, // 13 - [FS_CMD_GET_INFO] = fs_cmd_get_info, // 14 - [FS_CMD_DUP] = fs_cmd_dup, // 15 - [FS_CMD_DUP2] = fs_cmd_dup2, // 16 - [FS_CMD_PIPE] = fs_cmd_pipe, // 17 - [FS_CMD_FCNTL] = fs_cmd_fcntl, // 18 + [FS_CMD_OPEN] = fs_cmd_open, // 1 + [FS_CMD_READ] = fs_cmd_read, // 2 + [FS_CMD_WRITE] = fs_cmd_write, // 3 + [FS_CMD_CLOSE] = fs_cmd_close, // 4 + [FS_CMD_SEEK] = fs_cmd_seek, // 5 + [FS_CMD_TELL] = fs_cmd_tell, // 6 + [FS_CMD_LIST] = fs_cmd_list, // 7 + [FS_CMD_DELETE] = fs_cmd_delete, // 8 + [FS_CMD_SIZE] = fs_cmd_size, // 9 + [FS_CMD_MKDIR] = fs_cmd_mkdir, // 10 + [FS_CMD_EXISTS] = fs_cmd_exists, // 11 + [FS_CMD_GETCWD] = fs_cmd_getcwd, // 12 + [FS_CMD_CHDIR] = fs_cmd_chdir, // 13 + [FS_CMD_GET_INFO] = fs_cmd_get_info, // 14 + [FS_CMD_DUP] = fs_cmd_dup, // 15 + [FS_CMD_DUP2] = fs_cmd_dup2, // 16 + [FS_CMD_PIPE] = fs_cmd_pipe, // 17 + [FS_CMD_FCNTL] = fs_cmd_fcntl, // 18 + [FS_CMD_STATFS] = fs_cmd_statfs, // 19 + [FS_CMD_MOUNT_COUNT] = fs_cmd_mount_count, // 20 + [FS_CMD_MOUNT_INFO] = fs_cmd_mount_info, // 21 }; static uint64_t sys_cmd_set_bg_color(const syscall_args_t *args) { diff --git a/src/sys/syscall.h b/src/sys/syscall.h index 5ef981b..210ed86 100644 --- a/src/sys/syscall.h +++ b/src/sys/syscall.h @@ -53,6 +53,9 @@ typedef struct { #define FS_CMD_DUP2 16 #define FS_CMD_PIPE 17 #define FS_CMD_FCNTL 18 +#define FS_CMD_STATFS 19 +#define FS_CMD_MOUNT_COUNT 20 +#define FS_CMD_MOUNT_INFO 21 #define SYSTEM_CMD_SET_BG_COLOR 1 #define SYSTEM_CMD_SET_BG_PATTERN 2 diff --git a/src/userland/cli/df.c b/src/userland/cli/df.c new file mode 100644 index 0000000..277b71b --- /dev/null +++ b/src/userland/cli/df.c @@ -0,0 +1,317 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +// BOREDOS_APP_DESC: Display free disk space +#include +#include +#include +#include +#include + +#define MULTIPLIER_DEFAULT 0 +#define MULTIPLIER_B 1 +#define MULTIPLIER_G 2 +#define MULTIPLIER_H_1000 3 +#define MULTIPLIER_H_1024 4 +#define MULTIPLIER_K 5 +#define MULTIPLIER_M 6 +#define MULTIPLIER_P 7 + +static int multiplier_mode = MULTIPLIER_K; +static bool opt_all = false; +static bool opt_total = false; +static bool opt_inodes = false; +static bool opt_cached = false; +static bool opt_export = false; +static bool opt_local = false; +static bool opt_type = false; +static bool opt_libxo = false; +static bool opt_comma = false; + +static void print_usage(void) { + printf("Usage: df [OPTIONS]\n"); + printf(" -a, --all Include dummy file systems\n"); + printf(" -B, --block-size=SIZE Use SIZE-byte blocks\n"); + printf(" -b Use 512-byte blocks\n"); + printf(" -g Use 1-Gigabyte blocks\n"); + printf(" -h, --human-readable Print sizes in powers of 1024 (e.g., 1023M)\n"); + printf(" -H, --si Print sizes in powers of 1000 (e.g., 1.1G)\n"); + printf(" -i, --inodes List inode information instead of block usage\n"); + printf(" -k Like --block-size=1K\n"); + printf(" -l, --local Limit listing to local file systems\n"); + printf(" -m Like --block-size=1M\n"); + printf(" -P, --portability Use the POSIX output format\n"); + printf(" -T, --print-type Print file system type\n"); + printf(" -c, --total Produce a grand total\n"); + printf(" -n Use previously obtained statistics (no-op)\n"); + printf(" -Y Export-friendly format\n"); + printf(" --libxo Structured output (JSON-like)\n"); + printf(" , Use comma separator for numbers\n"); +} + +static void format_number(uint64_t num, char *out, bool use_comma) { + if (!use_comma) { + sprintf(out, "%llu", (unsigned long long)num); + return; + } + char temp[64]; + sprintf(temp, "%llu", (unsigned long long)num); + int len = strlen(temp); + int out_idx = 0; + for (int i = 0; i < len; i++) { + out[out_idx++] = temp[i]; + if ((len - i - 1) > 0 && (len - i - 1) % 3 == 0) { + out[out_idx++] = ','; + } + } + out[out_idx] = '\0'; +} + +static void format_human_readable(uint64_t bytes, char *out, bool pow1000, bool use_comma) { + const char *suffixes1024[] = {"", "K", "M", "G", "T", "P"}; + const char *suffixes1000[] = {"", "k", "M", "G", "T", "P"}; + uint64_t base = pow1000 ? 1000 : 1024; + int s = 0; + double d = (double)bytes; + while (d >= base && s < 5) { + d /= base; + s++; + } + + char temp[64]; + if (s == 0) { + format_number(bytes, out, use_comma); + } else { + if (d >= 10.0) { + sprintf(temp, "%.0f%s", d, pow1000 ? suffixes1000[s] : suffixes1024[s]); + } else { + sprintf(temp, "%.1f%s", d, pow1000 ? suffixes1000[s] : suffixes1024[s]); + } + strcpy(out, temp); + } +} + +static void format_size(uint64_t bytes, char *out) { + uint64_t blocks = 0; + switch (multiplier_mode) { + case MULTIPLIER_B: + case MULTIPLIER_P: + blocks = (bytes + 511) / 512; + format_number(blocks, out, opt_comma); + break; + case MULTIPLIER_G: + blocks = (bytes + (1024ULL * 1024 * 1024 - 1)) / (1024ULL * 1024 * 1024); + format_number(blocks, out, opt_comma); + break; + case MULTIPLIER_H_1000: + format_human_readable(bytes, out, true, opt_comma); + break; + case MULTIPLIER_H_1024: + format_human_readable(bytes, out, false, opt_comma); + break; + case MULTIPLIER_K: + default: + blocks = (bytes + 1023) / 1024; + format_number(blocks, out, opt_comma); + break; + case MULTIPLIER_M: + blocks = (bytes + (1024 * 1024 - 1)) / (1024 * 1024); + format_number(blocks, out, opt_comma); + break; + } +} + +int main(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--all") == 0) opt_all = true; + else if (strcmp(argv[i], "-b") == 0) multiplier_mode = MULTIPLIER_B; + else if (strcmp(argv[i], "-g") == 0) multiplier_mode = MULTIPLIER_G; + else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--human-readable") == 0) multiplier_mode = MULTIPLIER_H_1024; + else if (strcmp(argv[i], "-H") == 0 || strcmp(argv[i], "--si") == 0) multiplier_mode = MULTIPLIER_H_1000; + else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--inodes") == 0) opt_inodes = true; + else if (strcmp(argv[i], "-k") == 0) multiplier_mode = MULTIPLIER_K; + else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--local") == 0) opt_local = true; + else if (strcmp(argv[i], "-m") == 0) multiplier_mode = MULTIPLIER_M; + else if (strcmp(argv[i], "-P") == 0 || strcmp(argv[i], "--portability") == 0) multiplier_mode = MULTIPLIER_P; + else if (strcmp(argv[i], "-T") == 0 || strcmp(argv[i], "--print-type") == 0) opt_type = true; + else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--total") == 0) opt_total = true; + else if (strcmp(argv[i], "-n") == 0) opt_cached = true; + else if (strcmp(argv[i], "-Y") == 0) opt_export = true; + else if (strcmp(argv[i], "--libxo") == 0) opt_libxo = true; + else if (strcmp(argv[i], ",") == 0) opt_comma = true; + else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(); + return 0; + } else if (argv[i][0] == '-') { + // Check clustered flags (e.g., -Th) + for (size_t j = 1; j < strlen(argv[i]); j++) { + char c = argv[i][j]; + if (c == 'a') opt_all = true; + else if (c == 'b') multiplier_mode = MULTIPLIER_B; + else if (c == 'g') multiplier_mode = MULTIPLIER_G; + else if (c == 'h') multiplier_mode = MULTIPLIER_H_1024; + else if (c == 'H') multiplier_mode = MULTIPLIER_H_1000; + else if (c == 'i') opt_inodes = true; + else if (c == 'k') multiplier_mode = MULTIPLIER_K; + else if (c == 'l') opt_local = true; + else if (c == 'm') multiplier_mode = MULTIPLIER_M; + else if (c == 'P') multiplier_mode = MULTIPLIER_P; + else if (c == 'T') opt_type = true; + else if (c == 'c') opt_total = true; + else if (c == 'n') opt_cached = true; + else if (c == 'Y') opt_export = true; + else { + printf("df: invalid option -- '%c'\n", c); + print_usage(); + return 1; + } + } + } + } + + int mount_count = sys_fs_mount_count(); + if (mount_count < 0) { + printf("df: cannot get mount count\n"); + return 1; + } + + if (opt_libxo) { + printf("{\n \"storage-system-information\": {\n \"filesystem\": [\n"); + } else { + if (opt_type) { + if (multiplier_mode == MULTIPLIER_H_1024 || multiplier_mode == MULTIPLIER_H_1000) { + printf("%-16s %-8s %-9s %-9s %-9s %-5s %s\n", "Filesystem", "Type", "Size", "Used", "Avail", "Use%", "Mounted on"); + } else if (opt_inodes) { + printf("%-16s %-8s %-9s %-9s %-9s %-5s %s\n", "Filesystem", "Type", "Inodes", "IUsed", "IFree", "IUse%", "Mounted on"); + } else { + const char *block_str = "1K-blocks"; + if (multiplier_mode == MULTIPLIER_B || multiplier_mode == MULTIPLIER_P) block_str = "512-blocks"; + else if (multiplier_mode == MULTIPLIER_G) block_str = "1G-blocks"; + else if (multiplier_mode == MULTIPLIER_M) block_str = "1M-blocks"; + printf("%-16s %-8s %-10s %-10s %-10s %-5s %s\n", "Filesystem", "Type", block_str, "Used", "Available", "Use%", "Mounted on"); + } + } else { + if (multiplier_mode == MULTIPLIER_H_1024 || multiplier_mode == MULTIPLIER_H_1000) { + printf("%-16s %-9s %-9s %-9s %-5s %s\n", "Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on"); + } else if (opt_inodes) { + printf("%-16s %-9s %-9s %-9s %-5s %s\n", "Filesystem", "Inodes", "IUsed", "IFree", "IUse%", "Mounted on"); + } else { + const char *block_str = "1K-blocks"; + if (multiplier_mode == MULTIPLIER_B || multiplier_mode == MULTIPLIER_P) block_str = "512-blocks"; + else if (multiplier_mode == MULTIPLIER_G) block_str = "1G-blocks"; + else if (multiplier_mode == MULTIPLIER_M) block_str = "1M-blocks"; + + if (multiplier_mode == MULTIPLIER_P) { + printf("%-16s %-10s %-10s %-10s %-5s %s\n", "Filesystem", "512-blocks", "Used", "Available", "Capacity", "Mounted on"); + } else { + printf("%-16s %-10s %-10s %-10s %-5s %s\n", "Filesystem", block_str, "Used", "Available", "Use%", "Mounted on"); + } + } + } + } + + uint64_t grand_total_bytes = 0; + uint64_t grand_used_bytes = 0; + uint64_t grand_avail_bytes = 0; + + bool first_libxo = true; + + for (int i = 0; i < mount_count; i++) { + mount_info_t m_info; + if (sys_fs_mount_info(i, &m_info) != 0) continue; + + bool is_pseudo = (strcmp(m_info.fs_type, "ramfs") == 0 && strcmp(m_info.path, "/") != 0) || + strcmp(m_info.fs_type, "procfs") == 0 || + strcmp(m_info.fs_type, "sysfs") == 0; + + if (is_pseudo && !opt_all) continue; + + vfs_statfs_t stat; + if (sys_fs_statfs(m_info.path, &stat) != 0) continue; + + uint64_t total_bytes = stat.total_blocks * stat.block_size; + uint64_t free_bytes = stat.free_blocks * stat.block_size; + uint64_t used_bytes = total_bytes - free_bytes; + + if (strcmp(m_info.path, "/") == 0) { + if (total_bytes == 0) { + total_bytes = 32 * 1024 * 1024; + used_bytes = 1024 * 1024; + free_bytes = total_bytes - used_bytes; + } + } + + grand_total_bytes += total_bytes; + grand_used_bytes += used_bytes; + grand_avail_bytes += free_bytes; + + double use_percent = 0; + if (total_bytes > 0) use_percent = ((double)used_bytes / (double)total_bytes) * 100.0; + + char use_str[16]; + if (is_pseudo && total_bytes == 0) strcpy(use_str, "-"); + else sprintf(use_str, "%.0f%%", use_percent); + + if (opt_libxo) { + if (!first_libxo) printf(",\n"); + first_libxo = false; + printf(" {\n"); + printf(" \"name\": \"%s\",\n", m_info.device); + if (opt_type) printf(" \"type\": \"%s\",\n", m_info.fs_type); + printf(" \"total-blocks\": %llu,\n", (unsigned long long)total_bytes); + printf(" \"used-blocks\": %llu,\n", (unsigned long long)used_bytes); + printf(" \"available-blocks\": %llu,\n", (unsigned long long)free_bytes); + printf(" \"used-percent\": %.0f,\n", use_percent); + printf(" \"mounted-on\": \"%s\"\n", m_info.path); + printf(" }"); + } else { + char t_str[32], u_str[32], f_str[32]; + + if (opt_inodes) { + strcpy(t_str, "0"); + strcpy(u_str, "0"); + strcpy(f_str, "0"); + strcpy(use_str, "-"); + } else { + format_size(total_bytes, t_str); + format_size(used_bytes, u_str); + format_size(free_bytes, f_str); + } + + char dev_name[64]; + if (opt_export) { + sprintf(dev_name, "dev_%s", m_info.device); + } else { + strcpy(dev_name, m_info.device); + } + + if (opt_type) { + printf("%-16s %-8s %-10s %-10s %-10s %-5s %s\n", dev_name, m_info.fs_type, t_str, u_str, f_str, use_str, m_info.path); + } else { + printf("%-16s %-10s %-10s %-10s %-5s %s\n", dev_name, t_str, u_str, f_str, use_str, m_info.path); + } + } + } + + if (opt_libxo) { + printf("\n ]\n }\n}\n"); + } else if (opt_total && !opt_inodes) { + char t_str[32], u_str[32], f_str[32]; + format_size(grand_total_bytes, t_str); + format_size(grand_used_bytes, u_str); + format_size(grand_avail_bytes, f_str); + + double use_percent = 0; + if (grand_total_bytes > 0) use_percent = ((double)grand_used_bytes / (double)grand_total_bytes) * 100.0; + char use_str[16]; + sprintf(use_str, "%.0f%%", use_percent); + + if (opt_type) { + printf("%-16s %-8s %-10s %-10s %-10s %-5s -\n", "total", "-", t_str, u_str, f_str, use_str); + } else { + printf("%-16s %-10s %-10s %-10s %-5s -\n", "total", t_str, u_str, f_str, use_str); + } + } + + return 0; +} diff --git a/src/userland/cli/du.c b/src/userland/cli/du.c new file mode 100644 index 0000000..744724e --- /dev/null +++ b/src/userland/cli/du.c @@ -0,0 +1,203 @@ +// Copyright (c) 2026 zeyadhost (https://github.com/zeyadhost) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include +#include +#include +#include +#include + +#define MAX_ENTRIES 1024 +#define DU_KB 1024ULL +#define DU_MB (1024ULL * 1024ULL) +#define DU_GB (1024ULL * 1024ULL * 1024ULL) + +static int opt_summarize = 0; +static int opt_all = 0; +static int opt_max_depth = -1; +static int opt_total = 0; +static int opt_bytes = 0; + +static uint64_t grand_total = 0; + +static void usage(void) { + printf("Usage: du [options]..[file]\n"); + printf("Summarize disk usage of the set of FILEs, recursively for directories.\n\n"); + printf("Options:\n"); + printf(" -s, --summarize display only a total for each argument\n"); + printf(" -a, --all write counts for all files, not just directories\n"); + printf(" -d, --max-depth=N print the total for a directory only if it is N or\n"); + printf(" fewer levels below the command line argument\n"); + printf(" -c, --total produce a grand total\n"); + printf(" -b, --bytes print sizes in bytes\n"); + printf(" -H, --human-readable print sizes in human readable format (default)\n"); + printf(" --help display this help and exit\n"); +} + +static void print_size(uint64_t bytes, const char *path) { + if (opt_bytes) { + printf("%llu\t%s\n", (unsigned long long)bytes, path); + return; + } + + char size_str[32]; + uint64_t unit = 1; + const char *suffix = "B"; + + if (bytes >= DU_GB) { + unit = DU_GB; + suffix = "GB"; + } else if (bytes >= DU_MB) { + unit = DU_MB; + suffix = "MB"; + } else if (bytes >= DU_KB) { + unit = DU_KB; + suffix = "KB"; + } + + if (unit == 1) { + snprintf(size_str, sizeof(size_str), "%llu%s", (unsigned long long)bytes, suffix); + } else { + // Round to one decimal place + uint64_t whole = bytes / unit; + uint64_t rem = bytes % unit; + uint64_t tenth = (rem * 10ULL + unit / 2ULL) / unit; + + if (tenth >= 10ULL) { + whole++; + tenth = 0; + } + + if (tenth == 0) { + snprintf(size_str, sizeof(size_str), "%llu%s", (unsigned long long)whole, suffix); + } else { + snprintf(size_str, sizeof(size_str), "%llu.%llu%s", (unsigned long long)whole, (unsigned long long)tenth, suffix); + } + } + printf("%s\t%s\n", size_str, path); +} + +static void join_path(char *dest, size_t size, const char *p1, const char *p2) { + if (strcmp(p1, "/") == 0) { + snprintf(dest, size, "/%s", p2); + } else if (p1[strlen(p1) - 1] == '/') { + snprintf(dest, size, "%s%s", p1, p2); + } else { + snprintf(dest, size, "%s/%s", p1, p2); + } +} + +static uint64_t do_du(const char *path, int depth) { + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) < 0) { + printf("du: cannot access '%s'\n", path); + return 0; + } + + if (!info.is_directory) { + if (opt_all || (depth == 0)) { + if (opt_max_depth == -1 || depth <= opt_max_depth) { + if (!opt_summarize || depth == 0) { + print_size(info.size, path); + } + } + } + return info.size; + } + + uint64_t total_size = info.size; + + FAT32_FileInfo *entries = malloc(sizeof(FAT32_FileInfo) * MAX_ENTRIES); + if (!entries) { + printf("du: out of memory for '%s'\n", path); + return total_size; + } + + int count = sys_list(path, entries, MAX_ENTRIES); + if (count < 0) { + printf("du: cannot read directory '%s'\n", path); + free(entries); + return total_size; + } + + // Recurse into subdirectories + for (int i = 0; i < count; i++) { + if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) { + continue; + } + + char child_path[1024]; + join_path(child_path, sizeof(child_path), path, entries[i].name); + + total_size += do_du(child_path, depth + 1); + } + + free(entries); + + // Print directory size at this depth + if (!opt_summarize) { + if (opt_max_depth == -1 || depth <= opt_max_depth) { + print_size(total_size, path); + } + } else if (depth == 0) { + // With -s, only print the root of each requested path + print_size(total_size, path); + } + + return total_size; +} + +int main(int argc, char **argv) { + char **paths = malloc(sizeof(char*) * argc); + int num_paths = 0; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--summarize") == 0) { + opt_summarize = 1; + } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--all") == 0) { + opt_all = 1; + } else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--total") == 0) { + opt_total = 1; + } else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--bytes") == 0) { + opt_bytes = 1; + } else if (strcmp(argv[i], "-H") == 0 || strcmp(argv[i], "--human-readable") == 0) { + // No-op: human-readable is the default + } else if (strcmp(argv[i], "-d") == 0) { + if (i + 1 < argc) { + opt_max_depth = atoi(argv[++i]); + } else { + printf("du: option requires an argument -- '-d'\n"); + return 1; + } + } else if (strncmp(argv[i], "--max-depth=", 12) == 0) { + opt_max_depth = atoi(argv[i] + 12); + } else if (strcmp(argv[i], "--help") == 0) { + usage(); + free(paths); + return 0; + } else if (argv[i][0] == '-') { + printf("du: invalid option -- '%s'\n", argv[i]); + usage(); + free(paths); + return 1; + } else { + paths[num_paths++] = argv[i]; + } + } + + if (num_paths == 0) { + grand_total += do_du(".", 0); + } else { + for (int i = 0; i < num_paths; i++) { + grand_total += do_du(paths[i], 0); + } + } + + if (opt_total) { + print_size(grand_total, "total"); + } + + free(paths); + return 0; +} diff --git a/src/userland/cli/hexdump.c b/src/userland/cli/hexdump.c index f722d63..dc701d3 100644 --- a/src/userland/cli/hexdump.c +++ b/src/userland/cli/hexdump.c @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 Chris (boreddevnl) +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. // BOREDOS_APP_DESC: Display file contents in hexadecimal. diff --git a/src/userland/cli/ps.c b/src/userland/cli/ps.c index f34554d..0d65cb8 100644 --- a/src/userland/cli/ps.c +++ b/src/userland/cli/ps.c @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 Chris (boreddevnl) +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. // BOREDOS_APP_DESC: List running processes. diff --git a/src/userland/games/2048.c b/src/userland/games/2048.c index 091c723..118aa28 100644 --- a/src/userland/games/2048.c +++ b/src/userland/games/2048.c @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. // BOREDOS_APP_DESC: 2048 number puzzle game. // BOREDOS_APP_ICONS: /Library/images/icons/colloid/applications-games.png #include "libc/syscall.h" diff --git a/src/userland/games/snake.c b/src/userland/games/snake.c index 69ae859..4c6e77b 100644 --- a/src/userland/games/snake.c +++ b/src/userland/games/snake.c @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. // BOREDOS_APP_DESC: Classic snake arcade game. // BOREDOS_APP_ICONS: /Library/images/icons/colloid/cartridges.png #include "libc/syscall.h" diff --git a/src/userland/gui/keylog.c b/src/userland/gui/keylog.c index 851d1d5..ab4ddd7 100644 --- a/src/userland/gui/keylog.c +++ b/src/userland/gui/keylog.c @@ -1,16 +1,12 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #include "libc/syscall.h" #include "libc/libui.h" #include "libc/stdlib.h" #include #include -/* -@Lluciocc -The most SIMPLE keylogger for debug -FEAT: -- Log every key pressed in the terminal and gives their keycode -*/ - #define WINDOW_W 400 #define WINDOW_H 200 diff --git a/src/userland/gui/settings.c b/src/userland/gui/settings.c index 84a6345..ad0fefb 100644 --- a/src/userland/gui/settings.c +++ b/src/userland/gui/settings.c @@ -6,6 +6,7 @@ #include "libc/syscall.h" #include "libc/libui.h" #include "libc/stdlib.h" +#include "libc/string.h" #include "stb_image.h" #include #include @@ -65,13 +66,14 @@ static widget_button_t btn_apply, btn_back; static widget_slider_t slider_mouse; static widget_slider_t slider_cursor_size; -#define MAX_WALLPAPERS 10 - static widget_button_t btn_main_wallpaper, btn_main_network, btn_main_desktop, btn_main_mouse, btn_main_fonts, btn_main_display, btn_main_keyboard; static widget_button_t btn_wp_colors[6]; static widget_button_t btn_wp_patterns[2]; static widget_button_t btn_wp_apply; -static widget_button_t btn_wp_thumbs[MAX_WALLPAPERS]; +static widget_button_t *btn_wp_thumbs = NULL; + +#define WALLPAPER_THUMB_W 80 +#define WALLPAPER_THUMB_H 50 static widget_button_t btn_net_init; static widget_button_t btn_net_set_ip, btn_net_set_dns; @@ -89,8 +91,30 @@ static font_entry_t *fonts = NULL; static int font_capacity = 0; static widget_scrollbar_t font_scrollbar; static int font_scroll_y = 0; + +typedef struct { + char path[128]; + char name[64]; + uint32_t thumb[WALLPAPER_THUMB_W * WALLPAPER_THUMB_H]; + _Bool valid; +} wallpaper_entry_t; + +static wallpaper_entry_t *wallpapers = NULL; +static int wallpaper_count = 0; +static int wallpaper_capacity = 0; +static widget_scrollbar_t wallpaper_scrollbar; +static int wallpaper_scroll_y = 0; +static int wallpaper_load_state = 0; +static int wallpaper_next_load_index = 0; +static bool wallpaper_allow_decode = false; +static bool wallpaper_worker_spawned = false; +static char settings_executable_path[256] = {0}; static widget_textbox_t tb_custom_w, tb_custom_h; +#define WALLPAPER_LOADING_NOT_STARTED 0 +#define WALLPAPER_LOADING_ACTIVE 1 +#define WALLPAPER_LOADING_DONE 2 + #define SETTINGS_ICON_MAIN_SIZE 32 #define SETTINGS_ICON_LIST_SIZE 18 @@ -193,19 +217,6 @@ static char net_status[64] = ""; static uint32_t pattern_lumberjack[PATTERN_SIZE * PATTERN_SIZE]; static uint32_t pattern_blue_diamond[PATTERN_SIZE * PATTERN_SIZE]; -#define WALLPAPER_THUMB_W 80 -#define WALLPAPER_THUMB_H 50 - -typedef struct { - char path[128]; - char name[64]; - uint32_t thumb[WALLPAPER_THUMB_W * WALLPAPER_THUMB_H]; - _Bool valid; -} wallpaper_entry_t; - -static wallpaper_entry_t wallpapers[MAX_WALLPAPERS]; -static int wallpaper_count = 0; - static _Bool desktop_snap_to_grid = 1; static _Bool desktop_auto_align = 1; static int desktop_max_rows_per_col = 10; @@ -248,6 +259,45 @@ static void format_scale_tenths(int scale_tenths, char *str) { str[len] = 0; } +static void draw_truncated_string(ui_window_t win, int x, int y, const char *str, int max_width, uint32_t color) { + if (!str || max_width <= 0) return; + + int full_width = ui_get_string_width(str); + if (full_width <= max_width) { + ui_draw_string(win, x, y, str, color); + return; + } + + const char *ellipsis = "..."; + int ellipsis_width = ui_get_string_width(ellipsis); + int avail_width = max_width - ellipsis_width; + if (avail_width <= 0) { + ui_draw_string(win, x, y, ellipsis, color); + return; + } + + char buf[64]; + int pos = 0; + int width = 0; + + while (str[pos] && pos < 63) { + buf[pos] = str[pos]; + buf[pos + 1] = '\0'; + width = ui_get_string_width(buf); + if (width > avail_width) break; + pos++; + } + + if (pos == 0) { + ui_draw_string(win, x, y, ellipsis, color); + return; + } + + buf[pos] = '\0'; + ui_draw_string(win, x, y, buf, color); + ui_draw_string(win, x + width, y, ellipsis, color); +} + static void generate_lumberjack_pattern(void) { uint32_t red = 0xFFDC143C; uint32_t dark_grey = 0xFF404040; @@ -435,89 +485,262 @@ static int is_supported_image(const char *name) { static void decode_wallpapers_task(void *arg) { (void)arg; wallpaper_count = 0; - FAT32_FileInfo info[MAX_WALLPAPERS]; - int count = sys_list("/Library/images/Wallpapers", info, MAX_WALLPAPERS); - if (count < 0) return; + wallpaper_capacity = 0; + wallpaper_next_load_index = 0; + wallpaper_load_state = WALLPAPER_LOADING_NOT_STARTED; - for (int i = 0; i < count && wallpaper_count < MAX_WALLPAPERS; i++) { - if (info[i].is_directory) continue; // Skip directories - + FAT32_FileInfo info[512]; + int count = sys_list("/Library/images/Wallpapers", info, 512); + if (count < 0) count = 0; + + for (int i = 0; i < count; i++) { + if (info[i].is_directory) continue; + if (!is_supported_image(info[i].name)) continue; + wallpaper_count++; + } + + if (wallpaper_count <= 0) { + wallpaper_count = 0; + wallpaper_capacity = 0; + return; + } + + wallpaper_capacity = wallpaper_count; + wallpapers = (wallpaper_entry_t *)malloc(wallpaper_capacity * sizeof(wallpaper_entry_t)); + if (!wallpapers) { + wallpaper_count = 0; + wallpaper_capacity = 0; + return; + } + + int dst_idx = 0; + for (int i = 0; i < count && dst_idx < wallpaper_capacity; i++) { + if (info[i].is_directory) continue; if (!is_supported_image(info[i].name)) continue; - wallpaper_entry_t *wp = &wallpapers[wallpaper_count]; - // Set path + wallpaper_entry_t *wp = &wallpapers[dst_idx]; char *pref = "/Library/images/Wallpapers/"; - int pl = 0; while (pref[pl]) { wp->path[pl] = pref[pl]; pl++; } - int nl = 0; while (info[i].name[nl]) { wp->path[pl+nl] = info[i].name[nl]; nl++; } + int pl = 0; + while (pref[pl]) { wp->path[pl] = pref[pl]; pl++; } + int nl = 0; + while (info[i].name[nl]) { wp->path[pl+nl] = info[i].name[nl]; nl++; } wp->path[pl+nl] = 0; - // Set name (strip extension) int dot_idx = -1; for (int j = 0; info[i].name[j]; j++) if (info[i].name[j] == '.') dot_idx = j; int name_len = (dot_idx != -1) ? dot_idx : nl; for (int j = 0; j < name_len && j < 63; j++) wp->name[j] = info[i].name[j]; wp->name[(name_len < 63) ? name_len : 63] = 0; - char cache_path[256]; - int cp = 0; - char *cpref = "/Library/Caches/Thumbnails/"; - while (cpref[cp]) { cache_path[cp] = cpref[cp]; cp++; } - int cn = 0; - while (info[i].name[cn]) { cache_path[cp+cn] = info[i].name[cn]; cn++; } - char *csuf = ".bin"; - int cs = 0; - while (csuf[cs]) { cache_path[cp+cn+cs] = csuf[cs]; cs++; } - cache_path[cp+cn+cs] = 0; - - int cfd = sys_open(cache_path, "r"); - if (cfd >= 0) { - sys_read(cfd, wp->thumb, WALLPAPER_THUMB_W * WALLPAPER_THUMB_H * 4); - sys_close(cfd); - wp->valid = 1; - } else { - int fd = sys_open(wp->path, "r"); - if (fd >= 0) { - int size = sys_seek(fd, 0, 2); // SEEK_END - sys_seek(fd, 0, 0); // SEEK_SET - if (size > 0 && size < 8 * 1024 * 1024) { - unsigned char *buf = (unsigned char *)malloc(size); - if (buf) { - sys_read(fd, buf, size); - int img_w, img_h, channels; - unsigned char *img = stbi_load_from_memory(buf, size, &img_w, &img_h, &channels, 4); - if (img && img_w > 0 && img_h > 0) { - scale_rgba_to_argb(img, img_w, img_h, wp->thumb, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H); - wp->valid = 1; - stbi_image_free(img); - - sys_mkdir("/Library/Caches"); - sys_mkdir("/Library/Caches/Thumbnails"); - int swfd = sys_open(cache_path, "w"); - if (swfd >= 0) { - sys_write_fs(swfd, wp->thumb, WALLPAPER_THUMB_W * WALLPAPER_THUMB_H * 4); - sys_close(swfd); - } - } - free(buf); - } - } - sys_close(fd); - } - } - - wallpaper_count++; + wp->valid = 0; + dst_idx++; } } -static void load_wallpapers(void) { - void *job_args[1] = { NULL }; - sys_parallel_run(decode_wallpapers_task, job_args, 1); +static void on_wallpaper_scroll(void *user_data, int new_scroll_y) { + (void)user_data; + wallpaper_scroll_y = new_scroll_y; +} - for (int i = 0; i < wallpaper_count; i++) { - int tx = (i % 3) * (WALLPAPER_THUMB_W + 15); - int ty = (i / 3) * (WALLPAPER_THUMB_H + 30); - widget_button_init(&btn_wp_thumbs[i], 8 + tx, 306 + ty, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 20, ""); +static int wallpaper_scroll_region_y(void) { + return 46 - wallpaper_scroll_y; +} + +static void set_settings_executable_path(const char *argv0) { + if (!argv0 || argv0[0] == '\0') return; + + if (argv0[0] == '/') { + int i = 0; + while (argv0[i] && i < (int)sizeof(settings_executable_path) - 1) { + settings_executable_path[i] = argv0[i]; + i++; + } + settings_executable_path[i] = '\0'; + return; } + + char cwd[256]; + if (sys_getcwd(cwd, sizeof(cwd)) > 0) { + int pos = 0; + for (int i = 0; cwd[i] && pos < (int)sizeof(settings_executable_path) - 1; i++) { + settings_executable_path[pos++] = cwd[i]; + } + if (pos < (int)sizeof(settings_executable_path) - 1) { + settings_executable_path[pos++] = '/'; + } + for (int i = 0; argv0[i] && pos < (int)sizeof(settings_executable_path) - 1; i++) { + settings_executable_path[pos++] = argv0[i]; + } + settings_executable_path[pos] = '\0'; + } else { + int i = 0; + while (argv0[i] && i < (int)sizeof(settings_executable_path) - 1) { + settings_executable_path[i] = argv0[i]; + i++; + } + settings_executable_path[i] = '\0'; + } +} + +static void spawn_wallpaper_worker(void) { + if (wallpaper_worker_spawned || settings_executable_path[0] == '\0') return; + + sys_spawn(settings_executable_path, "--wallpaper-thumb-worker", SPAWN_FLAG_BACKGROUND, 0); + wallpaper_worker_spawned = true; +} + +static void load_wallpaper_thumbnail(int idx) { + if (idx < 0 || idx >= wallpaper_count || wallpapers[idx].valid) return; + + wallpaper_entry_t *wp = &wallpapers[idx]; + + char cache_path[256]; + int cp = 0; + const char *cpref = "/Library/Caches/Thumbnails/"; + while (cpref[cp]) { cache_path[cp] = cpref[cp]; cp++; } + + int last_slash = 0; + for (int i = 0; wp->path[i]; i++) { + if (wp->path[i] == '/') last_slash = i + 1; + } + int cn = 0; + while (wp->path[last_slash + cn]) { cache_path[cp+cn] = wp->path[last_slash + cn]; cn++; } + const char *csuf = ".bin"; + int cs = 0; + while (csuf[cs]) { cache_path[cp+cn+cs] = csuf[cs]; cs++; } + cache_path[cp+cn+cs] = 0; + + int cfd = sys_open(cache_path, "r"); + if (cfd >= 0) { + sys_read(cfd, wp->thumb, WALLPAPER_THUMB_W * WALLPAPER_THUMB_H * 4); + sys_close(cfd); + wp->valid = 1; + return; + } + + if (!wallpaper_allow_decode) { + return; + } + + int fd = sys_open(wp->path, "r"); + if (fd >= 0) { + int size = sys_seek(fd, 0, 2); + sys_seek(fd, 0, 0); + if (size > 0 && size < 8 * 1024 * 1024) { + unsigned char *buf = (unsigned char *)malloc(size); + if (buf) { + if (sys_read(fd, buf, size) > 0) { + int img_w, img_h, channels; + unsigned char *img = stbi_load_from_memory(buf, size, &img_w, &img_h, &channels, 4); + if (img && img_w > 0 && img_h > 0) { + scale_rgba_to_argb(img, img_w, img_h, wp->thumb, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H); + wp->valid = 1; + stbi_image_free(img); + + int swfd = sys_open(cache_path, "w"); + if (swfd >= 0) { + sys_write_fs(swfd, wp->thumb, WALLPAPER_THUMB_W * WALLPAPER_THUMB_H * 4); + sys_close(swfd); + } + } + } + free(buf); + } + } + sys_close(fd); + } +} + +static bool process_wallpaper_loading_step(void) { + if (wallpaper_load_state != WALLPAPER_LOADING_ACTIVE) return false; + if (wallpaper_count <= 0) return false; + + int checked = 0; + bool loaded_any = false; + while (checked < wallpaper_count) { + int idx = wallpaper_next_load_index; + wallpaper_next_load_index++; + if (wallpaper_next_load_index >= wallpaper_count) wallpaper_next_load_index = 0; + + if (!wallpapers[idx].valid) { + load_wallpaper_thumbnail(idx); + if (wallpapers[idx].valid) { + loaded_any = true; + break; + } + } + checked++; + } + + if (!loaded_any) { + bool all_valid = true; + for (int i = 0; i < wallpaper_count; i++) { + if (!wallpapers[i].valid) { + all_valid = false; + break; + } + } + if (all_valid) { + wallpaper_load_state = WALLPAPER_LOADING_DONE; + } + } + + return loaded_any; +} + +static void load_wallpapers(void) { + if (wallpaper_load_state == WALLPAPER_LOADING_ACTIVE || + wallpaper_load_state == WALLPAPER_LOADING_DONE) { + return; + } + + if (btn_wp_thumbs) { + free(btn_wp_thumbs); + btn_wp_thumbs = NULL; + } + if (wallpapers) { + free(wallpapers); + wallpapers = NULL; + } + + wallpaper_count = 0; + wallpaper_capacity = 0; + wallpaper_next_load_index = 0; + wallpaper_scroll_y = 0; + + sys_mkdir("/Library/Caches"); + sys_mkdir("/Library/Caches/Thumbnails"); + + decode_wallpapers_task(NULL); + + if (wallpaper_count > 0) { + btn_wp_thumbs = (widget_button_t *)malloc(wallpaper_count * sizeof(widget_button_t)); + if (btn_wp_thumbs) { + for (int i = 0; i < wallpaper_count; i++) { + widget_button_init(&btn_wp_thumbs[i], 8, 0, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 20, ""); + } + } + } + + widget_scrollbar_init(&wallpaper_scrollbar, 330, 46, 12, 454); + int wallpaper_rows = (wallpaper_count + 2) / 3; + int wallpaper_list_height = wallpaper_rows * (WALLPAPER_THUMB_H + 30); + wallpaper_scrollbar.content_height = 260 + wallpaper_list_height; + wallpaper_scrollbar.on_scroll = (void(*)(void*,int))on_wallpaper_scroll; + wallpaper_load_state = WALLPAPER_LOADING_ACTIVE; + wallpaper_allow_decode = false; + spawn_wallpaper_worker(); +} + +static int wallpaper_thumbnail_worker_main(void) { + wallpaper_allow_decode = true; + sys_mkdir("/Library/Caches"); + sys_mkdir("/Library/Caches/Thumbnails"); + decode_wallpapers_task(NULL); + for (int i = 0; i < wallpaper_count; i++) { + load_wallpaper_thumbnail(i); + } + return 0; } static uint32_t parse_rgb_separate(const char *r, const char *g, const char *b) { @@ -596,46 +819,63 @@ static void control_panel_paint_main(ui_window_t win) { static void control_panel_paint_wallpaper(ui_window_t win) { int offset_x = 8; - int offset_y = 6; - + int page_top = wallpaper_scroll_region_y(); + widget_button_draw(&settings_ctx, &btn_back); - - ui_draw_string(win, offset_x, offset_y + 40, "Presets:", COLOR_DARK_TEXT); - - int button_y = offset_y + 65; + + ui_draw_string(win, offset_x, page_top, "Presets:", COLOR_DARK_TEXT); + int button_x = offset_x; - - // Colors + int button_y = page_top + 25; + + btn_wp_colors[0].x = button_x; + btn_wp_colors[0].y = button_y; + btn_wp_colors[1].x = button_x + 100; + btn_wp_colors[1].y = button_y; + btn_wp_colors[2].x = button_x + 200; + btn_wp_colors[2].y = button_y; + widget_button_draw(&settings_ctx, &btn_wp_colors[0]); ui_draw_rect(win, button_x + 8, button_y + 6, 18, 13, COLOR_COFFEE); ui_draw_string(win, button_x + 35, button_y + 8, "Coffee", COLOR_DARK_TEXT); - + widget_button_draw(&settings_ctx, &btn_wp_colors[1]); ui_draw_rect(win, button_x + 108, button_y + 6, 18, 13, COLOR_TEAL); ui_draw_string(win, button_x + 135, button_y + 8, "Teal", COLOR_DARK_TEXT); - + widget_button_draw(&settings_ctx, &btn_wp_colors[2]); ui_draw_rect(win, button_x + 208, button_y + 6, 18, 13, COLOR_GREEN); ui_draw_string(win, button_x + 235, button_y + 8, "Green", COLOR_DARK_TEXT); - + button_y += 35; + btn_wp_colors[3].x = button_x; + btn_wp_colors[3].y = button_y; + btn_wp_colors[4].x = button_x + 100; + btn_wp_colors[4].y = button_y; + btn_wp_colors[5].x = button_x + 200; + btn_wp_colors[5].y = button_y; + widget_button_draw(&settings_ctx, &btn_wp_colors[3]); ui_draw_rect(win, button_x + 8, button_y + 6, 18, 13, COLOR_BLUE_BG); ui_draw_string(win, button_x + 35, button_y + 8, "Blue", COLOR_DARK_TEXT); - + widget_button_draw(&settings_ctx, &btn_wp_colors[4]); ui_draw_rect(win, button_x + 108, button_y + 6, 18, 13, COLOR_PURPLE); ui_draw_string(win, button_x + 132, button_y + 8, "Purple", COLOR_DARK_TEXT); - + widget_button_draw(&settings_ctx, &btn_wp_colors[5]); ui_draw_rect(win, button_x + 208, button_y + 6, 18, 13, COLOR_GREY); ui_draw_string(win, button_x + 235, button_y + 8, "Grey", COLOR_DARK_TEXT); - - // Patterns + button_y += 40; ui_draw_string(win, offset_x, button_y, "Patterns:", COLOR_DARK_TEXT); - + button_y += 20; + btn_wp_patterns[0].x = button_x; + btn_wp_patterns[0].y = button_y; + btn_wp_patterns[1].x = button_x + 153; + btn_wp_patterns[1].y = button_y; + widget_button_draw(&settings_ctx, &btn_wp_patterns[0]); for (int py = 0; py < 10; py++) { for (int px = 0; px < 12; px++) { @@ -647,7 +887,7 @@ static void control_panel_paint_wallpaper(ui_window_t win) { } } ui_draw_string(win, button_x + 28, button_y + 8, "Lumberjack", COLOR_DARK_TEXT); - + widget_button_draw(&settings_ctx, &btn_wp_patterns[1]); for (int py = 0; py < 8; py++) { for (int px = 0; px < 10; px++) { @@ -660,42 +900,63 @@ static void control_panel_paint_wallpaper(ui_window_t win) { } } ui_draw_string(win, button_x + 165, button_y + 8, "Blue Diamond", COLOR_DARK_TEXT); - - // Custom color + button_y += 40; ui_draw_string(win, offset_x, button_y, "Custom color:", COLOR_DARK_TEXT); button_y += 20; - + + tb_r.x = 33; + tb_r.y = button_y + 4; + tb_g.x = 123; + tb_g.y = button_y + 4; + tb_b.x = 213; + tb_b.y = button_y + 4; + ui_draw_string(win, button_x, button_y + 4, "R:", COLOR_DARK_TEXT); tb_r.focused = (focused_field == 0); widget_textbox_draw(&settings_ctx, &tb_r); - + ui_draw_string(win, button_x + 90, button_y + 4, "G:", COLOR_DARK_TEXT); tb_g.focused = (focused_field == 1); widget_textbox_draw(&settings_ctx, &tb_g); - + ui_draw_string(win, button_x + 180, button_y + 4, "B:", COLOR_DARK_TEXT); tb_b.focused = (focused_field == 2); widget_textbox_draw(&settings_ctx, &tb_b); - + + btn_wp_apply.x = 8; + btn_wp_apply.y = button_y + 33; widget_button_draw(&settings_ctx, &btn_wp_apply); ui_draw_string(win, button_x + 18, button_y + 33, "Apply", COLOR_DARK_TEXT); - - // Wallpapers section + button_y += 60; ui_draw_string(win, offset_x, button_y, "Wallpapers:", COLOR_DARK_TEXT); button_y += 20; - + + int list_y = button_y; + int list_h = 180; + for (int i = 0; i < wallpaper_count; i++) { int tx = (i % 3) * (WALLPAPER_THUMB_W + 15); int ty = (i / 3) * (WALLPAPER_THUMB_H + 30); - + + int item_y = list_y + ty; + + if (item_y + (WALLPAPER_THUMB_H + 20) < 0 || item_y > 500) continue; + + btn_wp_thumbs[i].x = button_x + tx; + btn_wp_thumbs[i].y = item_y; widget_button_draw(&settings_ctx, &btn_wp_thumbs[i]); if (wallpapers[i].valid) { - ui_draw_image(win, button_x + tx + 4, button_y + ty + 4, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H, wallpapers[i].thumb); + ui_draw_image(win, button_x + tx + 4, item_y + 4, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H, wallpapers[i].thumb); + } else { + ui_draw_rect(win, button_x + tx + 4, item_y + 4, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H, 0xFF2A2A2A); + ui_draw_rect(win, button_x + tx + 6, item_y + WALLPAPER_THUMB_H/2 - 1, WALLPAPER_THUMB_W - 4, 2, 0xFF3E3E3E); } - ui_draw_string(win, button_x + tx + 8, button_y + ty + WALLPAPER_THUMB_H + 8, wallpapers[i].name, 0xFFFFFFFF); + draw_truncated_string(win, button_x + tx + 8, item_y + WALLPAPER_THUMB_H + 8, wallpapers[i].name, WALLPAPER_THUMB_W - 8, 0xFFFFFFFF); } + + widget_scrollbar_draw(&settings_ctx, &wallpaper_scrollbar); } static void control_panel_paint_network(ui_window_t win) { @@ -1117,6 +1378,55 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click } if (current_view == VIEW_WALLPAPER) { + int page_base = wallpaper_scroll_region_y(); + int button_y = page_base + 25; + btn_wp_colors[0].x = 8; + btn_wp_colors[0].y = button_y; + btn_wp_colors[1].x = 108; + btn_wp_colors[1].y = button_y; + btn_wp_colors[2].x = 208; + btn_wp_colors[2].y = button_y; + + button_y += 35; + btn_wp_colors[3].x = 8; + btn_wp_colors[3].y = button_y; + btn_wp_colors[4].x = 108; + btn_wp_colors[4].y = button_y; + btn_wp_colors[5].x = 208; + btn_wp_colors[5].y = button_y; + + button_y += 40; + btn_wp_patterns[0].x = 8; + btn_wp_patterns[0].y = button_y; + btn_wp_patterns[1].x = 153; + btn_wp_patterns[1].y = button_y; + + button_y += 20; + button_y += 40; + tb_r.x = 33; + tb_r.y = button_y + 4; + tb_g.x = 123; + tb_g.y = button_y + 4; + tb_b.x = 213; + tb_b.y = button_y + 4; + btn_wp_apply.x = 8; + btn_wp_apply.y = button_y + 33; + + button_y += 20; + button_y += 60; + int list_y = button_y + 20; + + for (int i = 0; i < wallpaper_count; i++) { + int tx = (i % 3) * (WALLPAPER_THUMB_W + 15); + int ty = (i / 3) * (WALLPAPER_THUMB_H + 30); + + int item_y = list_y + ty; + if (item_y + (WALLPAPER_THUMB_H + 20) < 0 || item_y > 500) continue; + + btn_wp_thumbs[i].x = 8 + tx; + btn_wp_thumbs[i].y = item_y; + } + if (widget_textbox_handle_mouse(&settings_ctx, &tb_r, x, y, is_click, NULL)) { focused_field = 0; input_cursor = tb_r.cursor_pos; return; } @@ -1126,7 +1436,7 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click if (widget_textbox_handle_mouse(&settings_ctx, &tb_b, x, y, is_click, NULL)) { focused_field = 2; input_cursor = tb_b.cursor_pos; return; } - + for (int i=0; i<6; i++) { if (widget_button_handle_mouse(&btn_wp_colors[i], x, y, is_down, is_click, NULL)) { if (is_click) { @@ -1152,9 +1462,17 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click } return; } - for (int i=0; i 1 && strcmp(argv[1], "--wallpaper-thumb-worker") == 0) { + return wallpaper_thumbnail_worker_main(); + } ui_window_t win = ui_window_create("Settings", 200, 150, 350, 500); if (!win) return 1; @@ -1451,8 +1776,6 @@ int main(int argc, char **argv) { control_panel_paint(win); ui_mark_dirty(win, 0, 0, 350, 500); - load_wallpapers(); // load after first paint to avoid startup delay - gui_event_t ev; while (1) { bool dirty = false; @@ -1510,11 +1833,15 @@ int main(int argc, char **argv) { sys_exit(0); } - if (dirty) { + if (dirty || (current_view == VIEW_WALLPAPER && process_wallpaper_loading_step())) { control_panel_paint(win); ui_mark_dirty(win, 0, 0, 350, 500); } } else { + if (current_view == VIEW_WALLPAPER && process_wallpaper_loading_step()) { + control_panel_paint(win); + ui_mark_dirty(win, 0, 0, 350, 500); + } sleep(10); } } diff --git a/src/userland/libc/syscall.c b/src/userland/libc/syscall.c index b8fe4d6..e4f4272 100644 --- a/src/userland/libc/syscall.c +++ b/src/userland/libc/syscall.c @@ -161,6 +161,18 @@ int sys_fcntl(int fd, int cmd, int val) { return (int)syscall4(SYS_FS, FS_CMD_FCNTL, (uint64_t)fd, (uint64_t)cmd, (uint64_t)val); } +int sys_fs_statfs(const char *path, vfs_statfs_t *stat) { + return (int)syscall3(SYS_FS, FS_CMD_STATFS, (uint64_t)path, (uint64_t)stat); +} + +int sys_fs_mount_count(void) { + return (int)syscall1(SYS_FS, FS_CMD_MOUNT_COUNT); +} + +int sys_fs_mount_info(int index, mount_info_t *info) { + return (int)syscall3(SYS_FS, FS_CMD_MOUNT_INFO, (uint64_t)index, (uint64_t)info); +} + int sys_tty_create(void) { return (int)syscall2(SYS_SYSTEM, SYSTEM_CMD_TTY_CREATE, 0); } diff --git a/src/userland/libc/syscall.h b/src/userland/libc/syscall.h index 0bce2b7..476262c 100644 --- a/src/userland/libc/syscall.h +++ b/src/userland/libc/syscall.h @@ -33,6 +33,9 @@ #define FS_CMD_DUP2 16 #define FS_CMD_PIPE 17 #define FS_CMD_FCNTL 18 +#define FS_CMD_STATFS 19 +#define FS_CMD_MOUNT_COUNT 20 +#define FS_CMD_MOUNT_INFO 21 // System Commands (via SYS_SYSTEM) #define SYSTEM_CMD_SET_BG_COLOR 1 @@ -155,6 +158,18 @@ typedef struct { int sys_get_os_info(os_info_t *info); // FS API +typedef struct { + uint64_t total_blocks; + uint64_t free_blocks; + uint64_t block_size; +} vfs_statfs_t; + +typedef struct { + char path[256]; + char device[32]; + char fs_type[16]; +} mount_info_t; + int sys_open(const char *path, const char *mode); int sys_read(int fd, void *buf, uint32_t len); int sys_write_fs(int fd, const void *buf, uint32_t len); @@ -171,6 +186,9 @@ int sys_dup(int oldfd); int sys_dup2(int oldfd, int newfd); int sys_pipe(int pipefd[2]); int sys_fcntl(int fd, int cmd, int val); +int sys_fs_statfs(const char *path, vfs_statfs_t *stat); +int sys_fs_mount_count(void); +int sys_fs_mount_info(int index, mount_info_t *info); int sys_tty_create(void); int sys_tty_read_out(int tty_id, char *buf, int len); diff --git a/src/userland/libc/utf-8.c b/src/userland/libc/utf-8.c index edc5af6..ad803b7 100644 --- a/src/userland/libc/utf-8.c +++ b/src/userland/libc/utf-8.c @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #include "utf-8.h" static int utf8_write_replacement(char *out) { diff --git a/src/userland/libc/utf-8.h b/src/userland/libc/utf-8.h index 1af7b36..b9ee454 100644 --- a/src/userland/libc/utf-8.h +++ b/src/userland/libc/utf-8.h @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #ifndef UTF_8_H #define UTF_8_H diff --git a/src/wm/utf-8.h b/src/wm/utf-8.h index 1af7b36..b9ee454 100644 --- a/src/wm/utf-8.h +++ b/src/wm/utf-8.h @@ -1,3 +1,6 @@ +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. #ifndef UTF_8_H #define UTF_8_H