mirror of
https://github.com/JannisHeydemann/BoredOS.git
synced 2026-05-30 10:26:59 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b77e8c48e | ||
|
|
1ce08c70b0 | ||
|
|
fca67f68a9 | ||
|
|
c330382436 | ||
|
|
f0c2963793 | ||
|
|
3b24bc882c | ||
|
|
2b44e59e9f | ||
|
|
7a2769e8e3 | ||
|
|
1a6e30b52e | ||
|
|
69847adee6 | ||
|
|
f402e5e4f0 | ||
|
|
684ed774ee | ||
|
|
9ed8eac3e5 | ||
|
|
c6d512b0f2 | ||
|
|
0b7a134282 | ||
|
|
91b67bd8d5 | ||
|
|
e60f232812 | ||
|
|
3169ec51cb | ||
|
|
beb2c724ff | ||
|
|
bf3c2cb578 | ||
|
|
823e9c0ce7 | ||
|
|
0ddb1e7610 | ||
|
|
32a6bb4d72 | ||
|
|
d8e680604c | ||
|
|
2e28f860cb | ||
|
|
9634ebb086 | ||
|
|
d7d97b5a97 | ||
|
|
4a3752583c | ||
|
|
9de8ee143c | ||
|
|
8d5fa53d3e | ||
|
|
ad8db32305 | ||
|
|
92928e55fb | ||
|
|
31eb7afdc6 | ||
|
|
ad9fac3e28 | ||
|
|
70cd296d19 | ||
|
|
b7020152c1 | ||
|
|
63749b8734 | ||
|
|
4e8ea5acd2 | ||
|
|
5c199e028a | ||
|
|
ec2a9d1883 | ||
|
|
4c46650c64 | ||
|
|
1ee2fcad9e | ||
|
|
5c29ac1473 | ||
|
|
81743261bf | ||
|
|
4eeb907342 | ||
|
|
e527f63af7 | ||
|
|
1e19963a8d | ||
|
|
60ab70a49d | ||
|
|
d9bcc4aff7 | ||
|
|
5604866882 | ||
|
|
e95c82b162 | ||
|
|
9fb307e603 | ||
|
|
a7c3cccce7 | ||
|
|
7eb55f3a59 | ||
|
|
72baf6506d | ||
|
|
2817ad51da | ||
|
|
5b10127e02 | ||
|
|
1404a6ae4f | ||
|
|
7b7f134e27 | ||
|
|
0491c4ad0f | ||
|
|
c6fe9971d8 | ||
|
|
88f178e368 | ||
|
|
d824b4610a | ||
|
|
a4f16b0604 | ||
|
|
83413fdd2b | ||
|
|
2a918bc7ba | ||
|
|
2624b4f8df |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
||||||
2
LICENSE
2
LICENSE
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
Copyright(C) Chris (boreddevnl) 2024-2026
|
Copyright(C) Chris (boreddevnl) 2024-2026
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/\>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|||||||
101
Makefile
101
Makefile
@@ -132,58 +132,56 @@ $(KERNEL_ELF): $(OBJ_FILES)
|
|||||||
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
||||||
$(MAKE) -C $(SRC_DIR)/userland
|
$(MAKE) -C $(SRC_DIR)/userland
|
||||||
|
|
||||||
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
$(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||||
|
rm -rf $(BUILD_DIR)/initrd
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/bin
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/Library/images/gif
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/Library/DOOM
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/docs
|
||||||
|
|
||||||
|
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||||
|
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/bin/; fi \
|
||||||
|
done
|
||||||
|
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||||
|
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; fi \
|
||||||
|
done
|
||||||
|
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||||
|
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; fi \
|
||||||
|
done
|
||||||
|
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||||
|
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; fi \
|
||||||
|
done
|
||||||
|
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
|
||||||
|
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \
|
||||||
|
done
|
||||||
|
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi
|
||||||
|
@for f in $$(find docs -name '*.md' 2>/dev/null); do \
|
||||||
|
if [ -f "$$f" ]; then \
|
||||||
|
dir=$$(dirname "$$f"); \
|
||||||
|
mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \
|
||||||
|
cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \
|
||||||
|
fi \
|
||||||
|
done
|
||||||
|
@if [ -f README.md ]; then cp README.md $(BUILD_DIR)/initrd/; fi
|
||||||
|
@if [ -f LICENSE ]; then cp LICENSE $(BUILD_DIR)/initrd/; fi
|
||||||
|
|
||||||
|
cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar *
|
||||||
|
|
||||||
|
$(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup
|
||||||
rm -rf $(ISO_DIR)
|
rm -rf $(ISO_DIR)
|
||||||
mkdir -p $(ISO_DIR)
|
mkdir -p $(ISO_DIR)
|
||||||
mkdir -p $(ISO_DIR)/EFI/BOOT
|
mkdir -p $(ISO_DIR)/EFI/BOOT
|
||||||
|
|
||||||
cp $(KERNEL_ELF) $(ISO_DIR)/
|
cp $(KERNEL_ELF) $(ISO_DIR)/
|
||||||
cp limine.conf $(ISO_DIR)/
|
cp limine.conf $(ISO_DIR)/
|
||||||
mkdir -p $(ISO_DIR)/bin
|
|
||||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
|
||||||
if [ -f "$$f" ]; then \
|
|
||||||
basename=$$(basename "$$f"); \
|
|
||||||
cp "$$f" $(ISO_DIR)/bin/; \
|
|
||||||
echo " module_path: boot():/bin/$$basename" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
|
cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/
|
||||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then \
|
echo " module_path: boot():/initrd.tar" >> $(ISO_DIR)/limine.conf
|
||||||
mkdir -p $(ISO_DIR)/Library/DOOM; \
|
|
||||||
cp $(SRC_DIR)/userland/games/doom/doom1.wad $(ISO_DIR)/Library/DOOM/; \
|
|
||||||
echo " module_path: boot():/Library/DOOM/doom1.wad" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
|
|
||||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
|
||||||
if [ -f "$$f" ]; then \
|
|
||||||
basename=$$(basename "$$f"); \
|
|
||||||
cp "$$f" $(ISO_DIR)/Library/images/Wallpapers/; \
|
|
||||||
echo " module_path: boot():/Library/images/Wallpapers/$$basename" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
|
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
|
||||||
|
|
||||||
mkdir -p $(ISO_DIR)/Library/images/gif
|
|
||||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
|
||||||
if [ -f "$$f" ]; then \
|
|
||||||
basename=$$(basename "$$f"); \
|
|
||||||
cp "$$f" $(ISO_DIR)/Library/images/gif/; \
|
|
||||||
echo " module_path: boot():/Library/images/gif/$$basename" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir -p $(ISO_DIR)/docs
|
|
||||||
@for f in $$(find docs -name '*.md'); do \
|
|
||||||
if [ -f "$$f" ]; then \
|
|
||||||
dir=$$(dirname "$$f"); \
|
|
||||||
mkdir -p $(ISO_DIR)/"$$dir"; \
|
|
||||||
cp "$$f" $(ISO_DIR)/"$$dir"/; \
|
|
||||||
echo " module_path: boot():/$$f" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
cp limine/limine-bios.sys $(ISO_DIR)/
|
cp limine/limine-bios.sys $(ISO_DIR)/
|
||||||
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
||||||
cp limine/limine-uefi-cd.bin $(ISO_DIR)/
|
cp limine/limine-uefi-cd.bin $(ISO_DIR)/
|
||||||
@@ -191,25 +189,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
|||||||
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
|
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
|
||||||
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
||||||
|
|
||||||
mkdir -p $(ISO_DIR)/Library/Fonts
|
|
||||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
|
||||||
if [ -f "$$f" ]; then \
|
|
||||||
basename=$$(basename "$$f"); \
|
|
||||||
cp "$$f" $(ISO_DIR)/Library/Fonts/; \
|
|
||||||
echo " module_path: boot():/Library/Fonts/$$basename" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
@if [ -f README.md ]; then \
|
|
||||||
cp README.md $(ISO_DIR)/; \
|
|
||||||
echo " module_path: boot():/README.md" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
@if [ -f LICENSE ]; then \
|
|
||||||
cp LICENSE $(ISO_DIR)/; \
|
|
||||||
echo " module_path: boot():/LICENSE" >> $(ISO_DIR)/limine.conf; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
|
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
|
||||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||||
--efi-boot limine-uefi-cd.bin \
|
--efi-boot limine-uefi-cd.bin \
|
||||||
|
|||||||
138
README.md
138
README.md
@@ -1,97 +1,87 @@
|
|||||||
# BoredOS
|
# BoredOS
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="boredos.svg" alt="BoredOS Logo" width="450" />
|
<img src="boredos.svg" alt="BoredOS Logo" width="450" />
|
||||||
|
<p><em>A modern x86_64 hobbyist operating system built from the ground up.</em></p>
|
||||||
|
|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||||
|

|
||||||
|

|
||||||
</div>
|
</div>
|
||||||
BoredOS is a simple x86_64 hobbyist operating system.
|
|
||||||
It features a DE (and WM), a FAT32 filesystem, customizable UI and much much more!
|
---
|
||||||
|
|
||||||
|
BoredOS is a x86_64 operating system featuring a custom Desktop Environment (DE), a dedicated Window Manager (BoredWM), and a FAT32 filesystem. It balances low-level kernel exploration with a surprisingly capable userspace.
|
||||||
|
|
||||||

|

|
||||||
*this screenshot might be outdated*
|
> [!NOTE]
|
||||||
|
> *The screenshot above may represent a previous build and is subject to change as the UI evolves.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- userspace
|
|
||||||
- JPG image support
|
|
||||||
- Disk manager
|
|
||||||
- Drag and drop mouse centered UI
|
|
||||||
- Customizable UI
|
|
||||||
- Basic Networking Stack
|
|
||||||
- Bored WM
|
|
||||||
- FAT32 filesystem
|
|
||||||
- 64-bit long mode support
|
|
||||||
- Multiboot2 compliant
|
|
||||||
- Text editor
|
|
||||||
- Markdown Viewer
|
|
||||||
- Minesweeper
|
|
||||||
- Markdown Viewer
|
|
||||||
- GUI Text editor
|
|
||||||
- Paint application
|
|
||||||
- IDT
|
|
||||||
- Ability to run on actual x86_64 hardware
|
|
||||||
- CLI
|
|
||||||
- (Limited) C Compiler
|
|
||||||
|
|
||||||
## Documentation
|
### System Architecture
|
||||||
|
* **64-bit Long Mode:** Fully utilizing the x86_64 architecture.
|
||||||
|
* **Symmetric Multi-Processing (SMP):** Full support for multi-core CPUs via Limine SMP.
|
||||||
|
* **LAPIC & IPI Scheduling:** Advanced interrupt handling and inter-processor communication for task distribution.
|
||||||
|
* **SMP-Safe Spinlocks:** Robust kernel-wide synchronization for VFS, process management, and the GUI.
|
||||||
|
* **Multiboot2 Compliant:** Bootable on real hardware and modern emulators.
|
||||||
|
* **Kernel Core:** Interrupt Descriptor Table (IDT) management and a robust syscall interface.
|
||||||
|
* **Filesystem:** Full **FAT32** support for persistent and in-memory storage.
|
||||||
|
* **Networking:** Includes the lwIP networking stack.
|
||||||
|
|
||||||
BoredOS has comprehensive documentation available in the [`docs/`](docs/) directory covering architecture, the build system, and application development SDKs.
|
### Graphical User Interface
|
||||||
|
* **BoredWM:** A custom Window Manager with drag-and-drop, mouse-centered interaction.
|
||||||
|
* **Customization:** Adjustable UI to suit your aesthetic.
|
||||||
|
* **Media Support:** Built-in image decoding.
|
||||||
|
|
||||||
- **[Index / Table of Contents](docs/README.md)**
|
### Included Applications
|
||||||
- **[Architecture Overview](docs/architecture/core.md)**
|
* **Productivity:** GUI Text Editor calculator, Markdown Viewer, a simple browser and BoredWord.
|
||||||
- **[Building and Running](docs/build/usage.md)**
|
* **Creativity:** A Paint application.
|
||||||
- **[Application Development Guide](docs/appdev/custom_apps.md)**
|
* **Utilities:** Terminal, Task Manager, File Explorer, Clock and a (limited) C Compiler.
|
||||||
|
* **Games:** Minesweeper and DOOM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
Explore the internal workings of BoredOS via our comprehensive guides in the [`docs/`](docs/) directory.
|
||||||
|
|
||||||
|
* 📖 **[Documentation Index](docs/README.md)** – Start here.
|
||||||
|
* 🏗️ **[Architecture Overview](docs/architecture/core.md)** – Deep dive into the kernel.
|
||||||
|
* 🔨 **[Building and Running](docs/build/usage.md)** – Setup your build environment.
|
||||||
|
* 🚀 **[AppDev SDK](docs/appdev/custom_apps.md)** – Build your own apps for BoredOS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support the Journey
|
||||||
|
|
||||||
|
If you find this project interesting or helpful, consider fueling the development with a coffee!
|
||||||
|
|
||||||
|
<a href="https://buymeacoffee.com/boreddevhq" target="_blank">
|
||||||
|
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" style="border-radius: 8px;" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Project Disclaimer & Heritage
|
||||||
|
|
||||||
|
**BoredOS** is the successor to **BrewKernel**, a project initiated in 2023.
|
||||||
|
|
||||||
###
|
While BrewKernel served as the foundational learning ground for this OS, it has been officially **deprecated and archived**. It no longer receives updates, bug fixes, or pull request reviews. BoredOS represents a complete architectural reboot, applying years of lessons learned to create a cleaner, more modular, and more capable system.
|
||||||
###
|
|
||||||
|
|
||||||
<h2 align="left">Help me brew some coffee! ☕️</h2>
|
> [!IMPORTANT]
|
||||||
|
> Please ensure all issues, discussions, and contributions are directed to this repository. Legacy BrewKernel code is preserved for historical purposes only and is not compatible with BoredOS.
|
||||||
|
|
||||||
###
|
---
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
If you enjoy this project, and like what i'm doing here, consider buying me a coffee!
|
|
||||||
<br><br>
|
|
||||||
<a href="https://buymeacoffee.com/boreddevnl" target="_blank">
|
|
||||||
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" style="border-radius: 8px;" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
|
|
||||||
## This project was previously labeled as "BrewKernel"
|
|
||||||
Brewkernel was a text only very simple (and messy) project i started 3 years ago. It was my first work in OSDev and i absolutely loved it. It sadly just got too messy and i myself couldn't understand my own code anymore. About a year ago i started work on BoredOS, and pushed a *"working"* version of it a few days ago as of writing this *(Feb. 10 2026)*
|
|
||||||
Brewkernel has already been deprecated and will not be accepting any pull requests or fix any issues as it is now a public archive.
|
|
||||||
Thanks to everyone who helped me with Brewkernel, even if it were just ideas, and intend to keep working on this for the forseeable future!
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2024-2026 boreddevnl
|
**Copyright (C) 2024-2026 boreddevnl**
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
Distributed under the **GNU General Public License v3**. See the `LICENSE` file for details.
|
||||||
|
|
||||||
NOTICE
|
> [!IMPORTANT]
|
||||||
------
|
> This product includes software developed by Chris ("boreddevnl"). You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the `NOTICE` file for more details.
|
||||||
|
|
||||||
This product includes software developed by Chris ("boreddevnl") as part of the BoredOS (Previously Brewkernel/BrewOS) project.
|
|
||||||
|
|
||||||
Copyright (C) 2024–2026 Chris / boreddevnl (previously boreddevhq)
|
|
||||||
|
|
||||||
All source files in this repository contain copyright and license
|
|
||||||
headers that must be preserved in redistributions and derivative works.
|
|
||||||
|
|
||||||
If you distribute or modify this project (in whole or in part),
|
|
||||||
you MUST:
|
|
||||||
|
|
||||||
- Retain all copyright and license headers at the top of each file.
|
|
||||||
- Include this NOTICE file along with any redistributions or
|
|
||||||
derivative works.
|
|
||||||
- Provide clear attribution to the original author in documentation
|
|
||||||
or credits where appropriate.
|
|
||||||
|
|
||||||
The above attribution requirements are informational and intended to
|
|
||||||
ensure proper credit is given. They do not alter or supersede the
|
|
||||||
terms of the GNU General Public License (GPL), which governs this work.
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 130" width="100%">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 415 130" width="100%">
|
||||||
<style>
|
<style>
|
||||||
text {
|
text {
|
||||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,25 +1,35 @@
|
|||||||
# BoredOS Documentation
|
<div align="center">
|
||||||
|
<h1>BoredOS Documentation</h1>
|
||||||
|
<p><em>Internal guides, architecture, and application development.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Welcome to the internal documentation for BoredOS! This directory contains detailed guides on how the OS functions, how to build it, and how to develop applications for it.
|
Welcome to the internal documentation for BoredOS! This directory contains detailed guides on how the OS functions, how to build it, and how to develop applications for it.
|
||||||
|
|
||||||
## Table of Contents
|
## 📚 Table of Contents
|
||||||
|
|
||||||
The documentation is organized into three main categories:
|
The documentation is organized into three main categories:
|
||||||
|
|
||||||
### 1. [Architecture](architecture/)
|
### 1. 🏗️ [Architecture](architecture/)
|
||||||
Explains the logical layout of the kernel and internal components.
|
Explains the logical layout of the kernel and internal components.
|
||||||
- [`Core`](architecture/core.md): Kernel source layout and the boot process (Limine, Multiboot2).
|
- [`Core`](architecture/core.md): Kernel source layout and the boot process (Limine, Multiboot2).
|
||||||
- [`Memory`](architecture/memory.md): Physical Memory Management (PMM) and Virtual Memory Management (VMM).
|
- [`Memory`](architecture/memory.md): Physical Memory Management (PMM) and Virtual Memory Management (VMM).
|
||||||
- [`Filesystem`](architecture/filesystem.md): Virtual File System (VFS) and the RAM-based FAT32 simulation.
|
- [`Filesystem`](architecture/filesystem.md): Virtual File System (VFS) and the RAM-based FAT32 simulation.
|
||||||
- [`Window Manager`](architecture/window_manager.md): How the built-in Window Manager natively handles graphics, events, and compositing.
|
- [`Window Manager`](architecture/window_manager.md): How the built-in Window Manager natively handles graphics, events, and compositing.
|
||||||
|
|
||||||
### 2. [Building and Deployment](build/)
|
### 2. 🔨 [Building and Deployment](build/)
|
||||||
Instructions for compiling the OS from source.
|
Instructions for compiling the OS from source.
|
||||||
- [`Toolchain`](build/toolchain.md): Prerequisites and cross-compiler setup (`x86_64-elf-gcc`, `nasm`, `xorriso`).
|
- [`Toolchain`](build/toolchain.md): Prerequisites and cross-compiler setup (`x86_64-elf-gcc`, `nasm`, `xorriso`).
|
||||||
- [`Usage`](build/usage.md): Understanding the Makefile targets, QEMU emulation, and flashing to bare metal hardware.
|
- [`Usage`](build/usage.md): Understanding the Makefile targets, QEMU emulation, and flashing to bare metal hardware.
|
||||||
|
|
||||||
### 3. [Application Development](appdev/)
|
### 3. 🚀 [Application Development](appdev/)
|
||||||
The SDK and toolchain guides for creating your own `.elf` userland binaries.
|
The SDK and toolchain guides for creating your own `.elf` userland binaries.
|
||||||
- [`SDK Reference`](appdev/sdk_reference.md): Explanation of the custom `libc` wrappers (`stdlib.h`, `string.h`) and system calls.
|
- [`SDK Reference`](appdev/sdk_reference.md): Explanation of the custom `libc` wrappers (`stdlib.h`, `string.h`) and system calls.
|
||||||
- [`UI API`](appdev/ui_api.md): Drawing on the screen, creating windows, and polling the event loop using `libui.h`.
|
- [`UI API`](appdev/ui_api.md): Drawing on the screen, creating windows, and polling the event loop using `libui.h`.
|
||||||
|
- [`Widget API`](appdev/widget_api.md): High-level UI components like buttons, textboxes, and scrollbars using `libwidget.h`.
|
||||||
- [`Custom Apps`](appdev/custom_apps.md): A step-by-step tutorial on writing a new graphical C application, editing the Makefile, and bundling it into the ISO.
|
- [`Custom Apps`](appdev/custom_apps.md): A step-by-step tutorial on writing a new graphical C application, editing the Makefile, and bundling it into the ISO.
|
||||||
|
- [`Example Apps`](appdev/examples/README.md): A collection of sample C applications ranging from basic terminal output to advanced TCP networking.
|
||||||
|
- [`Grapher`](appdev/grapher.md): Full reference for the built-in mathematical graphing application — equation syntax, keyboard controls, architecture, and configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
# Creating a Custom App (Step-by-Step)
|
<div align="center">
|
||||||
|
<h1>Creating a Custom App</h1>
|
||||||
|
<p><em>A step-by-step tutorial on writing a new graphical C application.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
This guide explains how to write a new "Hello World" application locally, compile it as an `.elf` binary into the `bin/` folder, and launch it inside BoredOS.
|
This guide explains how to write a new "Hello World" application locally, compile it as an `.elf` binary into the `bin/` folder, and launch it inside BoredOS.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> **Looking for working code?** Check out the [Examples Directory](examples/README.md) for full source code demonstrating basic CLI, Windows, Animations, and TCP Networking.
|
||||||
|
|
||||||
## Step 1: Write the C Source
|
## Step 1: Write the C Source
|
||||||
|
|
||||||
Applications reside entirely in the `src/userland/` directory. Create a new file, for example, `src/userland/gui/hello.c`.
|
Applications reside entirely in the `src/userland/` directory. Create a new file, for example, `src/userland/gui/hello.c`.
|
||||||
@@ -13,30 +21,31 @@ Applications reside entirely in the `src/userland/` directory. Create a new file
|
|||||||
// src/userland/gui/hello.c
|
// src/userland/gui/hello.c
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <libui.h>
|
#include <libui.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
// Attempt to open a 300x200 window
|
// Attempt to open a 300x200 window
|
||||||
int wid = ui_create_window("My Custom App", 300, 200, 0);
|
ui_window_t wid = ui_window_create("My Custom App", 100, 100, 300, 200);
|
||||||
if (wid < 0) {
|
if (wid < 0) {
|
||||||
printf("Error creating window!\n");
|
printf("Error creating window!\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write text in center
|
// Write text in center
|
||||||
ui_draw_string(wid, "Hello, BoredOS!!", 50, 90, 0xFFFFFFFF);
|
ui_draw_string(wid, 50, 90, "Hello, BoredOS!!", 0xFFFFFFFF);
|
||||||
|
|
||||||
// Commit drawing to screen
|
// Commit drawing to screen
|
||||||
ui_swap_buffers(wid);
|
ui_mark_dirty(wid, 0, 0, 300, 200);
|
||||||
|
|
||||||
ui_event_t event;
|
gui_event_t event;
|
||||||
while (1) {
|
while (1) {
|
||||||
if (ui_poll_event(&event)) {
|
if (ui_get_event(wid, &event)) {
|
||||||
if (event.window_id == wid && event.type == UI_EVENT_WINDOW_CLOSE) {
|
if (event.type == GUI_EVENT_CLOSE) {
|
||||||
break; // Exit loop if 'X' is clicked
|
break; // Exit loop if 'X' is clicked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syscall1(SYSTEM_CMD_YIELD, 0);
|
sys_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0; // Returning 0 smoothly exits the process via crt0.asm
|
return 0; // Returning 0 smoothly exits the process via crt0.asm
|
||||||
@@ -73,6 +82,9 @@ The main overarching `Makefile` (in the project root) takes binaries from `src/u
|
|||||||
1. When BoredOS boots, launch the **Terminal** application.
|
1. When BoredOS boots, launch the **Terminal** application.
|
||||||
2. The OS automatically maps built applications to standard shell commands. Simply type your application's filename (without the `.elf` extension).
|
2. The OS automatically maps built applications to standard shell commands. Simply type your application's filename (without the `.elf` extension).
|
||||||
3. Type `hello` in the terminal and press Enter.
|
3. Type `hello` in the terminal and press Enter.
|
||||||
4. Your custom window will appear!
|
4. Your custom window will appear!
|
||||||
|
|
||||||
*you can also open your app by opening the file explorer and navigating to the bin directory and double clicking the executable.*
|
> [!NOTE]
|
||||||
|
> You can also open your app by opening the file explorer, navigating to the `bin` directory, and double-clicking the executable.
|
||||||
|
|
||||||
|
---
|
||||||
52
docs/appdev/examples/01_hello_cli.md
Normal file
52
docs/appdev/examples/01_hello_cli.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Example 01: Hello CLI</h1>
|
||||||
|
<p><em>The absolute basics. Writing a terminal program.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This example demonstrates the bare minimum structure of a BoredOS application that outputs text to the standard output (usually the Terminal executing the binary).
|
||||||
|
|
||||||
|
## 📝 Concepts Introduced
|
||||||
|
* Including `stdlib.h` for basic IO.
|
||||||
|
* The `main()` entry point.
|
||||||
|
* Using `printf()` for formatted output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 The Code (`src/userland/cli/hello_world.c`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
// Standard library initialization is handled automatically by crt0.asm
|
||||||
|
|
||||||
|
// Print a simple string to the terminal
|
||||||
|
printf("Hello, World from BoredOS Userland!\n");
|
||||||
|
|
||||||
|
// Print some formatted data
|
||||||
|
int favorite_number = 67;
|
||||||
|
printf("Did you know my favorite number is %d?\n", favorite_number);
|
||||||
|
|
||||||
|
// Returning from main automatically terminates the process cleanly
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ How it Works
|
||||||
|
|
||||||
|
1. **`#include <stdlib.h>`**: We include the SDK's standard library header which gives us access to `printf`.
|
||||||
|
2. **`int main(...)`**: Every process begins execution here (managed transparently by `crt0.asm`).
|
||||||
|
3. **`printf(...)`**: The SDK routes this call internally directly to the `SYS_WRITE` system call, making it available on the terminal.
|
||||||
|
4. **`return 0`**: A successful exit code.
|
||||||
|
|
||||||
|
## 🚀 Running It
|
||||||
|
|
||||||
|
If you build the project, you can open the Terminal and type:
|
||||||
|
```sh
|
||||||
|
/ # hello_world
|
||||||
|
Hello, World from BoredOS Userland!
|
||||||
|
Did you know my favorite number is 67?
|
||||||
|
/ #
|
||||||
|
```
|
||||||
71
docs/appdev/examples/02_basic_window.md
Normal file
71
docs/appdev/examples/02_basic_window.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Example 02: Basic Window</h1>
|
||||||
|
<p><em>An introduction to libui and creating graphical apps.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This example demonstrates how to create an empty window that stays active on the screen until the user explicitly closes it by clicking the 'X' button.
|
||||||
|
|
||||||
|
## 📝 Concepts Introduced
|
||||||
|
* Including `libui.h` and the event structure.
|
||||||
|
* Creating a `ui_window_t` handle.
|
||||||
|
* Creating an infinite event loop using `ui_get_event()`.
|
||||||
|
* Yielding CPU time to the kernel via `sys_yield()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 The Code (`src/userland/gui/basic_window.c`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <libui.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
// 1. Ask the Window Manager to create a new window
|
||||||
|
// Arguments are: Title, X Position, Y Position, Width, Height
|
||||||
|
ui_window_t wid = ui_window_create("My First GUI", 100, 100, 400, 300);
|
||||||
|
|
||||||
|
if (wid < 0) {
|
||||||
|
printf("Failed to create the window!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Define our event object
|
||||||
|
gui_event_t event;
|
||||||
|
|
||||||
|
// 3. Enter the main event loop
|
||||||
|
while (1) {
|
||||||
|
// ui_get_event is non-blocking. It returns true if an event was waiting.
|
||||||
|
if (ui_get_event(wid, &event)) {
|
||||||
|
|
||||||
|
// Check what type of event occurred
|
||||||
|
if (event.type == GUI_EVENT_CLOSE) {
|
||||||
|
// The user clicked the 'X' button in the titlebar!
|
||||||
|
printf("Window closed cleanly by user.\n");
|
||||||
|
break; // Break the infinite loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. CRITICAL: Yield the remainder of our timeslice
|
||||||
|
// If we don't do this, the while(1) loop will consume 100% of the CPU
|
||||||
|
// and starve the rest of the OS!
|
||||||
|
sys_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returning from main will automatically destroy the window and exit the process.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ How it Works
|
||||||
|
|
||||||
|
1. **Window Handle (`wid`)**: `ui_window_create` sends a request to the kernel. The kernel allocates the memory for the window and returns a numerical ID (the handle) that we use for all future interactions with that specific window.
|
||||||
|
2. **The Event Loop**: Graphical programs run forever until closed. The `while (1)` loop serves this purpose.
|
||||||
|
3. **Polling**: `ui_get_event` asks the kernel, "Hey, did the user click my window or press a key since the last time I asked?". It is non-blocking, so it immediately returns `false` if nothing happened.
|
||||||
|
4. **CPU Yielding**: Since we are constantly polling in a tight loop, we call `sys_yield()` at the end of the loop frame. This politely tells the OS scheduler, "I'm done checking for events, go ahead and let another program run for a bit."
|
||||||
|
|
||||||
|
## 🚀 Running It
|
||||||
|
|
||||||
|
Launch the Terminal and type `basic_window`. You'll see an empty window appear that you can move around the screen!
|
||||||
92
docs/appdev/examples/03_bouncing_ball.md
Normal file
92
docs/appdev/examples/03_bouncing_ball.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Example 03: Bouncing Ball</h1>
|
||||||
|
<p><em>Animating graphics and managing application state.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This example builds upon the `02_basic_window` guide. It demonstrates how to constantly update the screen to simulate a bouncing square moving freely inside the window bounds.
|
||||||
|
|
||||||
|
## 📝 Concepts Introduced
|
||||||
|
* Maintaining application state across frames (Velocity/Position).
|
||||||
|
* Drawing primitives (`ui_fill_rect`, `ui_draw_string`).
|
||||||
|
* The importance of clearing the screen on a new frame.
|
||||||
|
* Explicitly forcing standard visual updates via `ui_mark_dirty()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 The Code (`src/userland/gui/bounce.c`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <libui.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
// Window Dimensions
|
||||||
|
#define W_WIDTH 400
|
||||||
|
#define W_HEIGHT 300
|
||||||
|
// Square Dimensions
|
||||||
|
#define SQ_SIZE 30
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
ui_window_t wid = ui_window_create("Bouncing Box Animation", 50, 50, W_WIDTH, W_HEIGHT);
|
||||||
|
if (wid < 0) return 1;
|
||||||
|
|
||||||
|
// Define object state variables
|
||||||
|
int pos_x = 50;
|
||||||
|
int pos_y = 50;
|
||||||
|
int vel_x = 2; // Move 2 pixels per frame horizontally
|
||||||
|
int vel_y = 2; // Move 2 pixels per frame vertically
|
||||||
|
|
||||||
|
gui_event_t event;
|
||||||
|
while (1) {
|
||||||
|
// 1. Process Events
|
||||||
|
while (ui_get_event(wid, &event)) {
|
||||||
|
if (event.type == GUI_EVENT_CLOSE) {
|
||||||
|
return 0; // Exit cleanly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Physics & Logic Update
|
||||||
|
pos_x += vel_x;
|
||||||
|
pos_y += vel_y;
|
||||||
|
|
||||||
|
// Collision logic (Bounce off edges)
|
||||||
|
// The window has a 20px title bar, so the usable client height is W_HEIGHT - 20.
|
||||||
|
if (pos_x <= 0 || pos_x + SQ_SIZE >= W_WIDTH) {
|
||||||
|
vel_x = -vel_x; // Reverse horizontal direction
|
||||||
|
}
|
||||||
|
if (pos_y <= 0 || pos_y + SQ_SIZE >= W_HEIGHT - 20) {
|
||||||
|
vel_y = -vel_y; // Reverse vertical direction
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Rendering Update
|
||||||
|
// Step A: Clear the entire background to Black (0xFF000000)
|
||||||
|
ui_draw_rect(wid, 0, 0, W_WIDTH, W_HEIGHT, 0xFF000000);
|
||||||
|
|
||||||
|
// Step B: Draw our shape in Red (0xFFFF0000) at the new position
|
||||||
|
ui_draw_rect(wid, pos_x, pos_y, SQ_SIZE, SQ_SIZE, 0xFFFF0000);
|
||||||
|
|
||||||
|
// Step C: Draw some UI text over the animation in White
|
||||||
|
ui_draw_string(wid, 10, 10, "BoredOS Animation Demo!", 0xFFFFFFFF);
|
||||||
|
|
||||||
|
// Step D: Instruct the compositor to flush our drawing buffer to the physical screen
|
||||||
|
ui_mark_dirty(wid, 0, 0, W_WIDTH, W_HEIGHT);
|
||||||
|
|
||||||
|
// 4. Yield and throttle
|
||||||
|
sys_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ How it Works
|
||||||
|
|
||||||
|
1. **State Management**: We store `pos_x`, `pos_y`, `vel_x`, and `vel_y`. These variables represent the "physics" of our system. Notice that they update *outside* the event-checking logic so that the animation runs even if the user isn't clicking the mouse.
|
||||||
|
2. **Screen Clearing**: We *must* fill the screen with black (`ui_draw_rect(wid, 0, 0, W_WIDTH, W_HEIGHT, ...)`). If we don't clear the screen, the red square will leave a permanent trailing smear everywhere it goes!
|
||||||
|
3. **The Double Buffer**: `ui_draw_rect` and `ui_draw_string` do not immediately appear on your monitor. They just color a hidden buffer within the kernel.
|
||||||
|
4. **`ui_mark_dirty`**: This is the crucial command that tells the kernel Window Manager, "I'm done drawing my frame. Can you quickly copy my hidden buffer over to the real screen now?"
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Because `sys_yield()`'s pause duration depends heavily on CPU load and how many other processes are running (or QEMU emulation speed), tying physics/movement strictly to loops can make the game run faster on faster computers. Advanced developers will want to calculate delta time (time elapsed since the last frame) for smooth motion.
|
||||||
92
docs/appdev/examples/04_tcp_client.md
Normal file
92
docs/appdev/examples/04_tcp_client.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Example 04: TCP HTTP Client</h1>
|
||||||
|
<p><em>Utilizing lwIP to establish an outbound TCP connection.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This advanced example demonstrates the steps required to use the raw network system calls to establish a connection with an external HTTP server and dump the response over the terminal.
|
||||||
|
|
||||||
|
## 📝 Concepts Introduced
|
||||||
|
* Verifying the network state (`sys_network_is_initialized`, `sys_network_has_ip`).
|
||||||
|
* Performing DNS lookups manually via `sys_dns_lookup`.
|
||||||
|
* Managing strict TCP flow logic (`sys_tcp_connect`, send, block for receive).
|
||||||
|
* Using the terminal `SYS_WRITE` output for debugging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 The Code (`src/userland/cli/http_get.c`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
if (!sys_network_is_initialized() || !sys_network_has_ip()) {
|
||||||
|
printf("Network is unreachable! Make sure you inited the network first!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Resolve host name to IP
|
||||||
|
const char *target_host = "boreddev.nl";
|
||||||
|
net_ipv4_address_t server_ip;
|
||||||
|
|
||||||
|
printf("Resolving %s...\n", target_host);
|
||||||
|
if (sys_dns_lookup(target_host, &server_ip) < 0) {
|
||||||
|
printf("DNS Lookup failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Resolved to: %d.%d.%d.%d\n", server_ip.bytes[0], server_ip.bytes[1],
|
||||||
|
server_ip.bytes[2], server_ip.bytes[3]);
|
||||||
|
|
||||||
|
// 2. Establish a TCP connection on port 80 (HTTP)
|
||||||
|
printf("Connecting...\n");
|
||||||
|
if (sys_tcp_connect(&server_ip, 80) < 0) {
|
||||||
|
printf("Connection failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Connected! Sending GET request...\n");
|
||||||
|
|
||||||
|
// 3. Format and send the raw HTTP Request
|
||||||
|
char request[256];
|
||||||
|
strcpy(request, "GET / HTTP/1.1\r\nHost: ");
|
||||||
|
strcat(request, target_host);
|
||||||
|
strcat(request, "\r\nConnection: close\r\n\r\n");
|
||||||
|
|
||||||
|
if (sys_tcp_send(request, strlen(request)) < 0) {
|
||||||
|
printf("Failed to send data.\n");
|
||||||
|
sys_tcp_close();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Block and wait for response data
|
||||||
|
char recv_buf[512];
|
||||||
|
int bytes_received;
|
||||||
|
|
||||||
|
printf("\n--- RESPONSE ---\n");
|
||||||
|
while ((bytes_received = sys_tcp_recv(recv_buf, sizeof(recv_buf) - 1)) > 0) {
|
||||||
|
recv_buf[bytes_received] = '\0'; // Null-terminate the chunk
|
||||||
|
printf("%s", recv_buf); // Print the chunk to stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Cleanup
|
||||||
|
printf("\n--- END RESPONSE ---\n");
|
||||||
|
sys_tcp_close();
|
||||||
|
printf("Connection closed.\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ How it Works
|
||||||
|
|
||||||
|
1. **Network Setup**: First, we must ensure the host machine or QEMU environment gave BoredOS a valid IP address via DHCP. The `sys_network_has_ip()` check prevents our app from hanging trying to route data to nowhere.
|
||||||
|
2. **DNS (`sys_dns_lookup`)**: Since we want to connect to a domain name, not a raw IP, we query the DNS server configured by the OS (which it received via DHCP).
|
||||||
|
3. **Connection (`sys_tcp_connect`)**: We block the application thread while the OS performs the 3-way TCP handshake over port 80.
|
||||||
|
4. **Payload (`sys_tcp_send`)**: We format a compliant HTTP/1.1 payload representing a simple GET request for the root directory `/`.
|
||||||
|
5. **Chunked Receiving (`sys_tcp_recv`)**: The server's response might be larger than our `recv_buf` (512 bytes). Therefore, we loop. `sys_tcp_recv` blocks execution until data arrives. If it returns `0`, the remote server cleanly closed the connection (which happens automatically because we specified `Connection: close` in our request payload!).
|
||||||
|
|
||||||
|
## 🚀 Running It
|
||||||
|
|
||||||
|
Make sure QEMU is running with networking enabled. Launch the terminal and type `http_get`. You will see the raw headers and HTML source of the target webpage scroll down the CLI interface!
|
||||||
28
docs/appdev/examples/README.md
Normal file
28
docs/appdev/examples/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Example Applications</h1>
|
||||||
|
<p><em>From basic output to complex Graphical and Network applications.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Welcome to the examples directory! These guides are designed to help you understand how to write C applications for the BoredOS userland, utilizing the custom `libc` SDK.
|
||||||
|
|
||||||
|
The examples are listed in order of increasing complexity. Click on a tutorial to view the complete source code and an explanation of the concepts it introduces.
|
||||||
|
|
||||||
|
## 🟢 Beginner
|
||||||
|
|
||||||
|
* **[`01_hello_cli.md`](01_hello_cli.md)**: The absolute basics. Learn how to write a simple Terminal program that outputs text and processes standard system calls.
|
||||||
|
* **[`02_basic_window.md`](02_basic_window.md)**: An introduction to `libui.h`. Learn how to create an empty window, set up a basic event loop, and handle the "Close" button cleanly.
|
||||||
|
|
||||||
|
## 🟡 Intermediate
|
||||||
|
|
||||||
|
* **[`03_bouncing_ball.md`](03_bouncing_ball.md)**: Dive deeper into graphical rendering. This example introduces the `ui_mark_dirty` command, framerate independence via `sys_yield()`, and state management to animate a shape moving around the screen and bouncing off the window edges.
|
||||||
|
|
||||||
|
## 🔴 Advanced
|
||||||
|
|
||||||
|
* **[`04_tcp_client.md`](04_tcp_client.md)**: Using the lwIP networking stack. This example demonstrates how to perform a DNS lookup, connect to an external server over TCP (like an HTTP server), send a raw request, and print the response to the terminal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If you want to test these out, simply create a new `.c` file in `src/userland/cli/` (for terminal apps) or `src/userland/gui/` (for windowed apps), paste the example code, then run `make clean && make run` from the project root!
|
||||||
345
docs/appdev/grapher.md
Normal file
345
docs/appdev/grapher.md
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Grapher</h1>
|
||||||
|
<p><em>An interactive mathematical expression plotter for BoredOS, supporting both 2D and 3D visualizations.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Grapher is a built-in GUI application that lets you type any mathematical equation and see it plotted in real time. It supports 2D explicit and implicit curves as well as full 3D surface visualization — including both explicit surfaces (`z = f(x, y)`) and implicit surfaces (`f(x, y, z) = c`).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Grapher is located at `src/userland/gui/grapher.c`. It runs as a standard BoredOS GUI process and can be launched from the terminal or from the dock.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features at a Glance
|
||||||
|
|
||||||
|
| Feature | Details |
|
||||||
|
|---|---|
|
||||||
|
| **2D Explicit** | Plot `y = f(x)` curves |
|
||||||
|
| **2D Implicit** | Plot any `f(x, y) = g(x, y)` contour via marching squares |
|
||||||
|
| **3D Explicit** | Plot `z = f(x, y)` surfaces |
|
||||||
|
| **3D Implicit** | Plot any `f(x, y, z) = c` surface |
|
||||||
|
| **Rendering modes** | Wireframe and filled polygon modes |
|
||||||
|
| **Height coloring** | Surfaces are colored by a blue→green→yellow→red gradient based on Z height |
|
||||||
|
| **Phong-style shading** | Filled mode computes per-face normals and applies diffuse + ambient lighting |
|
||||||
|
| **Parallel rendering** | Evaluation and projection are distributed across 4 worker threads via `sys_parallel_run` |
|
||||||
|
| **Preset equations** | 7 built-in presets accessible from the toolbar |
|
||||||
|
| **Auto-fit** | 2D view auto-fits the Y axis to the plotted curve on first plot |
|
||||||
|
| **Atomic Color-Depth Buffer** | All 3D drawing uses a 64-bit atomic buffer to prevent depth/color race conditions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Launching Grapher
|
||||||
|
|
||||||
|
From the BoredOS terminal:
|
||||||
|
```sh
|
||||||
|
grapher
|
||||||
|
```
|
||||||
|
|
||||||
|
Or click the **Grapher icon** in the system dock.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Toolbar Controls
|
||||||
|
|
||||||
|
| Control | Function |
|
||||||
|
|---|---|
|
||||||
|
| **Equation box** | Type your mathematical expression, then press **Enter** or **Plot** |
|
||||||
|
| **Plot button** | Parse and render the current equation |
|
||||||
|
| **Wire / Filled button** | Toggle wireframe vs. shaded polygon mode (3D only) |
|
||||||
|
| **Presets button** | Open a dropdown of example equations |
|
||||||
|
|
||||||
|
### Status Bar Controls (3D mode)
|
||||||
|
|
||||||
|
| Control | Function |
|
||||||
|
|---|---|
|
||||||
|
| **`+` button** | Increase the 3D world range (zoom out in world space) |
|
||||||
|
| **`-` button** | Decrease the 3D world range (zoom in in world space) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
| Shortcut | Action |
|
||||||
|
|---|---|
|
||||||
|
| **Enter** (in equation box) | Plot the equation |
|
||||||
|
| **Ctrl + R** | Reset the view to defaults |
|
||||||
|
| **F** | Toggle filled / wireframe rendering (3D mode) |
|
||||||
|
| **Scroll wheel** | Zoom in/out (2D mode adjusts viewport; 3D mode adjusts camera zoom) |
|
||||||
|
| **Right-click drag** | Rotate the 3D surface |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Writing Equations
|
||||||
|
|
||||||
|
Grapher parses equations entered as plain text. It supports a subset of mathematical notation with automatic implicit multiplication.
|
||||||
|
|
||||||
|
### Supported Functions
|
||||||
|
|
||||||
|
| Syntax | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `sin(x)` | Sine |
|
||||||
|
| `cos(x)` | Cosine |
|
||||||
|
| `tan(x)` | Tangent |
|
||||||
|
| `sqrt(x)` | Square root |
|
||||||
|
| `abs(x)` | Absolute value |
|
||||||
|
| `log(x)` | Natural logarithm (base *e*) |
|
||||||
|
|
||||||
|
### Supported Operators
|
||||||
|
|
||||||
|
| Operator | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `+` `-` `*` `/` | Arithmetic |
|
||||||
|
| `^` | Exponentiation (right-associative) |
|
||||||
|
| `(` `)` | Grouping |
|
||||||
|
|
||||||
|
### Special Values
|
||||||
|
|
||||||
|
| Token | Value |
|
||||||
|
|---|---|
|
||||||
|
| `pi` or `PI` | π ≈ 3.14159… |
|
||||||
|
|
||||||
|
### Implicit Multiplication
|
||||||
|
|
||||||
|
Adjacent tokens that would normally require a `*` are multiplied automatically:
|
||||||
|
|
||||||
|
```
|
||||||
|
2x → 2 * x
|
||||||
|
3sin(x) → 3 * sin(x)
|
||||||
|
(x+1)(x) → (x+1) * x
|
||||||
|
```
|
||||||
|
|
||||||
|
### How Equations Are Classified
|
||||||
|
|
||||||
|
Grapher looks at which variables appear in your equation to automatically choose the rendering mode:
|
||||||
|
|
||||||
|
| Equation form | Auto-detected as |
|
||||||
|
|---|---|
|
||||||
|
| `y = f(x)` or just `f(x)` | 2D explicit |
|
||||||
|
| `f(x, y) = g(x, y)` | 2D implicit |
|
||||||
|
| `z = f(x, y)` | 3D explicit |
|
||||||
|
| `f(x, y, z) = c` | 3D implicit |
|
||||||
|
|
||||||
|
If you omit the `=` sign, Grapher treats the input as `y = <expression>` when no `y` or `z` is present, or as `<expression> = 0` otherwise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Equations
|
||||||
|
|
||||||
|
### 2D Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
y = sin(x)
|
||||||
|
y = x^2
|
||||||
|
y = cos(x)*x
|
||||||
|
y = abs(x) - 2
|
||||||
|
x^2 + y^2 = 25 ← circle (implicit)
|
||||||
|
y = log(x)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3D Explicit Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
z = sin(x)*cos(y)
|
||||||
|
z = x^2 - y^2 ← saddle surface
|
||||||
|
z = sqrt(25 - x^2 - y^2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3D Implicit Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
x^2 + y^2 + z^2 = 25 ← sphere
|
||||||
|
x^2 + y^2 = 16 ← cylinder
|
||||||
|
x^2 + y^2 - z^2 = 1 ← hyperboloid
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Navigation Controls
|
||||||
|
|
||||||
|
### 2D Mode
|
||||||
|
|
||||||
|
| Input | Action |
|
||||||
|
|---|---|
|
||||||
|
| **Scroll up** | Zoom in |
|
||||||
|
| **Scroll down** | Zoom out |
|
||||||
|
| **Ctrl+R** | Reset to default view (`x: [-10, 10]`) |
|
||||||
|
|
||||||
|
### 3D Mode
|
||||||
|
|
||||||
|
| Input | Action |
|
||||||
|
|---|---|
|
||||||
|
| **Right-click drag** | Rotate the surface (orbit camera) |
|
||||||
|
| **Scroll up** | Zoom camera in |
|
||||||
|
| **Scroll down** | Zoom camera out |
|
||||||
|
| **`+` / `-` buttons** | Increase / decrease world range |
|
||||||
|
| **Ctrl+R** | Reset rotation and zoom |
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> In 3D mode, the surface auto-rotates slowly by default. This can be disabled by setting `#define ROTATE 0` in the source file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
Grapher is implemented as a single self-contained C file. Below is a high-level breakdown of its major components:
|
||||||
|
|
||||||
|
### Math Library
|
||||||
|
|
||||||
|
Grapher uses the BoredOS freestanding **`libc/math.h`** library, which provides all the math functions it needs without depending on a host standard library:
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|---|---|
|
||||||
|
| `sin`, `cos`, `tan` | Trigonometry via Taylor series (8 terms, range-reduced to `[-π, π]`) |
|
||||||
|
| `sqrt` | Newton-Raphson iteration (25 steps) |
|
||||||
|
| `log` | Natural logarithm via Padé-style series |
|
||||||
|
| `log2`, `log10` | Derived from `log` |
|
||||||
|
| `exp` | Range-reduced Taylor series for `e^x` |
|
||||||
|
| `pow` | Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))` |
|
||||||
|
| `fabs`, `fmod` | Absolute value and floating-point remainder |
|
||||||
|
| `floor`, `ceil` | Rounding |
|
||||||
|
| `sinh`, `cosh`, `tanh` | Hyperbolic functions |
|
||||||
|
| `hypot`, `fmin`, `fmax`, `fclamp` | Utility helpers |
|
||||||
|
|
||||||
|
The constants `M_PI`, `M_E`, `M_LN2`, `M_SQRT2` are also defined in the header.
|
||||||
|
|
||||||
|
This library is automatically linked into every userland ELF — any app can `#include "math.h"` to use it.
|
||||||
|
|
||||||
|
### Expression Parser
|
||||||
|
|
||||||
|
Equations are parsed in three stages:
|
||||||
|
|
||||||
|
1. **Tokenizer** (`tokenize`) — converts the input string into a flat token array. Handles implicit multiplication by inserting `*` tokens where needed.
|
||||||
|
2. **Recursive Descent Parser** (`parse_expr`, `parse_term`, `parse_power`, `parse_unary`, `parse_atom`) — produces an Abstract Syntax Tree (AST) with up to `MAX_NODES = 128` nodes.
|
||||||
|
3. **Bytecode Compiler** (`compile_ast`) — walks the AST in post-order and emits a flat instruction sequence for a simple stack machine. This avoids recursive evaluation during rendering hot paths.
|
||||||
|
|
||||||
|
The resulting bytecode is then executed by `run_bc` for every sample point.
|
||||||
|
|
||||||
|
### Rendering Pipeline
|
||||||
|
|
||||||
|
#### 2D Rendering
|
||||||
|
|
||||||
|
- **Explicit** — evaluates `y = f(x)` at every pixel column and connects adjacent samples with Bresenham lines.
|
||||||
|
- **Implicit** — applies **marching squares** on a 200×130 grid to find sign changes in `f(x,y) - g(x,y)` and plots intersection pixels.
|
||||||
|
|
||||||
|
#### 3D Rendering
|
||||||
|
|
||||||
|
The 3D pipeline uses a multi-pass system parallelized across worker threads:
|
||||||
|
|
||||||
|
| Pass | Function | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | **Evaluation** | Samples the surface at grid points. For implicit surfaces, this uses **tri-axis marching**. |
|
||||||
|
| 2 | **Projection** | Projects 3D world coordinates to 2D screen coordinates with perspective. |
|
||||||
|
| 3 | **Drawing** | Rasterizes wireframe lines or filled triangles with Z-buffering. |
|
||||||
|
|
||||||
|
##### Tri-Axis Marching (Implicit Surfaces)
|
||||||
|
|
||||||
|
Unlike explicit surfaces that only need one evaluation per grid point, implicit surfaces require finding roots of $f(x, y, z) = 0$. To ensure complete surface connectivity and eliminate "cracks," Grapher marches along all three primary axes:
|
||||||
|
|
||||||
|
1. **X-Axis Pass**: For every $(y, z)$ pair, march along $x$.
|
||||||
|
2. **Y-Axis Pass**: For every $(x, z)$ pair, march along $y$.
|
||||||
|
3. **Z-Axis Pass**: For every $(x, y)$ pair, march along $z$.
|
||||||
|
|
||||||
|
Each pass uses a multi-stage root finder (170 linear steps followed by 15 bisection iterations). By sampling along all three axes, the engine "catches" surfaces that are nearly parallel to any specific marching direction, ensuring that vertical walls and steep gradients are rendered solidly from any viewing angle.
|
||||||
|
|
||||||
|
##### Atomic Color-Depth Buffer
|
||||||
|
|
||||||
|
To prevent "z-fighting" and race conditions between parallel threads, Grapher uses a 64-bit atomic buffer (`graph_czb`). Each 64-bit word stores:
|
||||||
|
- **Upper 32 bits**: Z-depth (integer).
|
||||||
|
- **Lower 32 bits**: Pixel color (0xAARRGGBB).
|
||||||
|
|
||||||
|
A single `__atomic_compare_exchange_n` operation ensures that a pixel's color and depth are updated together only if the new depth is closer to the camera than the existing one.
|
||||||
|
|
||||||
|
Surface normals are estimated using central finite differences of the implicit function.
|
||||||
|
|
||||||
|
#### Filled Mod
|
||||||
|
|
||||||
|
When filled mode is active, each quad cell is split into two triangles. The average surface normal across the four corner vertices is computed and fed into `apply_shading`, which calculates:
|
||||||
|
|
||||||
|
```
|
||||||
|
intensity = ambient(0.3) + diffuse(0.7) * dot(normal, light_direction)
|
||||||
|
```
|
||||||
|
|
||||||
|
The light direction is fixed at `(0.577, 0.707, 0.408)` (normalized diagonal).
|
||||||
|
|
||||||
|
#### Z-Buffer
|
||||||
|
|
||||||
|
The depth buffer (`graph_zb`) stores integer depth values. `gfb_pixel_z` uses a **compare-and-swap (CAS) loop** via `__atomic_compare_exchange_n` so multiple parallel draw threads cannot produce race conditions.
|
||||||
|
|
||||||
|
### Coordinate Systems
|
||||||
|
|
||||||
|
#### 2D
|
||||||
|
|
||||||
|
World coordinates map linearly to screen pixels:
|
||||||
|
|
||||||
|
```c
|
||||||
|
screen_x = (wx - view_x_min) / (view_x_max - view_x_min) * graph_w
|
||||||
|
screen_y = (view_y_max - wy) / (view_y_max - view_y_min) * graph_h
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3D
|
||||||
|
|
||||||
|
Points are first rotated by two Euler angles (`rot_y`, `rot_x`) then projected with a simple perspective divide:
|
||||||
|
|
||||||
|
```
|
||||||
|
persp = d / (pz + d) // d = range_3d * 5
|
||||||
|
sx = px * scale * persp + screen_cx
|
||||||
|
sy = -py * scale * persp + screen_cy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Constants
|
||||||
|
|
||||||
|
These can be changed at the top of `grapher.c` to tune behaviour:
|
||||||
|
|
||||||
|
| Constant | Default | Effect |
|
||||||
|
|---|---|---|
|
||||||
|
| `ROTATE` | `1` | Set to `0` to disable auto-rotation in 3D mode |
|
||||||
|
| `GRID_3D` | `41` | Grid resolution for 3D sampling. Higher = more detail, much slower |
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Setting `GRID_3D` too high (e.g. 9000) will exhaust available memory. The `surf` grid and `surf_x`/`surf_y_3d` arrays are statically allocated at compile time: memory usage grows as **O(GRID_3D²)**. Values above ~512 are not recommended.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> `GRID_3D = 256` gives a good balance of detail and performance on typical BoredOS hardware emulation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Palette
|
||||||
|
|
||||||
|
|
||||||
|
3D surfaces are colored by height using a 4-stop rainbow ramp:
|
||||||
|
|
||||||
|
```
|
||||||
|
Low → Blue → Cyan → Green → Yellow → Red → High
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preset Equations
|
||||||
|
|
||||||
|
The built-in presets are shown in the dropdown when you click **Presets**:
|
||||||
|
|
||||||
|
| Label | Type |
|
||||||
|
|---|---|
|
||||||
|
| `y = sin(x)` | 2D explicit |
|
||||||
|
| `y = x^2` | 2D explicit |
|
||||||
|
| `y = cos(x)*x` | 2D explicit |
|
||||||
|
| `z = sin(x)*cos(y)` | 3D explicit |
|
||||||
|
| `z = x^2 - y^2` | 3D explicit |
|
||||||
|
| `x^2+y^2+z^2=25` | 3D implicit (sphere) |
|
||||||
|
| `x^2+y^2=16` | 3D implicit (cylinder) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
- **No parameter slider** — equations are static; there is no way to animate a parameter.
|
||||||
|
- **No multiple equations** — only one equation can be graphed at a time.
|
||||||
|
- **Implicit surface precision** — extremely thin or high-frequency implicit surfaces may still have small artifacts if the grid resolution (`GRID_3D`) is too low.
|
||||||
|
- **3D implicit performance** — tri-axis marching evaluates the function significantly more times than explicit rendering; high resolutions will impact frame rate.
|
||||||
|
- **Integer axis labels only for large values** — very large axis values are capped at `>2G` or `<-2G` due to `itoa` limitations.
|
||||||
@@ -1,36 +1,214 @@
|
|||||||
# Userland SDK Reference
|
<div align="center">
|
||||||
|
<h1>Userland SDK Reference</h1>
|
||||||
|
<p><em>Comprehensive manual for custom libc and system calls in BoredOS.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
BoredOS provides a custom `libc` implementation necessary for writing userland applications (`.elf` binaries). By avoiding a full-blown standard library like `glibc`, the OS ensures a minimal executable footprint tailored strictly to the existing kernel features.
|
BoredOS provides a custom `libc` implementation necessary for writing userland applications (`.elf` binaries). By avoiding a full-blown standard library like `glibc`, the OS ensures a minimal executable footprint tailored strictly to the existing kernel features.
|
||||||
|
|
||||||
## The Custom libc Structure (`src/userland/libc/`)
|
All headers are located in `src/userland/libc/` (standard functions) and `src/wm/` (UI and widgets).
|
||||||
|
- `stdlib.h`: Memory, strings, and basic I/O.
|
||||||
|
- `math.h`: Freestanding floating-point math library.
|
||||||
|
- `libui.h`: Core window and drawing API.
|
||||||
|
- `libwidget.h`: High-level UI components.
|
||||||
|
|
||||||
The SDK comprises a few key files containing wrappers around kernel system calls:
|
## Standard Library (`stdlib.h` & `string.h`)
|
||||||
|
|
||||||
- `stdlib.h` / `stdlib.c`: Memory allocation (`malloc`, `free`), integer conversion (`itoa`, `atoi`), printing (`printf`, `sprintf`), and random numbers (`rand`, `srand`).
|
The standard library wrappers provide memory management, string manipulation, and basic IO formatting without needing direct syscalls.
|
||||||
- `string.h` / `string.c`: String manipulation utilities (`strlen`, `strcpy`, `strcmp`, `memset`, `memcpy`).
|
|
||||||
- `syscall.h` / `syscall.c`: The raw interface to issue `syscall` assembly instructions, routing requests to the kernel.
|
|
||||||
- `libui.h` / `libui.c`: Graphical interface commands (creating windows, drawing pixels, events).
|
|
||||||
|
|
||||||
## System Calls Overview
|
### Memory Allocation
|
||||||
|
* `void* malloc(size_t size);` - Allocate a block of memory on the heap.
|
||||||
|
* `void free(void* ptr);` - Free a previously allocated memory block.
|
||||||
|
* `void* calloc(size_t nmemb, size_t size);` - Allocate and zero-out a block of memory for an array.
|
||||||
|
* `void* realloc(void* ptr, size_t size);` - Resize an existing memory block.
|
||||||
|
|
||||||
When a userland application wants to interact with the hardware (print to screen, read a file, create a window), it must ask the kernel via a **System Call**.
|
### Memory Manipulation (`string.h`)
|
||||||
|
* `void* memset(void *s, int c, size_t n);` - Fill a block of memory with a specific byte.
|
||||||
|
* `void* memcpy(void *dest, const void *src, size_t n);` - Copy memory from source to destination.
|
||||||
|
* `void* memmove(void *dest, const void *src, size_t n);` - Safely copy overlapping memory blocks.
|
||||||
|
* `int memcmp(const void *s1, const void *s2, size_t n);` - Compare two memory blocks.
|
||||||
|
|
||||||
In BoredOS (`x86_64`), system calls are issued using the `syscall` instruction. The kernel intercepts this instruction and inspects the processor's RAX register to figure out *what* the application wants to do.
|
### String Utilities
|
||||||
|
* `size_t strlen(const char *s);` - Get the length of a string.
|
||||||
|
* `int strcmp(const char *s1, const char *s2);` - Compare two strings lexicographically.
|
||||||
|
* `char* strcpy(char *dest, const char *src);` - Copy a string to a destination buffer.
|
||||||
|
* `char* strcat(char *dest, const char *src);` - Concatenate two strings.
|
||||||
|
|
||||||
|
### Conversion and Formatting
|
||||||
|
* `int atoi(const char *nptr);` - String to integer conversion.
|
||||||
|
* `void itoa(int n, char *buf);` - Integer to string conversion.
|
||||||
|
|
||||||
|
### Input / Output
|
||||||
|
* `void puts(const char *s);` - Print a string followed by a newline to the standard output.
|
||||||
|
* `void printf(const char *fmt, ...);` - Formatted print to standard output (supports `%d`, `%s`, `%x`, etc.).
|
||||||
|
|
||||||
|
### Process Control
|
||||||
|
* `void exit(int status);` - Terminate the current process.
|
||||||
|
* `void sleep(int ms);` - Pause execution for a specified number of milliseconds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Math Library (`math.h`)
|
||||||
|
|
||||||
|
BoredOS ships a freestanding floating-point math library in `libc/math.h`. It uses pure arithmetic — Taylor series, Newton-Raphson, and range-reduction — with no dependency on a host `libm` or hardware math intrinsics. It is automatically linked into every userland ELF.
|
||||||
|
|
||||||
The custom `libc` provides `syscallX` wrapper functions that abstract the assembly details:
|
|
||||||
```c
|
```c
|
||||||
// Example: Performing a minimal system call from userland
|
#include "math.h"
|
||||||
int sys_write(int fd, const char *buf, int len) {
|
|
||||||
return syscall3(SYS_WRITE, fd, (uint64_t)buf, len);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Notable System Calls
|
### Constants
|
||||||
|
|
||||||
- **`SYS_WRITE` (1)**: Currently acts as a generic output mechanism for `printf`, typically routing text to the kernel's serial output for debugging, or to an active text-mode console.
|
| Constant | Value | Description |
|
||||||
- **`SYS_GUI` (3)**: The primary multiplexer for all window manager operations. The arguments define subcommands (like `UI_CREATE_WINDOW`, `UI_FILL_RECT`).
|
|---|---|---|
|
||||||
- **`SYS_FS` (4)**: Interacts with the virtual filesystem (e.g., `FS_CMD_OPEN`, `FS_CMD_READ`). Under the hood, this reads from the loaded RAMFS or an attached physical ATA disk via the native FAT32 driver.
|
| `M_PI` | 3.14159… | π |
|
||||||
- **`SYS_EXIT` (60)**: Terminates the current process and returns control to the kernel.
|
| `M_E` | 2.71828… | Euler's number |
|
||||||
- **`SYSTEM_CMD_YIELD` (43)**: Instructs the process scheduler to pause the current process and let another process run.
|
| `M_LN2` | 0.69315… | Natural log of 2 |
|
||||||
|
| `M_SQRT2` | 1.41421… | √2 |
|
||||||
|
| `HUGE_VAL` | ~+∞ | Overflow sentinel |
|
||||||
|
|
||||||
If you are developing a new application, **do not invoke syscalls manually**. Instead, include `stdlib.h` and use the C functions provided.
|
### Functions
|
||||||
|
|
||||||
|
#### Absolute value & remainder
|
||||||
|
* `double fabs(double x);` — Absolute value.
|
||||||
|
* `double fmod(double x, double y);` — Floating-point remainder. Returns `0` when `y == 0`.
|
||||||
|
|
||||||
|
#### Rounding
|
||||||
|
* `double floor(double x);` — Largest integer ≤ x.
|
||||||
|
* `double ceil(double x);` — Smallest integer ≥ x.
|
||||||
|
|
||||||
|
#### Trigonometry *(arguments in radians)*
|
||||||
|
* `double sin(double x);` — Sine. Range-reduced to `[-π, π]` then computed via 8-term Taylor series.
|
||||||
|
* `double cos(double x);` — Cosine. Computed via `sin(x + π/2)`.
|
||||||
|
* `double tan(double x);` — Tangent. Returns sentinel `1e15` near poles.
|
||||||
|
|
||||||
|
#### Exponential & logarithm
|
||||||
|
* `double sqrt(double x);` — Square root via Newton-Raphson (25 iterations). Returns `0` for `x ≤ 0`.
|
||||||
|
* `double log(double x);` — Natural logarithm (ln). Returns `-1e30` for `x ≤ 0`.
|
||||||
|
* `double log2(double x);` — Base-2 logarithm.
|
||||||
|
* `double log10(double x);` — Base-10 logarithm.
|
||||||
|
* `double exp(double x);` — e^x. Saturates to `1e300` for `x > 700`, `0` for `x < -700`.
|
||||||
|
* `double pow(double base, double exponent);` — General power. Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))`.
|
||||||
|
|
||||||
|
#### Hyperbolic
|
||||||
|
* `double sinh(double x);` — Hyperbolic sine.
|
||||||
|
* `double cosh(double x);` — Hyperbolic cosine.
|
||||||
|
* `double tanh(double x);` — Hyperbolic tangent.
|
||||||
|
|
||||||
|
#### Utility
|
||||||
|
* `double hypot(double x, double y);` — `sqrt(x² + y²)` without intermediate overflow.
|
||||||
|
* `double fmin(double a, double b);` — Minimum of two values.
|
||||||
|
* `double fmax(double a, double b);` — Maximum of two values.
|
||||||
|
* `double fclamp(double x, double lo, double hi);` — Clamps `x` into `[lo, hi]`.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The implementation file is named `libc/libmath.c` (not `libc/math.c`) to avoid a name collision with the `math` CLI calculator app in userland. The public header is still included as `#include "math.h"`.
|
||||||
|
|
||||||
|
For advanced operations, `syscall.h` provides direct wrappers into the kernel.
|
||||||
|
|
||||||
|
### Process & System Info
|
||||||
|
* `void sys_exit(int status);` - Raw exit syscall.
|
||||||
|
* `int sys_write(int fd, const char *buf, int len);` - Write to a file descriptor (usually fd 1 for stdout).
|
||||||
|
* `void* sys_sbrk(int incr);` - Expand or shrink the process data segment (used internally by `malloc`).
|
||||||
|
* `void sys_kill(int pid);` - Send a kill signal to a process.
|
||||||
|
* `void sys_yield(void);` - Yield the remainder of the process's timeslice to the scheduler.
|
||||||
|
* `int sys_system(int cmd, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);` - Execute a privileged system command (e.g., reboot, shutdown, beep).
|
||||||
|
* `int sys_get_os_info(os_info_t *info);` - Populate an `os_info_t` struct with version and environment details.
|
||||||
|
* `uint64_t sys_get_shell_config(const char *key);` - Retrieve internal shell configuration values.
|
||||||
|
* `void sys_set_text_color(uint32_t color);` - Set the raw CLI text output color.
|
||||||
|
|
||||||
|
### File System API (VFS)
|
||||||
|
Interacting with files and directories using the Virtual File System.
|
||||||
|
* `int sys_open(const char *path, const char *mode);` - Open a file, returning a file descriptor.
|
||||||
|
* `int sys_read(int fd, void *buf, uint32_t len);` - Read from an open file.
|
||||||
|
* `int sys_write_fs(int fd, const void *buf, uint32_t len);` - Write to an open file.
|
||||||
|
* `void sys_close(int fd);` - Close an open file descriptor.
|
||||||
|
* `int sys_seek(int fd, int offset, int whence);` - Reposition the file offset.
|
||||||
|
* `uint32_t sys_tell(int fd);` - Get the current file offset.
|
||||||
|
* `uint32_t sys_size(int fd);` - Get the total file size.
|
||||||
|
* `int sys_delete(const char *path);` - Delete a file.
|
||||||
|
* `int sys_mkdir(const char *path);` - Create a new directory.
|
||||||
|
* `int sys_exists(const char *path);` - Check if a path exists on the filesystem.
|
||||||
|
* `int sys_getcwd(char *buf, int size);` - Get the current working directory string.
|
||||||
|
* `int sys_chdir(const char *path);` - Change the current working directory.
|
||||||
|
* `int sys_list(const char *path, FAT32_FileInfo *entries, int max_entries);` - List directory contents into an array of `FAT32_FileInfo` structs.
|
||||||
|
* `int sys_get_file_info(const char *path, FAT32_FileInfo *info);` - Retrieve metadata for a specific file.
|
||||||
|
|
||||||
|
### Networking Stack API
|
||||||
|
BoredOS includes lwIP for hardware TCP/UDP networking.
|
||||||
|
|
||||||
|
#### Configuration and Status
|
||||||
|
* `int sys_network_init(void);` - Initialize the network stack.
|
||||||
|
* `int sys_network_is_initialized(void);` - Check stack status.
|
||||||
|
* `int sys_network_dhcp_acquire(void);` - Request an IP configuration from a DHCP server.
|
||||||
|
* `int sys_network_has_ip(void);` - Check if the system has a valid IP address.
|
||||||
|
* `int sys_network_get_mac(net_mac_address_t *mac);` - Get the physical MAC address of the NIC.
|
||||||
|
* `int sys_network_get_nic_name(char *name_out);` - Get the name of the active network interface card.
|
||||||
|
* `int sys_network_get_ip(net_ipv4_address_t *ip);` - Get current local IPv4 address.
|
||||||
|
* `int sys_network_set_ip(const net_ipv4_address_t *ip);` - Manually assign a static IP.
|
||||||
|
* `int sys_network_get_gateway(net_ipv4_address_t *ip);` - Get the default gateway IP.
|
||||||
|
* `int sys_network_get_dns(net_ipv4_address_t *ip);` - Get the primary DNS server IP.
|
||||||
|
* `int sys_set_dns_server(const net_ipv4_address_t *ip);` - Set the primary DNS server.
|
||||||
|
* `int sys_get_dns_server(net_ipv4_address_t *ip);` - Retrieve configured DNS server.
|
||||||
|
* `int sys_network_get_stat(int stat_type);` - Get network statistics (packets in/out, drops, etc.).
|
||||||
|
* `void sys_network_force_unlock(void);` - Force release of network stack locks (use with caution).
|
||||||
|
|
||||||
|
#### Communication
|
||||||
|
* `int sys_icmp_ping(const net_ipv4_address_t *dest_ip);` - Send an ICMP echo request.
|
||||||
|
* `int sys_udp_send(const net_ipv4_address_t *dest_ip, uint16_t dest_port, uint16_t src_port, const void *data, size_t data_len);` - Send a raw UDP datagram.
|
||||||
|
* `int sys_dns_lookup(const char *name, net_ipv4_address_t *out_ip);` - Resolve a hostname to an IPv4 address.
|
||||||
|
* `int sys_tcp_connect(const net_ipv4_address_t *ip, uint16_t port);` - Establish a TCP connection to a remote host.
|
||||||
|
* `int sys_tcp_send(const void *data, size_t len);` - Send data over an active TCP socket.
|
||||||
|
* `int sys_tcp_recv(void *buf, size_t max_len);` - Receive data from a TCP socket (blocking).
|
||||||
|
* `int sys_tcp_recv_nb(void *buf, size_t max_len);` - Receive data from a TCP socket (non-blocking).
|
||||||
|
* `int sys_tcp_close(void);` - Close the active TCP socket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Data Structures
|
||||||
|
|
||||||
|
### `os_info_t`
|
||||||
|
Contains detailed build and version information about the OS.
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
char os_name[64];
|
||||||
|
char os_version[64];
|
||||||
|
char os_codename[64];
|
||||||
|
char kernel_name[64];
|
||||||
|
char kernel_version[64];
|
||||||
|
char build_date[64];
|
||||||
|
char build_time[64];
|
||||||
|
char build_arch[64];
|
||||||
|
} os_info_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
### `FAT32_FileInfo`
|
||||||
|
Represents a filesystem entry.
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
char name[256];
|
||||||
|
uint32_t size;
|
||||||
|
uint8_t is_directory;
|
||||||
|
uint32_t start_cluster;
|
||||||
|
uint16_t write_date;
|
||||||
|
uint16_t write_time;
|
||||||
|
} FAT32_FileInfo;
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ProcessInfo`
|
||||||
|
Provides status information for an active process.
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
uint32_t pid;
|
||||||
|
char name[64];
|
||||||
|
uint64_t ticks;
|
||||||
|
size_t used_memory;
|
||||||
|
} ProcessInfo;
|
||||||
|
```
|
||||||
|
|
||||||
|
### IP / MAC Addresses
|
||||||
|
Wrappers for raw byte arrays.
|
||||||
|
```c
|
||||||
|
typedef struct { uint8_t bytes[6]; } net_mac_address_t;
|
||||||
|
typedef struct { uint8_t bytes[4]; } net_ipv4_address_t;
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,85 +1,111 @@
|
|||||||
# UI API (`libui.h`)
|
<div align="center">
|
||||||
|
<h1>UI API (<code>libui.h</code>)</h1>
|
||||||
|
<p><em>Comprehensive manual for interacting with the Window Manager.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
For an application to be visible on the screen, it must interact with the BoredOS Window Manager (WM). The tools required for this are located in `src/userland/libc/libui.h` and `libui.c`.
|
---
|
||||||
|
|
||||||
## Core Concepts
|
The UI library (`libui.h`) is the sole mechanism for Graphical Userland Applications to draw to the screen and receive input events in BoredOS. It wraps `SYS_GUI` kernel calls.
|
||||||
|
|
||||||
The UI library sends requests (via `SYS_GUI`) to the kernel to reserve an area on the screen (a `Window`) and then issues commands to color specific pixels within that area. The kernel is responsible for compositing this area over other windows.
|
## Window Management
|
||||||
|
|
||||||
## Example: Creating a Window
|
A "Window" is a reserved drawing canvas managed by the compositor.
|
||||||
|
|
||||||
First, include the library and define an event structure:
|
* `ui_window_t ui_window_create(const char *title, int x, int y, int w, int h);`
|
||||||
|
Creates a new window at `(x, y)` with dimensions `w`x`h`. Returns a window handle.
|
||||||
```c
|
**Flags** are currently embedded in the syscall; standard windows include decorations (titlebar, borders).
|
||||||
#include <libui.h>
|
* `void ui_window_set_title(ui_window_t win, const char *title);`
|
||||||
#include <stdlib.h>
|
Dynamically update the text displayed in the window's titlebar.
|
||||||
|
* `void ui_window_set_resizable(ui_window_t win, bool resizable);`
|
||||||
int main(void) {
|
Enable or disable the user's ability to resize the window by dragging its edges.
|
||||||
// 1. Create the window
|
* `void ui_get_screen_size(uint64_t *out_w, uint64_t *out_h);`
|
||||||
// Arguments: Title, Width, Height, Flags (e.g. 0 for bordered window)
|
Query the global screen resolution of the display.
|
||||||
int window_id = ui_create_window("Hello World App", 400, 300, 0);
|
|
||||||
|
|
||||||
if (window_id < 0) {
|
|
||||||
printf("Failed to create window!\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... Event loop will go here ...
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Drawing Primitives
|
## Drawing Primitives
|
||||||
|
|
||||||
The library offers functions to mutate the window's internal buffer. After issuing drawing commands, you **must** instruct the kernel to push the changes onto the screen.
|
All drawing functions write to an off-screen buffer associated with the window. **You must call `ui_mark_dirty()` to instruct the compositor to push your changes to the physical screen.**
|
||||||
|
|
||||||
|
* `void ui_draw_rect(ui_window_t win, int x, int y, int w, int h, uint32_t color);`
|
||||||
|
Draw a solid filled rectangle.
|
||||||
|
* `void ui_draw_rounded_rect_filled(ui_window_t win, int x, int y, int w, int h, int radius, uint32_t color);`
|
||||||
|
Fill a rectangle with rounded corners of a specified `radius`.
|
||||||
|
* `void ui_draw_image(ui_window_t win, int x, int y, int w, int h, uint32_t *image_data);`
|
||||||
|
Blit a raw ARGB pixel buffer (`image_data`) directly into the window canvas.
|
||||||
|
* `void ui_mark_dirty(ui_window_t win, int x, int y, int w, int h);`
|
||||||
|
Mark a specific rectangular region of the window as "dirty". The Window Manager will redraw this area on the next compositing pass.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Colors are defined as 32-bit unsigned integers in **ARGB** format: `0xAARRGGBB`.
|
||||||
|
> E.g., `0xFF000000` is opaque black, `0xFFFF0000` is opaque red.
|
||||||
|
|
||||||
|
## Text Rendering
|
||||||
|
|
||||||
|
BoredOS provides multiple text rendering methodologies, including a default system font and scaled/bitmap alternatives.
|
||||||
|
|
||||||
|
* `void ui_draw_string(ui_window_t win, int x, int y, const char *str, uint32_t color);`
|
||||||
|
Draw text using the default system typeface.
|
||||||
|
* `void ui_draw_string_bitmap(ui_window_t win, int x, int y, const char *str, uint32_t color);`
|
||||||
|
Draw text using a secondary fast bitmap font renderer.
|
||||||
|
* `void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale);`
|
||||||
|
Draw text scaled up or down by a floating-point multiplier.
|
||||||
|
* `void ui_draw_string_scaled_sloped(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale, float slope);`
|
||||||
|
Draw scaled text with an italic-like slope/shear applied.
|
||||||
|
* `void ui_set_font(ui_window_t win, const char *path);`
|
||||||
|
Load and set a custom `.ttf` or bitmap font from the filesystem for this window.
|
||||||
|
|
||||||
|
### Font Metrics
|
||||||
|
Used for calculating layout bounds before drawing:
|
||||||
|
* `uint32_t ui_get_string_width(const char *str);`
|
||||||
|
* `uint32_t ui_get_font_height(void);`
|
||||||
|
* `uint32_t ui_get_string_width_scaled(const char *str, float scale);`
|
||||||
|
* `uint32_t ui_get_font_height_scaled(float scale);`
|
||||||
|
|
||||||
|
## Event Handling
|
||||||
|
|
||||||
|
Applications must continuously poll for events inside an infinite `$while(1)` loop.
|
||||||
|
|
||||||
|
* `bool ui_get_event(ui_window_t win, gui_event_t *ev);`
|
||||||
|
Returns `true` if an event was waiting in the queue, populating the `ev` structure. Returns `false` if the queue is empty.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Because `ui_get_event` is non-blocking, you must call `sys_yield();` inside your event loop if no event was received. In BoredOS's **Multi-Core (SMP)** architecture, failing to yield will pin a CPU core to 100% usage, potentially starving other processes.
|
||||||
|
>
|
||||||
|
> All UI syscalls are **Thread-Safe** at the kernel level via the global GUI spinlock.
|
||||||
|
|
||||||
|
### Graphical Event Structure
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Fill the entire window with a solid blue background
|
typedef struct {
|
||||||
// Arguments: Window ID, X, Y, Width, Height, ARGB Color value
|
int type; // Specifies the event class (see below)
|
||||||
ui_fill_rect(window_id, 0, 0, 400, 300, 0xFF0000FF);
|
int arg1; // Generic argument 1
|
||||||
|
int arg2; // Generic argument 2
|
||||||
// Tell the kernel to commit the drawing commands to the screen
|
int arg3; // Generic argument 3
|
||||||
ui_swap_buffers(window_id);
|
} gui_event_t;
|
||||||
```
|
```
|
||||||
|
|
||||||
Available rendering methods:
|
### Event Types & Arguments
|
||||||
- `ui_fill_rect(id, x, y, w, h, color)`: Draw a solid rectangle.
|
|
||||||
- `ui_draw_rect(id, x, y, w, h, color)`: Draw an outline of a rectangle.
|
|
||||||
- `ui_draw_line(id, x0, y0, x1, y1, color)`: Bresenham line algorithm.
|
|
||||||
- `ui_draw_string(id, string, x, y, color)`: Render text using the kernel's built-in font.
|
|
||||||
- `ui_update_region(id, x, y, w, h)`: A targeted version of `ui_swap_buffers` that only updates a specific area, saving performance.
|
|
||||||
|
|
||||||
## Handling the Event Loop
|
| Event Constant | `type` ID | Trigger | `arg1` | `arg2` | `arg3` |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| `GUI_EVENT_NONE` | `0` | Empty event | - | - | - |
|
||||||
|
| `GUI_EVENT_PAINT` | `1` | Window needs redrawing | - | - | - |
|
||||||
|
| `GUI_EVENT_CLICK` | `2` | Mouse click down | X Coord | Y Coord | Button State |
|
||||||
|
| `GUI_EVENT_RIGHT_CLICK` | `3` | Mouse right-click down | X Coord | Y Coord | Button State |
|
||||||
|
| `GUI_EVENT_CLOSE` | `4` | User clicked 'X' button | - | - | - |
|
||||||
|
| `GUI_EVENT_KEY` | `5` | Keyboard key pressed | Keycode | Modifiers | - |
|
||||||
|
| `GUI_EVENT_KEYUP` | `10` | Keyboard key released | Keycode | Modifiers | - |
|
||||||
|
| `GUI_EVENT_MOUSE_DOWN` | `6` | Generic mouse button down | X Coord | Y Coord | Button State |
|
||||||
|
| `GUI_EVENT_MOUSE_UP` | `7` | Generic mouse button release | X Coord | Y Coord | Button State |
|
||||||
|
| `GUI_EVENT_MOUSE_MOVE` | `8` | Mouse cursor moved | X Coord | Y Coord | - |
|
||||||
|
| `GUI_EVENT_MOUSE_WHEEL` | `9` | Scroll wheel rotated | Scroll Delta | - | - |
|
||||||
|
| `GUI_EVENT_RESIZE` | `11` | Window dimensions changed| New Width | New Height | - |
|
||||||
|
|
||||||
Graphical applications are event-driven. They stay alive inside a `while (1)` loop, periodically asking the kernel if the user clicked the mouse or pressed a key inside their window.
|
*(Note: Coordinate arguments (`arg1`, `arg2`) for mouse events are typically relative to the top-left corner of the window's client area).*
|
||||||
|
|
||||||
```c
|
---
|
||||||
ui_event_t event;
|
|
||||||
|
|
||||||
// Main UI Loop
|
> [!TIP]
|
||||||
while (1) {
|
> **Looking for Buttons, TextBoxes, or Scrollbars?**
|
||||||
// ui_poll_event is non-blocking. It returns 1 if an event occurred, 0 otherwise.
|
> While `libui.h` provides the foundation for drawing, most applications should use the higher-level [**Widget API**](widget_api.md) (`libwidget.h`) for standard interactive components.
|
||||||
if (ui_poll_event(&event)) {
|
|
||||||
|
|
||||||
// The WM dispatch sets event.window_id
|
|
||||||
// We only care about events meant for our specific window
|
|
||||||
if (event.window_id == window_id) {
|
|
||||||
|
|
||||||
if (event.type == UI_EVENT_MOUSE_DOWN) {
|
---
|
||||||
printf("User clicked at X:%d Y:%d\n", event.mouse_x, event.mouse_y);
|
|
||||||
|
|
||||||
// Respond visually to the click
|
|
||||||
ui_fill_rect(window_id, event.mouse_x, event.mouse_y, 10, 10, 0xFFFF0000); // Red dot
|
|
||||||
ui_swap_buffers(window_id);
|
|
||||||
}
|
|
||||||
else if (event.type == UI_EVENT_WINDOW_CLOSE) {
|
|
||||||
// Start tearing down the application safely
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent 100% CPU usage by yielding execution time back to the OS scheduler
|
|
||||||
syscall1(SYSTEM_CMD_YIELD, 0);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
108
docs/appdev/widget_api.md
Normal file
108
docs/appdev/widget_api.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>Widget API (<code>libwidget.h</code>)</h1>
|
||||||
|
<p><em>High-level UI components for BoredOS applications.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The Widget library (`libwidget.h`) provides a set of reusable UI components built on top of `libui.h`. It uses an abstract `widget_context_t` to decouple component logic from specific drawing implementations, making it easier to build complex graphical interfaces.
|
||||||
|
|
||||||
|
## Widget Context
|
||||||
|
|
||||||
|
To use any widget, you must first define a `widget_context_t`. This structure contains function pointers for basic drawing operations (rects, strings) and theme preferences.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
void *user_data;
|
||||||
|
void (*draw_rect)(void *user_data, int x, int y, int w, int h, uint32_t color);
|
||||||
|
void (*draw_rounded_rect_filled)(void *user_data, int x, int y, int w, int h, int r, uint32_t color);
|
||||||
|
void (*draw_string)(void *user_data, int x, int y, const char *str, uint32_t color);
|
||||||
|
int (*measure_string_width)(void *user_data, const char *str);
|
||||||
|
void (*mark_dirty)(void *user_data, int x, int y, int w, int h);
|
||||||
|
bool use_light_theme;
|
||||||
|
} widget_context_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Usually, `user_data` is set to your `ui_window_t` handle, and the functions are simple wrappers around `ui_draw_rect`, `ui_draw_string`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button (`widget_button_t`)
|
||||||
|
|
||||||
|
Standard interactive button with hover and click states.
|
||||||
|
|
||||||
|
* `void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text);`
|
||||||
|
* `void widget_button_draw(widget_context_t *ctx, widget_button_t *btn);`
|
||||||
|
* `bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data);`
|
||||||
|
|
||||||
|
### Usage Example:
|
||||||
|
```c
|
||||||
|
widget_button_t my_btn;
|
||||||
|
widget_button_init(&my_btn, 10, 10, 80, 25, "Click Me");
|
||||||
|
my_btn.on_click = my_callback_func;
|
||||||
|
|
||||||
|
// In your event loop:
|
||||||
|
widget_button_handle_mouse(&my_btn, ev.arg1, ev.arg2, is_down, is_clicked, my_data);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scrollbar (`widget_scrollbar_t`)
|
||||||
|
|
||||||
|
Vertical scrollbar supporting dragging and track-paging.
|
||||||
|
|
||||||
|
* `void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h);`
|
||||||
|
* `void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y);`
|
||||||
|
* `void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb);`
|
||||||
|
* `bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data);`
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The scrollbar automatically calculates the "thumb" size based on the ratio of `h` to `content_height`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TextBox (`widget_textbox_t`)
|
||||||
|
|
||||||
|
Editable text field with focus support and keyboard handling.
|
||||||
|
|
||||||
|
* `void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len);`
|
||||||
|
* `void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb);`
|
||||||
|
* `bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);`
|
||||||
|
* `bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data);`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dropdown (`widget_dropdown_t`)
|
||||||
|
|
||||||
|
Selection menu for picking one item from a list.
|
||||||
|
|
||||||
|
* `void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count);`
|
||||||
|
* `void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd);`
|
||||||
|
* `bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data);`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checkbox / Radio (`widget_checkbox_t`)
|
||||||
|
|
||||||
|
Toggleable options with support for circular "Radio" style or square "Checkbox" style.
|
||||||
|
|
||||||
|
* `void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio);`
|
||||||
|
* `void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb);`
|
||||||
|
* `bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data);`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Event Integration
|
||||||
|
|
||||||
|
Widgets are designed to be polled within your `libui` event loop. Most handle-mouse functions return `true` if the event was "consumed" by the widget, allowing you to stop further processing for that event.
|
||||||
|
|
||||||
|
```c
|
||||||
|
if (ui_get_event(win, &ev)) {
|
||||||
|
bool handled = false;
|
||||||
|
handled |= widget_button_handle_mouse(&btn, ev.arg1, ev.arg2, is_down, is_clicked, NULL);
|
||||||
|
if (!handled) {
|
||||||
|
// Handle global window events...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
# Core Architecture
|
<div align="center">
|
||||||
|
<h1>Core Architecture</h1>
|
||||||
|
<p><em>Overview of BoredOS kernel layout, boot process, and userspace transition.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
BoredOS is a 64-bit hobbyist operating system designed for the x86_64 architecture. While it features kernel-space drivers and a built-in window manager, it supports fully-isolated userspace applications and includes a networking stack.
|
BoredOS is a 64-bit hobbyist operating system designed for the x86_64 architecture. While it features kernel-space drivers and a built-in window manager, it supports fully-isolated userspace applications and includes a networking stack.
|
||||||
|
|
||||||
This document serves as an overview of the core architecture and the layout of the kernel source code.
|
This document serves as an overview of the core architecture and the layout of the kernel source code.
|
||||||
|
|
||||||
## Source Code Layout (`src/`)
|
## Source Code Layout (`src/`)
|
||||||
|
|
||||||
The OS heavily relies on module separation. The `src/` directory is logically split into several domains:
|
The OS heavily relies on module separation. The `src/` directory is logically split into several domains:
|
||||||
|
|
||||||
@@ -14,7 +19,7 @@ The OS heavily relies on module separation. The `src/` directory is logically sp
|
|||||||
- **`fs/`**: Filesystem implementations. The system uses a Virtual File System (VFS) abstraction alongside an in-memory FAT32 filesystem with support for drives over ATA that are formatted as FAT32 (plain/MBR).
|
- **`fs/`**: Filesystem implementations. The system uses a Virtual File System (VFS) abstraction alongside an in-memory FAT32 filesystem with support for drives over ATA that are formatted as FAT32 (plain/MBR).
|
||||||
- **`mem/`**: Physical and virtual memory management. It controls page frame allocation, paging, and kernel heap operations.
|
- **`mem/`**: Physical and virtual memory management. It controls page frame allocation, paging, and kernel heap operations.
|
||||||
- **`net/`**: The networking stack. BoredOS relies on `lwIP` for processing IPv4 and TCP/UDP traffic, interacting with a range of NICs via `net/nic/`.
|
- **`net/`**: The networking stack. BoredOS relies on `lwIP` for processing IPv4 and TCP/UDP traffic, interacting with a range of NICs via `net/nic/`.
|
||||||
- **`sys/`**: System calls and process management. The ELF loader resides here, parsing userland binaries and setting them up for execution.
|
- **`sys/`**: System calls and process management. The ELF loader resides here, alongside the Symmetric Multi-Processing (**smp.c**) bringup and Local APIC (**lapic.c**) management logic.
|
||||||
- **`wm/`**: The graphical subsystem. It handles drawing primitives, window structures, font rendering, and double-buffering.
|
- **`wm/`**: The graphical subsystem. It handles drawing primitives, window structures, font rendering, and double-buffering.
|
||||||
- **`userland/`**: Out-of-kernel components. This includes the custom SDK/compiler environment (`libc/`) and user applications (`cli/`, `gui/`, `games/`).
|
- **`userland/`**: Out-of-kernel components. This includes the custom SDK/compiler environment (`libc/`) and user applications (`cli/`, `gui/`, `games/`).
|
||||||
|
|
||||||
@@ -23,17 +28,31 @@ The OS heavily relies on module separation. The `src/` directory is logically sp
|
|||||||
BoredOS uses **Limine** as its primary bootloader.
|
BoredOS uses **Limine** as its primary bootloader.
|
||||||
|
|
||||||
1. **Limine Initialization**: The machine firmware (BIOS or UEFI) loads Limine. Limine parses `limine.conf`, sets up an early graphical framebuffer, and reads the kernel ELF file into memory.
|
1. **Limine Initialization**: The machine firmware (BIOS or UEFI) loads Limine. Limine parses `limine.conf`, sets up an early graphical framebuffer, and reads the kernel ELF file into memory.
|
||||||
2. **Multiboot2 Protocol**: The kernel expects the Limine boot protocol (which is compatible with modern Multiboot specifications). Passing a framebuffer and memory map is handled natively by Limine's request structures (defined locally via `limine.h`).
|
2. **Multiboot2 & SMP Protocol**: The kernel expects the Limine boot protocol. It makes a specific **SMP Request** to Limine to locate and bring up all available CPU cores.
|
||||||
3. **Kernel Entry (`main.c`)**: The entry point `_start` is called. It immediately initializes the serial port for debugging, sets up core structures (GDT/IDT), initializes the physical memory manager based on the Limine memory map, and starts the virtual memory manager.
|
3. **Kernel Entry (`main.c`)**: The entry point `_start` is called on the Bootstrap Processor (BSP). It initializes the serial port, GDT/IDT, memory management, and paging.
|
||||||
4. **Driver Initialization**: PCI buses are scanned, finding the network card or disk controllers. The filesystem is mounted.
|
4. **AP Bringup**: The BSP calls `smp_init()`, which sends the Startup Inter-Processor Interrupt (SIPI) sequence to all Application Processors (APs). Each AP initializes its own local GDT, TSS, and Page Tables before entering an idle loop.
|
||||||
5. **Window Manager**: The UI is drawn on top of the Limine-provided framebuffer.
|
5. **Driver Initialization**: PCI buses are scanned, finding the network card or disk controllers. The filesystem is mounted.
|
||||||
|
6. **Window Manager**: The UI is drawn on top of the Limine-provided framebuffer.
|
||||||
|
|
||||||
|
## 🧵 Multi-Core & Scheduling
|
||||||
|
|
||||||
|
BoredOS utilizes Symmetric Multi-Processing (SMP) to distribute workloads across all available CPU cores.
|
||||||
|
|
||||||
|
- **LAPIC & IPIs**: Each CPU has its own Local APIC. The kernel uses Inter-Processor Interrupts (IPIs) for inter-core communication, specifically for triggering the scheduler on other cores (`vector 0x41`).
|
||||||
|
- **Scheduler**: A round-robin scheduler runs on each core. Processes are pinned to specific CPUs (CPU Affinity) to maintain cache locality. The BSP timer interrupt (`60Hz`) broadcasts a scheduling IPI to all core to ensure balanced execution.
|
||||||
|
- **Spinlocks**: Since multiple cores can access kernel structures (VFS, Process List) simultaneously, the kernel uses **interrupt-safe spinlocks** to prevent race conditions.
|
||||||
|
|
||||||
## Userland Transition
|
## Userland Transition
|
||||||
|
|
||||||
The OS supports privilege separation (Ring 0 vs. Ring 3). When an application (like `browser.elf` or `viewer.elf`) is launched, the kernel:
|
The OS supports privilege separation (Ring 0 vs. Ring 3). When an application is launched, the kernel:
|
||||||
1. Loads the ELF file from the filesystem using the ELF parser in `sys/elf.c`.
|
|
||||||
2. Allocates a new virtual address space (Page Directory) for the process.
|
|
||||||
3. Maps the executable segments according to the ELF headers.
|
|
||||||
4. Switches to User Mode (Ring 3) via the `iretq` instruction, jumping into the application's entry point (`crt0.asm`).
|
|
||||||
|
|
||||||
Programs then interact with the core kernel using system calls (`syscall.c`).
|
1. Loads the ELF file from the filesystem.
|
||||||
|
2. Assigns the process to a CPU core via a round-robin distribution strategy.
|
||||||
|
3. Allocates a new virtual address space (Page Directory) for the process.
|
||||||
|
4. Maps the executable segments according to the ELF headers.
|
||||||
|
5. Switches to User Mode (Ring 3) via the `iretq` instruction.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Programs interact with the core kernel using system calls (`syscall.c`). Multitasking is achieved by pre-empting user processes on their respective cores.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
# Filesystem Architecture
|
<div align="center">
|
||||||
|
<h1>Filesystem Architecture</h1>
|
||||||
|
<p><em>Virtual File System layer and FAT32 abstraction in BoredOS.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
BoredOS implements a rudimentary but functional filesystem layer designed to support reading system assets and user applications during runtime.
|
BoredOS implements a rudimentary but functional filesystem layer designed to support reading system assets and user applications during runtime.
|
||||||
|
|
||||||
@@ -10,19 +15,23 @@ Key VFS functionalities include:
|
|||||||
- **File Descriptors**: Mapping integer IDs to internal file structures for userland processes.
|
- **File Descriptors**: Mapping integer IDs to internal file structures for userland processes.
|
||||||
- **Standard Operations**: Standardizing `open()`, `read()`, `write()`, `close()`, `seek()`, and directory listings.
|
- **Standard Operations**: Standardizing `open()`, `read()`, `write()`, `close()`, `seek()`, and directory listings.
|
||||||
- **Path Parsing**: Resolving absolute and relative paths.
|
- **Path Parsing**: Resolving absolute and relative paths.
|
||||||
|
- **SMP Safety**: All VFS and underlying FAT32 operations are protected by a global **Spinlock**. This ensures that multiple cores can safely read from the filesystem simultaneously without corrupting internal file seek pointers or directory cache states.
|
||||||
|
|
||||||
## FAT32 Implementation
|
## FAT32 Implementation
|
||||||
|
|
||||||
The primary filesystem logic in `fat32.c` has a dual nature, supporting both an in-memory RAM filesystem for booting and standard block devices for external storage.
|
The primary filesystem logic in `fat32.c` handles both in-memory RAM-based filesystem simulation and physical ATA block devices.
|
||||||
|
|
||||||
### Booting and the RAMFS
|
### Storage Support
|
||||||
Since BoredOS boots from a CD-ROM ISO image generated by `xorriso`, it does not read directly off the CD to execute applications.
|
|
||||||
1. **ISO Booting**: During boot, Limine loads necessary files (such as userland `.elf` binaries, fonts, and wallpapers) into memory as standard boot modules.
|
|
||||||
2. **RAM Simulation**: The FAT32 filesystem code parses these loaded memory modules and automatically constructs a synthetic FAT32 directory tree inside RAM.
|
|
||||||
3. **Root Filesystem**: All active execution of built-in GUI and CLI apps occurs off this read-only, in-memory FAT32 simulation.
|
|
||||||
|
|
||||||
### ATA Disk Support
|
BoredOS supports two main types of storage for its FAT32 implementation:
|
||||||
Beyond the core RAMFS used for booting, the FAT32 implementation natively supports interacting with permanent storage:
|
|
||||||
1. **ATA Block Driver**: The kernel features an ATA block device driver capable of communicating with physical hard disks (or raw disk images attached via QEMU).
|
1. **RAMFS (Boot Modules)**: During boot, Limine loads necessary files (such as userland `.elf` binaries, fonts, and wallpapers) into memory as standard boot modules. The FAT32 code parses these loaded memory modules and automatically constructs a synthetic FAT32 directory tree inside RAM (mounted as `A:`).
|
||||||
2. **Partition Compatibility**: The driver can recognize and natively mount external ATA disks formatted as single FAT32 filesystems or structured with a Master Boot Record (MBR) partition table.
|
2. **ATA Drives**: The kernel includes a basic PIO-based ATA driver that can detect and read/write to physical IDE/PATA hard disks.
|
||||||
3. **VFS Integration**: When external storage is mounted, the VFS delegates operations down directly to the FAT32 driver, which will read native sectors across the ATA interface.
|
- **GPT is NOT supported**: Currently, only **MBR (Master Boot Record)** partition tables or **raw (partitionless)** disks are supported.
|
||||||
|
- **Filesystem**: The partition must be formatted as **FAT32**.
|
||||||
|
|
||||||
|
### Auto-detection
|
||||||
|
The `Disk Manager` automatically probes primary and secondary IDE channels during initialization. If a valid FAT32 partition is found (either directly at sector 0 or via an MBR partition table), the disk is assigned a drive letter (starting from `B:`) and becomes accessible to the VFS.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
# Memory Management
|
<div align="center">
|
||||||
|
<h1>Memory Management</h1>
|
||||||
|
<p><em>Physical and Virtual Memory coordination in x86_64 Long Mode.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Memory management in BoredOS is split into physical and virtual layers, designed to support both kernel operations and userland isolation on the x86_64 architecture.
|
Memory management in BoredOS is split into physical and virtual layers, designed to support both kernel operations and userland isolation on the x86_64 architecture.
|
||||||
|
|
||||||
@@ -9,15 +14,27 @@ The PMM is responsible for tracking which physical RAM frames (usually 4KB each)
|
|||||||
1. **Memory Map**: During boot, Limine provides a memory map detailing the available, reserved, and unusable physical memory regions.
|
1. **Memory Map**: During boot, Limine provides a memory map detailing the available, reserved, and unusable physical memory regions.
|
||||||
2. **Bitmap Allocator**: The core PMM uses a bitmap-based allocation strategy. Each bit in the bitmap represents a single physical page (frame). If a bit is `1`, the page is in use; if `0`, it is free.
|
2. **Bitmap Allocator**: The core PMM uses a bitmap-based allocation strategy. Each bit in the bitmap represents a single physical page (frame). If a bit is `1`, the page is in use; if `0`, it is free.
|
||||||
3. **Allocation**: When a new page is requested (e.g., for userland space or kernel heap), the PMM scans the bitmap for the first available zero bit, marks it as used, and returns the physical address.
|
3. **Allocation**: When a new page is requested (e.g., for userland space or kernel heap), the PMM scans the bitmap for the first available zero bit, marks it as used, and returns the physical address.
|
||||||
|
4. **SMP Safety**: In a multi-core environment, the PMM and VMM are protected by **Spinlocks** to prevent two CPUs from allocating the same frame or modifying page tables simultaneously.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 4KB frame sizes strike a balance between allocation speed and minimal memory fragmentation, fitting directly with the page tables.
|
||||||
|
|
||||||
## Virtual Memory Management (VMM) and Paging
|
## Virtual Memory Management (VMM) and Paging
|
||||||
|
|
||||||
BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing the virtual address space between the kernel and userland.
|
BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing the virtual address space between the kernel and userland.
|
||||||
|
|
||||||
- **Kernel Space**: The kernel relies on a higher-half design where its code, data, and heap are mapped to high addresses (typically above `0xFFFF800000000000`). This ensures the kernel remains mapped and accessible regardless of which user process is currently active.
|
- **Kernel Space**: The kernel relies on a higher-half design where its code, data, and heap are mapped to high addresses (typically above `0xFFFF800000000000`).
|
||||||
- **User Space**: Userland applications are loaded into lower virtual addresses (starting frequently around `0x40000000`).
|
- **Per-CPU Structures**: Each CPU core maintains its own architectural state in memory:
|
||||||
- **Page Faults**: The `mem/` subsystem registers an Interrupt Service Routine (ISR) for page faults (Interrupt 14). If a process accesses unmapped memory, the handler determines whether to allocate a new frame (e.g., for stack growth or lazy loading) or terminate the process for a segmentation fault.
|
* **Per-CPU GDT**: Each core is initialized with its own Global Descriptor Table.
|
||||||
|
* **Per-CPU TSS**: Each core has a dedicated Task State Segment containing the `RSP0` pointer for its own kernel stack, ensuring safe interrupt handling across cores.
|
||||||
|
- **User Space**: Userland applications are loaded into lower virtual addresses.
|
||||||
|
- **Page Faults**: The `mem/` subsystem registers an Interrupt Service Routine (ISR) for page faults (Interrupt 14). If a process accesses unmapped memory, the handler determines whether to allocate a new frame or terminate the process.
|
||||||
|
|
||||||
## Kernel Heap
|
## Kernel Heap
|
||||||
|
|
||||||
Dynamic allocation within the kernel (`kmalloc` and `kfree`) is layered on top of the physical allocator. The kernel maintains its own heap area in virtual memory. When the heap requires more space, it requests physical frames from the PMM and maps them into the kernel's virtual address space using the VMM.
|
Dynamic allocation within the kernel (`kmalloc` and `kfree`) is layered on top of the physical allocator. The kernel maintains its own heap area in virtual memory. When the heap requires more space, it requests physical frames from the PMM and maps them into the kernel's virtual address space.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The kernel heap is a shared resource; therefore, all `kmalloc` and `kfree` operations are guarded by a global spinlock to ensure thread safety during multi-core execution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
# Window Manager (WM)
|
<div align="center">
|
||||||
|
<h1>Window Manager (WM)</h1>
|
||||||
|
<p><em>The native graphical subsystem compositing and event routing.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
BoredOS features a fully custom, graphical Window Manager built directly into the kernel, residing in the `src/wm/` directory. It is responsible for compositing the screen, handling window logic, rendering text, and dispatching UI events.
|
BoredOS features a fully custom, graphical Window Manager built directly into the kernel, residing in the `src/wm/` directory. It is responsible for compositing the screen, handling window logic, rendering text, and dispatching UI events.
|
||||||
|
|
||||||
@@ -8,7 +13,10 @@ BoredOS features a fully custom, graphical Window Manager built directly into th
|
|||||||
2. **Double Buffering**: To prevent screen tearing, the WM does not draw directly to the screen. It allocates a "back buffer" in kernel memory equal to the size of the screen. All drawing operations (lines, rectangles, windows) happen on this back buffer.
|
2. **Double Buffering**: To prevent screen tearing, the WM does not draw directly to the screen. It allocates a "back buffer" in kernel memory equal to the size of the screen. All drawing operations (lines, rectangles, windows) happen on this back buffer.
|
||||||
3. **Compositing**: Once per frame or upon request, the entire back buffer (or dirty regions) is copied to the actual Limine physical framebuffer memory, making the changes visible instantly.
|
3. **Compositing**: Once per frame or upon request, the entire back buffer (or dirty regions) is copied to the actual Limine physical framebuffer memory, making the changes visible instantly.
|
||||||
|
|
||||||
## Window System (`wm.c`)
|
> [!TIP]
|
||||||
|
> The performance of the window manager heavily depends on minimizing the "dirty regions" drawn in the compositing loop rather than sweeping the whole screen.
|
||||||
|
|
||||||
|
## 🪟 Window System (`wm.c`)
|
||||||
|
|
||||||
The windowing system is built around a linked list of `Window` structures.
|
The windowing system is built around a linked list of `Window` structures.
|
||||||
|
|
||||||
@@ -24,10 +32,17 @@ The WM acts as the central hub for input routing.
|
|||||||
2. **Hit Testing**: The WM checks these coordinates against the bounding boxes of existing windows. It handles dragging logic (if the user clicks a title bar) or focus changes.
|
2. **Hit Testing**: The WM checks these coordinates against the bounding boxes of existing windows. It handles dragging logic (if the user clicks a title bar) or focus changes.
|
||||||
3. **Event Queue**: If a userland application owns the window that was clicked, the WM packages the input (coordinates, button state) into an event message and drops it into the owning process's event queue. The application can retrieve these via the custom libc UI functions.
|
3. **Event Queue**: If a userland application owns the window that was clicked, the WM packages the input (coordinates, button state) into an event message and drops it into the owning process's event queue. The application can retrieve these via the custom libc UI functions.
|
||||||
|
|
||||||
## Userland API (`libui.c`)
|
- **Event Polling**: The UI loop inside an app continuously calls `ui_poll_event()` to respond to mouse clicks and window movement dispatched by the kernel WM.
|
||||||
|
|
||||||
Applications do not talk to the hardware directly. Instead, they use a library (`libui.c`) which makes specialized system calls (`SYS_GUI`).
|
## 🧵 Multi-Core Safety & Performance
|
||||||
|
|
||||||
- **Window Creation**: `ui_create_window()` asks the kernel to instantiate a new window object and returns a handle.
|
With the introduction of Symmetric Multi-Processing (SMP), the Window Manager (WM) was redesigned to ensure stability and high performance across multiple cores.
|
||||||
- **Drawing**: Applications can request the kernel to fill rectangles or plot pixels inside their designated window area.
|
|
||||||
- **Event Polling**: The UI loop inside an app continuously calls `ui_poll_event()` to respond to mouse clicks and window movement dispatched by the kernel WM.
|
1. **Granular Window Locks**: Each `Window` object possesses its own `spinlock_t lock;`. User applications concurrently draw directly into their own window buffers without stalling the rest of the system. The global `wm_lock` is reserved strictly for altering global structures like window z-order or syncing buffers to the screen compositing layer.
|
||||||
|
2. **Per-CPU Rendering State**: To facilitate simultaneous GUI system calls across all CPU cores, the low-level rendering context (`g_render_target` array) is isolated per-CPU using the core ID. This allows completely lockless multi-core pixel rasterization, drastically reducing rendering bottlenecks.
|
||||||
|
3. **Deferred Compositing**: Final screen composition (`wm_paint`) is scheduled to the main kernel idle loop on the Bootstrap Processor (BSP). This enables application cores to continue processing logic seamlessly while the GUI asynchronously handles flipping the physical framebuffer.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Because application rendering (rasterizing geometry into a window's backbuffer) is SMP-safe and lock-free across cores, GUI performance scales linearly with the number of CPUs active.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
BIN
screenshot.jpg
BIN
screenshot.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 342 KiB |
@@ -7,9 +7,13 @@ global isr1_wrapper
|
|||||||
global isr8_wrapper
|
global isr8_wrapper
|
||||||
global isr12_wrapper
|
global isr12_wrapper
|
||||||
global isr14_wrapper
|
global isr14_wrapper
|
||||||
|
global isr128_wrapper
|
||||||
|
global isr_sched_ipi_wrapper
|
||||||
extern timer_handler
|
extern timer_handler
|
||||||
extern keyboard_handler
|
extern keyboard_handler
|
||||||
extern mouse_handler
|
extern mouse_handler
|
||||||
|
extern sched_ipi_handler
|
||||||
|
extern syscall_handler_c
|
||||||
extern exception_handler_c
|
extern exception_handler_c
|
||||||
|
|
||||||
; Helper to send EOI (End of Interrupt) to PIC
|
; Helper to send EOI (End of Interrupt) to PIC
|
||||||
@@ -41,7 +45,7 @@ isr%2_wrapper:
|
|||||||
push r14
|
push r14
|
||||||
push r15
|
push r15
|
||||||
|
|
||||||
; Save SSE/FPU state
|
; Save SSE/FPU state (fxsave requires 16-byte alignment)
|
||||||
sub rsp, 512
|
sub rsp, 512
|
||||||
fxsave [rsp]
|
fxsave [rsp]
|
||||||
|
|
||||||
@@ -85,6 +89,12 @@ isr1_wrapper:
|
|||||||
isr12_wrapper:
|
isr12_wrapper:
|
||||||
ISR_NOERRCODE mouse_handler, 44
|
ISR_NOERRCODE mouse_handler, 44
|
||||||
|
|
||||||
|
isr_sched_ipi_wrapper:
|
||||||
|
ISR_NOERRCODE sched_ipi_handler, 65
|
||||||
|
|
||||||
|
isr128_wrapper:
|
||||||
|
ISR_NOERRCODE syscall_handler_c, 128
|
||||||
|
|
||||||
; Common exception macro for exceptions WITHOUT error code
|
; Common exception macro for exceptions WITHOUT error code
|
||||||
%macro EXCEPTION_NOERRCODE 1
|
%macro EXCEPTION_NOERRCODE 1
|
||||||
global exc%1_wrapper
|
global exc%1_wrapper
|
||||||
@@ -154,7 +164,7 @@ exception_common:
|
|||||||
push r14
|
push r14
|
||||||
push r15
|
push r15
|
||||||
|
|
||||||
; Save SSE/FPU state
|
; Save SSE/FPU state (fxsave requires 16-byte alignment)
|
||||||
sub rsp, 512
|
sub rsp, 512
|
||||||
fxsave [rsp]
|
fxsave [rsp]
|
||||||
|
|
||||||
|
|||||||
@@ -113,14 +113,35 @@ void k_shutdown(void) {
|
|||||||
outw(0x4004, 0x3400); // VirtualBox fallback
|
outw(0x4004, 0x3400); // VirtualBox fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
volatile uint64_t beep_end_tick = 0;
|
||||||
|
bool beep_active = false;
|
||||||
|
|
||||||
void k_beep(int freq, int ms) {
|
void k_beep(int freq, int ms) {
|
||||||
if (freq <= 0) return;
|
if (freq <= 0) {
|
||||||
|
outb(0x61, inb(0x61) & 0xFC);
|
||||||
|
beep_active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
int div = 1193180 / freq;
|
int div = 1193180 / freq;
|
||||||
outb(0x43, 0xB6);
|
outb(0x43, 0xB6);
|
||||||
outb(0x42, div & 0xFF);
|
outb(0x42, div & 0xFF);
|
||||||
outb(0x42, (div >> 8) & 0xFF);
|
outb(0x42, (div >> 8) & 0xFF);
|
||||||
outb(0x61, inb(0x61) | 0x03);
|
outb(0x61, inb(0x61) | 0x03);
|
||||||
k_sleep(ms);
|
|
||||||
outb(0x61, inb(0x61) & 0xFC);
|
uint32_t ticks = ms / 16;
|
||||||
|
if (ticks == 0 && ms > 0) ticks = 1;
|
||||||
|
extern volatile uint64_t kernel_ticks;
|
||||||
|
beep_end_tick = kernel_ticks + ticks;
|
||||||
|
beep_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void k_beep_process(void) {
|
||||||
|
if (beep_active) {
|
||||||
|
extern volatile uint64_t kernel_ticks;
|
||||||
|
if (kernel_ticks >= beep_end_tick) {
|
||||||
|
outb(0x61, inb(0x61) & 0xFC);
|
||||||
|
beep_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
// Kernel string utilities
|
// Kernel string utilities
|
||||||
void k_memset(void *dest, int val, size_t len);
|
void k_memset(void *dest, int val, size_t len);
|
||||||
@@ -23,5 +24,6 @@ void k_sleep(int ms);
|
|||||||
void k_reboot(void);
|
void k_reboot(void);
|
||||||
void k_shutdown(void);
|
void k_shutdown(void);
|
||||||
void k_beep(int freq, int ms);
|
void k_beep(int freq, int ms);
|
||||||
|
void k_beep_process(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,9 +16,13 @@
|
|||||||
#include "wm.h"
|
#include "wm.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
|
#include "tar.h"
|
||||||
#include "memory_manager.h"
|
#include "memory_manager.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "wallpaper.h"
|
#include "wallpaper.h"
|
||||||
|
#include "smp.h"
|
||||||
|
#include "work_queue.h"
|
||||||
|
#include "lapic.h"
|
||||||
|
|
||||||
// --- Limine Requests ---
|
// --- Limine Requests ---
|
||||||
__attribute__((used, section(".requests")))
|
__attribute__((used, section(".requests")))
|
||||||
@@ -42,11 +46,19 @@ static volatile struct limine_module_request module_request = {
|
|||||||
.revision = 0
|
.revision = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
__attribute__((used, section(".requests")))
|
||||||
|
static volatile struct limine_smp_request smp_request = {
|
||||||
|
.id = LIMINE_SMP_REQUEST,
|
||||||
|
.revision = 0,
|
||||||
|
.flags = 0
|
||||||
|
};
|
||||||
|
|
||||||
__attribute__((used, section(".requests_start")))
|
__attribute__((used, section(".requests_start")))
|
||||||
static volatile struct limine_request *const requests_start_marker[] = {
|
static volatile struct limine_request *const requests_start_marker[] = {
|
||||||
(struct limine_request *)&framebuffer_request,
|
(struct limine_request *)&framebuffer_request,
|
||||||
(struct limine_request *)&memmap_request,
|
(struct limine_request *)&memmap_request,
|
||||||
(struct limine_request *)&module_request,
|
(struct limine_request *)&module_request,
|
||||||
|
(struct limine_request *)&smp_request,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,21 +211,31 @@ void kmain(void) {
|
|||||||
if (fs_starts_with(clean_path, "boot():")) clean_path += 7;
|
if (fs_starts_with(clean_path, "boot():")) clean_path += 7;
|
||||||
else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8;
|
else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8;
|
||||||
|
|
||||||
char dir_path[256];
|
int len = 0;
|
||||||
int last_slash = -1;
|
while(clean_path[len]) len++;
|
||||||
for (int j = 0; clean_path[j]; j++) {
|
|
||||||
if (clean_path[j] == '/') last_slash = j;
|
|
||||||
}
|
|
||||||
if (last_slash > 0) {
|
|
||||||
for (int j = 0; j < last_slash; j++) dir_path[j] = clean_path[j];
|
|
||||||
dir_path[last_slash] = '\0';
|
|
||||||
fat32_mkdir_recursive(dir_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
FAT32_FileHandle *fh = fat32_open(clean_path, "w");
|
if (len >= 4 && clean_path[len-4] == '.' && clean_path[len-3] == 't' && clean_path[len-2] == 'a' && clean_path[len-1] == 'r') {
|
||||||
if (fh && fh->valid) {
|
serial_write("[DEBUG] Parsing TAR initrd: ");
|
||||||
fat32_write(fh, mod->address, mod->size);
|
serial_write(clean_path);
|
||||||
fat32_close(fh);
|
serial_write("\n");
|
||||||
|
tar_parse(mod->address, mod->size);
|
||||||
|
} else {
|
||||||
|
char dir_path[256];
|
||||||
|
int last_slash = -1;
|
||||||
|
for (int j = 0; clean_path[j]; j++) {
|
||||||
|
if (clean_path[j] == '/') last_slash = j;
|
||||||
|
}
|
||||||
|
if (last_slash > 0) {
|
||||||
|
for (int j = 0; j < last_slash; j++) dir_path[j] = clean_path[j];
|
||||||
|
dir_path[last_slash] = '\0';
|
||||||
|
fat32_mkdir_recursive(dir_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
FAT32_FileHandle *fh = fat32_open(clean_path, "w");
|
||||||
|
if (fh && fh->valid) {
|
||||||
|
fat32_write(fh, mod->address, mod->size);
|
||||||
|
fat32_close(fh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,11 +253,25 @@ void kmain(void) {
|
|||||||
ps2_init();
|
ps2_init();
|
||||||
asm("sti");
|
asm("sti");
|
||||||
|
|
||||||
|
// Initialize LAPIC for IPI support
|
||||||
|
lapic_init();
|
||||||
|
|
||||||
|
// Initialize SMP — bring up all CPU cores
|
||||||
|
if (smp_request.response != NULL) {
|
||||||
|
uint32_t online = smp_init(smp_request.response);
|
||||||
|
serial_write("[DEBUG] SMP init complete, CPUs online: ");
|
||||||
|
serial_write_num(online);
|
||||||
|
serial_write("\n");
|
||||||
|
} else {
|
||||||
|
serial_write("[DEBUG] No SMP response from bootloader\n");
|
||||||
|
// Still init as single-CPU
|
||||||
|
smp_init(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
wm_init();
|
wm_init();
|
||||||
|
|
||||||
asm volatile("sti");
|
asm volatile("sti");
|
||||||
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
wm_process_input();
|
wm_process_input();
|
||||||
wm_process_deferred_thumbs();
|
wm_process_deferred_thumbs();
|
||||||
|
|||||||
33
src/core/version.c
Normal file
33
src/core/version.c
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// 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.
|
||||||
|
#include "syscall.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||||
|
|
||||||
|
void get_os_info(os_info_t *info) {
|
||||||
|
if (!info) return;
|
||||||
|
|
||||||
|
char *p = (char *)info;
|
||||||
|
for (size_t i = 0; i < sizeof(os_info_t); i++) p[i] = 0;
|
||||||
|
|
||||||
|
const char *os_name = "BoredOS";
|
||||||
|
const char *os_version = "26.4";
|
||||||
|
const char *os_codename = "Geometry";
|
||||||
|
const char *kernel_name = "Boredkernel";
|
||||||
|
const char *kernel_version = "3.2.3";
|
||||||
|
const char *build_date = __DATE__;
|
||||||
|
const char *build_time = __TIME__;
|
||||||
|
const char *build_arch = "x86_64";
|
||||||
|
|
||||||
|
int j;
|
||||||
|
j = 0; while (os_name[j] && j < 63) { info->os_name[j] = os_name[j]; j++; } info->os_name[j] = '\0';
|
||||||
|
j = 0; while (os_version[j] && j < 63) { info->os_version[j] = os_version[j]; j++; } info->os_version[j] = '\0';
|
||||||
|
j = 0; while (os_codename[j] && j < 63) { info->os_codename[j] = os_codename[j]; j++; } info->os_codename[j] = '\0';
|
||||||
|
j = 0; while (kernel_name[j] && j < 63) { info->kernel_name[j] = kernel_name[j]; j++; } info->kernel_name[j] = '\0';
|
||||||
|
j = 0; while (kernel_version[j] && j < 63) { info->kernel_version[j] = kernel_version[j]; j++; } info->kernel_version[j] = '\0';
|
||||||
|
j = 0; while (build_date[j] && j < 63) { info->build_date[j] = build_date[j]; j++; } info->build_date[j] = '\0';
|
||||||
|
j = 0; while (build_time[j] && j < 63) { info->build_time[j] = build_time[j]; j++; } info->build_time[j] = '\0';
|
||||||
|
j = 0; while (build_arch[j] && j < 63) { info->build_arch[j] = build_arch[j]; j++; } info->build_arch[j] = '\0';
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "wm.h"
|
#include "wm.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include "lapic.h"
|
||||||
|
#include "smp.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
extern void serial_print(const char *s);
|
extern void serial_print(const char *s);
|
||||||
@@ -18,9 +20,19 @@ uint64_t timer_handler(registers_t *regs) {
|
|||||||
wm_timer_tick();
|
wm_timer_tick();
|
||||||
network_process_frames();
|
network_process_frames();
|
||||||
|
|
||||||
|
extern void k_beep_process(void);
|
||||||
|
k_beep_process();
|
||||||
|
|
||||||
outb(0x20, 0x20); // EOI after processing to prevent nested timer interrupts
|
outb(0x20, 0x20); // EOI after processing to prevent nested timer interrupts
|
||||||
extern uint64_t process_schedule(uint64_t current_rsp);
|
extern uint64_t process_schedule(uint64_t current_rsp);
|
||||||
return process_schedule((uint64_t)regs);
|
uint64_t new_rsp = process_schedule((uint64_t)regs);
|
||||||
|
|
||||||
|
// SMP: Wake AP cores to run their assigned processes
|
||||||
|
if (smp_cpu_count() > 1) {
|
||||||
|
lapic_send_ipi_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_rsp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Keyboard ---
|
// --- Keyboard ---
|
||||||
|
|||||||
BIN
src/fonts/Emoji/NotoEmoji-VariableFont_wght.ttf
Normal file
BIN
src/fonts/Emoji/NotoEmoji-VariableFont_wght.ttf
Normal file
Binary file not shown.
138
src/fs/fat32.c
138
src/fs/fat32.c
@@ -8,6 +8,10 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "wm.h"
|
#include "wm.h"
|
||||||
|
#include "spinlock.h"
|
||||||
|
|
||||||
|
// Global lock for FAT32 operations (SMP safety)
|
||||||
|
static spinlock_t fat32_lock = SPINLOCK_INIT;
|
||||||
|
|
||||||
|
|
||||||
#define MAX_FILES 256
|
#define MAX_FILES 256
|
||||||
@@ -228,6 +232,16 @@ static uint32_t ramfs_allocate_cluster(void) {
|
|||||||
return cluster;
|
return cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ramfs_count_files_in_dir(const char *normalized_path) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < MAX_FILES; i++) {
|
||||||
|
if (files[i].used && fs_strcmp(files[i].parent_path, normalized_path) == 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
static bool check_desktop_limit(const char *normalized_path) {
|
static bool check_desktop_limit(const char *normalized_path) {
|
||||||
if (desktop_file_limit < 0) return true;
|
if (desktop_file_limit < 0) return true;
|
||||||
if (fs_strlen(normalized_path) > 9 &&
|
if (fs_strlen(normalized_path) > 9 &&
|
||||||
@@ -241,10 +255,8 @@ static bool check_desktop_limit(const char *normalized_path) {
|
|||||||
if (*p == '/') return true;
|
if (*p == '/') return true;
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
FAT32_FileInfo *info = (FAT32_FileInfo*)kmalloc(256 * sizeof(FAT32_FileInfo));
|
|
||||||
if (!info) return true;
|
int count = ramfs_count_files_in_dir("/Desktop");
|
||||||
int count = fat32_list_directory("/Desktop", info, 256);
|
|
||||||
kfree(info);
|
|
||||||
if (count >= desktop_file_limit) return false;
|
if (count >= desktop_file_limit) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1218,8 +1230,8 @@ char fat32_get_current_drive(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FAT32_FileHandle* fat32_open(const char *path, const char *mode) {
|
FAT32_FileHandle* fat32_open(const char *path, const char *mode) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1234,13 +1246,13 @@ FAT32_FileHandle* fat32_open(const char *path, const char *mode) {
|
|||||||
handle = realfs_open(drive, p, mode);
|
handle = realfs_open(drive, p, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fat32_close(FAT32_FileHandle *handle) {
|
void fat32_close(FAT32_FileHandle *handle) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
if (handle && handle->valid) {
|
if (handle && handle->valid) {
|
||||||
if (handle->drive != 'A' && handle->mode != 0) { // Both read and write modes for real drives
|
if (handle->drive != 'A' && handle->mode != 0) { // Both read and write modes for real drives
|
||||||
Disk *d = disk_get_by_letter(handle->drive);
|
Disk *d = disk_get_by_letter(handle->drive);
|
||||||
@@ -1265,14 +1277,14 @@ void fat32_close(FAT32_FileHandle *handle) {
|
|||||||
}
|
}
|
||||||
handle->valid = false;
|
handle->valid = false;
|
||||||
}
|
}
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
int fat32_read(FAT32_FileHandle *handle, void *buffer, int size) {
|
int fat32_read(FAT32_FileHandle *handle, void *buffer, int size) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
if (!handle || !handle->valid || handle->mode != 0) {
|
if (!handle || !handle->valid || handle->mode != 0) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1283,15 +1295,15 @@ int fat32_read(FAT32_FileHandle *handle, void *buffer, int size) {
|
|||||||
ret = realfs_read(handle, buffer, size);
|
ret = realfs_read(handle, buffer, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fat32_write(FAT32_FileHandle *handle, const void *buffer, int size) {
|
int fat32_write(FAT32_FileHandle *handle, const void *buffer, int size) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
if (!handle || !handle->valid || (handle->mode != 1 && handle->mode != 2)) {
|
if (!handle || !handle->valid || (handle->mode != 1 && handle->mode != 2)) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1302,15 +1314,15 @@ int fat32_write(FAT32_FileHandle *handle, const void *buffer, int size) {
|
|||||||
ret = realfs_write(handle, buffer, size);
|
ret = realfs_write(handle, buffer, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fat32_seek(FAT32_FileHandle *handle, int offset, int whence) {
|
int fat32_seek(FAT32_FileHandle *handle, int offset, int whence) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
if (!handle || !handle->valid) {
|
if (!handle || !handle->valid) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1349,7 +1361,7 @@ int fat32_seek(FAT32_FileHandle *handle, int offset, int whence) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return new_position;
|
return new_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1487,13 +1499,13 @@ bool fat32_mkdir(const char *path) {
|
|||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
|
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
if (drive != 'A') {
|
if (drive != 'A') {
|
||||||
bool res = realfs_mkdir(drive, p);
|
bool res = realfs_mkdir(drive, p);
|
||||||
wm_notify_fs_change();
|
wm_notify_fs_change();
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1501,18 +1513,18 @@ bool fat32_mkdir(const char *path) {
|
|||||||
fat32_normalize_path(p, normalized);
|
fat32_normalize_path(p, normalized);
|
||||||
|
|
||||||
if (ramfs_find_file(normalized)) {
|
if (ramfs_find_file(normalized)) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!check_desktop_limit(normalized)) {
|
if (!check_desktop_limit(normalized)) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileEntry *entry = ramfs_find_free_entry();
|
FileEntry *entry = ramfs_find_free_entry();
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1525,33 +1537,33 @@ bool fat32_mkdir(const char *path) {
|
|||||||
entry->attributes = ATTR_DIRECTORY;
|
entry->attributes = ATTR_DIRECTORY;
|
||||||
|
|
||||||
wm_notify_fs_change();
|
wm_notify_fs_change();
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fat32_rmdir(const char *path) {
|
bool fat32_rmdir(const char *path) {
|
||||||
if (parse_drive_from_path(&path) != 'A') return false;
|
if (parse_drive_from_path(&path) != 'A') return false;
|
||||||
|
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
char normalized[FAT32_MAX_PATH];
|
char normalized[FAT32_MAX_PATH];
|
||||||
fat32_normalize_path(path, normalized);
|
fat32_normalize_path(path, normalized);
|
||||||
|
|
||||||
FileEntry *entry = ramfs_find_file(normalized);
|
FileEntry *entry = ramfs_find_file(normalized);
|
||||||
if (!entry || !(entry->attributes & ATTR_DIRECTORY)) {
|
if (!entry || !(entry->attributes & ATTR_DIRECTORY)) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->used = false;
|
entry->used = false;
|
||||||
wm_notify_fs_change();
|
wm_notify_fs_change();
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fat32_delete(const char *path) {
|
bool fat32_delete(const char *path) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1565,7 +1577,7 @@ bool fat32_delete(const char *path) {
|
|||||||
|
|
||||||
FileEntry *entry = ramfs_find_file(normalized);
|
FileEntry *entry = ramfs_find_file(normalized);
|
||||||
if (!entry || (entry->attributes & ATTR_DIRECTORY)) {
|
if (!entry || (entry->attributes & ATTR_DIRECTORY)) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1577,13 +1589,13 @@ bool fat32_delete(const char *path) {
|
|||||||
result = realfs_delete(drive, p);
|
result = realfs_delete(drive, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fat32_get_info(const char *path, FAT32_FileInfo *info) {
|
int fat32_get_info(const char *path, FAT32_FileInfo *info) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1619,13 +1631,13 @@ int fat32_get_info(const char *path, FAT32_FileInfo *info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fat32_exists(const char *path) {
|
bool fat32_exists(const char *path) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1644,7 +1656,7 @@ bool fat32_exists(const char *path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return exists;
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1653,13 +1665,13 @@ bool fat32_rename(const char *old_path, const char *new_path) {
|
|||||||
if (parse_drive_from_path(&old_path) != 'A') return false;
|
if (parse_drive_from_path(&old_path) != 'A') return false;
|
||||||
if (parse_drive_from_path(&new_path) != 'A') return false;
|
if (parse_drive_from_path(&new_path) != 'A') return false;
|
||||||
|
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
FileEntry *entry = ramfs_find_file(old_path); // Need to normalize inside find? yes ramfs_find calls normalize
|
FileEntry *entry = ramfs_find_file(old_path); // Need to normalize inside find? yes ramfs_find calls normalize
|
||||||
if (!entry) { asm volatile("push %0; popfq" : : "r"(rflags)); return false; }
|
if (!entry) { spinlock_release_irqrestore(&fat32_lock, rflags); return false; }
|
||||||
|
|
||||||
// Check destination
|
// Check destination
|
||||||
if (ramfs_find_file(new_path)) { asm volatile("push %0; popfq" : : "r"(rflags)); return false; }
|
if (ramfs_find_file(new_path)) { spinlock_release_irqrestore(&fat32_lock, rflags); return false; }
|
||||||
|
|
||||||
size_t old_len = fs_strlen(old_path);
|
size_t old_len = fs_strlen(old_path);
|
||||||
// Logic from original rename...
|
// Logic from original rename...
|
||||||
@@ -1689,13 +1701,13 @@ bool fat32_rename(const char *old_path, const char *new_path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
wm_notify_fs_change();
|
wm_notify_fs_change();
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fat32_is_directory(const char *path) {
|
bool fat32_is_directory(const char *path) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1718,13 +1730,13 @@ bool fat32_is_directory(const char *path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return is_dir;
|
return is_dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fat32_list_directory(const char *path, FAT32_FileInfo *entries, int max_entries) {
|
int fat32_list_directory(const char *path, FAT32_FileInfo *entries, int max_entries) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1749,13 +1761,13 @@ int fat32_list_directory(const char *path, FAT32_FileInfo *entries, int max_entr
|
|||||||
count = realfs_list_directory(drive, p, entries, max_entries);
|
count = realfs_list_directory(drive, p, entries, max_entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fat32_chdir(const char *path) {
|
bool fat32_chdir(const char *path) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
const char *p = path;
|
const char *p = path;
|
||||||
char drive = parse_drive_from_path(&p);
|
char drive = parse_drive_from_path(&p);
|
||||||
@@ -1767,11 +1779,11 @@ bool fat32_chdir(const char *path) {
|
|||||||
current_dir[1] = 0;
|
current_dir[1] = 0;
|
||||||
// If just switching drive (e.g. "B:"), return true
|
// If just switching drive (e.g. "B:"), return true
|
||||||
if (p[0] == 0) {
|
if (p[0] == 0) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1787,17 +1799,17 @@ bool fat32_chdir(const char *path) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fat32_get_current_dir(char *buffer, int size) {
|
void fat32_get_current_dir(char *buffer, int size) {
|
||||||
uint64_t rflags;
|
// SMP: Use FAT32 spinlock
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
uint64_t rflags = spinlock_acquire_irqsave(&fat32_lock);
|
||||||
|
|
||||||
int len = 0;
|
int len = 0;
|
||||||
buffer[0] = current_drive;
|
buffer[0] = current_drive;
|
||||||
@@ -1811,5 +1823,5 @@ void fat32_get_current_dir(char *buffer, int size) {
|
|||||||
buffer[len + i] = current_dir[i];
|
buffer[len + i] = current_dir[i];
|
||||||
}
|
}
|
||||||
buffer[len + dir_len] = 0;
|
buffer[len + dir_len] = 0;
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&fat32_lock, rflags);
|
||||||
}
|
}
|
||||||
128
src/fs/tar.c
Normal file
128
src/fs/tar.c
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
// 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.
|
||||||
|
#include "tar.h"
|
||||||
|
#include "fat32.h"
|
||||||
|
|
||||||
|
// The standard TAR header block is 512 bytes.
|
||||||
|
struct tar_header {
|
||||||
|
char filename[100];
|
||||||
|
char mode[8];
|
||||||
|
char uid[8];
|
||||||
|
char gid[8];
|
||||||
|
char size[12];
|
||||||
|
char mtime[12];
|
||||||
|
char chksum[8];
|
||||||
|
char typeflag;
|
||||||
|
char linkname[100];
|
||||||
|
char magic[6];
|
||||||
|
char version[2];
|
||||||
|
char uname[32];
|
||||||
|
char gname[32];
|
||||||
|
char devmajor[8];
|
||||||
|
char devminor[8];
|
||||||
|
char prefix[155];
|
||||||
|
char pad[12];
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
// Helper: parse tar octal field representation
|
||||||
|
static uint64_t tar_parse_octal(const char *str, int size) {
|
||||||
|
uint64_t result = 0;
|
||||||
|
while (size-- > 0) {
|
||||||
|
if (*str >= '0' && *str <= '7') {
|
||||||
|
result = (result << 3) + (*str - '0');
|
||||||
|
}
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Make directories sequentially for nested paths
|
||||||
|
static void tar_mkdir_recursive(const char *path) {
|
||||||
|
char temp[256];
|
||||||
|
int i = 0;
|
||||||
|
if (path[0] == '/') {
|
||||||
|
temp[0] = '/';
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
while (path[i] && i < 255) {
|
||||||
|
temp[i] = path[i];
|
||||||
|
if (path[i] == '/') {
|
||||||
|
temp[i] = '\0';
|
||||||
|
fat32_mkdir(temp);
|
||||||
|
temp[i] = '/';
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i > 0 && temp[i - 1] != '/') {
|
||||||
|
temp[i] = '\0';
|
||||||
|
fat32_mkdir(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tar_parse(void *archive, uint64_t archive_size) {
|
||||||
|
uint8_t *ptr = (uint8_t *)archive;
|
||||||
|
uint8_t *end = ptr + archive_size;
|
||||||
|
|
||||||
|
while (ptr + 512 <= end) {
|
||||||
|
struct tar_header *header = (struct tar_header *)ptr;
|
||||||
|
|
||||||
|
// End of archive is marked by empty blocks
|
||||||
|
if (header->filename[0] == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t file_size = tar_parse_octal(header->size, 11);
|
||||||
|
|
||||||
|
char full_path[256];
|
||||||
|
// Ensure path starts with a '/' for VFS consistency
|
||||||
|
if (header->filename[0] != '/') {
|
||||||
|
full_path[0] = '/';
|
||||||
|
int j = 0;
|
||||||
|
while (header->filename[j] && j < 254) {
|
||||||
|
full_path[j + 1] = header->filename[j];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
full_path[j + 1] = '\0';
|
||||||
|
} else {
|
||||||
|
int j = 0;
|
||||||
|
while (header->filename[j] && j < 255) {
|
||||||
|
full_path[j] = header->filename[j];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
full_path[j] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->typeflag == '5') {
|
||||||
|
// It's a directory
|
||||||
|
tar_mkdir_recursive(full_path);
|
||||||
|
} else if (header->typeflag == '0' || header->typeflag == '\0') {
|
||||||
|
// It's a normal file
|
||||||
|
// First ensure the parent directory exists
|
||||||
|
char parent_path[256];
|
||||||
|
int last_slash = -1;
|
||||||
|
for (int j = 0; full_path[j]; j++) {
|
||||||
|
parent_path[j] = full_path[j];
|
||||||
|
if (full_path[j] == '/') {
|
||||||
|
last_slash = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last_slash > 0) {
|
||||||
|
parent_path[last_slash] = '\0';
|
||||||
|
tar_mkdir_recursive(parent_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the file data block directly into the VFS
|
||||||
|
FAT32_FileHandle *fh = fat32_open(full_path, "w");
|
||||||
|
if (fh && fh->valid) {
|
||||||
|
fat32_write(fh, ptr + 512, file_size);
|
||||||
|
fat32_close(fh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance pointer to the next file header
|
||||||
|
// Header block (512) + File data (padded to 512-byte multiples)
|
||||||
|
uint64_t data_blocks = (file_size + 511) / 512;
|
||||||
|
ptr += 512 + (data_blocks * 512);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/fs/tar.h
Normal file
13
src/fs/tar.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// 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.
|
||||||
|
#ifndef TAR_H
|
||||||
|
#define TAR_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Parse a TAR archive located in memory and extract its contents into the current filesystem (fatal32 RAM disk).
|
||||||
|
void tar_parse(void *archive, uint64_t archive_size);
|
||||||
|
|
||||||
|
#endif
|
||||||
BIN
src/images/wallpapers/bored.jpg
Normal file
BIN
src/images/wallpapers/bored.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 574 KiB |
@@ -5,6 +5,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "limine.h"
|
#include "limine.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "spinlock.h"
|
||||||
|
|
||||||
// --- Internal State ---
|
// --- Internal State ---
|
||||||
// memory_pool is no longer a single pointer, as we now manage multiple regions.
|
// memory_pool is no longer a single pointer, as we now manage multiple regions.
|
||||||
@@ -16,6 +17,7 @@ static size_t total_allocated = 0;
|
|||||||
static size_t peak_allocated = 0;
|
static size_t peak_allocated = 0;
|
||||||
static uint32_t allocation_counter = 0;
|
static uint32_t allocation_counter = 0;
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
|
static spinlock_t mm_lock = SPINLOCK_INIT;
|
||||||
|
|
||||||
extern void serial_write(const char *str);
|
extern void serial_write(const char *str);
|
||||||
extern void serial_write_num(uint32_t n);
|
extern void serial_write_num(uint32_t n);
|
||||||
@@ -174,8 +176,7 @@ static void remove_block_at(int idx) {
|
|||||||
void* kmalloc_aligned(size_t size, size_t alignment) {
|
void* kmalloc_aligned(size_t size, size_t alignment) {
|
||||||
if (!initialized || size == 0) return NULL;
|
if (!initialized || size == 0) return NULL;
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags = spinlock_acquire_irqsave(&mm_lock);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
|
||||||
|
|
||||||
if (alignment == 0) alignment = 8;
|
if (alignment == 0) alignment = 8;
|
||||||
size = (size + 7) & ~7ULL; // Ensure size is multiple of 8
|
size = (size + 7) & ~7ULL; // Ensure size is multiple of 8
|
||||||
@@ -245,12 +246,12 @@ void* kmalloc_aligned(size_t size, size_t alignment) {
|
|||||||
if (total_allocated > peak_allocated) peak_allocated = total_allocated;
|
if (total_allocated > peak_allocated) peak_allocated = total_allocated;
|
||||||
|
|
||||||
mem_memset(ptr, 0, size);
|
mem_memset(ptr, 0, size);
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,8 +262,7 @@ void* kmalloc(size_t size) {
|
|||||||
void kfree(void *ptr) {
|
void kfree(void *ptr) {
|
||||||
if (ptr == NULL || !initialized) return;
|
if (ptr == NULL || !initialized) return;
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags = spinlock_acquire_irqsave(&mm_lock);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
|
||||||
|
|
||||||
int block_idx = -1;
|
int block_idx = -1;
|
||||||
for (int i = 0; i < block_count; i++) {
|
for (int i = 0; i < block_count; i++) {
|
||||||
@@ -273,7 +273,7 @@ void kfree(void *ptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (block_idx == -1) {
|
if (block_idx == -1) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ void kfree(void *ptr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* krealloc(void *ptr, size_t new_size) {
|
void* krealloc(void *ptr, size_t new_size) {
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ int network_tcp_connect(const ipv4_address_t *ip, uint16_t port) {
|
|||||||
|
|
||||||
uint32_t start = sys_now();
|
uint32_t start = sys_now();
|
||||||
asm volatile("sti");
|
asm volatile("sti");
|
||||||
while (sys_now() - start < 5000) { // 5 second timeout
|
while (sys_now() - start < 15000) { // 15 second timeout
|
||||||
network_poll_internal();
|
network_poll_internal();
|
||||||
if (tcp_connect_done) { asm volatile("cli"); network_processing = 0; return 0; }
|
if (tcp_connect_done) { asm volatile("cli"); network_processing = 0; return 0; }
|
||||||
if (tcp_connect_error) { asm volatile("cli"); network_processing = 0; return -1; }
|
if (tcp_connect_error) { asm volatile("cli"); network_processing = 0; return -1; }
|
||||||
@@ -253,7 +253,7 @@ int network_tcp_recv(void *buf, size_t max_len) {
|
|||||||
if (tcp_closed) { network_processing = 0; return 0; } // End of stream
|
if (tcp_closed) { network_processing = 0; return 0; } // End of stream
|
||||||
uint32_t start = sys_now();
|
uint32_t start = sys_now();
|
||||||
asm volatile("sti");
|
asm volatile("sti");
|
||||||
while (sys_now() - start < 5000) { // 5 second timeout
|
while (sys_now() - start < 30000) { // 30 second timeout
|
||||||
network_poll_internal();
|
network_poll_internal();
|
||||||
if (tcp_recv_queue) break;
|
if (tcp_recv_queue) break;
|
||||||
if (tcp_closed) break;
|
if (tcp_closed) break;
|
||||||
@@ -281,7 +281,10 @@ int network_tcp_recv_nb(void *buf, size_t max_len) {
|
|||||||
if (network_processing) return -1;
|
if (network_processing) return -1;
|
||||||
network_processing = 1;
|
network_processing = 1;
|
||||||
|
|
||||||
|
network_poll_internal();
|
||||||
|
|
||||||
if (!tcp_recv_queue) {
|
if (!tcp_recv_queue) {
|
||||||
|
if (tcp_closed) { network_processing = 0; return -2; }
|
||||||
network_processing = 0;
|
network_processing = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,19 @@ static void *gdt_memset(void *s, int c, size_t n) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define GDT_ENTRIES 7
|
// Base GDT: 5 segments + 1 TSS (2 entries) = 7 entries for BSP.
|
||||||
|
// For SMP: we add 2 entries per additional CPU for their TSS.
|
||||||
|
// Max supported: 32 CPUs → 5 + 2*32 = 69 entries max.
|
||||||
|
#define GDT_BASE_ENTRIES 5 // NULL, KCode, KData, UData, UCode
|
||||||
|
#define GDT_MAX_ENTRIES 69 // 5 + 2*32
|
||||||
|
|
||||||
struct gdt_entry gdt[GDT_ENTRIES];
|
struct gdt_entry gdt[GDT_MAX_ENTRIES];
|
||||||
struct gdt_ptr gdtr;
|
struct gdt_ptr gdtr;
|
||||||
struct tss_entry tss;
|
struct tss_entry tss; // BSP TSS (CPU 0)
|
||||||
|
|
||||||
|
// Per-CPU TSS array (dynamically allocated for AP cores)
|
||||||
|
static struct tss_entry *ap_tss_array = NULL;
|
||||||
|
static uint32_t ap_tss_count = 0;
|
||||||
|
|
||||||
extern void gdt_flush(uint64_t);
|
extern void gdt_flush(uint64_t);
|
||||||
extern void tss_flush(void);
|
extern void tss_flush(void);
|
||||||
@@ -35,8 +43,8 @@ static void gdt_set_gate(int num, uint32_t base, uint32_t limit, uint8_t access,
|
|||||||
gdt[num].access = access;
|
gdt[num].access = access;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gdt_set_tss_gate(int num, uint64_t base, uint32_t limit, uint8_t access, uint8_t gran) {
|
// Write a 16-byte TSS descriptor into GDT entries [num] and [num+1]
|
||||||
// A TSS entry in x86_64 is 16 bytes (takes up 2 adjacent GDT entries)
|
static void gdt_set_tss_gate_at(int num, uint64_t base, uint32_t limit, uint8_t access, uint8_t gran) {
|
||||||
struct {
|
struct {
|
||||||
uint16_t limit_low;
|
uint16_t limit_low;
|
||||||
uint16_t base_low;
|
uint16_t base_low;
|
||||||
@@ -65,44 +73,98 @@ void tss_set_stack(uint64_t kernel_stack) {
|
|||||||
tss.rsp0 = kernel_stack;
|
tss.rsp0 = kernel_stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tss_set_stack_cpu(uint32_t cpu_id, uint64_t kernel_stack) {
|
||||||
|
if (cpu_id == 0) {
|
||||||
|
tss.rsp0 = kernel_stack;
|
||||||
|
} else if (ap_tss_array && cpu_id - 1 < ap_tss_count) {
|
||||||
|
ap_tss_array[cpu_id - 1].rsp0 = kernel_stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void gdt_init(void) {
|
void gdt_init(void) {
|
||||||
gdtr.limit = (sizeof(struct gdt_entry) * GDT_ENTRIES) - 1;
|
// Start with 7 entries (5 segments + BSP TSS taking 2)
|
||||||
|
gdtr.limit = (sizeof(struct gdt_entry) * 7) - 1;
|
||||||
gdtr.base = (uint64_t)&gdt;
|
gdtr.base = (uint64_t)&gdt;
|
||||||
|
|
||||||
// NULL segment
|
// NULL segment
|
||||||
gdt_set_gate(0, 0, 0, 0, 0);
|
gdt_set_gate(0, 0, 0, 0, 0);
|
||||||
|
|
||||||
// Kernel Code segment (Ring 0, 64-bit)
|
// Kernel Code segment (Ring 0, 64-bit)
|
||||||
// 0x9A: Present(1), Ring(0), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
|
|
||||||
// 0xAF: Long Mode (64-bit) (L=1, DB=0)
|
|
||||||
gdt_set_gate(1, 0, 0, 0x9A, 0xAF);
|
gdt_set_gate(1, 0, 0, 0x9A, 0xAF);
|
||||||
|
|
||||||
// Kernel Data segment (Ring 0)
|
// Kernel Data segment (Ring 0)
|
||||||
// 0x92: Present(1), Ring(0), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
|
|
||||||
gdt_set_gate(2, 0, 0, 0x92, 0xAF);
|
gdt_set_gate(2, 0, 0, 0x92, 0xAF);
|
||||||
|
|
||||||
// User Data segment (Ring 3)
|
// User Data segment (Ring 3)
|
||||||
// 0xF2: Present(1), Ring(3), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
|
|
||||||
gdt_set_gate(3, 0, 0, 0xF2, 0xAF);
|
gdt_set_gate(3, 0, 0, 0xF2, 0xAF);
|
||||||
|
|
||||||
// User Code segment (Ring 3, 64-bit)
|
// User Code segment (Ring 3, 64-bit)
|
||||||
// 0xFA: Present(1), Ring(3), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
|
|
||||||
// 0xAF: Long Mode (64-bit)
|
|
||||||
gdt_set_gate(4, 0, 0, 0xFA, 0xAF);
|
gdt_set_gate(4, 0, 0, 0xFA, 0xAF);
|
||||||
|
|
||||||
// TSS segment (takes entries 5 and 6 technically because it's 16 bytes)
|
// BSP TSS segment (entries 5 and 6)
|
||||||
gdt_memset(&tss, 0, sizeof(struct tss_entry));
|
gdt_memset(&tss, 0, sizeof(struct tss_entry));
|
||||||
tss.iopb_offset = sizeof(struct tss_entry);
|
tss.iopb_offset = sizeof(struct tss_entry);
|
||||||
|
|
||||||
// Allocate a default Ring 0 interrupt stack in case an interrupt fires early or
|
|
||||||
// the scheduler hasn't set one up yet for a task.
|
|
||||||
void* initial_tss_stack = kmalloc_aligned(4096, 4096);
|
void* initial_tss_stack = kmalloc_aligned(4096, 4096);
|
||||||
if (initial_tss_stack) {
|
if (initial_tss_stack) {
|
||||||
tss.rsp0 = (uint64_t)initial_tss_stack + 4096;
|
tss.rsp0 = (uint64_t)initial_tss_stack + 4096;
|
||||||
}
|
}
|
||||||
|
|
||||||
gdt_set_tss_gate(5, (uint64_t)&tss, sizeof(struct tss_entry) - 1, 0x89, 0x00);
|
gdt_set_tss_gate_at(5, (uint64_t)&tss, sizeof(struct tss_entry) - 1, 0x89, 0x00);
|
||||||
|
|
||||||
gdt_flush((uint64_t)&gdtr);
|
gdt_flush((uint64_t)&gdtr);
|
||||||
tss_flush();
|
tss_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SMP: Add TSS entries for all AP cores and reload the GDT.
|
||||||
|
void gdt_init_ap_tss(uint32_t cpu_count) {
|
||||||
|
if (cpu_count <= 1) return; // No APs
|
||||||
|
|
||||||
|
uint32_t ap_count = cpu_count - 1;
|
||||||
|
ap_tss_count = ap_count;
|
||||||
|
|
||||||
|
// Allocate per-CPU TSS structures
|
||||||
|
ap_tss_array = (struct tss_entry *)kmalloc(ap_count * sizeof(struct tss_entry));
|
||||||
|
if (!ap_tss_array) return;
|
||||||
|
gdt_memset(ap_tss_array, 0, ap_count * sizeof(struct tss_entry));
|
||||||
|
|
||||||
|
// Each AP TSS goes at GDT slot 7 + (i*2) (since slot 5-6 is BSP TSS)
|
||||||
|
for (uint32_t i = 0; i < ap_count; i++) {
|
||||||
|
int gdt_slot = 7 + (i * 2);
|
||||||
|
if (gdt_slot + 1 >= GDT_MAX_ENTRIES) break;
|
||||||
|
|
||||||
|
ap_tss_array[i].iopb_offset = sizeof(struct tss_entry);
|
||||||
|
|
||||||
|
// Allocate a kernel stack for this AP's interrupt handling
|
||||||
|
void *ap_int_stack = kmalloc_aligned(8192, 4096);
|
||||||
|
if (ap_int_stack) {
|
||||||
|
ap_tss_array[i].rsp0 = (uint64_t)ap_int_stack + 8192;
|
||||||
|
}
|
||||||
|
|
||||||
|
gdt_set_tss_gate_at(gdt_slot, (uint64_t)&ap_tss_array[i],
|
||||||
|
sizeof(struct tss_entry) - 1, 0x89, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update GDT limit to include all new entries
|
||||||
|
uint32_t total_entries = 7 + (ap_count * 2);
|
||||||
|
if (total_entries > GDT_MAX_ENTRIES) total_entries = GDT_MAX_ENTRIES;
|
||||||
|
gdtr.limit = (sizeof(struct gdt_entry) * total_entries) - 1;
|
||||||
|
|
||||||
|
// Reload GDTR on BSP with the expanded limit.
|
||||||
|
// We must NOT call tss_flush() here — the BSP TSS is already loaded
|
||||||
|
// and marked "busy" (0x8B). Trying to LTR a busy TSS causes GPF.
|
||||||
|
asm volatile("lgdt %0" : : "m"(gdtr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMP: Load the TSS for a specific AP. Called from ap_entry().
|
||||||
|
void gdt_load_ap_tss(uint32_t cpu_id) {
|
||||||
|
if (cpu_id == 0) {
|
||||||
|
// BSP uses slot 5 → selector 0x28
|
||||||
|
asm volatile("mov $0x28, %%ax; ltr %%ax" ::: "ax");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AP cpu_id maps to GDT slot 7 + ((cpu_id-1) * 2)
|
||||||
|
uint16_t selector = (uint16_t)((7 + ((cpu_id - 1) * 2)) * sizeof(struct gdt_entry));
|
||||||
|
asm volatile("ltr %0" : : "r"(selector));
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,4 +48,11 @@ struct gdt_ptr {
|
|||||||
void gdt_init(void);
|
void gdt_init(void);
|
||||||
void tss_set_stack(uint64_t kernel_stack);
|
void tss_set_stack(uint64_t kernel_stack);
|
||||||
|
|
||||||
|
// SMP: Initialize per-CPU TSS entries. Call after smp detects cpu_count.
|
||||||
|
void gdt_init_ap_tss(uint32_t cpu_count);
|
||||||
|
// SMP: Load the TSS for a specific CPU (called from AP entry).
|
||||||
|
void gdt_load_ap_tss(uint32_t cpu_id);
|
||||||
|
// SMP: Set kernel stack for a specific CPU's TSS.
|
||||||
|
void tss_set_stack_cpu(uint32_t cpu_id, uint64_t kernel_stack);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -59,7 +59,17 @@ uint64_t exception_handler_c(registers_t *regs) {
|
|||||||
serial_write(buf);
|
serial_write(buf);
|
||||||
|
|
||||||
if ((regs->cs & 0x3) != 0) {
|
if ((regs->cs & 0x3) != 0) {
|
||||||
serial_write("\nUSER MODE EXCEPTION - Terminating process.\n");
|
serial_write("\n*** USER MODE EXCEPTION ***\nVector: 0x");
|
||||||
|
k_itoa_hex(vector, buf);
|
||||||
|
serial_write(buf);
|
||||||
|
serial_write("\nRIP: 0x");
|
||||||
|
k_itoa_hex(regs->rip, buf);
|
||||||
|
serial_write(buf);
|
||||||
|
serial_write("\nError Code: 0x");
|
||||||
|
k_itoa_hex(regs->err_code, buf);
|
||||||
|
serial_write(buf);
|
||||||
|
serial_write("\nTerminating process.\n");
|
||||||
|
|
||||||
if (cmd_get_cursor_col() != 0) cmd_write("\n");
|
if (cmd_get_cursor_col() != 0) cmd_write("\n");
|
||||||
cmd_write("*** USER EXCEPTION ***\nVector: "); cmd_write_hex(vector);
|
cmd_write("*** USER EXCEPTION ***\nVector: "); cmd_write_hex(vector);
|
||||||
cmd_write("\nRIP: "); cmd_write_hex(regs->rip);
|
cmd_write("\nRIP: "); cmd_write_hex(regs->rip);
|
||||||
@@ -230,6 +240,13 @@ void idt_register_interrupts(void) {
|
|||||||
idt_set_gate(30, exc30_wrapper, cs, 0x8E);
|
idt_set_gate(30, exc30_wrapper, cs, 0x8E);
|
||||||
idt_set_gate(31, exc31_wrapper, cs, 0x8E);
|
idt_set_gate(31, exc31_wrapper, cs, 0x8E);
|
||||||
|
|
||||||
|
// SMP: Scheduling IPI for AP cores (vector 0x41 = 65)
|
||||||
|
extern void isr_sched_ipi_wrapper(void);
|
||||||
|
idt_set_gate(0x41, isr_sched_ipi_wrapper, cs, 0x8E);
|
||||||
|
|
||||||
|
// Syscall Handler (vector 128) - DPL 3 for user access
|
||||||
|
extern void isr128_wrapper(void);
|
||||||
|
idt_set_gate(128, isr128_wrapper, cs, 0xEE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void idt_load(void) {
|
void idt_load(void) {
|
||||||
|
|||||||
54
src/sys/lapic.c
Normal file
54
src/sys/lapic.c
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// 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.
|
||||||
|
#include "lapic.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
extern void serial_write(const char *str);
|
||||||
|
|
||||||
|
// LAPIC is at physical 0xFEE00000. Access via HHDM.
|
||||||
|
static volatile uint32_t *lapic_base = 0;
|
||||||
|
|
||||||
|
// LAPIC register offsets (byte offsets, divided by 4 for uint32_t* indexing)
|
||||||
|
#define LAPIC_ID (0x020 / 4)
|
||||||
|
#define LAPIC_EOI (0x0B0 / 4)
|
||||||
|
#define LAPIC_SVR (0x0F0 / 4)
|
||||||
|
#define LAPIC_ICR_LOW (0x300 / 4)
|
||||||
|
#define LAPIC_ICR_HIGH (0x310 / 4)
|
||||||
|
|
||||||
|
void lapic_enable(void) {
|
||||||
|
if (!lapic_base) return;
|
||||||
|
// Enable the LAPIC by setting the Spurious Interrupt Vector Register
|
||||||
|
// Bit 8 = APIC Software Enable, vector = 0xFF (spurious)
|
||||||
|
lapic_base[LAPIC_SVR] = 0x1FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lapic_init(void) {
|
||||||
|
extern uint64_t hhdm_offset;
|
||||||
|
lapic_base = (volatile uint32_t *)(hhdm_offset + 0xFEE00000ULL);
|
||||||
|
|
||||||
|
lapic_enable();
|
||||||
|
serial_write("[LAPIC] Initialized at HHDM + 0xFEE00000\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void lapic_eoi(void) {
|
||||||
|
if (lapic_base) {
|
||||||
|
lapic_base[LAPIC_EOI] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lapic_send_ipi_all(void) {
|
||||||
|
if (!lapic_base) return;
|
||||||
|
|
||||||
|
// Send IPI to all excluding self
|
||||||
|
// ICR format:
|
||||||
|
// bits 7:0 = vector (IPI_SCHED_VECTOR = 0x41)
|
||||||
|
// bits 10:8 = delivery mode (000 = Fixed)
|
||||||
|
// bit 11 = destination mode (0 = Physical)
|
||||||
|
// bit 14 = level (1 = Assert)
|
||||||
|
// bits 19:18 = destination shorthand (11 = All Excluding Self)
|
||||||
|
uint32_t icr_low = IPI_SCHED_VECTOR | (0b11 << 18) | (1 << 14);
|
||||||
|
|
||||||
|
// Writing ICR_LOW triggers the IPI send
|
||||||
|
lapic_base[LAPIC_ICR_LOW] = icr_low;
|
||||||
|
}
|
||||||
24
src/sys/lapic.h
Normal file
24
src/sys/lapic.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// 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.
|
||||||
|
#ifndef LAPIC_H
|
||||||
|
#define LAPIC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// IPI vector used for scheduling on APs
|
||||||
|
#define IPI_SCHED_VECTOR 0x41
|
||||||
|
|
||||||
|
// Initialize LAPIC access (maps registers via HHDM)
|
||||||
|
void lapic_init(void);
|
||||||
|
|
||||||
|
// Enable LAPIC (set SVR bit 8)
|
||||||
|
void lapic_enable(void);
|
||||||
|
|
||||||
|
// Send End-of-Interrupt to the local APIC
|
||||||
|
void lapic_eoi(void);
|
||||||
|
|
||||||
|
// Send a scheduling IPI to all APs (excludes self)
|
||||||
|
void lapic_send_ipi_all(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -10,16 +10,23 @@
|
|||||||
#include "memory_manager.h"
|
#include "memory_manager.h"
|
||||||
#include "elf.h"
|
#include "elf.h"
|
||||||
#include "wm.h"
|
#include "wm.h"
|
||||||
|
#include "spinlock.h"
|
||||||
|
#include "smp.h"
|
||||||
|
#include "lapic.h"
|
||||||
|
|
||||||
extern void cmd_write(const char *str);
|
extern void cmd_write(const char *str);
|
||||||
extern void serial_write(const char *str);
|
extern void serial_write(const char *str);
|
||||||
|
|
||||||
#define MAX_PROCESSES 16
|
#define MAX_PROCESSES 16
|
||||||
|
#define MAX_CPUS_SCHED 32
|
||||||
process_t processes[MAX_PROCESSES] __attribute__((aligned(16)));
|
process_t processes[MAX_PROCESSES] __attribute__((aligned(16)));
|
||||||
int process_count = 0;
|
int process_count = 0;
|
||||||
static process_t* current_process = NULL;
|
static process_t* current_process[MAX_CPUS_SCHED] = {0}; // Per-CPU
|
||||||
static uint32_t next_pid = 0;
|
static uint32_t next_pid = 0;
|
||||||
static void *free_kernel_stack_later = NULL;
|
static void *free_kernel_stack_later = NULL;
|
||||||
|
static uint64_t free_pml4_later = 0;
|
||||||
|
static spinlock_t runqueue_lock = SPINLOCK_INIT;
|
||||||
|
static uint32_t next_cpu_assign = 1; // Round-robin CPU assignment (start from CPU 1)
|
||||||
|
|
||||||
void process_init(void) {
|
void process_init(void) {
|
||||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||||
@@ -47,11 +54,17 @@ void process_init(void) {
|
|||||||
kernel_proc->used_memory = 32768; // Kernel stack
|
kernel_proc->used_memory = 32768; // Kernel stack
|
||||||
|
|
||||||
kernel_proc->next = kernel_proc; // Circular linked list
|
kernel_proc->next = kernel_proc; // Circular linked list
|
||||||
current_process = kernel_proc;
|
kernel_proc->cpu_affinity = 0; // Kernel always on BSP
|
||||||
|
current_process[0] = kernel_proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_create(void* entry_point, bool is_user) {
|
process_t* process_create(void (*entry_point)(void), bool is_user) {
|
||||||
if (process_count >= MAX_PROCESSES) return;
|
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||||
|
|
||||||
|
if (process_count >= MAX_PROCESSES) {
|
||||||
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
process_t *new_proc = &processes[process_count++];
|
process_t *new_proc = &processes[process_count++];
|
||||||
new_proc->pid = next_pid++;
|
new_proc->pid = next_pid++;
|
||||||
@@ -64,7 +77,10 @@ void process_create(void* entry_point, bool is_user) {
|
|||||||
new_proc->pml4_phys = paging_get_pml4_phys();
|
new_proc->pml4_phys = paging_get_pml4_phys();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new_proc->pml4_phys) return;
|
if (!new_proc->pml4_phys) {
|
||||||
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Allocate aligned stack
|
// 2. Allocate aligned stack
|
||||||
void* user_stack = kmalloc_aligned(4096, 4096);
|
void* user_stack = kmalloc_aligned(4096, 4096);
|
||||||
@@ -131,15 +147,18 @@ void process_create(void* entry_point, bool is_user) {
|
|||||||
asm volatile("fninit");
|
asm volatile("fninit");
|
||||||
new_proc->fpu_initialized = true;
|
new_proc->fpu_initialized = true;
|
||||||
|
|
||||||
// Add to linked list (Critical Section)
|
new_proc->cpu_affinity = 0; // Non-ELF processes stay on BSP
|
||||||
uint64_t rflags;
|
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
// Add to linked list
|
||||||
new_proc->next = current_process->next;
|
new_proc->next = current_process[0]->next;
|
||||||
current_process->next = new_proc;
|
current_process[0]->next = new_proc;
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
|
||||||
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
|
return new_proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t* process_create_elf(const char* filepath, const char* args_str) {
|
process_t* process_create_elf(const char* filepath, const char* args_str) {
|
||||||
|
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||||
process_t *new_proc = NULL;
|
process_t *new_proc = NULL;
|
||||||
|
|
||||||
// Find an available slot
|
// Find an available slot
|
||||||
@@ -151,10 +170,14 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new_proc) return NULL;
|
if (!new_proc) {
|
||||||
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
new_proc->pid = next_pid++;
|
new_proc->pid = next_pid++;
|
||||||
new_proc->is_user = true;
|
new_proc->is_user = true;
|
||||||
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
|
|
||||||
// 1. Setup Page Table
|
// 1. Setup Page Table
|
||||||
new_proc->pml4_phys = paging_create_user_pml4_phys();
|
new_proc->pml4_phys = paging_create_user_pml4_phys();
|
||||||
@@ -315,12 +338,21 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
|
|||||||
asm volatile("fninit");
|
asm volatile("fninit");
|
||||||
new_proc->fpu_initialized = true;
|
new_proc->fpu_initialized = true;
|
||||||
|
|
||||||
|
// Assign to an AP core via round-robin (if SMP is active)
|
||||||
|
uint32_t cpu_count = smp_cpu_count();
|
||||||
|
if (cpu_count > 1) {
|
||||||
|
new_proc->cpu_affinity = next_cpu_assign;
|
||||||
|
next_cpu_assign++;
|
||||||
|
if (next_cpu_assign >= cpu_count) next_cpu_assign = 1; // Wrap, skip CPU 0
|
||||||
|
} else {
|
||||||
|
new_proc->cpu_affinity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Add to linked list (Critical Section)
|
// Add to linked list (Critical Section)
|
||||||
uint64_t rflags;
|
rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
new_proc->next = current_process[0]->next;
|
||||||
new_proc->next = current_process->next;
|
current_process[0]->next = new_proc;
|
||||||
current_process->next = new_proc;
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
|
||||||
|
|
||||||
serial_write("[PROCESS] Spawned ELF Executable: ");
|
serial_write("[PROCESS] Spawned ELF Executable: ");
|
||||||
serial_write(filepath);
|
serial_write(filepath);
|
||||||
@@ -328,8 +360,19 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
|
|||||||
return new_proc;
|
return new_proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process_t* process_get_current_for_cpu(uint32_t cpu_id) {
|
||||||
|
if (cpu_id >= MAX_CPUS_SCHED) return NULL;
|
||||||
|
return current_process[cpu_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_set_current_for_cpu(uint32_t cpu_id, process_t* p) {
|
||||||
|
if (cpu_id >= MAX_CPUS_SCHED) return;
|
||||||
|
current_process[cpu_id] = p;
|
||||||
|
}
|
||||||
|
|
||||||
process_t* process_get_current(void) {
|
process_t* process_get_current(void) {
|
||||||
return current_process;
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
|
return current_process[cpu];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t process_schedule(uint64_t current_rsp) {
|
uint64_t process_schedule(uint64_t current_rsp) {
|
||||||
@@ -337,42 +380,71 @@ uint64_t process_schedule(uint64_t current_rsp) {
|
|||||||
kfree(free_kernel_stack_later);
|
kfree(free_kernel_stack_later);
|
||||||
free_kernel_stack_later = NULL;
|
free_kernel_stack_later = NULL;
|
||||||
}
|
}
|
||||||
|
if (free_pml4_later) {
|
||||||
|
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||||
|
paging_destroy_user_pml4_phys(free_pml4_later);
|
||||||
|
free_pml4_later = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!current_process || !current_process->next || current_process == current_process->next)
|
uint32_t my_cpu = smp_this_cpu_id();
|
||||||
|
process_t *cur = current_process[my_cpu];
|
||||||
|
|
||||||
|
if (!cur || !cur->next || cur == cur->next)
|
||||||
return current_rsp;
|
return current_rsp;
|
||||||
|
|
||||||
// Save context
|
// Save context
|
||||||
current_process->rsp = current_rsp;
|
cur->rsp = current_rsp;
|
||||||
|
|
||||||
// Switch to next ready process
|
// Switch to next ready process assigned to this CPU
|
||||||
extern uint32_t wm_get_ticks(void);
|
extern uint32_t wm_get_ticks(void);
|
||||||
uint32_t now = wm_get_ticks();
|
uint32_t now = wm_get_ticks();
|
||||||
|
|
||||||
process_t *start = current_process;
|
process_t *start = cur;
|
||||||
process_t *next_proc = current_process->next;
|
process_t *next_proc = cur->next;
|
||||||
|
|
||||||
while (next_proc != start) {
|
while (next_proc != start) {
|
||||||
if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) {
|
// Only consider processes assigned to our CPU and not terminated
|
||||||
break;
|
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF) {
|
||||||
|
if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next_proc = next_proc->next;
|
next_proc = next_proc->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_process = next_proc;
|
// If we didn't find a ready process for our CPU, stay on current (unless we are terminated)
|
||||||
|
if (next_proc->cpu_affinity != my_cpu || next_proc->pid == 0xFFFFFFFF) {
|
||||||
|
// Fallback to idle if current is terminated
|
||||||
|
if (cur && cur->pid == 0xFFFFFFFF) {
|
||||||
|
// Find the idle process for this CPU
|
||||||
|
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||||
|
if (processes[i].pid == 0 || (processes[i].cpu_affinity == my_cpu && processes[i].is_user == false)) {
|
||||||
|
next_proc = &processes[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return current_rsp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_process[my_cpu] = next_proc;
|
||||||
|
|
||||||
// Update Kernel Stack for User Mode interrupts and System Calls
|
// Update Kernel Stack for User Mode interrupts and System Calls
|
||||||
if (current_process->is_user && current_process->kernel_stack) {
|
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
|
||||||
tss_set_stack(current_process->kernel_stack);
|
tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack);
|
||||||
extern uint64_t kernel_syscall_stack;
|
if (my_cpu == 0) {
|
||||||
kernel_syscall_stack = current_process->kernel_stack;
|
extern uint64_t kernel_syscall_stack;
|
||||||
|
kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch page table
|
// Switch page table
|
||||||
paging_switch_directory(current_process->pml4_phys);
|
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||||
|
|
||||||
current_process->ticks++;
|
current_process[my_cpu]->ticks++;
|
||||||
|
|
||||||
return current_process->rsp;
|
return current_process[my_cpu]->rsp;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t* process_get_by_pid(uint32_t pid) {
|
process_t* process_get_by_pid(uint32_t pid) {
|
||||||
@@ -403,6 +475,9 @@ static void process_cleanup_inner(process_t *proc) {
|
|||||||
extern void cmd_process_finished(void);
|
extern void cmd_process_finished(void);
|
||||||
cmd_process_finished();
|
cmd_process_finished();
|
||||||
|
|
||||||
|
extern void network_cleanup(void);
|
||||||
|
network_cleanup();
|
||||||
|
|
||||||
extern void network_cleanup_pcb(void *pcb);
|
extern void network_cleanup_pcb(void *pcb);
|
||||||
// TODO: We need per-process PCB tracking to call this safely
|
// TODO: We need per-process PCB tracking to call this safely
|
||||||
// For now, let's NOT call global network_cleanup
|
// For now, let's NOT call global network_cleanup
|
||||||
@@ -411,8 +486,7 @@ static void process_cleanup_inner(process_t *proc) {
|
|||||||
void process_terminate(process_t *to_delete) {
|
void process_terminate(process_t *to_delete) {
|
||||||
if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return;
|
if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return;
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
|
||||||
|
|
||||||
process_cleanup_inner(to_delete);
|
process_cleanup_inner(to_delete);
|
||||||
|
|
||||||
@@ -424,31 +498,40 @@ void process_terminate(process_t *to_delete) {
|
|||||||
|
|
||||||
if (prev == to_delete) {
|
if (prev == to_delete) {
|
||||||
// Only one process (should be kernel), cannot terminate.
|
// Only one process (should be kernel), cannot terminate.
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Remove current from list
|
// 3. Remove current from list
|
||||||
prev->next = to_delete->next;
|
prev->next = to_delete->next;
|
||||||
|
|
||||||
if (to_delete == current_process) {
|
// Update per-CPU current_process if this was the current on any CPU
|
||||||
current_process = to_delete->next;
|
uint32_t cpu_count = smp_cpu_count();
|
||||||
// WARNING: If this was called as a regular function and not via a task switch,
|
for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) {
|
||||||
// the stack might be in a weird state. But usually we call this via window manager
|
if (current_process[c] == to_delete) {
|
||||||
// or other external triggers.
|
process_t *np = to_delete->next;
|
||||||
|
while (np != to_delete) {
|
||||||
|
if (np->cpu_affinity == c && np->pid != 0xFFFFFFFF) break;
|
||||||
|
np = np->next;
|
||||||
|
}
|
||||||
|
if (np == to_delete || np->cpu_affinity != c) {
|
||||||
|
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||||
|
if (processes[i].pid == 0 || (processes[i].cpu_affinity == c && processes[i].is_user == false)) {
|
||||||
|
np = &processes[i]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_process[c] = np;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark slot as free
|
// Mark slot as free
|
||||||
to_delete->pid = 0xFFFFFFFF;
|
to_delete->pid = 0xFFFFFFFF;
|
||||||
|
to_delete->cpu_affinity = 0xFFFFFFFF;
|
||||||
|
|
||||||
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
||||||
if (to_delete->kernel_stack_alloc) {
|
// Defer kernel stack until we switch away from it
|
||||||
if (to_delete == current_process) {
|
to_delete->kernel_stack_alloc = NULL;
|
||||||
free_kernel_stack_later = to_delete->kernel_stack_alloc;
|
|
||||||
} else {
|
|
||||||
kfree(to_delete->kernel_stack_alloc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||||
if (to_delete->pml4_phys && to_delete->is_user) {
|
if (to_delete->pml4_phys && to_delete->is_user) {
|
||||||
@@ -459,53 +542,73 @@ void process_terminate(process_t *to_delete) {
|
|||||||
to_delete->kernel_stack_alloc = NULL;
|
to_delete->kernel_stack_alloc = NULL;
|
||||||
to_delete->pml4_phys = 0;
|
to_delete->pml4_phys = 0;
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t process_terminate_current(void) {
|
uint64_t process_terminate_current(void) {
|
||||||
uint64_t rflags;
|
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
|
||||||
|
|
||||||
if (!current_process || current_process->pid == 0) {
|
uint32_t my_cpu = smp_this_cpu_id();
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
process_t *cur = current_process[my_cpu];
|
||||||
|
|
||||||
|
if (!cur || cur->pid == 0) {
|
||||||
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_cleanup_inner(current_process);
|
process_cleanup_inner(cur);
|
||||||
|
|
||||||
// 2. Find previous process in circular list
|
// 2. Find previous process in circular list
|
||||||
process_t *prev = current_process;
|
process_t *prev = cur;
|
||||||
while (prev->next != current_process) {
|
while (prev->next != cur) {
|
||||||
prev = prev->next;
|
prev = prev->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Remove current from list
|
// 3. Remove current from list
|
||||||
process_t *to_delete = current_process;
|
process_t *to_delete = cur;
|
||||||
|
|
||||||
if (prev == current_process) {
|
if (prev == cur) {
|
||||||
// Only one process (should be kernel), cannot terminate.
|
// Only one process (should be kernel), cannot terminate.
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
return to_delete->rsp;
|
return to_delete->rsp;
|
||||||
}
|
}
|
||||||
|
|
||||||
prev->next = to_delete->next;
|
prev->next = to_delete->next;
|
||||||
current_process = to_delete->next;
|
|
||||||
|
process_t *next_proc = to_delete->next;
|
||||||
|
while (next_proc != to_delete) {
|
||||||
|
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF) break;
|
||||||
|
next_proc = next_proc->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_proc == to_delete || next_proc->cpu_affinity != my_cpu) {
|
||||||
|
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||||
|
if (processes[i].pid == 0 || (processes[i].cpu_affinity == my_cpu && processes[i].is_user == false)) {
|
||||||
|
next_proc = &processes[i]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_process[my_cpu] = next_proc;
|
||||||
|
|
||||||
// Mark slot as free
|
// Mark slot as free
|
||||||
to_delete->pid = 0xFFFFFFFF;
|
to_delete->pid = 0xFFFFFFFF;
|
||||||
|
to_delete->cpu_affinity = 0xFFFFFFFF;
|
||||||
|
to_delete->ui_window = NULL;
|
||||||
|
to_delete->is_terminal_proc = false;
|
||||||
|
|
||||||
// 4. Load context for the NEXT process
|
// 4. Load context for the NEXT process
|
||||||
if (current_process->is_user && current_process->kernel_stack) {
|
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
|
||||||
tss_set_stack(current_process->kernel_stack);
|
tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack);
|
||||||
extern uint64_t kernel_syscall_stack;
|
if (my_cpu == 0) {
|
||||||
kernel_syscall_stack = current_process->kernel_stack;
|
extern uint64_t kernel_syscall_stack;
|
||||||
|
kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paging_switch_directory(current_process->pml4_phys);
|
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||||
|
|
||||||
// 5. Actually free the memory (after switching state to avoid issues)
|
// 5. Free memory
|
||||||
// We only safely free the user stack. Immediate freeing of the current
|
|
||||||
// kernel stack is unsafe while we are still running on it.
|
|
||||||
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
||||||
|
|
||||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||||
@@ -513,17 +616,24 @@ uint64_t process_terminate_current(void) {
|
|||||||
paging_destroy_user_pml4_phys(to_delete->pml4_phys);
|
paging_destroy_user_pml4_phys(to_delete->pml4_phys);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear pointers to avoid double-free during slot reuse
|
|
||||||
to_delete->user_stack_alloc = NULL;
|
to_delete->user_stack_alloc = NULL;
|
||||||
free_kernel_stack_later = to_delete->kernel_stack_alloc;
|
free_kernel_stack_later = to_delete->kernel_stack_alloc;
|
||||||
to_delete->kernel_stack_alloc = NULL; // Leak the small kernel stack for safety
|
to_delete->kernel_stack_alloc = NULL;
|
||||||
to_delete->pml4_phys = 0;
|
to_delete->pml4_phys = 0;
|
||||||
|
|
||||||
uint64_t next_rsp = current_process->rsp;
|
uint64_t next_rsp = current_process[my_cpu]->rsp;
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||||
return next_rsp;
|
return next_rsp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SMP: IPI handler called on AP cores when BSP broadcasts scheduling IPI
|
||||||
|
uint64_t sched_ipi_handler(registers_t *regs) {
|
||||||
|
lapic_eoi(); // Acknowledge the IPI
|
||||||
|
|
||||||
|
// Run the scheduler for this CPU
|
||||||
|
return process_schedule((uint64_t)regs);
|
||||||
|
}
|
||||||
|
|
||||||
void process_push_gui_event(process_t *proc, gui_event_t *ev) {
|
void process_push_gui_event(process_t *proc, gui_event_t *ev) {
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ typedef struct process {
|
|||||||
uint64_t ticks;
|
uint64_t ticks;
|
||||||
uint64_t sleep_until;
|
uint64_t sleep_until;
|
||||||
size_t used_memory;
|
size_t used_memory;
|
||||||
|
uint32_t cpu_affinity; // Which CPU this process runs on (0 = BSP)
|
||||||
} __attribute__((aligned(16))) process_t;
|
} __attribute__((aligned(16))) process_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -62,14 +63,19 @@ typedef struct {
|
|||||||
} ProcessInfo;
|
} ProcessInfo;
|
||||||
|
|
||||||
void process_init(void);
|
void process_init(void);
|
||||||
void process_create(void* entry_point, bool is_user);
|
process_t* process_create(void (*entry_point)(void), bool is_user);
|
||||||
process_t* process_create_elf(const char* filepath, const char* args_str);
|
process_t* process_create_elf(const char* filepath, const char* args_str);
|
||||||
process_t* process_get_current(void);
|
process_t* process_get_current(void);
|
||||||
|
void process_set_current_for_cpu(uint32_t cpu_id, process_t* p);
|
||||||
|
process_t* process_get_current_for_cpu(uint32_t cpu_id);
|
||||||
uint64_t process_schedule(uint64_t current_rsp);
|
uint64_t process_schedule(uint64_t current_rsp);
|
||||||
uint64_t process_terminate_current(void);
|
uint64_t process_terminate_current(void);
|
||||||
void process_terminate(process_t *proc);
|
void process_terminate(process_t *proc);
|
||||||
process_t* process_get_by_pid(uint32_t pid);
|
process_t* process_get_by_pid(uint32_t pid);
|
||||||
|
|
||||||
|
// SMP: IPI handler for AP scheduling (called from ISR)
|
||||||
|
uint64_t sched_ipi_handler(registers_t *regs);
|
||||||
|
|
||||||
void process_push_gui_event(process_t *proc, gui_event_t *ev);
|
void process_push_gui_event(process_t *proc, gui_event_t *ev);
|
||||||
process_t* process_get_by_ui_window(void* win);
|
process_t* process_get_by_ui_window(void* win);
|
||||||
|
|
||||||
|
|||||||
182
src/sys/smp.c
Normal file
182
src/sys/smp.c
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
// 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.
|
||||||
|
#include "smp.h"
|
||||||
|
#include "limine.h"
|
||||||
|
#include "memory_manager.h"
|
||||||
|
#include "gdt.h"
|
||||||
|
#include "idt.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "paging.h"
|
||||||
|
#include "process.h"
|
||||||
|
#include "work_queue.h"
|
||||||
|
|
||||||
|
extern void serial_write(const char *str);
|
||||||
|
extern void serial_write_num(uint32_t n);
|
||||||
|
extern void serial_write_hex(uint64_t n);
|
||||||
|
|
||||||
|
static cpu_state_t *cpu_states = NULL;
|
||||||
|
static uint32_t total_cpus = 0;
|
||||||
|
static uint32_t bsp_lapic_id = 0;
|
||||||
|
|
||||||
|
static uint32_t read_lapic_id(void) {
|
||||||
|
uint32_t eax, ebx, ecx, edx;
|
||||||
|
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
|
||||||
|
return (ebx >> 24) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t smp_this_cpu_id(void) {
|
||||||
|
if (total_cpus <= 1) return 0;
|
||||||
|
uint32_t lapic = read_lapic_id();
|
||||||
|
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||||
|
if (cpu_states[i].lapic_id == lapic) return i;
|
||||||
|
}
|
||||||
|
return 0; // Fallback to BSP
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t smp_cpu_count(void) {
|
||||||
|
return total_cpus;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu_state_t *smp_get_cpu(uint32_t cpu_id) {
|
||||||
|
if (cpu_id >= total_cpus) return NULL;
|
||||||
|
return &cpu_states[cpu_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ap_entry(struct limine_smp_info *info) {
|
||||||
|
uint32_t my_id = (uint32_t)(info->extra_argument);
|
||||||
|
uint64_t cr0;
|
||||||
|
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||||
|
cr0 &= ~(1ULL << 2);
|
||||||
|
cr0 |= (1ULL << 1);
|
||||||
|
cr0 |= (1ULL << 5);
|
||||||
|
asm volatile("mov %0, %%cr0" : : "r"(cr0));
|
||||||
|
|
||||||
|
uint64_t cr4;
|
||||||
|
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||||
|
cr4 |= (1ULL << 9);
|
||||||
|
cr4 |= (1ULL << 10);
|
||||||
|
asm volatile("mov %0, %%cr4" : : "r"(cr4));
|
||||||
|
asm volatile("fninit");
|
||||||
|
|
||||||
|
extern struct gdt_ptr gdtr;
|
||||||
|
extern void gdt_flush(uint64_t);
|
||||||
|
gdt_flush((uint64_t)&gdtr);
|
||||||
|
|
||||||
|
gdt_load_ap_tss(my_id);
|
||||||
|
|
||||||
|
extern void idt_load(void);
|
||||||
|
idt_load();
|
||||||
|
|
||||||
|
uint64_t kernel_cr3 = paging_get_pml4_phys();
|
||||||
|
asm volatile("mov %0, %%cr3" : : "r"(kernel_cr3));
|
||||||
|
|
||||||
|
extern void lapic_enable(void);
|
||||||
|
lapic_enable();
|
||||||
|
|
||||||
|
cpu_states[my_id].online = true;
|
||||||
|
|
||||||
|
serial_write("[SMP] AP ");
|
||||||
|
serial_write_num(my_id);
|
||||||
|
serial_write(" online (LAPIC ");
|
||||||
|
serial_write_num(cpu_states[my_id].lapic_id);
|
||||||
|
serial_write(")\n");
|
||||||
|
|
||||||
|
process_t *ap_idle = process_create(NULL, false);
|
||||||
|
ap_idle->cpu_affinity = my_id;
|
||||||
|
process_set_current_for_cpu(my_id, ap_idle);
|
||||||
|
asm volatile("sti");
|
||||||
|
|
||||||
|
work_queue_drain_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- SMP Initialization ---
|
||||||
|
uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||||
|
if (!smp_resp || smp_resp->cpu_count <= 1) {
|
||||||
|
total_cpus = 1;
|
||||||
|
cpu_states = (cpu_state_t *)kmalloc(sizeof(cpu_state_t));
|
||||||
|
if (!cpu_states) return 1;
|
||||||
|
extern void mem_memset(void *, int, size_t);
|
||||||
|
mem_memset(cpu_states, 0, sizeof(cpu_state_t));
|
||||||
|
cpu_states[0].cpu_id = 0;
|
||||||
|
cpu_states[0].lapic_id = read_lapic_id();
|
||||||
|
cpu_states[0].online = true;
|
||||||
|
serial_write("[SMP] Single CPU mode\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_cpus = (uint32_t)smp_resp->cpu_count;
|
||||||
|
bsp_lapic_id = smp_resp->bsp_lapic_id;
|
||||||
|
|
||||||
|
serial_write("[SMP] Detected ");
|
||||||
|
serial_write_num(total_cpus);
|
||||||
|
serial_write(" CPUs. BSP LAPIC ID: ");
|
||||||
|
serial_write_num(bsp_lapic_id);
|
||||||
|
serial_write("\n");
|
||||||
|
|
||||||
|
cpu_states = (cpu_state_t *)kmalloc(total_cpus * sizeof(cpu_state_t));
|
||||||
|
if (!cpu_states) {
|
||||||
|
serial_write("[SMP] ERROR: Failed to allocate CPU state array!\n");
|
||||||
|
total_cpus = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
extern void mem_memset(void *, int, size_t);
|
||||||
|
mem_memset(cpu_states, 0, total_cpus * sizeof(cpu_state_t));
|
||||||
|
|
||||||
|
gdt_init_ap_tss(total_cpus);
|
||||||
|
|
||||||
|
uint32_t bsp_index = 0;
|
||||||
|
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||||
|
struct limine_smp_info *cpu = smp_resp->cpus[i];
|
||||||
|
cpu_states[i].cpu_id = i;
|
||||||
|
cpu_states[i].lapic_id = cpu->lapic_id;
|
||||||
|
|
||||||
|
if (cpu->lapic_id == bsp_lapic_id) {
|
||||||
|
cpu_states[i].online = true;
|
||||||
|
bsp_index = i;
|
||||||
|
serial_write("[SMP] BSP CPU ");
|
||||||
|
serial_write_num(i);
|
||||||
|
serial_write(" (LAPIC ");
|
||||||
|
serial_write_num(cpu->lapic_id);
|
||||||
|
serial_write(") online\n");
|
||||||
|
} else {
|
||||||
|
void *ap_stack = kmalloc_aligned(65536, 65536);
|
||||||
|
if (!ap_stack) {
|
||||||
|
serial_write("[SMP] ERROR: Failed to allocate AP stack!\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cpu_states[i].kernel_stack = (uint64_t)ap_stack + 65536;
|
||||||
|
cpu_states[i].kernel_stack_alloc = ap_stack;
|
||||||
|
cpu_states[i].online = false;
|
||||||
|
|
||||||
|
cpu->extra_argument = i;
|
||||||
|
|
||||||
|
serial_write("[SMP] Starting AP ");
|
||||||
|
serial_write_num(i);
|
||||||
|
serial_write(" (LAPIC ");
|
||||||
|
serial_write_num(cpu->lapic_id);
|
||||||
|
serial_write(")...\n");
|
||||||
|
|
||||||
|
__atomic_store_n(&cpu->goto_address, ap_entry, __ATOMIC_SEQ_CST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile uint32_t timeout = 10000000;
|
||||||
|
uint32_t online_count = 0;
|
||||||
|
while (timeout-- > 0) {
|
||||||
|
online_count = 0;
|
||||||
|
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||||
|
if (cpu_states[i].online) online_count++;
|
||||||
|
}
|
||||||
|
if (online_count == total_cpus) break;
|
||||||
|
asm volatile("pause");
|
||||||
|
}
|
||||||
|
|
||||||
|
serial_write("[SMP] All ");
|
||||||
|
serial_write_num(online_count);
|
||||||
|
serial_write(" of ");
|
||||||
|
serial_write_num(total_cpus);
|
||||||
|
serial_write(" CPUs online\n");
|
||||||
|
|
||||||
|
return online_count;
|
||||||
|
}
|
||||||
36
src/sys/smp.h
Normal file
36
src/sys/smp.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// 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.
|
||||||
|
#ifndef SMP_H
|
||||||
|
#define SMP_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "spinlock.h"
|
||||||
|
|
||||||
|
// Per-CPU state. Dynamically allocated at boot based on actual CPU count.
|
||||||
|
typedef struct cpu_state {
|
||||||
|
uint32_t cpu_id; // Logical CPU index (0 = BSP)
|
||||||
|
uint32_t lapic_id; // Local APIC ID from Limine
|
||||||
|
uint64_t kernel_stack; // Top of kernel stack for this CPU
|
||||||
|
void *kernel_stack_alloc; // Base allocation for kfree
|
||||||
|
volatile bool online; // True once AP is fully initialized
|
||||||
|
} cpu_state_t;
|
||||||
|
|
||||||
|
// Initialize SMP — call after GDT/IDT/memory init but before wm_init.
|
||||||
|
// Pass the Limine SMP response. APs will be started and will enter their
|
||||||
|
// idle loops. Returns the number of CPUs brought online.
|
||||||
|
struct limine_smp_response;
|
||||||
|
uint32_t smp_init(struct limine_smp_response *smp_resp);
|
||||||
|
|
||||||
|
// Get the current CPU index (0 = BSP). Uses CPUID to read LAPIC ID,
|
||||||
|
// then looks up in the cpu table.
|
||||||
|
uint32_t smp_this_cpu_id(void);
|
||||||
|
|
||||||
|
// Total number of CPUs online.
|
||||||
|
uint32_t smp_cpu_count(void);
|
||||||
|
|
||||||
|
// Get per-CPU state by index.
|
||||||
|
cpu_state_t *smp_get_cpu(uint32_t cpu_id);
|
||||||
|
|
||||||
|
#endif
|
||||||
62
src/sys/spinlock.h
Normal file
62
src/sys/spinlock.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// 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.
|
||||||
|
#ifndef SPINLOCK_H
|
||||||
|
#define SPINLOCK_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Simple test-and-set spinlock for x86_64 SMP.
|
||||||
|
// Uses 'lock xchg' for acquire and a plain store for release.
|
||||||
|
// Includes 'pause' to reduce bus contention while spinning.
|
||||||
|
|
||||||
|
typedef volatile uint32_t spinlock_t;
|
||||||
|
|
||||||
|
#define SPINLOCK_INIT 0
|
||||||
|
|
||||||
|
static inline void spinlock_acquire(spinlock_t *lock) {
|
||||||
|
while (1) {
|
||||||
|
// Try to set the lock from 0 -> 1
|
||||||
|
uint32_t prev;
|
||||||
|
asm volatile("lock xchgl %0, %1"
|
||||||
|
: "=r"(prev), "+m"(*lock)
|
||||||
|
: "0"((uint32_t)1)
|
||||||
|
: "memory");
|
||||||
|
if (prev == 0) return; // We got the lock
|
||||||
|
// Spin with pause (reduces power and bus traffic)
|
||||||
|
while (*lock) {
|
||||||
|
asm volatile("pause" ::: "memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void spinlock_release(spinlock_t *lock) {
|
||||||
|
asm volatile("" ::: "memory"); // compiler barrier
|
||||||
|
*lock = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to acquire without blocking. Returns 1 if acquired, 0 if not.
|
||||||
|
static inline int spinlock_try(spinlock_t *lock) {
|
||||||
|
uint32_t prev;
|
||||||
|
asm volatile("lock xchgl %0, %1"
|
||||||
|
: "=r"(prev), "+m"(*lock)
|
||||||
|
: "0"((uint32_t)1)
|
||||||
|
: "memory");
|
||||||
|
return (prev == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRQ-safe spinlock: saves flags, disables interrupts, then acquires.
|
||||||
|
// Use when the lock may be contended from interrupt context.
|
||||||
|
static inline uint64_t spinlock_acquire_irqsave(spinlock_t *lock) {
|
||||||
|
uint64_t flags;
|
||||||
|
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
|
||||||
|
spinlock_acquire(lock);
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void spinlock_release_irqrestore(spinlock_t *lock, uint64_t flags) {
|
||||||
|
spinlock_release(lock);
|
||||||
|
asm volatile("push %0; popfq" : : "r"(flags) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "wm.h"
|
#include "wm.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
#include "paging.h"
|
#include "paging.h"
|
||||||
|
#include "work_queue.h"
|
||||||
|
#include "smp.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "pci.h"
|
#include "pci.h"
|
||||||
@@ -19,6 +21,8 @@
|
|||||||
#include "font_manager.h"
|
#include "font_manager.h"
|
||||||
#include "graphics.h"
|
#include "graphics.h"
|
||||||
|
|
||||||
|
extern bool ps2_ctrl_pressed;
|
||||||
|
|
||||||
// Read MSR
|
// Read MSR
|
||||||
static inline uint64_t rdmsr(uint32_t msr) {
|
static inline uint64_t rdmsr(uint32_t msr) {
|
||||||
uint32_t low, high;
|
uint32_t low, high;
|
||||||
@@ -33,71 +37,91 @@ static inline void wrmsr(uint32_t msr, uint64_t value) {
|
|||||||
asm volatile("wrmsr" : : "c"(msr), "a"(low), "d"(high));
|
asm volatile("wrmsr" : : "c"(msr), "a"(low), "d"(high));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implemented in assembly
|
extern void isr128_wrapper(void);
|
||||||
extern void syscall_entry(void);
|
extern void* kmalloc(size_t size);
|
||||||
|
extern void kfree(void* ptr);
|
||||||
|
|
||||||
extern uint64_t kernel_syscall_stack;
|
typedef struct {
|
||||||
|
void (*fn)(void *);
|
||||||
|
void *arg;
|
||||||
|
uint64_t pml4_phys;
|
||||||
|
volatile int *completion_counter;
|
||||||
|
} smp_user_task_t;
|
||||||
|
|
||||||
|
static void smp_user_wrapper(void *arg) {
|
||||||
|
smp_user_task_t *task = (smp_user_task_t *)arg;
|
||||||
|
if (!task) return;
|
||||||
|
|
||||||
|
uint64_t old_cr3;
|
||||||
|
asm volatile("mov %%cr3, %0" : "=r"(old_cr3));
|
||||||
|
|
||||||
|
// Switch to user address space if necessary
|
||||||
|
bool switch_cr3 = (task->pml4_phys != 0 && task->pml4_phys != old_cr3);
|
||||||
|
if (switch_cr3) {
|
||||||
|
asm volatile("mov %0, %%cr3" :: "r"(task->pml4_phys) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task->fn) {
|
||||||
|
task->fn(task->arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (switch_cr3) {
|
||||||
|
asm volatile("mov %0, %%cr3" :: "r"(old_cr3) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task->completion_counter) {
|
||||||
|
__sync_fetch_and_add(task->completion_counter, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void syscall_init(void) {
|
void syscall_init(void) {
|
||||||
void* stack = kmalloc(16384);
|
|
||||||
kernel_syscall_stack = (uint64_t)stack + 16384;
|
|
||||||
uint64_t efer = rdmsr(MSR_EFER);
|
|
||||||
efer |= 1; // SCE bit is bit 0
|
|
||||||
wrmsr(MSR_EFER, efer);
|
|
||||||
|
|
||||||
|
|
||||||
uint64_t star = ((uint64_t)0x08 << 32) | ((uint64_t)0x13 << 48);
|
|
||||||
wrmsr(MSR_STAR, star);
|
|
||||||
|
|
||||||
wrmsr(MSR_LSTAR, (uint64_t)syscall_entry);
|
|
||||||
|
|
||||||
wrmsr(MSR_FMASK, 0x200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_close(Window *win) {
|
static void user_window_close(Window *win) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_CLOSE };
|
gui_event_t ev = { .type = GUI_EVENT_CLOSE };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_paint(Window *win) {
|
static void user_window_paint(Window *win) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_PAINT };
|
gui_event_t ev = { .type = GUI_EVENT_PAINT };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_click(Window *win, int x, int y) {
|
static void user_window_click(Window *win, int x, int y) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_CLICK, .arg1 = x, .arg2 = y };
|
gui_event_t ev = { .type = GUI_EVENT_CLICK, .arg1 = x, .arg2 = y };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_right_click(Window *win, int x, int y) {
|
static void user_window_right_click(Window *win, int x, int y) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_RIGHT_CLICK, .arg1 = x, .arg2 = y };
|
gui_event_t ev = { .type = GUI_EVENT_RIGHT_CLICK, .arg1 = x, .arg2 = y };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_mouse_down(Window *win, int x, int y) {
|
static void user_window_mouse_down(Window *win, int x, int y) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_DOWN, .arg1 = x, .arg2 = y };
|
gui_event_t ev = { .type = GUI_EVENT_MOUSE_DOWN, .arg1 = x, .arg2 = y };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_mouse_up(Window *win, int x, int y) {
|
static void user_window_mouse_up(Window *win, int x, int y) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_UP, .arg1 = x, .arg2 = y };
|
gui_event_t ev = { .type = GUI_EVENT_MOUSE_UP, .arg1 = x, .arg2 = y };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
static void user_window_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_MOVE, .arg1 = x, .arg2 = y, .arg3 = buttons };
|
gui_event_t ev = { .type = GUI_EVENT_MOUSE_MOVE, .arg1 = x, .arg2 = y, .arg3 = buttons };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
@@ -105,24 +129,24 @@ static void user_window_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
|||||||
|
|
||||||
// Helper function for WM to send mouse events
|
// Helper function for WM to send mouse events
|
||||||
void syscall_send_mouse_move_event(Window *win, int x, int y, uint8_t buttons) {
|
void syscall_send_mouse_move_event(Window *win, int x, int y, uint8_t buttons) {
|
||||||
if (!win || !win->data) return;
|
if (!win) return;
|
||||||
user_window_mouse_move(win, x, y, buttons);
|
user_window_mouse_move(win, x, y, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syscall_send_mouse_down_event(Window *win, int x, int y) {
|
void syscall_send_mouse_down_event(Window *win, int x, int y) {
|
||||||
if (!win || !win->data) return;
|
if (!win) return;
|
||||||
user_window_mouse_down(win, x, y);
|
user_window_mouse_down(win, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syscall_send_mouse_up_event(Window *win, int x, int y) {
|
void syscall_send_mouse_up_event(Window *win, int x, int y) {
|
||||||
if (!win || !win->data) return;
|
if (!win) return;
|
||||||
user_window_mouse_up(win, x, y);
|
user_window_mouse_up(win, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void user_window_key(Window *win, char c, bool pressed) {
|
static void user_window_key(Window *win, char c, bool pressed) {
|
||||||
process_t *proc = (process_t *)win->data;
|
process_t *proc = process_get_by_ui_window(win);
|
||||||
if (!proc) return;
|
if (!proc) return;
|
||||||
gui_event_t ev = { .type = pressed ? GUI_EVENT_KEY : GUI_EVENT_KEYUP, .arg1 = (int)c };
|
gui_event_t ev = { .type = pressed ? GUI_EVENT_KEY : GUI_EVENT_KEYUP, .arg1 = (int)c, .arg3 = (int)ps2_ctrl_pressed };
|
||||||
process_push_gui_event(proc, &ev);
|
process_push_gui_event(proc, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,15 +158,18 @@ static void user_window_resize(Window *win, int w, int h) {
|
|||||||
extern void kfree(void* ptr);
|
extern void kfree(void* ptr);
|
||||||
extern void serial_write(const char *str);
|
extern void serial_write(const char *str);
|
||||||
|
|
||||||
|
|
||||||
if (win->pixels) kfree(win->pixels);
|
if (win->pixels) kfree(win->pixels);
|
||||||
if (win->comp_pixels) kfree(win->comp_pixels);
|
if (win->comp_pixels) kfree(win->comp_pixels);
|
||||||
|
|
||||||
win->pixels = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
win->pixels = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
||||||
win->comp_pixels = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
win->comp_pixels = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
||||||
|
|
||||||
|
win->w = w;
|
||||||
|
win->h = h;
|
||||||
|
|
||||||
if (win->pixels) {
|
if (win->pixels) {
|
||||||
for (int i = 0; i < w * h; i++) win->pixels[i] = 0;
|
extern void mem_memset(void *dest, int val, size_t len);
|
||||||
|
mem_memset(win->pixels, 0, w * h * sizeof(uint32_t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +257,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
win->cursor_pos = 0;
|
win->cursor_pos = 0;
|
||||||
win->data = proc;
|
win->data = proc;
|
||||||
win->font = NULL;
|
win->font = NULL;
|
||||||
|
win->lock = SPINLOCK_INIT;
|
||||||
|
|
||||||
serial_write("Kernel: Dims initialized.\n");
|
serial_write("Kernel: Dims initialized.\n");
|
||||||
|
|
||||||
@@ -284,7 +312,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags;
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
if (win->pixels) {
|
if (win->pixels) {
|
||||||
// Strict user-to-window relative clamping
|
// Strict user-to-window relative clamping
|
||||||
@@ -304,7 +334,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
draw_rect(win->x + params[0], win->y + params[1], params[2], params[3], color);
|
draw_rect(win->x + params[0], win->y + params[1], params[2], params[3], color);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
}
|
}
|
||||||
} else if (cmd == GUI_CMD_DRAW_ROUNDED_RECT_FILLED) {
|
} else if (cmd == GUI_CMD_DRAW_ROUNDED_RECT_FILLED) {
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
@@ -318,7 +349,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags;
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
if (win->pixels) {
|
if (win->pixels) {
|
||||||
int rx = (int)params[0]; int ry = (int)params[1];
|
int rx = (int)params[0]; int ry = (int)params[1];
|
||||||
@@ -336,7 +369,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
}
|
}
|
||||||
} else if (cmd == GUI_CMD_DRAW_STRING) {
|
} else if (cmd == GUI_CMD_DRAW_STRING) {
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
@@ -359,7 +393,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
kernel_str[i] = 0;
|
kernel_str[i] = 0;
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags;
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
||||||
|
|
||||||
@@ -371,10 +407,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
int cur_x = ux;
|
int cur_x = ux;
|
||||||
const char *s = kernel_str;
|
const char *s = kernel_str;
|
||||||
while (*s) {
|
while (*s) {
|
||||||
font_manager_render_char_scaled(font, cur_x, baseline, *s, color, font->pixel_height, put_pixel);
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
char buf[2] = {*s, 0};
|
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, font->pixel_height, put_pixel);
|
||||||
cur_x += font_manager_get_string_width_scaled(font, buf, font->pixel_height);
|
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, font->pixel_height);
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
draw_string(ux, uy, kernel_str, color);
|
draw_string(ux, uy, kernel_str, color);
|
||||||
@@ -387,17 +422,17 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
int cur_x = win->x + ux;
|
int cur_x = win->x + ux;
|
||||||
const char *s = kernel_str;
|
const char *s = kernel_str;
|
||||||
while (*s) {
|
while (*s) {
|
||||||
font_manager_render_char_scaled(font, cur_x, baseline, *s, color, font->pixel_height, put_pixel);
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
char buf[2] = {*s, 0};
|
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, font->pixel_height, put_pixel);
|
||||||
cur_x += font_manager_get_string_width_scaled(font, buf, font->pixel_height);
|
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, font->pixel_height);
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
draw_string(win->x + ux, win->y + uy, kernel_str, color);
|
draw_string(win->x + ux, win->y + uy, kernel_str, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
}
|
}
|
||||||
} else if (cmd == 10) { // GUI_CMD_DRAW_STRING_BITMAP
|
} else if (cmd == 10) { // GUI_CMD_DRAW_STRING_BITMAP
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
@@ -420,7 +455,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
kernel_str[i] = 0;
|
kernel_str[i] = 0;
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags;
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
if (win->pixels) {
|
if (win->pixels) {
|
||||||
if (ux >= -100 && ux < win->w && uy >= -100 && uy < (win->h - 20)) {
|
if (ux >= -100 && ux < win->w && uy >= -100 && uy < (win->h - 20)) {
|
||||||
@@ -432,7 +469,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
draw_string_bitmap(win->x + ux, win->y + uy, kernel_str, color);
|
draw_string_bitmap(win->x + ux, win->y + uy, kernel_str, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
}
|
}
|
||||||
} else if (cmd == 11) { // GUI_CMD_DRAW_STRING_SCALED
|
} else if (cmd == 11) { // GUI_CMD_DRAW_STRING_SCALED
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
@@ -459,7 +497,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
kernel_str[i] = 0;
|
kernel_str[i] = 0;
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags;
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
||||||
|
|
||||||
@@ -471,10 +511,9 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
int cur_x = ux;
|
int cur_x = ux;
|
||||||
const char *s = kernel_str;
|
const char *s = kernel_str;
|
||||||
while (*s) {
|
while (*s) {
|
||||||
font_manager_render_char_scaled(font, cur_x, baseline, *s, color, scale, put_pixel);
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
char buf[2] = {*s, 0};
|
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||||
cur_x += font_manager_get_string_width_scaled(font, buf, scale);
|
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
draw_string_scaled(ux, uy, kernel_str, color, scale);
|
draw_string_scaled(ux, uy, kernel_str, color, scale);
|
||||||
@@ -487,17 +526,95 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
int cur_x = win->x + ux;
|
int cur_x = win->x + ux;
|
||||||
const char *s = kernel_str;
|
const char *s = kernel_str;
|
||||||
while (*s) {
|
while (*s) {
|
||||||
font_manager_render_char_scaled(font, cur_x, baseline, *s, color, scale, put_pixel);
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
char buf[2] = {*s, 0};
|
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||||
cur_x += font_manager_get_string_width_scaled(font, buf, scale);
|
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
draw_string_scaled(win->x + ux, win->y + uy, kernel_str, color, scale);
|
draw_string_scaled(win->x + ux, win->y + uy, kernel_str, color, scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
|
}
|
||||||
|
} else if (cmd == 18) { // GUI_CMD_DRAW_STRING_SCALED_SLOPED
|
||||||
|
Window *win = (Window *)arg2;
|
||||||
|
uint64_t coords = arg3;
|
||||||
|
int ux = coords & 0xFFFFFFFF;
|
||||||
|
int uy = coords >> 32;
|
||||||
|
const char *user_str = (const char *)arg4;
|
||||||
|
|
||||||
|
// Unpack color, scale, slope from arg5
|
||||||
|
uint64_t packed1 = arg5;
|
||||||
|
uint32_t color = packed1 & 0xFFFFFFFF;
|
||||||
|
uint32_t scale_bits = packed1 >> 32;
|
||||||
|
float scale = *(float*)&scale_bits;
|
||||||
|
|
||||||
|
// Slope is passed via arg6 in the system call, but syscall5 only takes 5 args.
|
||||||
|
// Oh right, we only have syscall5. Let's make a packed struct or just use a generic pointer for coords.
|
||||||
|
// Even better, let's just make it a pointer to a struct.
|
||||||
|
// Wait, I will just use `regs->r9` (arg6) directly since the syscall handler has access to all registers:
|
||||||
|
uint64_t arg6 = regs->r9;
|
||||||
|
uint32_t slope_bits = arg6 & 0xFFFFFFFF;
|
||||||
|
float slope = *(float*)&slope_bits;
|
||||||
|
|
||||||
|
if (win && user_str) {
|
||||||
|
extern void draw_string_scaled_sloped(int x, int y, const char *str, uint32_t color, float scale, float slope);
|
||||||
|
extern void graphics_set_render_target(uint32_t *buffer, int w, int h);
|
||||||
|
|
||||||
|
// Copy string safely to kernel stack buffer
|
||||||
|
char kernel_str[256];
|
||||||
|
int i = 0;
|
||||||
|
while (i < 255 && user_str[i]) {
|
||||||
|
kernel_str[i] = user_str[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
kernel_str[i] = 0;
|
||||||
|
|
||||||
|
uint64_t rflags;
|
||||||
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
|
ttf_font_t *font = win->font ? (ttf_font_t*)win->font : graphics_get_current_ttf();
|
||||||
|
|
||||||
|
if (win->pixels) {
|
||||||
|
if (ux >= -100 && ux < win->w && uy >= -100 && uy < (win->h - 20)) {
|
||||||
|
graphics_set_render_target(win->pixels, win->w, win->h - 20);
|
||||||
|
if (font) {
|
||||||
|
int baseline = uy + font_manager_get_font_ascent_scaled(font, scale) - 2;
|
||||||
|
int cur_x = ux;
|
||||||
|
const char *s = kernel_str;
|
||||||
|
while (*s) {
|
||||||
|
extern void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t));
|
||||||
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
|
font_manager_render_char_sloped(font, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||||
|
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
draw_string_scaled_sloped(ux, uy, kernel_str, color, scale, slope);
|
||||||
|
}
|
||||||
|
graphics_set_render_target(NULL, 0, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (font) {
|
||||||
|
int baseline = win->y + uy + font_manager_get_font_ascent_scaled(font, scale) - 2;
|
||||||
|
int cur_x = win->x + ux;
|
||||||
|
const char *s = kernel_str;
|
||||||
|
while (*s) {
|
||||||
|
extern void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t));
|
||||||
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
|
font_manager_render_char_sloped(font, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||||
|
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
draw_string_scaled_sloped(win->x + ux, win->y + uy, kernel_str, color, scale, slope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
}
|
}
|
||||||
} else if (cmd == GUI_CMD_DRAW_IMAGE) {
|
} else if (cmd == GUI_CMD_DRAW_IMAGE) {
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
@@ -508,31 +625,32 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
for (int i = 0; i < 4; i++) params[i] = u_params[i];
|
for (int i = 0; i < 4; i++) params[i] = u_params[i];
|
||||||
|
|
||||||
uint64_t rflags;
|
uint64_t rflags;
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
bool use_wm_lock = (win->pixels == NULL);
|
||||||
|
if (use_wm_lock) rflags = wm_lock_acquire();
|
||||||
|
else rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
|
|
||||||
if (win->pixels) {
|
if (win->pixels) {
|
||||||
int rx = (int)params[0]; int ry = (int)params[1];
|
int rx = (int)params[0]; int ry = (int)params[1];
|
||||||
int rw = (int)params[2]; int rh = (int)params[3];
|
int rw = (int)params[2]; int rh = (int)params[3];
|
||||||
|
int src_w = rw;
|
||||||
int src_x_offset = 0;
|
int src_x_offset = 0;
|
||||||
int src_y_offset = 0;
|
int src_y_offset = 0;
|
||||||
|
|
||||||
if (rx < 0) { src_x_offset = -rx; rw += rx; rx = 0; }
|
if (rx < 0) { src_x_offset = -rx; rw += rx; rx = 0; }
|
||||||
if (ry < 0) { src_y_offset = -ry; rh += ry; ry = 0; }
|
if (ry < 0) { src_y_offset = -ry; rh += ry; ry = 0; }
|
||||||
if (rx + rw > win->w) rw = win->w - rx;
|
if (rx + rw > win->w) rw = win->w - rx;
|
||||||
if (ry + rh > (win->h - 20)) rh = (win->h - 20) - ry;
|
if (ry + rh > (win->h - 20)) rh = (win->h - 20) - ry;
|
||||||
|
|
||||||
if (rw > 0 && rh > 0) {
|
if (rw > 0 && rh > 0) {
|
||||||
for (int y = 0; y < rh; y++) {
|
for (int y = 0; y < rh; y++) {
|
||||||
uint32_t *dest = &win->pixels[(ry + y) * win->w + rx];
|
uint32_t *dest = &win->pixels[(ry + y) * win->w + rx];
|
||||||
uint32_t *src = &image_data[(src_y_offset + y) * (int)params[2] + src_x_offset];
|
uint32_t *src = &image_data[(src_y_offset + y) * src_w + src_x_offset];
|
||||||
for (int x = 0; x < rw; x++) {
|
for (int x = 0; x < rw; x++) {
|
||||||
uint32_t s = src[x];
|
uint32_t s = src[x];
|
||||||
uint8_t alpha = (s >> 24) & 0xFF;
|
uint8_t alpha = (s >> 24) & 0xFF;
|
||||||
if (alpha == 0xFF) {
|
if (alpha == 0xFF) {
|
||||||
dest[x] = s;
|
dest[x] = s;
|
||||||
} else if (alpha == 0) {
|
} else if (alpha > 0) {
|
||||||
// Skip
|
|
||||||
} else {
|
|
||||||
uint32_t d = dest[x];
|
uint32_t d = dest[x];
|
||||||
uint32_t rb = ((s & 0xFF00FF) * alpha + (d & 0xFF00FF) * (255 - alpha)) >> 8;
|
uint32_t rb = ((s & 0xFF00FF) * alpha + (d & 0xFF00FF) * (255 - alpha)) >> 8;
|
||||||
uint32_t g = ((s & 0x00FF00) * alpha + (d & 0x00FF00) * (255 - alpha)) >> 8;
|
uint32_t g = ((s & 0x00FF00) * alpha + (d & 0x00FF00) * (255 - alpha)) >> 8;
|
||||||
@@ -543,9 +661,11 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
if (use_wm_lock) wm_lock_release(rflags);
|
||||||
|
else spinlock_release_irqrestore(&win->lock, rflags);
|
||||||
}
|
}
|
||||||
} else if (cmd == GUI_CMD_MARK_DIRTY) {
|
} else if (cmd == GUI_CMD_MARK_DIRTY) {
|
||||||
|
uint64_t rflags = wm_lock_acquire();
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
uint64_t *u_params = (uint64_t *)arg3;
|
uint64_t *u_params = (uint64_t *)arg3;
|
||||||
if (win && u_params) {
|
if (win && u_params) {
|
||||||
@@ -554,11 +674,14 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
|
|
||||||
// Dual-buffer commit: copy pixels to comp_pixels
|
// Dual-buffer commit: copy pixels to comp_pixels
|
||||||
if (win->pixels && win->comp_pixels) {
|
if (win->pixels && win->comp_pixels) {
|
||||||
|
uint64_t win_rflags = spinlock_acquire_irqsave(&win->lock);
|
||||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||||
mem_memcpy(win->comp_pixels, win->pixels, (size_t)win->w * (win->h - 20) * 4);
|
mem_memcpy(win->comp_pixels, win->pixels, (size_t)win->w * (win->h - 20) * 4);
|
||||||
|
spinlock_release_irqrestore(&win->lock, win_rflags);
|
||||||
}
|
}
|
||||||
wm_mark_dirty(win->x + (int)params[0], win->y + (int)params[1], (int)params[2], (int)params[3]);
|
wm_mark_dirty(win->x + (int)params[0], win->y + (int)params[1], (int)params[2], (int)params[3]);
|
||||||
}
|
}
|
||||||
|
wm_lock_release(rflags);
|
||||||
} else if (cmd == GUI_CMD_GET_EVENT) {
|
} else if (cmd == GUI_CMD_GET_EVENT) {
|
||||||
Window *win = (Window *)arg2;
|
Window *win = (Window *)arg2;
|
||||||
gui_event_t *ev_out = (gui_event_t *)arg3;
|
gui_event_t *ev_out = (gui_event_t *)arg3;
|
||||||
@@ -929,16 +1052,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
} else if (cmd == 14) { // SYSTEM_CMD_BEEP
|
} else if (cmd == 14) { // SYSTEM_CMD_BEEP
|
||||||
int freq = (int)arg2;
|
int freq = (int)arg2;
|
||||||
int ms = (int)arg3;
|
int ms = (int)arg3;
|
||||||
if (freq > 0) {
|
extern void k_beep(int freq, int ms);
|
||||||
int div = 1193180 / freq;
|
k_beep(freq, ms);
|
||||||
outb(0x43, 0xB6);
|
|
||||||
outb(0x42, div & 0xFF);
|
|
||||||
outb(0x42, (div >> 8) & 0xFF);
|
|
||||||
outb(0x61, inb(0x61) | 0x03);
|
|
||||||
}
|
|
||||||
// Sleep - kernel side
|
|
||||||
k_sleep(ms);
|
|
||||||
outb(0x61, inb(0x61) & 0xFC);
|
|
||||||
return 0;
|
return 0;
|
||||||
} else if (cmd == 15) { // SYSTEM_CMD_MEMINFO
|
} else if (cmd == 15) { // SYSTEM_CMD_MEMINFO
|
||||||
uint64_t *out = (uint64_t *)arg2;
|
uint64_t *out = (uint64_t *)arg2;
|
||||||
@@ -1121,7 +1236,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
size_t total_used = stats.used_memory;
|
size_t total_used = stats.used_memory;
|
||||||
size_t user_used = 0;
|
size_t user_used = 0;
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0) {
|
if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0 && processes[i].is_user) {
|
||||||
user_used += processes[i].used_memory;
|
user_used += processes[i].used_memory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1129,13 +1244,19 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
else processes[0].used_memory = 0;
|
else processes[0].used_memory = 0;
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < 16; i++) { // MAX_PROCESSES is 16
|
for (int i = 0; i < 16; i++) {
|
||||||
if (processes[i].pid != 0xFFFFFFFF) {
|
if (processes[i].pid != 0xFFFFFFFF && (processes[i].is_user || processes[i].pid == 0)) {
|
||||||
out[count].pid = processes[i].pid;
|
out[count].pid = processes[i].pid;
|
||||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||||
mem_memcpy(out[count].name, processes[i].name, 64);
|
mem_memcpy(out[count].name, processes[i].name, 64);
|
||||||
out[count].ticks = processes[i].ticks;
|
|
||||||
|
|
||||||
|
if (processes[i].pid == 0) {
|
||||||
|
out[count].name[0] = 'k'; out[count].name[1] = 'e'; out[count].name[2] = 'r';
|
||||||
|
out[count].name[3] = 'n'; out[count].name[4] = 'e'; out[count].name[5] = 'l';
|
||||||
|
out[count].name[6] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
out[count].ticks = processes[i].ticks;
|
||||||
out[count].used_memory = processes[i].used_memory;
|
out[count].used_memory = processes[i].used_memory;
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
@@ -1189,6 +1310,42 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
} else if (cmd == 49) { // SYSTEM_CMD_GET_OS_INFO
|
||||||
|
os_info_t *info = (os_info_t *)arg2;
|
||||||
|
if (!info) return -1;
|
||||||
|
extern void get_os_info(os_info_t *info);
|
||||||
|
get_os_info(info);
|
||||||
|
return 0;
|
||||||
|
} else if (cmd == SYSTEM_CMD_PARALLEL_RUN) {
|
||||||
|
void (*user_fn)(void*) = (void (*)(void*))arg2;
|
||||||
|
void **args = (void **)arg3;
|
||||||
|
int count = (int)arg4;
|
||||||
|
|
||||||
|
if (count <= 0) return 0;
|
||||||
|
if (count > 64) count = 64;
|
||||||
|
|
||||||
|
volatile int completion_counter = count;
|
||||||
|
uint64_t current_pml4 = proc->pml4_phys;
|
||||||
|
|
||||||
|
smp_user_task_t tasks[64];
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
tasks[i].fn = user_fn;
|
||||||
|
tasks[i].arg = args[i];
|
||||||
|
tasks[i].pml4_phys = current_pml4;
|
||||||
|
tasks[i].completion_counter = &completion_counter;
|
||||||
|
|
||||||
|
extern void work_queue_submit(void (*fn)(void*), void *arg);
|
||||||
|
work_queue_submit(smp_user_wrapper, &tasks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern bool work_queue_drain_one(void);
|
||||||
|
while (completion_counter > 0) {
|
||||||
|
if (!work_queue_drain_one()) {
|
||||||
|
asm volatile("pause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,17 @@
|
|||||||
typedef struct Window Window;
|
typedef struct Window Window;
|
||||||
typedef struct registers_t registers_t;
|
typedef struct registers_t registers_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char os_name[64];
|
||||||
|
char os_version[64];
|
||||||
|
char os_codename[64];
|
||||||
|
char kernel_name[64];
|
||||||
|
char kernel_version[64];
|
||||||
|
char build_date[64];
|
||||||
|
char build_time[64];
|
||||||
|
char build_arch[64];
|
||||||
|
} os_info_t;
|
||||||
|
|
||||||
// MSRs used for syscalls in x86_64
|
// MSRs used for syscalls in x86_64
|
||||||
#define MSR_EFER 0xC0000080
|
#define MSR_EFER 0xC0000080
|
||||||
#define MSR_STAR 0xC0000081
|
#define MSR_STAR 0xC0000081
|
||||||
@@ -46,6 +57,8 @@ typedef struct registers_t registers_t;
|
|||||||
#define SYSTEM_CMD_GET_CPU_MODEL 45
|
#define SYSTEM_CMD_GET_CPU_MODEL 45
|
||||||
#define SYSTEM_CMD_SLEEP 46
|
#define SYSTEM_CMD_SLEEP 46
|
||||||
#define SYSTEM_CMD_SET_RESOLUTION 47
|
#define SYSTEM_CMD_SET_RESOLUTION 47
|
||||||
|
#define SYSTEM_CMD_GET_OS_INFO 49
|
||||||
|
#define SYSTEM_CMD_PARALLEL_RUN 50
|
||||||
|
|
||||||
void syscall_init(void);
|
void syscall_init(void);
|
||||||
uint64_t syscall_handler_c(registers_t *regs);
|
uint64_t syscall_handler_c(registers_t *regs);
|
||||||
|
|||||||
62
src/sys/work_queue.c
Normal file
62
src/sys/work_queue.c
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// 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.
|
||||||
|
#include "work_queue.h"
|
||||||
|
#include "spinlock.h"
|
||||||
|
|
||||||
|
extern void serial_write(const char *str);
|
||||||
|
|
||||||
|
#define WORK_QUEUE_SIZE 64
|
||||||
|
|
||||||
|
static work_item_t work_queue[WORK_QUEUE_SIZE];
|
||||||
|
static volatile int wq_head = 0;
|
||||||
|
static volatile int wq_tail = 0;
|
||||||
|
static spinlock_t wq_lock = SPINLOCK_INIT;
|
||||||
|
|
||||||
|
void work_queue_submit(work_fn_t fn, void *arg) {
|
||||||
|
if (!fn) return;
|
||||||
|
|
||||||
|
uint64_t flags = spinlock_acquire_irqsave(&wq_lock);
|
||||||
|
int next_tail = (wq_tail + 1) % WORK_QUEUE_SIZE;
|
||||||
|
if (next_tail == wq_head) {
|
||||||
|
// Queue full — drop the work item
|
||||||
|
spinlock_release_irqrestore(&wq_lock, flags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
work_queue[wq_tail].fn = fn;
|
||||||
|
work_queue[wq_tail].arg = arg;
|
||||||
|
wq_tail = next_tail;
|
||||||
|
spinlock_release_irqrestore(&wq_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool work_queue_drain_one(void) {
|
||||||
|
uint64_t flags = spinlock_acquire_irqsave(&wq_lock);
|
||||||
|
if (wq_head == wq_tail) {
|
||||||
|
spinlock_release_irqrestore(&wq_lock, flags);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
work_item_t item = work_queue[wq_head];
|
||||||
|
wq_head = (wq_head + 1) % WORK_QUEUE_SIZE;
|
||||||
|
spinlock_release_irqrestore(&wq_lock, flags);
|
||||||
|
|
||||||
|
// Execute outside the lock
|
||||||
|
if (item.fn) {
|
||||||
|
item.fn(item.arg);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void work_queue_drain_loop(void) {
|
||||||
|
while (1) {
|
||||||
|
// Try to drain all pending work
|
||||||
|
while (work_queue_drain_one()) {
|
||||||
|
// Keep draining
|
||||||
|
}
|
||||||
|
|
||||||
|
// No work — halt the CPU until an interrupt wakes us.
|
||||||
|
// With legacy PIC, APs don't receive timer interrupts, so they'll
|
||||||
|
// sleep until an IPI is sent (e.g., when work is submitted).
|
||||||
|
// This is ideal: APs use 0% CPU when idle.
|
||||||
|
asm volatile("sti; hlt; cli");
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/sys/work_queue.h
Normal file
33
src/sys/work_queue.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// 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.
|
||||||
|
#ifndef WORK_QUEUE_H
|
||||||
|
#define WORK_QUEUE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "spinlock.h"
|
||||||
|
|
||||||
|
// A simple work queue for offloading tasks to idle AP cores.
|
||||||
|
// Producer (BSP or any core) calls work_queue_submit().
|
||||||
|
// Consumer (AP idle loops) calls work_queue_drain_loop().
|
||||||
|
|
||||||
|
typedef void (*work_fn_t)(void *arg);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
work_fn_t fn;
|
||||||
|
void *arg;
|
||||||
|
} work_item_t;
|
||||||
|
|
||||||
|
// Submit a work item. Thread-safe (uses spinlock internally).
|
||||||
|
void work_queue_submit(work_fn_t fn, void *arg);
|
||||||
|
|
||||||
|
// Drain and execute all pending work items, then hlt until more arrive.
|
||||||
|
// Called from AP idle loops. Never returns.
|
||||||
|
void work_queue_drain_loop(void);
|
||||||
|
|
||||||
|
// Drain one item (if available). Returns true if work was done.
|
||||||
|
// Useful for BSP to optionally help if idle.
|
||||||
|
bool work_queue_drain_one(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -11,7 +11,7 @@ LDFLAGS = -m elf_x86_64 -nostdlib -static -no-pie -Ttext=0x40000000 --no-dynamic
|
|||||||
BIN_DIR = bin
|
BIN_DIR = bin
|
||||||
|
|
||||||
LIBC_SOURCES = $(wildcard libc/*.c)
|
LIBC_SOURCES = $(wildcard libc/*.c)
|
||||||
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o
|
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o
|
||||||
|
|
||||||
VPATH = cli gui sys games libc net
|
VPATH = cli gui sys games libc net
|
||||||
vpath %.c cli gui sys games libc net
|
vpath %.c cli gui sys games libc net
|
||||||
@@ -34,6 +34,9 @@ $(BIN_DIR)/crt0.o: crt0.asm
|
|||||||
$(BIN_DIR)/%.o: libc/%.c
|
$(BIN_DIR)/%.o: libc/%.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BIN_DIR)/libwidget.o: ../wm/libwidget.c
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
$(BIN_DIR)/stb_image.o: stb_image.c
|
$(BIN_DIR)/stb_image.o: stb_image.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
|||||||
@@ -237,13 +237,29 @@ int main(int argc, char **argv) {
|
|||||||
if (config.separator[0]) {
|
if (config.separator[0]) {
|
||||||
strcpy(info_lines[info_line_count++], config.separator);
|
strcpy(info_lines[info_line_count++], config.separator);
|
||||||
}
|
}
|
||||||
|
os_info_t os_info;
|
||||||
|
sys_get_os_info(&os_info);
|
||||||
|
|
||||||
if (config.os_label[0]) {
|
if (config.os_label[0]) {
|
||||||
strcpy(info_lines[info_line_count], config.os_label);
|
strcpy(info_lines[info_line_count], config.os_label);
|
||||||
strcat(info_lines[info_line_count++], ": BoredOS V1.71 'Retrowave'");
|
strcat(info_lines[info_line_count], ": ");
|
||||||
|
strcat(info_lines[info_line_count], os_info.os_name);
|
||||||
|
strcat(info_lines[info_line_count], " V");
|
||||||
|
strcat(info_lines[info_line_count], os_info.os_version);
|
||||||
|
strcat(info_lines[info_line_count], " '");
|
||||||
|
strcat(info_lines[info_line_count], os_info.os_codename);
|
||||||
|
strcat(info_lines[info_line_count], "'");
|
||||||
|
info_line_count++;
|
||||||
}
|
}
|
||||||
if (config.kernel_label[0]) {
|
if (config.kernel_label[0]) {
|
||||||
strcpy(info_lines[info_line_count], config.kernel_label);
|
strcpy(info_lines[info_line_count], config.kernel_label);
|
||||||
strcat(info_lines[info_line_count++], ": Boredkernel V3.1.1 x86_64");
|
strcat(info_lines[info_line_count], ": ");
|
||||||
|
strcat(info_lines[info_line_count], os_info.kernel_name);
|
||||||
|
strcat(info_lines[info_line_count], " V");
|
||||||
|
strcat(info_lines[info_line_count], os_info.kernel_version);
|
||||||
|
strcat(info_lines[info_line_count], " ");
|
||||||
|
strcat(info_lines[info_line_count], os_info.build_arch);
|
||||||
|
info_line_count++;
|
||||||
}
|
}
|
||||||
if (config.uptime_label[0]) {
|
if (config.uptime_label[0]) {
|
||||||
uint64_t ticks = sys_system(16, 0, 0, 0, 0);
|
uint64_t ticks = sys_system(16, 0, 0, 0, 0);
|
||||||
|
|||||||
@@ -1,339 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
|
||||||
<ProjectConfiguration Include="Debug|Win32">
|
|
||||||
<Configuration>Debug</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release|Win32">
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>Win32</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Debug|x64">
|
|
||||||
<Configuration>Debug</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release|x64">
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Label="Globals">
|
|
||||||
<VCProjectVersion>15.0</VCProjectVersion>
|
|
||||||
<ProjectGuid>{95A126D2-CC94-4840-BF05-80305041B5FB}</ProjectGuid>
|
|
||||||
<Keyword>Win32Proj</Keyword>
|
|
||||||
<RootNamespace>doomgeneric</RootNamespace>
|
|
||||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v141</PlatformToolset>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v141</PlatformToolset>
|
|
||||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v141</PlatformToolset>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v141</PlatformToolset>
|
|
||||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
|
||||||
<ImportGroup Label="ExtensionSettings">
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="Shared">
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
|
||||||
</ImportGroup>
|
|
||||||
<PropertyGroup Label="UserMacros" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<LinkIncremental>true</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<Optimization>Disabled</Optimization>
|
|
||||||
<SDLCheck>false</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<Optimization>Disabled</Optimization>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
|
||||||
<ClCompile>
|
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<Optimization>MaxSpeed</Optimization>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<SDLCheck>false</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<ClCompile>
|
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<Optimization>MaxSpeed</Optimization>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<DisableSpecificWarnings>4146;4996</DisableSpecificWarnings>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="am_map.c" />
|
|
||||||
<ClCompile Include="doomdef.c" />
|
|
||||||
<ClCompile Include="doomgeneric.c" />
|
|
||||||
<ClCompile Include="doomgeneric_win.c" />
|
|
||||||
<ClCompile Include="doomstat.c" />
|
|
||||||
<ClCompile Include="dstrings.c" />
|
|
||||||
<ClCompile Include="dummy.c" />
|
|
||||||
<ClCompile Include="d_event.c" />
|
|
||||||
<ClCompile Include="d_items.c" />
|
|
||||||
<ClCompile Include="d_iwad.c" />
|
|
||||||
<ClCompile Include="d_loop.c" />
|
|
||||||
<ClCompile Include="d_main.c" />
|
|
||||||
<ClCompile Include="d_mode.c" />
|
|
||||||
<ClCompile Include="d_net.c" />
|
|
||||||
<ClCompile Include="f_finale.c" />
|
|
||||||
<ClCompile Include="f_wipe.c" />
|
|
||||||
<ClCompile Include="gusconf.c" />
|
|
||||||
<ClCompile Include="g_game.c" />
|
|
||||||
<ClCompile Include="hu_lib.c" />
|
|
||||||
<ClCompile Include="hu_stuff.c" />
|
|
||||||
<ClCompile Include="icon.c" />
|
|
||||||
<ClCompile Include="info.c" />
|
|
||||||
<ClCompile Include="i_cdmus.c" />
|
|
||||||
<ClCompile Include="i_endoom.c" />
|
|
||||||
<ClCompile Include="i_input.c" />
|
|
||||||
<ClCompile Include="i_joystick.c" />
|
|
||||||
<ClCompile Include="i_scale.c" />
|
|
||||||
<ClCompile Include="i_sound.c" />
|
|
||||||
<ClCompile Include="i_system.c" />
|
|
||||||
<ClCompile Include="i_timer.c" />
|
|
||||||
<ClCompile Include="i_video.c" />
|
|
||||||
<ClCompile Include="memio.c" />
|
|
||||||
<ClCompile Include="m_argv.c" />
|
|
||||||
<ClCompile Include="m_bbox.c" />
|
|
||||||
<ClCompile Include="m_cheat.c" />
|
|
||||||
<ClCompile Include="m_config.c" />
|
|
||||||
<ClCompile Include="m_controls.c" />
|
|
||||||
<ClCompile Include="m_fixed.c" />
|
|
||||||
<ClCompile Include="m_menu.c" />
|
|
||||||
<ClCompile Include="m_misc.c" />
|
|
||||||
<ClCompile Include="m_random.c" />
|
|
||||||
<ClCompile Include="p_ceilng.c" />
|
|
||||||
<ClCompile Include="p_doors.c" />
|
|
||||||
<ClCompile Include="p_enemy.c" />
|
|
||||||
<ClCompile Include="p_floor.c" />
|
|
||||||
<ClCompile Include="p_inter.c" />
|
|
||||||
<ClCompile Include="p_lights.c" />
|
|
||||||
<ClCompile Include="p_map.c" />
|
|
||||||
<ClCompile Include="p_maputl.c" />
|
|
||||||
<ClCompile Include="p_mobj.c" />
|
|
||||||
<ClCompile Include="p_plats.c" />
|
|
||||||
<ClCompile Include="p_pspr.c" />
|
|
||||||
<ClCompile Include="p_saveg.c" />
|
|
||||||
<ClCompile Include="p_setup.c" />
|
|
||||||
<ClCompile Include="p_sight.c" />
|
|
||||||
<ClCompile Include="p_spec.c" />
|
|
||||||
<ClCompile Include="p_switch.c" />
|
|
||||||
<ClCompile Include="p_telept.c" />
|
|
||||||
<ClCompile Include="p_tick.c" />
|
|
||||||
<ClCompile Include="p_user.c" />
|
|
||||||
<ClCompile Include="r_bsp.c" />
|
|
||||||
<ClCompile Include="r_data.c" />
|
|
||||||
<ClCompile Include="r_draw.c" />
|
|
||||||
<ClCompile Include="r_main.c" />
|
|
||||||
<ClCompile Include="r_plane.c" />
|
|
||||||
<ClCompile Include="r_segs.c" />
|
|
||||||
<ClCompile Include="r_sky.c" />
|
|
||||||
<ClCompile Include="r_things.c" />
|
|
||||||
<ClCompile Include="sha1.c" />
|
|
||||||
<ClCompile Include="sounds.c" />
|
|
||||||
<ClCompile Include="statdump.c" />
|
|
||||||
<ClCompile Include="st_lib.c" />
|
|
||||||
<ClCompile Include="st_stuff.c" />
|
|
||||||
<ClCompile Include="s_sound.c" />
|
|
||||||
<ClCompile Include="tables.c" />
|
|
||||||
<ClCompile Include="v_video.c" />
|
|
||||||
<ClCompile Include="wi_stuff.c" />
|
|
||||||
<ClCompile Include="w_checksum.c" />
|
|
||||||
<ClCompile Include="w_file.c" />
|
|
||||||
<ClCompile Include="w_file_stdc.c" />
|
|
||||||
<ClCompile Include="w_main.c" />
|
|
||||||
<ClCompile Include="w_wad.c" />
|
|
||||||
<ClCompile Include="z_zone.c" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="am_map.h" />
|
|
||||||
<ClInclude Include="config.h" />
|
|
||||||
<ClInclude Include="deh_main.h" />
|
|
||||||
<ClInclude Include="deh_misc.h" />
|
|
||||||
<ClInclude Include="deh_str.h" />
|
|
||||||
<ClInclude Include="doom.h" />
|
|
||||||
<ClInclude Include="doomdata.h" />
|
|
||||||
<ClInclude Include="doomdef.h" />
|
|
||||||
<ClInclude Include="doomfeatures.h" />
|
|
||||||
<ClInclude Include="doomgeneric.h" />
|
|
||||||
<ClInclude Include="doomkeys.h" />
|
|
||||||
<ClInclude Include="doomstat.h" />
|
|
||||||
<ClInclude Include="doomtype.h" />
|
|
||||||
<ClInclude Include="dstrings.h" />
|
|
||||||
<ClInclude Include="d_englsh.h" />
|
|
||||||
<ClInclude Include="d_event.h" />
|
|
||||||
<ClInclude Include="d_items.h" />
|
|
||||||
<ClInclude Include="d_iwad.h" />
|
|
||||||
<ClInclude Include="d_loop.h" />
|
|
||||||
<ClInclude Include="d_main.h" />
|
|
||||||
<ClInclude Include="d_mode.h" />
|
|
||||||
<ClInclude Include="d_player.h" />
|
|
||||||
<ClInclude Include="d_textur.h" />
|
|
||||||
<ClInclude Include="d_think.h" />
|
|
||||||
<ClInclude Include="d_ticcmd.h" />
|
|
||||||
<ClInclude Include="f_finale.h" />
|
|
||||||
<ClInclude Include="f_wipe.h" />
|
|
||||||
<ClInclude Include="gusconf.h" />
|
|
||||||
<ClInclude Include="g_game.h" />
|
|
||||||
<ClInclude Include="hu_lib.h" />
|
|
||||||
<ClInclude Include="hu_stuff.h" />
|
|
||||||
<ClInclude Include="info.h" />
|
|
||||||
<ClInclude Include="i_cdmus.h" />
|
|
||||||
<ClInclude Include="i_endoom.h" />
|
|
||||||
<ClInclude Include="i_joystick.h" />
|
|
||||||
<ClInclude Include="i_scale.h" />
|
|
||||||
<ClInclude Include="i_sound.h" />
|
|
||||||
<ClInclude Include="i_swap.h" />
|
|
||||||
<ClInclude Include="i_system.h" />
|
|
||||||
<ClInclude Include="i_timer.h" />
|
|
||||||
<ClInclude Include="i_video.h" />
|
|
||||||
<ClInclude Include="memio.h" />
|
|
||||||
<ClInclude Include="m_argv.h" />
|
|
||||||
<ClInclude Include="m_bbox.h" />
|
|
||||||
<ClInclude Include="m_cheat.h" />
|
|
||||||
<ClInclude Include="m_config.h" />
|
|
||||||
<ClInclude Include="m_controls.h" />
|
|
||||||
<ClInclude Include="m_fixed.h" />
|
|
||||||
<ClInclude Include="m_menu.h" />
|
|
||||||
<ClInclude Include="m_misc.h" />
|
|
||||||
<ClInclude Include="m_random.h" />
|
|
||||||
<ClInclude Include="net_client.h" />
|
|
||||||
<ClInclude Include="net_dedicated.h" />
|
|
||||||
<ClInclude Include="net_defs.h" />
|
|
||||||
<ClInclude Include="net_gui.h" />
|
|
||||||
<ClInclude Include="net_io.h" />
|
|
||||||
<ClInclude Include="net_loop.h" />
|
|
||||||
<ClInclude Include="net_packet.h" />
|
|
||||||
<ClInclude Include="net_query.h" />
|
|
||||||
<ClInclude Include="net_sdl.h" />
|
|
||||||
<ClInclude Include="net_server.h" />
|
|
||||||
<ClInclude Include="p_inter.h" />
|
|
||||||
<ClInclude Include="p_local.h" />
|
|
||||||
<ClInclude Include="p_mobj.h" />
|
|
||||||
<ClInclude Include="p_pspr.h" />
|
|
||||||
<ClInclude Include="p_saveg.h" />
|
|
||||||
<ClInclude Include="p_setup.h" />
|
|
||||||
<ClInclude Include="p_spec.h" />
|
|
||||||
<ClInclude Include="p_tick.h" />
|
|
||||||
<ClInclude Include="r_bsp.h" />
|
|
||||||
<ClInclude Include="r_data.h" />
|
|
||||||
<ClInclude Include="r_defs.h" />
|
|
||||||
<ClInclude Include="r_draw.h" />
|
|
||||||
<ClInclude Include="r_local.h" />
|
|
||||||
<ClInclude Include="r_main.h" />
|
|
||||||
<ClInclude Include="r_plane.h" />
|
|
||||||
<ClInclude Include="r_segs.h" />
|
|
||||||
<ClInclude Include="r_sky.h" />
|
|
||||||
<ClInclude Include="r_state.h" />
|
|
||||||
<ClInclude Include="r_things.h" />
|
|
||||||
<ClInclude Include="sha1.h" />
|
|
||||||
<ClInclude Include="sounds.h" />
|
|
||||||
<ClInclude Include="statdump.h" />
|
|
||||||
<ClInclude Include="st_lib.h" />
|
|
||||||
<ClInclude Include="st_stuff.h" />
|
|
||||||
<ClInclude Include="s_sound.h" />
|
|
||||||
<ClInclude Include="tables.h" />
|
|
||||||
<ClInclude Include="v_patch.h" />
|
|
||||||
<ClInclude Include="v_video.h" />
|
|
||||||
<ClInclude Include="wi_stuff.h" />
|
|
||||||
<ClInclude Include="w_checksum.h" />
|
|
||||||
<ClInclude Include="w_file.h" />
|
|
||||||
<ClInclude Include="w_main.h" />
|
|
||||||
<ClInclude Include="w_merge.h" />
|
|
||||||
<ClInclude Include="w_wad.h" />
|
|
||||||
<ClInclude Include="z_zone.h" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
|
||||||
<ImportGroup Label="ExtensionTargets">
|
|
||||||
</ImportGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,558 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup>
|
|
||||||
<Filter Include="Source Files">
|
|
||||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
|
||||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="Header Files">
|
|
||||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
|
||||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="Resource Files">
|
|
||||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
|
||||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
|
||||||
</Filter>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="am_map.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_event.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_items.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_iwad.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_loop.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_main.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_mode.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="d_net.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="doomdef.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="doomstat.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="dstrings.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="dummy.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="f_finale.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="f_wipe.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="g_game.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="gusconf.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="hu_lib.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="hu_stuff.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_cdmus.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_endoom.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_joystick.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_scale.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_sound.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_system.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_timer.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="icon.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="info.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_argv.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_bbox.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_cheat.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_config.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_controls.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_fixed.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_menu.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_misc.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="m_random.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="memio.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_ceilng.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_doors.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_enemy.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_floor.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_inter.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_lights.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_map.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_maputl.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_mobj.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_plats.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_pspr.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_saveg.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_setup.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_sight.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_spec.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_switch.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_telept.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_tick.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="p_user.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_bsp.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_data.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_draw.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_main.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_plane.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_segs.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_sky.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="r_things.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="s_sound.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="sha1.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="sounds.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="st_lib.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="st_stuff.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="statdump.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="tables.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="v_video.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="w_checksum.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="w_file.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="w_file_stdc.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="w_main.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="w_wad.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="wi_stuff.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="z_zone.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="doomgeneric.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="doomgeneric_win.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_input.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="i_video.c">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="am_map.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="config.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_englsh.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_event.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_items.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_iwad.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_loop.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_main.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_mode.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_player.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_textur.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_think.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="d_ticcmd.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="deh_main.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="deh_misc.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="deh_str.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doom.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomdata.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomdef.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomfeatures.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomkeys.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomstat.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomtype.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="dstrings.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="f_finale.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="f_wipe.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="g_game.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="gusconf.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="hu_lib.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="hu_stuff.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_cdmus.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_endoom.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_joystick.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_scale.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_sound.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_swap.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_system.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_timer.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="i_video.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="info.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_argv.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_bbox.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_cheat.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_config.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_controls.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_fixed.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_menu.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_misc.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="m_random.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="memio.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_client.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_dedicated.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_defs.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_gui.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_io.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_loop.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_packet.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_query.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_sdl.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="net_server.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_inter.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_local.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_mobj.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_pspr.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_saveg.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_setup.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_spec.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="p_tick.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_bsp.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_data.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_defs.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_draw.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_local.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_main.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_plane.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_segs.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_sky.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_state.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="r_things.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="s_sound.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="sha1.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="sounds.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="st_lib.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="st_stuff.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="statdump.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="tables.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="v_patch.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="v_video.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="w_checksum.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="w_file.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="w_main.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="w_merge.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="w_wad.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="wi_stuff.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="z_zone.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="doomgeneric.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -6,26 +6,13 @@
|
|||||||
static ui_window_t doom_win = 0;
|
static ui_window_t doom_win = 0;
|
||||||
|
|
||||||
void DG_Init(void) {
|
void DG_Init(void) {
|
||||||
doom_win = ui_window_create("Yes, it's DOOM.", (1920 - DOOMGENERIC_RESX) / 2, (1080 - DOOMGENERIC_RESY) / 2, DOOMGENERIC_RESX, DOOMGENERIC_RESY);
|
doom_win = ui_window_create("DOOM", (1920 - DOOMGENERIC_RESX) / 2, (1080 - DOOMGENERIC_RESY) / 2, DOOMGENERIC_RESX, DOOMGENERIC_RESY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t scaled_buffer[600 * 900]; // DOOMGENERIC_RESX * DOOMGENERIC_RESY
|
static uint32_t scaled_buffer[600 * 900]; // DOOMGENERIC_RESX * DOOMGENERIC_RESY
|
||||||
|
|
||||||
void DG_DrawFrame(void) {
|
void DG_DrawFrame(void) {
|
||||||
if (doom_win) {
|
if (doom_win) {
|
||||||
// Doom's internal rendering is always 320x200.
|
|
||||||
// But doomgeneric seems to expect DOOMGENERIC_RESX x DOOMGENERIC_RESY.
|
|
||||||
// Actually, if we set DOOMGENERIC_RESX = 600, doom builds its internal tables based on DOOMGENERIC_RESX.
|
|
||||||
// Wait, Doom's standard resolution is 320x200. Let's find out what DG_ScreenBuffer dimensions are.
|
|
||||||
// According to Doom source, SCREENWIDTH and SCREENHEIGHT define the buffer size.
|
|
||||||
// So DG_ScreenBuffer is indeed DOOMGENERIC_RESX x DOOMGENERIC_RESY.
|
|
||||||
// However, the issue shown in the image is that the *game itself* only drew a 320x200 or 640x400 block in the corner!
|
|
||||||
|
|
||||||
// So the image wasn't scaled by Doom. Let's let LibUI do the scaling!
|
|
||||||
// We will tell Doom its resolution is 640x400 (which it knows how to handle correctly for the 16:10 aspect ratio),
|
|
||||||
// but we'll scale it to 600x900 (or whatever window size) before sending it to ui_draw_image.
|
|
||||||
|
|
||||||
// Wait, ui_draw_image doesn't scale natively yet. Let's do nearest-neighbor scaling.
|
|
||||||
int src_w = 640;
|
int src_w = 640;
|
||||||
int src_h = 400;
|
int src_h = 400;
|
||||||
int dst_w = DOOMGENERIC_RESX;
|
int dst_w = DOOMGENERIC_RESX;
|
||||||
|
|||||||
@@ -84,10 +84,9 @@ static void draw_ascii_logo(ui_window_t win, int x, int y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void about_paint(ui_window_t win) {
|
static void about_paint(ui_window_t win) {
|
||||||
int w = 340;
|
int w = 380;
|
||||||
int h = 240;
|
int h = 260;
|
||||||
|
|
||||||
// Clear background to prevent alpha-blended text from accumulating on repaints
|
|
||||||
ui_draw_rect(win, 0, 0, w, h, 0xFF1E1E1E);
|
ui_draw_rect(win, 0, 0, w, h, 0xFF1E1E1E);
|
||||||
|
|
||||||
int offset_x = 15;
|
int offset_x = 15;
|
||||||
@@ -96,19 +95,51 @@ static void about_paint(ui_window_t win) {
|
|||||||
draw_ascii_logo(win, 14, offset_y);
|
draw_ascii_logo(win, 14, offset_y);
|
||||||
|
|
||||||
int fh = ui_get_font_height();
|
int fh = ui_get_font_height();
|
||||||
ui_draw_string(win, offset_x, offset_y + 105, "BoredOS 'Retrowave'", 0xFFFFFFFF);
|
os_info_t os_info;
|
||||||
ui_draw_string(win, offset_x, offset_y + 105 + fh, "BoredOS Version 1.71", 0xFFFFFFFF);
|
sys_get_os_info(&os_info);
|
||||||
ui_draw_string(win, offset_x, offset_y + 105 + fh*2, "Kernel Version 3.1.1", 0xFFFFFFFF);
|
|
||||||
|
char os_name_str[128];
|
||||||
|
os_name_str[0] = 0;
|
||||||
|
strcat(os_name_str, os_info.os_name);
|
||||||
|
strcat(os_name_str, " '");
|
||||||
|
strcat(os_name_str, os_info.os_codename);
|
||||||
|
strcat(os_name_str, "'");
|
||||||
|
|
||||||
|
char os_version_str[128];
|
||||||
|
os_version_str[0] = 0;
|
||||||
|
strcat(os_version_str, os_info.os_name);
|
||||||
|
strcat(os_version_str, " Version ");
|
||||||
|
strcat(os_version_str, os_info.os_version);
|
||||||
|
|
||||||
|
char kernel_version_str[128];
|
||||||
|
kernel_version_str[0] = 0;
|
||||||
|
strcat(kernel_version_str, os_info.kernel_name);
|
||||||
|
strcat(kernel_version_str, " Version ");
|
||||||
|
strcat(kernel_version_str, os_info.kernel_version);
|
||||||
|
strcat(kernel_version_str, " ");
|
||||||
|
strcat(kernel_version_str, os_info.build_arch);
|
||||||
|
|
||||||
|
char build_date_str[128];
|
||||||
|
build_date_str[0] = 0;
|
||||||
|
strcat(build_date_str, "Build Date: ");
|
||||||
|
strcat(build_date_str, os_info.build_date);
|
||||||
|
strcat(build_date_str, " ");
|
||||||
|
strcat(build_date_str, os_info.build_time);
|
||||||
|
|
||||||
|
ui_draw_string(win, offset_x, offset_y + 105, os_name_str, 0xFFFFFFFF);
|
||||||
|
ui_draw_string(win, offset_x, offset_y + 105 + fh, os_version_str, 0xFFFFFFFF);
|
||||||
|
ui_draw_string(win, offset_x, offset_y + 105 + fh*2, kernel_version_str, 0xFFFFFFFF);
|
||||||
|
ui_draw_string(win, offset_x, offset_y + 105 + fh*3, build_date_str, 0xFFFFFFFF);
|
||||||
|
|
||||||
// Copyright
|
// Copyright
|
||||||
ui_draw_string(win, offset_x, offset_y + 105 + fh*3, "(C) 2026 boreddevnl.", 0xFFFFFFFF);
|
ui_draw_string(win, offset_x, offset_y + 105 + fh*4, "(C) 2026 boreddevnl.", 0xFFFFFFFF);
|
||||||
ui_draw_string(win, offset_x, offset_y + 105 + fh*4, "All rights reserved.", 0xFFFFFFFF);
|
ui_draw_string(win, offset_x, offset_y + 105 + fh*5, "All rights reserved.", 0xFFFFFFFF);
|
||||||
|
|
||||||
ui_mark_dirty(win, 0, 0, w, h);
|
ui_mark_dirty(win, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
ui_window_t win_about = ui_window_create("About BoredOS", 250, 180, 340, 240);
|
ui_window_t win_about = ui_window_create("About BoredOS", 250, 180, 380, 260);
|
||||||
|
|
||||||
about_paint(win_about);
|
about_paint(win_about);
|
||||||
|
|
||||||
@@ -121,7 +152,6 @@ int main(void) {
|
|||||||
sys_exit(0);
|
sys_exit(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Avoid high CPU usage
|
|
||||||
sleep(10);
|
sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1932
src/userland/gui/boredword.c
Normal file
1932
src/userland/gui/boredword.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,7 @@ typedef struct {
|
|||||||
float scale;
|
float scale;
|
||||||
int list_depth;
|
int list_depth;
|
||||||
int blockquote_depth;
|
int blockquote_depth;
|
||||||
|
int attr_w;
|
||||||
bool img_loading;
|
bool img_loading;
|
||||||
bool img_failed;
|
bool img_failed;
|
||||||
uint32_t **img_frames;
|
uint32_t **img_frames;
|
||||||
@@ -137,6 +138,42 @@ static int scroll_y = 0;
|
|||||||
static int total_content_height = 0;
|
static int total_content_height = 0;
|
||||||
static int focused_element = -1;
|
static int focused_element = -1;
|
||||||
|
|
||||||
|
#include "../../wm/libwidget.h"
|
||||||
|
|
||||||
|
static void browser_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||||
|
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
|
||||||
|
}
|
||||||
|
static void browser_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||||
|
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
|
||||||
|
}
|
||||||
|
static void browser_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||||
|
ui_draw_string((ui_window_t)user_data, x, y, str, color);
|
||||||
|
}
|
||||||
|
static int browser_measure_string_width(void *user_data, const char *str) {
|
||||||
|
(void)user_data;
|
||||||
|
return (int)ui_get_string_width(str);
|
||||||
|
}
|
||||||
|
static void browser_mark_dirty(void *user_data, int x, int y, int w, int h) {
|
||||||
|
ui_mark_dirty((ui_window_t)user_data, x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
static widget_context_t browser_ctx = {
|
||||||
|
.draw_rect = browser_draw_rect,
|
||||||
|
.draw_rounded_rect_filled = browser_draw_rounded_rect_filled,
|
||||||
|
.draw_string = browser_draw_string,
|
||||||
|
.measure_string_width = browser_measure_string_width,
|
||||||
|
.mark_dirty = browser_mark_dirty
|
||||||
|
};
|
||||||
|
|
||||||
|
static widget_scrollbar_t browser_scrollbar;
|
||||||
|
static void browser_on_scroll(void *user_data, int new_scroll_y) {
|
||||||
|
(void)user_data;
|
||||||
|
scroll_y = new_scroll_y;
|
||||||
|
}
|
||||||
|
static widget_textbox_t url_tb;
|
||||||
|
static widget_button_t btn_back;
|
||||||
|
static widget_button_t btn_home;
|
||||||
|
|
||||||
static void parse_html(const char *html);
|
static void parse_html(const char *html);
|
||||||
static void parse_html_incremental(const char *html, int safe_len);
|
static void parse_html_incremental(const char *html, int safe_len);
|
||||||
static void browser_reflow(void);
|
static void browser_reflow(void);
|
||||||
@@ -219,6 +256,9 @@ static int parse_ip(const char* str, net_ipv4_address_t* ip) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char dns_cache_host[256] = "";
|
||||||
|
static net_ipv4_address_t dns_cache_ip;
|
||||||
|
|
||||||
static int fetch_content(const char *url, char *dest_buf, int max_len, bool progressive) {
|
static int fetch_content(const char *url, char *dest_buf, int max_len, bool progressive) {
|
||||||
const char* host_start = url;
|
const char* host_start = url;
|
||||||
if (url[0] == 'h' && url[1] == 't' && url[2] == 't' && url[3] == 'p') {
|
if (url[0] == 'h' && url[1] == 't' && url[2] == 't' && url[3] == 'p') {
|
||||||
@@ -253,7 +293,13 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
|
|||||||
|
|
||||||
net_ipv4_address_t ip;
|
net_ipv4_address_t ip;
|
||||||
if (parse_ip(hostname, &ip) != 0) {
|
if (parse_ip(hostname, &ip) != 0) {
|
||||||
if (sys_dns_lookup(hostname, &ip) != 0) return 0;
|
if (str_iequals(hostname, dns_cache_host)) {
|
||||||
|
ip = dns_cache_ip;
|
||||||
|
} else {
|
||||||
|
if (sys_dns_lookup(hostname, &ip) != 0) return 0;
|
||||||
|
int k=0; while(hostname[k]) { dns_cache_host[k] = hostname[k]; k++; } dns_cache_host[k] = 0;
|
||||||
|
dns_cache_ip = ip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sys_tcp_connect(&ip, port) != 0) return 0;
|
if (sys_tcp_connect(&ip, port) != 0) return 0;
|
||||||
@@ -280,18 +326,100 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
|
|||||||
int total = 0;
|
int total = 0;
|
||||||
int last_render = 0;
|
int last_render = 0;
|
||||||
if (progressive) inc_parse_offset = 0;
|
if (progressive) inc_parse_offset = 0;
|
||||||
|
long long last_data_tick = sys_system(16, 0, 0, 0, 0);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
int len = sys_tcp_recv(dest_buf + total, max_len - 1 - total);
|
int len = sys_tcp_recv_nb(dest_buf + total, max_len - 1 - total);
|
||||||
if (len <= 0) break;
|
if (len < 0 && len != -2) break;
|
||||||
|
if (len == -2) break;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
long long now = sys_system(16, 0, 0, 0, 0);
|
||||||
|
if (now > last_data_tick + 1800) break; // 30 sec timeout
|
||||||
|
|
||||||
|
gui_event_t ev;
|
||||||
|
bool scrolled = false;
|
||||||
|
while (ui_get_event(win_browser, &ev)) {
|
||||||
|
if (ev.type == 9) { // GUI_EVENT_MOUSE_WHEEL
|
||||||
|
scroll_y += ev.arg1 * 20;
|
||||||
|
scrolled = true;
|
||||||
|
} else if (ev.type == 12) { // GUI_EVENT_CLOSE
|
||||||
|
sys_exit(0);
|
||||||
|
} else if (ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP || ev.type == GUI_EVENT_MOUSE_MOVE) {
|
||||||
|
bool is_down = (ev.type == GUI_EVENT_MOUSE_DOWN || (ev.type == GUI_EVENT_MOUSE_MOVE && browser_scrollbar.is_dragging));
|
||||||
|
if (widget_scrollbar_handle_mouse(&browser_scrollbar, ev.arg1, ev.arg2, is_down, &browser_ctx)) {
|
||||||
|
scroll_y = browser_scrollbar.scroll_y;
|
||||||
|
scrolled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (scrolled) {
|
||||||
|
int max_scroll = total_content_height - (win_h - URL_BAR_H);
|
||||||
|
if (max_scroll < 0) max_scroll = 0;
|
||||||
|
if (scroll_y > max_scroll) scroll_y = max_scroll;
|
||||||
|
if (scroll_y < 0) scroll_y = 0;
|
||||||
|
browser_reflow(); // Needs reflow in case of dimensions changing, but mostly just paint
|
||||||
|
browser_paint();
|
||||||
|
ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
|
||||||
|
}
|
||||||
|
sleep(10);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_data_tick = sys_system(16, 0, 0, 0, 0);
|
||||||
total += len;
|
total += len;
|
||||||
if (total >= max_len - 1) break;
|
if (total >= max_len - 1) break;
|
||||||
|
|
||||||
|
dest_buf[total] = 0;
|
||||||
|
char *body = strstr(dest_buf, "\r\n\r\n");
|
||||||
|
if (body) {
|
||||||
|
char temp = body[0];
|
||||||
|
body[0] = 0; // Null-terminate headers
|
||||||
|
|
||||||
|
int expected = -1;
|
||||||
|
char *cl = str_istrstr(dest_buf, "Content-Length:");
|
||||||
|
if (cl) {
|
||||||
|
cl += 15;
|
||||||
|
while (*cl == ' ') cl++;
|
||||||
|
expected = 0;
|
||||||
|
while (*cl >= '0' && *cl <= '9') {
|
||||||
|
expected = expected * 10 + (*cl - '0');
|
||||||
|
cl++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_chunked = 0;
|
||||||
|
char *te = str_istrstr(dest_buf, "Transfer-Encoding:");
|
||||||
|
if (te && str_istrstr(te, "chunked")) {
|
||||||
|
is_chunked = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[0] = temp; // Restore body
|
||||||
|
|
||||||
|
body += 4;
|
||||||
|
int body_len = total - (body - dest_buf);
|
||||||
|
|
||||||
|
if (expected != -1) {
|
||||||
|
if (body_len >= expected) break;
|
||||||
|
} else if (is_chunked) {
|
||||||
|
if (total >= 5 && dest_buf[total-5] == '0' && dest_buf[total-4] == '\r' &&
|
||||||
|
dest_buf[total-3] == '\n' && dest_buf[total-2] == '\r' && dest_buf[total-1] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (progressive && total - last_render > 32768) {
|
if (progressive && total - last_render > 32768) {
|
||||||
dest_buf[total] = 0;
|
dest_buf[total] = 0;
|
||||||
char *body = strstr(dest_buf, "\r\n\r\n");
|
char *body = strstr(dest_buf, "\r\n\r\n");
|
||||||
if (body) {
|
if (body) {
|
||||||
|
char temp = body[0];
|
||||||
|
body[0] = 0;
|
||||||
|
int is_chunked = strstr(dest_buf, "Transfer-Encoding: chunked") != NULL;
|
||||||
|
body[0] = temp;
|
||||||
|
|
||||||
body += 4;
|
body += 4;
|
||||||
if (!strstr(dest_buf, "Transfer-Encoding: chunked")) {
|
if (!is_chunked) {
|
||||||
int body_len = total - (body - dest_buf);
|
int body_len = total - (body - dest_buf);
|
||||||
int safe_len = body_len;
|
int safe_len = body_len;
|
||||||
while (safe_len > 0 && body[safe_len - 1] != '>') safe_len--;
|
while (safe_len > 0 && body[safe_len - 1] != '>') safe_len--;
|
||||||
@@ -333,8 +461,13 @@ static void decode_image(unsigned char *data, int len, RenderElement *el) {
|
|||||||
|
|
||||||
if (rgba && img_w_orig > 0 && img_h_orig > 0) {
|
if (rgba && img_w_orig > 0 && img_h_orig > 0) {
|
||||||
int fit_w = img_w_orig; int fit_h = img_h_orig;
|
int fit_w = img_w_orig; int fit_h = img_h_orig;
|
||||||
if (fit_w > win_w - 60) { fit_h = fit_h * (win_w - 60) / fit_w; fit_w = win_w - 60; }
|
if (el->attr_w > 0) {
|
||||||
if (fit_h > 400) { fit_w = fit_w * 400 / fit_h; fit_h = 400; }
|
fit_w = el->attr_w;
|
||||||
|
fit_h = (img_h_orig * fit_w) / img_w_orig;
|
||||||
|
} else {
|
||||||
|
if (fit_w > win_w - 60) { fit_h = fit_h * (win_w - 60) / fit_w; fit_w = win_w - 60; }
|
||||||
|
if (fit_h > 400) { fit_w = fit_w * 400 / fit_h; fit_h = 400; }
|
||||||
|
}
|
||||||
|
|
||||||
if (frame_count > 1 && delays) {
|
if (frame_count > 1 && delays) {
|
||||||
el->img_frames = malloc(frame_count * sizeof(uint32_t *));
|
el->img_frames = malloc(frame_count * sizeof(uint32_t *));
|
||||||
@@ -479,6 +612,7 @@ static int inc_list_type[16];
|
|||||||
static int inc_list_index[16];
|
static int inc_list_index[16];
|
||||||
static int inc_center_depth = 0;
|
static int inc_center_depth = 0;
|
||||||
static int inc_table_depth = 0;
|
static int inc_table_depth = 0;
|
||||||
|
static int inc_table_float_depth = 0;
|
||||||
static int inc_blockquote_depth = 0;
|
static int inc_blockquote_depth = 0;
|
||||||
static bool inc_is_bold = false;
|
static bool inc_is_bold = false;
|
||||||
static bool inc_is_italic = false;
|
static bool inc_is_italic = false;
|
||||||
@@ -521,7 +655,7 @@ static void flush_line(void) {
|
|||||||
if (el->tag == TAG_IMG && el->img_h + 10 > max_h) max_h = el->img_h + 10;
|
if (el->tag == TAG_IMG && el->img_h + 10 > max_h) max_h = el->img_h + 10;
|
||||||
if ((el->tag == TAG_INPUT || el->tag == TAG_BUTTON) && 20 + 10 > max_h) max_h = 20 + 10;
|
if ((el->tag == TAG_INPUT || el->tag == TAG_BUTTON) && 20 + 10 > max_h) max_h = 20 + 10;
|
||||||
if (el->tag == TAG_NONE) {
|
if (el->tag == TAG_NONE) {
|
||||||
int fh = ui_get_font_height_scaled(el->scale);
|
int fh = el->h;
|
||||||
if (fh + 4 > max_h) max_h = fh + 4;
|
if (fh + 4 > max_h) max_h = fh + 4;
|
||||||
if (fh > max_baseline) max_baseline = fh;
|
if (fh > max_baseline) max_baseline = fh;
|
||||||
}
|
}
|
||||||
@@ -531,7 +665,7 @@ static void flush_line(void) {
|
|||||||
RenderElement *el = &elements[line_elements[i]];
|
RenderElement *el = &elements[line_elements[i]];
|
||||||
el->x = offset_x;
|
el->x = offset_x;
|
||||||
if (el->tag == TAG_NONE) {
|
if (el->tag == TAG_NONE) {
|
||||||
int fh = ui_get_font_height_scaled(el->scale);
|
int fh = el->h;
|
||||||
el->y = cur_line_y + (max_baseline - fh);
|
el->y = cur_line_y + (max_baseline - fh);
|
||||||
} else {
|
} else {
|
||||||
el->y = cur_line_y;
|
el->y = cur_line_y;
|
||||||
@@ -550,9 +684,41 @@ static void browser_reflow(void) {
|
|||||||
line_element_count = 0;
|
line_element_count = 0;
|
||||||
total_content_height = 0;
|
total_content_height = 0;
|
||||||
|
|
||||||
|
int float_right_bottom_y = 0;
|
||||||
|
int float_start_idx = -1;
|
||||||
|
int float_start_y = 0;
|
||||||
|
|
||||||
for (int i = 0; i < element_count; i++) {
|
for (int i = 0; i < element_count; i++) {
|
||||||
RenderElement *el = &elements[i];
|
RenderElement *el = &elements[i];
|
||||||
|
|
||||||
|
if (el->tag == 8) {
|
||||||
|
flush_line();
|
||||||
|
float_start_idx = i;
|
||||||
|
float_start_y = cur_line_y;
|
||||||
|
continue;
|
||||||
|
} else if (el->tag == 9) {
|
||||||
|
if (float_start_idx != -1) {
|
||||||
|
flush_line();
|
||||||
|
float_right_bottom_y = cur_line_y;
|
||||||
|
int float_offset = win_w - SCROLL_BAR_W - 320 - 10;
|
||||||
|
|
||||||
|
elements[float_start_idx].x = float_offset > 0 ? float_offset + 10 : 10;
|
||||||
|
elements[float_start_idx].y = float_start_y;
|
||||||
|
elements[float_start_idx].w = 320;
|
||||||
|
elements[float_start_idx].h = cur_line_y - float_start_y;
|
||||||
|
|
||||||
|
if (float_offset > 0) {
|
||||||
|
for (int j = float_start_idx; j < i; j++) {
|
||||||
|
elements[j].x += float_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur_line_y = float_start_y;
|
||||||
|
cur_line_x = 10;
|
||||||
|
float_start_idx = -1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (el->tag == TAG_BR) {
|
if (el->tag == TAG_BR) {
|
||||||
flush_line();
|
flush_line();
|
||||||
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
||||||
@@ -562,18 +728,24 @@ static void browser_reflow(void) {
|
|||||||
if (el->tag == TAG_HR) {
|
if (el->tag == TAG_HR) {
|
||||||
flush_line();
|
flush_line();
|
||||||
el->w = win_w - SCROLL_BAR_W - 40 - (el->blockquote_depth * 40);
|
el->w = win_w - SCROLL_BAR_W - 40 - (el->blockquote_depth * 40);
|
||||||
|
if (float_start_idx != -1) el->w = 300 - 20;
|
||||||
|
else if (cur_line_y < float_right_bottom_y) el->w = win_w - 320 - SCROLL_BAR_W - 20;
|
||||||
line_elements[line_element_count++] = i;
|
line_elements[line_element_count++] = i;
|
||||||
flush_line();
|
flush_line();
|
||||||
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int max_x = win_w - SCROLL_BAR_W - 20 - (el->blockquote_depth * 40);
|
||||||
|
if (float_start_idx != -1) max_x = 310;
|
||||||
|
else if (cur_line_y < float_right_bottom_y) max_x = win_w - 320 - SCROLL_BAR_W - 20;
|
||||||
|
|
||||||
if (el->tag == TAG_NONE && el->content[0] == ' ' && el->content[1] == 0) {
|
if (el->tag == TAG_NONE && el->content[0] == ' ' && el->content[1] == 0) {
|
||||||
if (line_element_count == 0) continue;
|
if (line_element_count == 0) continue;
|
||||||
if (cur_line_x + el->w > win_w - SCROLL_BAR_W - 20 - (el->blockquote_depth * 40)) continue;
|
if (cur_line_x + el->w > max_x) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cur_line_x + el->w > win_w - SCROLL_BAR_W - 20 - (el->blockquote_depth * 40)) {
|
if (cur_line_x + el->w > max_x) {
|
||||||
flush_line();
|
flush_line();
|
||||||
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
cur_line_x = 10 + (el->list_depth * 20) + (el->blockquote_depth * 20);
|
||||||
}
|
}
|
||||||
@@ -714,7 +886,7 @@ static void parse_html(const char *html) {
|
|||||||
browser_clear();
|
browser_clear();
|
||||||
list_depth = 0;
|
list_depth = 0;
|
||||||
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
|
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
|
||||||
int i = 0; int center_depth = 0; int table_depth = 0; int blockquote_depth = 0; bool is_bold = false; bool is_italic = false; bool is_underline = false;
|
int i = 0; int center_depth = 0; int table_depth = 0; int table_float_depth = 0; int blockquote_depth = 0; bool is_bold = false; bool is_italic = false; bool is_underline = false;
|
||||||
uint32_t current_color = COLOR_TEXT;
|
uint32_t current_color = COLOR_TEXT;
|
||||||
char current_link[256] = "";
|
char current_link[256] = "";
|
||||||
float current_scale = 15.0f; float base_scale = 15.0f;
|
float current_scale = 15.0f; float base_scale = 15.0f;
|
||||||
@@ -758,7 +930,14 @@ static void parse_html(const char *html) {
|
|||||||
|
|
||||||
if (tag_name[0] == '/') {
|
if (tag_name[0] == '/') {
|
||||||
if (str_iequals(tag_name+1, "center")) { emit_br(); if (center_depth > 0) center_depth--; }
|
if (str_iequals(tag_name+1, "center")) { emit_br(); if (center_depth > 0) center_depth--; }
|
||||||
else if (str_iequals(tag_name+1, "table")) { emit_br(); if (table_depth > 0) table_depth--; table_col = 0; }
|
else if (str_iequals(tag_name+1, "table")) {
|
||||||
|
emit_br();
|
||||||
|
if (table_depth > 0 && table_depth == table_float_depth) {
|
||||||
|
table_float_depth = 0;
|
||||||
|
if (element_count < MAX_ELEMENTS) { RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 9; }
|
||||||
|
}
|
||||||
|
if (table_depth > 0) table_depth--; table_col = 0;
|
||||||
|
}
|
||||||
else if (str_iequals(tag_name+1, "tr")) { emit_br(); table_col = 0; }
|
else if (str_iequals(tag_name+1, "tr")) { emit_br(); table_col = 0; }
|
||||||
else if (str_iequals(tag_name+1, "td") || str_iequals(tag_name+1, "th")) {
|
else if (str_iequals(tag_name+1, "td") || str_iequals(tag_name+1, "th")) {
|
||||||
table_col++;
|
table_col++;
|
||||||
@@ -796,7 +975,7 @@ static void parse_html(const char *html) {
|
|||||||
current_form_id = 0; current_form_action[0] = 0;
|
current_form_id = 0; current_form_action[0] = 0;
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
|
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
|
||||||
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div") || str_iequals(tag_name+1, "address")) emit_br();
|
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div") || str_iequals(tag_name+1, "address")) { emit_br(); }
|
||||||
else if (str_iequals(tag_name+1, "pre") || str_iequals(tag_name+1, "xmp") || str_iequals(tag_name+1, "listing")) { emit_br(); is_pre = false; }
|
else if (str_iequals(tag_name+1, "pre") || str_iequals(tag_name+1, "xmp") || str_iequals(tag_name+1, "listing")) { emit_br(); is_pre = false; }
|
||||||
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
|
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
|
||||||
if (font_ptr > 0) {
|
if (font_ptr > 0) {
|
||||||
@@ -816,7 +995,17 @@ static void parse_html(const char *html) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (str_iequals(tag_name, "center")) { emit_br(); center_depth++; }
|
if (str_iequals(tag_name, "center")) { emit_br(); center_depth++; }
|
||||||
else if (str_iequals(tag_name, "table")) { emit_br(); table_depth++; table_col = 0; }
|
else if (str_iequals(tag_name, "table")) {
|
||||||
|
emit_br(); table_depth++; table_col = 0;
|
||||||
|
if (str_istrstr(attr_buf, "align=\"right\"") && table_float_depth == 0) {
|
||||||
|
table_float_depth = table_depth;
|
||||||
|
if (element_count < MAX_ELEMENTS) {
|
||||||
|
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 8;
|
||||||
|
char *bg_str = str_istrstr(attr_buf, "bgcolor=\"");
|
||||||
|
if (bg_str) el->color = parse_html_color(bg_str + 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (str_iequals(tag_name, "tr")) { emit_br(); table_col = 0; }
|
else if (str_iequals(tag_name, "tr")) { emit_br(); table_col = 0; }
|
||||||
else if (str_iequals(tag_name, "td") || str_iequals(tag_name, "th")) {
|
else if (str_iequals(tag_name, "td") || str_iequals(tag_name, "th")) {
|
||||||
if (table_col > 0) {
|
if (table_col > 0) {
|
||||||
@@ -920,7 +1109,9 @@ static void parse_html(const char *html) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name, "br")) emit_br();
|
else if (str_iequals(tag_name, "br")) emit_br();
|
||||||
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) emit_br();
|
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) {
|
||||||
|
emit_br();
|
||||||
|
}
|
||||||
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
||||||
else if (str_iequals(tag_name, "li")) {
|
else if (str_iequals(tag_name, "li")) {
|
||||||
emit_br();
|
emit_br();
|
||||||
@@ -983,6 +1174,8 @@ static void parse_html(const char *html) {
|
|||||||
RenderElement *el = &elements[element_count++];
|
RenderElement *el = &elements[element_count++];
|
||||||
memset(el, 0, sizeof(RenderElement));
|
memset(el, 0, sizeof(RenderElement));
|
||||||
el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
||||||
|
char *width_str = str_istrstr(attr_buf, "width=\"");
|
||||||
|
if (width_str) { int w = atoi(width_str + 7); if (w > 0) el->attr_w = w; }
|
||||||
char *src = str_istrstr(attr_buf, "src=\"");
|
char *src = str_istrstr(attr_buf, "src=\"");
|
||||||
if (src) {
|
if (src) {
|
||||||
src += 5; int l = 0;
|
src += 5; int l = 0;
|
||||||
@@ -995,6 +1188,8 @@ static void parse_html(const char *html) {
|
|||||||
RenderElement *el = &elements[element_count++];
|
RenderElement *el = &elements[element_count++];
|
||||||
memset(el, 0, sizeof(RenderElement));
|
memset(el, 0, sizeof(RenderElement));
|
||||||
el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
||||||
|
char *size_str = str_istrstr(attr_buf, "size=\"");
|
||||||
|
if (size_str) { int sz = atoi(size_str + 6); if (sz > 0) el->w = sz * 8; }
|
||||||
char *val = str_istrstr(attr_buf, "value=\"");
|
char *val = str_istrstr(attr_buf, "value=\"");
|
||||||
char *ph = str_istrstr(attr_buf, "placeholder=\"");
|
char *ph = str_istrstr(attr_buf, "placeholder=\"");
|
||||||
char *type = str_istrstr(attr_buf, "type=\"");
|
char *type = str_istrstr(attr_buf, "type=\"");
|
||||||
@@ -1145,7 +1340,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
browser_clear();
|
browser_clear();
|
||||||
list_depth = 0;
|
list_depth = 0;
|
||||||
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
|
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
|
||||||
inc_center_depth = 0; inc_table_depth = 0; inc_blockquote_depth = 0;
|
inc_center_depth = 0; inc_table_depth = 0; inc_table_float_depth = 0; inc_blockquote_depth = 0;
|
||||||
inc_is_bold = false; inc_is_italic = false; inc_is_underline = false;
|
inc_is_bold = false; inc_is_italic = false; inc_is_underline = false;
|
||||||
inc_current_color = COLOR_TEXT; inc_current_link[0] = 0;
|
inc_current_color = COLOR_TEXT; inc_current_link[0] = 0;
|
||||||
inc_current_scale = 15.0f; inc_base_scale = 15.0f;
|
inc_current_scale = 15.0f; inc_base_scale = 15.0f;
|
||||||
@@ -1162,6 +1357,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
int i = inc_parse_offset;
|
int i = inc_parse_offset;
|
||||||
int center_depth = inc_center_depth;
|
int center_depth = inc_center_depth;
|
||||||
int table_depth = inc_table_depth;
|
int table_depth = inc_table_depth;
|
||||||
|
int table_float_depth = inc_table_float_depth;
|
||||||
int blockquote_depth = inc_blockquote_depth;
|
int blockquote_depth = inc_blockquote_depth;
|
||||||
bool is_bold = inc_is_bold;
|
bool is_bold = inc_is_bold;
|
||||||
bool is_italic = inc_is_italic;
|
bool is_italic = inc_is_italic;
|
||||||
@@ -1207,7 +1403,14 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
|
|
||||||
if (tag_name[0] == '/') {
|
if (tag_name[0] == '/') {
|
||||||
if (str_iequals(tag_name+1, "center")) { emit_br(); if (center_depth > 0) center_depth--; }
|
if (str_iequals(tag_name+1, "center")) { emit_br(); if (center_depth > 0) center_depth--; }
|
||||||
else if (str_iequals(tag_name+1, "table")) { emit_br(); if (table_depth > 0) table_depth--; table_col = 0; }
|
else if (str_iequals(tag_name+1, "table")) {
|
||||||
|
emit_br();
|
||||||
|
if (table_depth > 0 && table_depth == table_float_depth) {
|
||||||
|
table_float_depth = 0;
|
||||||
|
if (element_count < MAX_ELEMENTS) { RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 9; }
|
||||||
|
}
|
||||||
|
if (table_depth > 0) table_depth--; table_col = 0;
|
||||||
|
}
|
||||||
else if (str_iequals(tag_name+1, "tr")) { emit_br(); table_col = 0; }
|
else if (str_iequals(tag_name+1, "tr")) { emit_br(); table_col = 0; }
|
||||||
else if (str_iequals(tag_name+1, "td") || str_iequals(tag_name+1, "th")) {
|
else if (str_iequals(tag_name+1, "td") || str_iequals(tag_name+1, "th")) {
|
||||||
table_col++;
|
table_col++;
|
||||||
@@ -1235,7 +1438,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
else if (tag_name[1] == 'h' && tag_name[2] >= '1' && tag_name[2] <= '6') { emit_br(); emit_br(); is_bold = false; is_italic = false; is_underline = false; base_scale = 15.0f; current_scale = 15.0f; }
|
else if (tag_name[1] == 'h' && tag_name[2] >= '1' && tag_name[2] <= '6') { emit_br(); emit_br(); is_bold = false; is_italic = false; is_underline = false; base_scale = 15.0f; current_scale = 15.0f; }
|
||||||
else if (str_iequals(tag_name+1, "form")) { emit_br(); current_form_id = 0; current_form_action[0] = 0; }
|
else if (str_iequals(tag_name+1, "form")) { emit_br(); current_form_id = 0; current_form_action[0] = 0; }
|
||||||
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
|
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
|
||||||
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div")) emit_br();
|
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div")) { emit_br(); }
|
||||||
else if (str_iequals(tag_name+1, "pre") || str_iequals(tag_name+1, "xmp") || str_iequals(tag_name+1, "listing")) { emit_br(); is_pre = false; }
|
else if (str_iequals(tag_name+1, "pre") || str_iequals(tag_name+1, "xmp") || str_iequals(tag_name+1, "listing")) { emit_br(); is_pre = false; }
|
||||||
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
|
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
|
||||||
if (inc_font_ptr > 0) {
|
if (inc_font_ptr > 0) {
|
||||||
@@ -1253,9 +1456,19 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
ui_window_set_title(win_browser, page_title);
|
ui_window_set_title(win_browser, page_title);
|
||||||
skip_content = true;
|
skip_content = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (str_iequals(tag_name, "center")) { emit_br(); center_depth++; }
|
if (str_iequals(tag_name, "center")) { emit_br(); center_depth++; }
|
||||||
else if (str_iequals(tag_name, "table")) { emit_br(); table_depth++; table_col = 0; }
|
else if (str_iequals(tag_name, "table")) {
|
||||||
|
emit_br(); table_depth++; table_col = 0;
|
||||||
|
if (str_istrstr(attr_buf, "align=\"right\"") && table_float_depth == 0) {
|
||||||
|
table_float_depth = table_depth;
|
||||||
|
if (element_count < MAX_ELEMENTS) {
|
||||||
|
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement)); el->tag = 8;
|
||||||
|
char *bg_str = str_istrstr(attr_buf, "bgcolor=\"");
|
||||||
|
if (bg_str) el->color = parse_html_color(bg_str + 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (str_iequals(tag_name, "tr")) { emit_br(); table_col = 0; }
|
else if (str_iequals(tag_name, "tr")) { emit_br(); table_col = 0; }
|
||||||
else if (str_iequals(tag_name, "td") || str_iequals(tag_name, "th")) {
|
else if (str_iequals(tag_name, "td") || str_iequals(tag_name, "th")) {
|
||||||
if (table_col > 0) {
|
if (table_col > 0) {
|
||||||
@@ -1314,10 +1527,13 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name, "br")) emit_br();
|
else if (str_iequals(tag_name, "br")) emit_br();
|
||||||
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) emit_br();
|
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) {
|
||||||
|
emit_br();
|
||||||
|
}
|
||||||
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
||||||
else if (str_iequals(tag_name, "li")) {
|
else if (str_iequals(tag_name, "li")) {
|
||||||
emit_br(); RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
|
emit_br();
|
||||||
|
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
|
||||||
el->tag = TAG_NONE; if (list_depth > 0 && list_type[list_depth - 1] == 1) { char num[16]; itoa(list_index[list_depth - 1]++, num); int l=0; while(num[l]) { el->content[l] = num[l]; l++; } el->content[l++] = '.'; el->content[l++] = ' '; el->content[l] = 0; }
|
el->tag = TAG_NONE; if (list_depth > 0 && list_type[list_depth - 1] == 1) { char num[16]; itoa(list_index[list_depth - 1]++, num); int l=0; while(num[l]) { el->content[l] = num[l]; l++; } el->content[l++] = '.'; el->content[l++] = ' '; el->content[l] = 0; }
|
||||||
else if (list_depth > 0 && list_type[list_depth - 1] == 2) { el->content[0] = ' '; el->content[1] = 0; } else { el->content[0] = (char)130; el->content[1] = ' '; el->content[2] = 0; }
|
else if (list_depth > 0 && list_type[list_depth - 1] == 2) { el->content[0] = ' '; el->content[1] = 0; } else { el->content[0] = (char)130; el->content[1] = ' '; el->content[2] = 0; }
|
||||||
el->w = ui_get_string_width_scaled(el->content, current_scale); el->h = ui_get_font_height_scaled(current_scale); el->color = current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
|
el->w = ui_get_string_width_scaled(el->content, current_scale); el->h = ui_get_font_height_scaled(current_scale); el->color = current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
|
||||||
@@ -1330,11 +1546,13 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
else if (str_iequals(tag_name, "hr")) { emit_br(); RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_HR; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth; el->h = 10; el->centered = true; emit_br(); }
|
else if (str_iequals(tag_name, "hr")) { emit_br(); RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_HR; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth; el->h = 10; el->centered = true; emit_br(); }
|
||||||
else if (str_iequals(tag_name, "img")) {
|
else if (str_iequals(tag_name, "img")) {
|
||||||
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
||||||
|
char *width_str = str_istrstr(attr_buf, "width=\""); if (width_str) { int w = atoi(width_str + 7); if (w > 0) el->attr_w = w; }
|
||||||
char *src = str_istrstr(attr_buf, "src=\""); if (src) { src += 5; int l = 0; while(src[l] && src[l] != '"' && l < 255) { el->attr_value[l] = src[l]; l++; } el->attr_value[l] = 0; el->img_loading = true; }
|
char *src = str_istrstr(attr_buf, "src=\""); if (src) { src += 5; int l = 0; while(src[l] && src[l] != '"' && l < 255) { el->attr_value[l] = src[l]; l++; } el->attr_value[l] = 0; el->img_loading = true; }
|
||||||
el->blockquote_depth = blockquote_depth;
|
el->blockquote_depth = blockquote_depth;
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name, "input")) {
|
else if (str_iequals(tag_name, "input")) {
|
||||||
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
||||||
|
char *size_str = str_istrstr(attr_buf, "size=\""); if (size_str) { int sz = atoi(size_str + 6); if (sz > 0) el->w = sz * 8; }
|
||||||
char *val = str_istrstr(attr_buf, "value=\""); char *ph = str_istrstr(attr_buf, "placeholder=\""); char *type = str_istrstr(attr_buf, "type=\""); char *name = str_istrstr(attr_buf, "name=\"");
|
char *val = str_istrstr(attr_buf, "value=\""); char *ph = str_istrstr(attr_buf, "placeholder=\""); char *type = str_istrstr(attr_buf, "type=\""); char *name = str_istrstr(attr_buf, "name=\"");
|
||||||
el->form_id = current_form_id; el->input_cursor = 0; el->input_scroll = 0; int l; l = 0; while(current_form_action[l]) { el->form_action[l] = current_form_action[l]; l++; } el->form_action[l] = 0;
|
el->form_id = current_form_id; el->input_cursor = 0; el->input_scroll = 0; int l; l = 0; while(current_form_action[l]) { el->form_action[l] = current_form_action[l]; l++; } el->form_action[l] = 0;
|
||||||
if (name) { name += 6; l = 0; while(name[l] && name[l] != '"' && l < 63) { el->input_name[l] = name[l]; l++; } el->input_name[l] = 0; } else { l = 0; const char *dn = "q"; while(dn[l]) { el->input_name[l] = dn[l]; l++; } el->input_name[l] = 0; }
|
if (name) { name += 6; l = 0; while(name[l] && name[l] != '"' && l < 63) { el->input_name[l] = name[l]; l++; } el->input_name[l] = 0; } else { l = 0; const char *dn = "q"; while(dn[l]) { el->input_name[l] = dn[l]; l++; } el->input_name[l] = 0; }
|
||||||
@@ -1420,7 +1638,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
}
|
}
|
||||||
emit_br();
|
emit_br();
|
||||||
|
|
||||||
inc_parse_offset = i; inc_center_depth = center_depth; inc_table_depth = table_depth; inc_blockquote_depth = blockquote_depth;
|
inc_parse_offset = i; inc_center_depth = center_depth; inc_table_depth = table_depth; inc_table_float_depth = table_float_depth; inc_blockquote_depth = blockquote_depth;
|
||||||
inc_is_bold = is_bold; inc_is_italic = is_italic; inc_is_underline = is_underline; inc_current_color = current_color;
|
inc_is_bold = is_bold; inc_is_italic = is_italic; inc_is_underline = is_underline; inc_current_color = current_color;
|
||||||
{ int k=0; while(current_link[k]) { inc_current_link[k] = current_link[k]; k++; } inc_current_link[k] = 0; }
|
{ int k=0; while(current_link[k]) { inc_current_link[k] = current_link[k]; k++; } inc_current_link[k] = 0; }
|
||||||
inc_current_scale = current_scale; inc_base_scale = base_scale; inc_is_space_pending = is_space_pending;
|
inc_current_scale = current_scale; inc_base_scale = base_scale; inc_is_space_pending = is_space_pending;
|
||||||
@@ -1431,65 +1649,66 @@ static void parse_html_incremental(const char *html, int safe_len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void browser_paint(void) {
|
static void browser_paint(void) {
|
||||||
|
browser_ctx.user_data = (void *)win_browser;
|
||||||
ui_draw_rect(win_browser, 0, 0, win_w, win_h, COLOR_BG);
|
ui_draw_rect(win_browser, 0, 0, win_w, win_h, COLOR_BG);
|
||||||
|
|
||||||
for (int i = 0; i < element_count; i++) {
|
for (int i = 0; i < element_count; i++) {
|
||||||
RenderElement *el = &elements[i];
|
RenderElement *el = &elements[i];
|
||||||
int draw_y = el->y - scroll_y + URL_BAR_H;
|
int draw_y = el->y - scroll_y + URL_BAR_H;
|
||||||
if (draw_y < URL_BAR_H - 400 || draw_y > win_h) continue;
|
int el_h = el->h;
|
||||||
|
if (el->tag == TAG_IMG && el->img_h > el_h) el_h = el->img_h;
|
||||||
|
if (draw_y + el_h < URL_BAR_H || draw_y > win_h) continue;
|
||||||
|
|
||||||
|
if (el->tag == 8) {
|
||||||
|
if (el->color != 0 && el->color != COLOR_BG) {
|
||||||
|
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h + 10, el->color);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (el->tag == TAG_IMG) {
|
if (el->tag == TAG_IMG) {
|
||||||
uint32_t *pixels = el->img_pixels;
|
uint32_t *pixels = el->img_pixels;
|
||||||
if (el->img_frames) pixels = el->img_frames[el->img_current_frame];
|
if (el->img_frames) pixels = el->img_frames[el->img_current_frame];
|
||||||
if (pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, pixels);
|
if (pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, pixels);
|
||||||
else ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
|
else ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
|
||||||
} else if (el->tag == TAG_INPUT) {
|
} else if (el->tag == TAG_INPUT) {
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFFFFFFF);
|
browser_ctx.use_light_theme = true;
|
||||||
uint32_t border = (focused_element == i) ? 0xFF0000FF : 0xFF808080;
|
char visible[128];
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, border);
|
|
||||||
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, border);
|
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, border);
|
|
||||||
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, border);
|
|
||||||
|
|
||||||
char visible[64];
|
|
||||||
int v_len = 0;
|
int v_len = 0;
|
||||||
int max_v = (el->w - 10) / 8;
|
int max_v = (el->w - 10) / 8;
|
||||||
if (max_v > 63) max_v = 63;
|
if (max_v > 127) max_v = 127;
|
||||||
for (int k = el->input_scroll; el->attr_value[k] && v_len < max_v; k++) {
|
for (int k = el->input_scroll; el->attr_value[k] && v_len < max_v; k++) {
|
||||||
visible[v_len++] = el->attr_value[k];
|
visible[v_len++] = el->attr_value[k];
|
||||||
}
|
}
|
||||||
visible[v_len] = 0;
|
visible[v_len] = 0;
|
||||||
ui_draw_string(win_browser, el->x + 5, draw_y + 2, visible, (focused_element == i) ? 0xFF000000 : 0xFF808080);
|
|
||||||
|
widget_textbox_t tb;
|
||||||
if (focused_element == i) {
|
widget_textbox_init(&tb, el->x, draw_y, el->w, el->h, visible, max_v);
|
||||||
int cursor_pos = el->input_cursor - el->input_scroll;
|
tb.cursor_pos = el->input_cursor - el->input_scroll;
|
||||||
if (cursor_pos >= 0 && cursor_pos < max_v) {
|
if (tb.cursor_pos < 0) tb.cursor_pos = 0;
|
||||||
char sub[64];
|
tb.focused = (focused_element == i);
|
||||||
int k;
|
widget_textbox_draw(&browser_ctx, &tb);
|
||||||
for (k = 0; k < cursor_pos && visible[k]; k++) sub[k] = visible[k];
|
browser_ctx.use_light_theme = false;
|
||||||
sub[k] = 0;
|
|
||||||
int cx = ui_get_string_width(sub);
|
|
||||||
ui_draw_rect(win_browser, el->x + 5 + cx, draw_y + 16, 8, 2, 0xFF000000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (el->tag == TAG_BUTTON) {
|
} else if (el->tag == TAG_BUTTON) {
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFDDDDDD);
|
browser_ctx.use_light_theme = true;
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, 0xFFFFFFFF);
|
widget_button_t btn;
|
||||||
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, 0xFF888888);
|
widget_button_init(&btn, el->x, draw_y, el->w, el->h, el->attr_value);
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, 0xFFFFFFFF);
|
widget_button_draw(&browser_ctx, &btn);
|
||||||
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, 0xFF888888);
|
browser_ctx.use_light_theme = false;
|
||||||
ui_draw_string(win_browser, el->x + 10, draw_y + 4, el->attr_value, 0xFF000000);
|
|
||||||
} else if (el->tag == TAG_RADIO) {
|
} else if (el->tag == TAG_RADIO) {
|
||||||
ui_draw_rounded_rect_filled(win_browser, el->x, draw_y, el->w, el->h, el->w/2, 0xFF808080);
|
browser_ctx.use_light_theme = true;
|
||||||
ui_draw_rounded_rect_filled(win_browser, el->x + 1, draw_y + 1, el->w - 2, el->h - 2, (el->w-2)/2, 0xFFFFFFFF);
|
widget_checkbox_t cb;
|
||||||
if (el->checked) {
|
widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", true);
|
||||||
ui_draw_rounded_rect_filled(win_browser, el->x + 4, draw_y + 4, el->w - 8, el->h - 8, (el->w-8)/2, 0xFF000000);
|
cb.checked = el->checked;
|
||||||
}
|
widget_checkbox_draw(&browser_ctx, &cb);
|
||||||
|
browser_ctx.use_light_theme = false;
|
||||||
} else if (el->tag == TAG_CHECKBOX) {
|
} else if (el->tag == TAG_CHECKBOX) {
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFF808080);
|
browser_ctx.use_light_theme = true;
|
||||||
ui_draw_rect(win_browser, el->x + 1, draw_y + 1, el->w - 2, el->h - 2, 0xFFFFFFFF);
|
widget_checkbox_t cb;
|
||||||
if (el->checked) {
|
widget_checkbox_init(&cb, el->x, draw_y, el->w, el->h, "", false);
|
||||||
ui_draw_rect(win_browser, el->x + 4, draw_y + 4, el->w - 8, el->h - 8, 0xFF000000);
|
cb.checked = el->checked;
|
||||||
}
|
widget_checkbox_draw(&browser_ctx, &cb);
|
||||||
|
browser_ctx.use_light_theme = false;
|
||||||
} else if (el->tag == TAG_HR) {
|
} else if (el->tag == TAG_HR) {
|
||||||
ui_draw_rect(win_browser, el->x, draw_y + el->h / 2, el->w, 2, 0xFF888888);
|
ui_draw_rect(win_browser, el->x, draw_y + el->h / 2, el->w, 2, 0xFF888888);
|
||||||
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
|
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
|
||||||
@@ -1504,47 +1723,37 @@ static void browser_paint(void) {
|
|||||||
ui_draw_string_scaled(win_browser, el->x + 1, draw_y - 1, el->content, el->color, el->scale);
|
ui_draw_string_scaled(win_browser, el->x + 1, draw_y - 1, el->content, el->color, el->scale);
|
||||||
}
|
}
|
||||||
if (el->underline) {
|
if (el->underline) {
|
||||||
int fh = ui_get_font_height_scaled(el->scale);
|
int fh = el->h;
|
||||||
ui_draw_rect(win_browser, el->x, draw_y + fh - 1, el->w, 1, el->color);
|
ui_draw_rect(win_browser, el->x, draw_y + fh - 1, el->w, 1, el->color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_draw_rect(win_browser, 0, 0, win_w, URL_BAR_H, COLOR_URL_BAR);
|
ui_draw_rect(win_browser, 0, 0, win_w, URL_BAR_H, COLOR_URL_BAR);
|
||||||
ui_draw_string(win_browser, 10, 8, url_input_buffer, COLOR_URL_TEXT);
|
|
||||||
if (focused_element == -1) {
|
widget_textbox_init(&url_tb, 10, 5, win_w - SCROLL_BAR_W - BTN_W*2 - BTN_PAD*2 - 20, 20, url_input_buffer, 511);
|
||||||
char sub[512];
|
url_tb.cursor_pos = url_cursor;
|
||||||
int k;
|
url_tb.focused = (focused_element == -1);
|
||||||
for (k = 0; k < url_cursor && url_input_buffer[k]; k++) sub[k] = url_input_buffer[k];
|
widget_textbox_draw(&browser_ctx, &url_tb);
|
||||||
sub[k] = 0;
|
|
||||||
int cx = ui_get_string_width(sub);
|
|
||||||
ui_draw_rect(win_browser, 10 + cx, 22, 8, 2, COLOR_URL_TEXT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back button
|
// Back button
|
||||||
int btn_y = (URL_BAR_H - BTN_H) / 2;
|
int btn_y = (URL_BAR_H - BTN_H) / 2;
|
||||||
uint32_t back_col = history_count > 0 ? 0xFF505050 : 0xFF404040;
|
widget_button_init(&btn_back, BACK_BTN_X, btn_y, BTN_W, BTN_H, "<");
|
||||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, BTN_W, BTN_H, back_col);
|
widget_button_draw(&browser_ctx, &btn_back);
|
||||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, BTN_W, 1, 0xFF606060);
|
|
||||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y, 1, BTN_H, 0xFF606060);
|
|
||||||
ui_draw_rect(win_browser, BACK_BTN_X, btn_y + BTN_H - 1, BTN_W, 1, 0xFF202020);
|
|
||||||
ui_draw_rect(win_browser, BACK_BTN_X + BTN_W - 1, btn_y, 1, BTN_H, 0xFF202020);
|
|
||||||
ui_draw_string(win_browser, BACK_BTN_X + 10, btn_y + 4, "<", history_count > 0 ? 0xFFFFFFFF : 0xFF808080);
|
|
||||||
|
|
||||||
// Home button
|
// Home button
|
||||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, BTN_W, BTN_H, 0xFF505050);
|
widget_button_init(&btn_home, HOME_BTN_X, btn_y, BTN_W, BTN_H, "H");
|
||||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, BTN_W, 1, 0xFF606060);
|
widget_button_draw(&browser_ctx, &btn_home);
|
||||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y, 1, BTN_H, 0xFF606060);
|
|
||||||
ui_draw_rect(win_browser, HOME_BTN_X, btn_y + BTN_H - 1, BTN_W, 1, 0xFF202020);
|
|
||||||
ui_draw_rect(win_browser, HOME_BTN_X + BTN_W - 1, btn_y, 1, BTN_H, 0xFF202020);
|
|
||||||
ui_draw_string(win_browser, HOME_BTN_X + 10, btn_y + 4, "H", 0xFFFFFFFF);
|
|
||||||
|
|
||||||
// Scroll bar
|
// Scroll bar
|
||||||
ui_draw_rect(win_browser, win_w - SCROLL_BAR_W, URL_BAR_H, SCROLL_BAR_W, win_h - URL_BAR_H, COLOR_SCROLL_BG);
|
int viewport_h = win_h - URL_BAR_H;
|
||||||
int thumb_h = (win_h - URL_BAR_H) * (win_h - URL_BAR_H) / (total_content_height > win_h ? total_content_height : win_h);
|
browser_scrollbar.x = win_w - SCROLL_BAR_W;
|
||||||
if (thumb_h < 20) thumb_h = 20;
|
browser_scrollbar.y = URL_BAR_H;
|
||||||
int thumb_y = URL_BAR_H + (scroll_y * (win_h - URL_BAR_H - thumb_h)) / (total_content_height > win_h - URL_BAR_H ? total_content_height - (win_h - URL_BAR_H) : 1);
|
browser_scrollbar.w = SCROLL_BAR_W;
|
||||||
ui_draw_rect(win_browser, win_w - SCROLL_BAR_W + 2, thumb_y, SCROLL_BAR_W - 4, thumb_h, COLOR_SCROLL_BTN);
|
browser_scrollbar.h = viewport_h;
|
||||||
|
browser_scrollbar.on_scroll = browser_on_scroll;
|
||||||
|
widget_scrollbar_update(&browser_scrollbar, total_content_height, scroll_y);
|
||||||
|
widget_scrollbar_draw(&browser_ctx, &browser_scrollbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void navigate(const char *url) {
|
static void navigate(const char *url) {
|
||||||
@@ -1593,34 +1802,53 @@ int main(int argc, char **argv) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ev.type == GUI_EVENT_CLICK) {
|
else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP || ev.type == GUI_EVENT_MOUSE_MOVE) {
|
||||||
int mx = ev.arg1;
|
int mx = ev.arg1;
|
||||||
if (mx >= win_w - SCROLL_BAR_W) {
|
int my = ev.arg2;
|
||||||
if (ev.arg2 < URL_BAR_H + (win_h - URL_BAR_H)/2) scroll_y -= 100;
|
bool is_down = (ev.type == GUI_EVENT_MOUSE_DOWN || (ev.type == GUI_EVENT_MOUSE_MOVE && browser_scrollbar.is_dragging));
|
||||||
else scroll_y += 100;
|
bool is_click = (ev.type == GUI_EVENT_CLICK);
|
||||||
if (scroll_y < 0) scroll_y = 0;
|
|
||||||
needs_repaint = true;
|
int old_scroll = scroll_y;
|
||||||
|
bool was_dragging = browser_scrollbar.is_dragging;
|
||||||
|
if (widget_scrollbar_handle_mouse(&browser_scrollbar, mx, my, is_down, &browser_ctx)) {
|
||||||
|
if (scroll_y != old_scroll || browser_scrollbar.is_dragging || was_dragging) {
|
||||||
|
needs_repaint = true;
|
||||||
|
}
|
||||||
|
if (ev.type == GUI_EVENT_MOUSE_MOVE) continue;
|
||||||
|
if (is_down || is_click) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.type != GUI_EVENT_CLICK && ev.type != GUI_EVENT_MOUSE_DOWN) continue;
|
||||||
|
|
||||||
|
if (my < URL_BAR_H) {
|
||||||
|
if (widget_textbox_handle_mouse(&url_tb, mx, my, is_click, NULL)) {
|
||||||
|
focused_element = -1;
|
||||||
|
needs_repaint = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (widget_button_handle_mouse(&btn_back, mx, my, is_down, is_click, NULL)) {
|
||||||
|
if (is_click && history_count > 0) {
|
||||||
|
history_count--;
|
||||||
|
int j=0; while(history_stack[history_count][j]) { url_input_buffer[j] = history_stack[history_count][j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
|
||||||
|
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
|
||||||
|
}
|
||||||
|
needs_repaint = true; continue;
|
||||||
|
}
|
||||||
|
if (widget_button_handle_mouse(&btn_home, mx, my, is_down, is_click, NULL)) {
|
||||||
|
if (is_click) {
|
||||||
|
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
|
||||||
|
const char *home = "http://find.boreddev.nl";
|
||||||
|
int j=0; while(home[j]) { url_input_buffer[j] = home[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
|
||||||
|
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
|
||||||
|
}
|
||||||
|
needs_repaint = true; continue;
|
||||||
|
}
|
||||||
|
if (is_click) {
|
||||||
|
focused_element = -1; needs_repaint = true;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ev.arg2 < URL_BAR_H) {
|
my = ev.arg2 - URL_BAR_H + scroll_y;
|
||||||
// Check back button
|
|
||||||
if (mx >= BACK_BTN_X && mx < BACK_BTN_X + BTN_W && history_count > 0) {
|
|
||||||
history_count--;
|
|
||||||
int j=0; while(history_stack[history_count][j]) { url_input_buffer[j] = history_stack[history_count][j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
|
|
||||||
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
|
|
||||||
needs_repaint = true; continue;
|
|
||||||
}
|
|
||||||
// Check home button
|
|
||||||
if (mx >= HOME_BTN_X && mx < HOME_BTN_X + BTN_W) {
|
|
||||||
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
|
|
||||||
const char *home = "http://find.boreddev.nl";
|
|
||||||
int j=0; while(home[j]) { url_input_buffer[j] = home[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
|
|
||||||
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
|
|
||||||
needs_repaint = true; continue;
|
|
||||||
}
|
|
||||||
focused_element = -1; needs_repaint = true; continue;
|
|
||||||
}
|
|
||||||
int my = ev.arg2 - URL_BAR_H + scroll_y;
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (int i = 0; i < element_count; i++) {
|
for (int i = 0; i < element_count; i++) {
|
||||||
RenderElement *el = &elements[i];
|
RenderElement *el = &elements[i];
|
||||||
@@ -1637,15 +1865,38 @@ int main(int argc, char **argv) {
|
|||||||
if (el->tag == TAG_RADIO) {
|
if (el->tag == TAG_RADIO) {
|
||||||
for (int k = 0; k < element_count; k++) {
|
for (int k = 0; k < element_count; k++) {
|
||||||
if (elements[k].tag == TAG_RADIO && elements[k].form_id == el->form_id && str_iequals(elements[k].input_name, el->input_name)) {
|
if (elements[k].tag == TAG_RADIO && elements[k].form_id == el->form_id && str_iequals(elements[k].input_name, el->input_name)) {
|
||||||
elements[k].checked = false;
|
if (elements[k].checked) {
|
||||||
|
elements[k].checked = false;
|
||||||
|
widget_checkbox_t cb;
|
||||||
|
widget_checkbox_init(&cb, elements[k].x, elements[k].y - scroll_y + URL_BAR_H, elements[k].w, elements[k].h, "", true);
|
||||||
|
cb.checked = false;
|
||||||
|
browser_ctx.use_light_theme = true;
|
||||||
|
widget_checkbox_draw(&browser_ctx, &cb);
|
||||||
|
browser_ctx.use_light_theme = false;
|
||||||
|
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
el->checked = true;
|
el->checked = true;
|
||||||
needs_repaint = true; found = true; break;
|
widget_checkbox_t cb;
|
||||||
|
widget_checkbox_init(&cb, el->x, el->y - scroll_y + URL_BAR_H, el->w, el->h, "", true);
|
||||||
|
cb.checked = true;
|
||||||
|
browser_ctx.use_light_theme = true;
|
||||||
|
widget_checkbox_draw(&browser_ctx, &cb);
|
||||||
|
browser_ctx.use_light_theme = false;
|
||||||
|
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||||
|
found = true; break;
|
||||||
}
|
}
|
||||||
if (el->tag == TAG_CHECKBOX) {
|
if (el->tag == TAG_CHECKBOX) {
|
||||||
el->checked = !el->checked;
|
el->checked = !el->checked;
|
||||||
needs_repaint = true; found = true; break;
|
widget_checkbox_t cb;
|
||||||
|
widget_checkbox_init(&cb, el->x, el->y - scroll_y + URL_BAR_H, el->w, el->h, "", false);
|
||||||
|
cb.checked = el->checked;
|
||||||
|
browser_ctx.use_light_theme = true;
|
||||||
|
widget_checkbox_draw(&browser_ctx, &cb);
|
||||||
|
browser_ctx.use_light_theme = false;
|
||||||
|
ui_mark_dirty(win_browser, cb.x, cb.y, cb.w, cb.h);
|
||||||
|
found = true; break;
|
||||||
}
|
}
|
||||||
if (el->tag == TAG_BUTTON) {
|
if (el->tag == TAG_BUTTON) {
|
||||||
int fid = el->form_id;
|
int fid = el->form_id;
|
||||||
@@ -1759,6 +2010,7 @@ int main(int argc, char **argv) {
|
|||||||
if (c == 13 || c == 10) {
|
if (c == 13 || c == 10) {
|
||||||
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
|
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
|
||||||
navigate(url_input_buffer); scroll_y = 0;
|
navigate(url_input_buffer); scroll_y = 0;
|
||||||
|
needs_repaint = true;
|
||||||
}
|
}
|
||||||
else if (c == 19) { if (url_cursor > 0) url_cursor--; }
|
else if (c == 19) { if (url_cursor > 0) url_cursor--; }
|
||||||
else if (c == 20) { int len = 0; while(url_input_buffer[len]) len++; if (url_cursor < len) url_cursor++; }
|
else if (c == 20) { int len = 0; while(url_input_buffer[len]) len++; if (url_cursor < len) url_cursor++; }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||||
#include "syscall.h"
|
#include "syscall.h"
|
||||||
#include "libui.h"
|
#include "libui.h"
|
||||||
|
#include "../../wm/libwidget.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "stdlib.h"
|
#include "stdlib.h"
|
||||||
|
|
||||||
@@ -25,6 +26,33 @@ static long long calc_decimal_divisor = 10;
|
|||||||
static char display_buffer[1024];
|
static char display_buffer[1024];
|
||||||
static int display_buf_len = 0;
|
static int display_buf_len = 0;
|
||||||
|
|
||||||
|
static widget_button_t buttons[20];
|
||||||
|
static const char *labels[] = {
|
||||||
|
"C", "sqr", "rt", "/",
|
||||||
|
"7", "8", "9", "*",
|
||||||
|
"4", "5", "6", "-",
|
||||||
|
"1", "2", "3", "+",
|
||||||
|
"0", ".", "BS", "="
|
||||||
|
};
|
||||||
|
|
||||||
|
static void calc_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||||
|
ui_draw_rect((ui_window_t)user_data, x, y, w, h, color);
|
||||||
|
}
|
||||||
|
static void calc_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||||
|
ui_draw_rounded_rect_filled((ui_window_t)user_data, x, y, w, h, r, color);
|
||||||
|
}
|
||||||
|
static void calc_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||||
|
ui_draw_string((ui_window_t)user_data, x, y, str, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static widget_context_t calc_ctx = {
|
||||||
|
.user_data = 0,
|
||||||
|
.draw_rect = calc_draw_rect,
|
||||||
|
.draw_rounded_rect_filled = calc_draw_rounded_rect_filled,
|
||||||
|
.draw_string = calc_draw_string,
|
||||||
|
.mark_dirty = NULL
|
||||||
|
};
|
||||||
|
|
||||||
static long long isqrt(long long n) {
|
static long long isqrt(long long n) {
|
||||||
if (n < 0) return -1;
|
if (n < 0) return -1;
|
||||||
if (n == 0) return 0;
|
if (n == 0) return 0;
|
||||||
@@ -97,27 +125,8 @@ static void calculator_paint(void) {
|
|||||||
int text_x = w - 15 - text_w;
|
int text_x = w - 15 - text_w;
|
||||||
ui_draw_string(win_calculator, text_x, 18, display_buffer, COLOR_DARK_TEXT);
|
ui_draw_string(win_calculator, text_x, 18, display_buffer, COLOR_DARK_TEXT);
|
||||||
|
|
||||||
const char *labels[] = {
|
|
||||||
"C", "sqr", "rt", "/",
|
|
||||||
"7", "8", "9", "*",
|
|
||||||
"4", "5", "6", "-",
|
|
||||||
"1", "2", "3", "+",
|
|
||||||
"0", ".", "BS", "="
|
|
||||||
};
|
|
||||||
|
|
||||||
int bw = 35;
|
|
||||||
int bh = 25;
|
|
||||||
int gap = 5;
|
|
||||||
int start_x = 10;
|
|
||||||
int start_y = 40;
|
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
int r = i / 4;
|
widget_button_draw(&calc_ctx, &buttons[i]);
|
||||||
int c = i % 4;
|
|
||||||
ui_draw_rounded_rect_filled(win_calculator, start_x + c*(bw+gap), start_y + r*(bh+gap), bw, bh, 4, COLOR_DARK_BORDER);
|
|
||||||
int label_x = start_x + c*(bw+gap) + 5;
|
|
||||||
int label_y = start_y + r*(bh+gap) + 9;
|
|
||||||
ui_draw_string(win_calculator, label_x, label_y, labels[i], COLOR_DARK_TEXT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,105 +144,93 @@ static void do_op(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void calculator_click(int x, int y) {
|
static void handle_button_click(int idx) {
|
||||||
int bw = 35;
|
const char *labels_map[] = {
|
||||||
int bh = 25;
|
"C", "s", "r", "/",
|
||||||
int gap = 5;
|
"7", "8", "9", "*",
|
||||||
int start_x = 10;
|
"4", "5", "6", "-",
|
||||||
int start_y = 35; // Matches the hitboxes
|
"1", "2", "3", "+",
|
||||||
|
"0", ".", "B", "="
|
||||||
|
};
|
||||||
|
char lbl = labels_map[idx][0];
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++) {
|
if (lbl >= '0' && lbl <= '9') {
|
||||||
int r = i / 4;
|
if (calc_new_entry || calc_error) {
|
||||||
int c = i % 4;
|
calc_curr = (lbl - '0') * SCALE;
|
||||||
int bx = start_x + c*(bw+gap);
|
calc_new_entry = false;
|
||||||
int by = start_y + r*(bh+gap);
|
calc_decimal_mode = false;
|
||||||
|
} else {
|
||||||
if (x >= bx && x < bx + bw && y >= by && y < by + bh) {
|
if (calc_decimal_mode) {
|
||||||
const char *labels[] = {
|
if (calc_decimal_divisor <= SCALE) {
|
||||||
"C", "s", "r", "/",
|
long long digit_val = ((long long)(lbl - '0') * SCALE) / calc_decimal_divisor;
|
||||||
"7", "8", "9", "*",
|
if (calc_curr >= 0) calc_curr += digit_val;
|
||||||
"4", "5", "6", "-",
|
else calc_curr -= digit_val;
|
||||||
"1", "2", "3", "+",
|
calc_decimal_divisor *= 10;
|
||||||
"0", ".", "B", "="
|
}
|
||||||
};
|
|
||||||
char lbl = labels[i][0];
|
|
||||||
|
|
||||||
if (lbl >= '0' && lbl <= '9') {
|
|
||||||
if (calc_new_entry || calc_error) {
|
|
||||||
calc_curr = (lbl - '0') * SCALE;
|
|
||||||
calc_new_entry = false;
|
|
||||||
calc_decimal_mode = false;
|
|
||||||
} else {
|
|
||||||
if (calc_decimal_mode) {
|
|
||||||
if (calc_decimal_divisor <= SCALE) {
|
|
||||||
long long digit_val = ((long long)(lbl - '0') * SCALE) / calc_decimal_divisor;
|
|
||||||
if (calc_curr >= 0) calc_curr += digit_val;
|
|
||||||
else calc_curr -= digit_val;
|
|
||||||
calc_decimal_divisor *= 10;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (calc_curr >= 0) calc_curr = calc_curr * 10 + (lbl - '0') * SCALE;
|
|
||||||
else calc_curr = calc_curr * 10 - (lbl - '0') * SCALE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
calc_error = false;
|
|
||||||
} else if (lbl == '.') {
|
|
||||||
if (calc_new_entry) {
|
|
||||||
calc_curr = 0;
|
|
||||||
calc_new_entry = false;
|
|
||||||
}
|
|
||||||
if (!calc_decimal_mode) {
|
|
||||||
calc_decimal_mode = true;
|
|
||||||
calc_decimal_divisor = 10;
|
|
||||||
}
|
|
||||||
} else if (lbl == 'C') {
|
|
||||||
calc_curr = 0; calc_acc = 0; calc_op = 0;
|
|
||||||
calc_new_entry = true; calc_error = false; calc_decimal_mode = false;
|
|
||||||
} else if (lbl == 'B') {
|
|
||||||
if (!calc_new_entry && !calc_error) {
|
|
||||||
if (calc_decimal_mode) {
|
|
||||||
if (calc_decimal_divisor > 10) {
|
|
||||||
calc_decimal_divisor /= 10;
|
|
||||||
long long unit = SCALE / calc_decimal_divisor;
|
|
||||||
calc_curr = (calc_curr / (unit * 10)) * (unit * 10);
|
|
||||||
} else {
|
|
||||||
calc_decimal_mode = false;
|
|
||||||
calc_decimal_divisor = 10;
|
|
||||||
calc_curr = (calc_curr / SCALE) * SCALE;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
calc_curr = (calc_curr / SCALE / 10) * SCALE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (lbl == 's') { // sqr
|
|
||||||
calc_curr = (calc_curr * calc_curr) / SCALE; calc_new_entry = true;
|
|
||||||
} else if (lbl == 'r') { // rt
|
|
||||||
long long s = isqrt(calc_curr);
|
|
||||||
if (s == -1) calc_error = true;
|
|
||||||
else calc_curr = s * 1000;
|
|
||||||
calc_new_entry = true;
|
|
||||||
} else if (lbl == '=') {
|
|
||||||
do_op();
|
|
||||||
calc_curr = calc_acc; calc_op = 0; calc_new_entry = true; calc_decimal_mode = false;
|
|
||||||
} else {
|
} else {
|
||||||
if (!calc_new_entry) {
|
if (calc_curr >= 0) calc_curr = calc_curr * 10 + (lbl - '0') * SCALE;
|
||||||
if (calc_op) do_op();
|
else calc_curr = calc_curr * 10 - (lbl - '0') * SCALE;
|
||||||
else calc_acc = calc_curr;
|
|
||||||
}
|
|
||||||
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update_display();
|
|
||||||
calculator_paint();
|
|
||||||
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
calc_error = false;
|
||||||
|
} else if (lbl == '.') {
|
||||||
|
if (calc_new_entry) {
|
||||||
|
calc_curr = 0;
|
||||||
|
calc_new_entry = false;
|
||||||
|
}
|
||||||
|
if (!calc_decimal_mode) {
|
||||||
|
calc_decimal_mode = true;
|
||||||
|
calc_decimal_divisor = 10;
|
||||||
|
}
|
||||||
|
} else if (lbl == 'C') {
|
||||||
|
calc_curr = 0; calc_acc = 0; calc_op = 0;
|
||||||
|
calc_new_entry = true; calc_error = false; calc_decimal_mode = false;
|
||||||
|
} else if (lbl == 'B') {
|
||||||
|
if (!calc_new_entry && !calc_error) {
|
||||||
|
if (calc_decimal_mode) {
|
||||||
|
if (calc_decimal_divisor > 10) {
|
||||||
|
calc_decimal_divisor /= 10;
|
||||||
|
long long unit = SCALE / calc_decimal_divisor;
|
||||||
|
calc_curr = (calc_curr / (unit * 10)) * (unit * 10);
|
||||||
|
} else {
|
||||||
|
calc_decimal_mode = false;
|
||||||
|
calc_decimal_divisor = 10;
|
||||||
|
calc_curr = (calc_curr / SCALE) * SCALE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
calc_curr = (calc_curr / SCALE / 10) * SCALE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (lbl == 's') { // sqr
|
||||||
|
calc_curr = (calc_curr * calc_curr) / SCALE; calc_new_entry = true;
|
||||||
|
} else if (lbl == 'r') { // rt
|
||||||
|
long long s = isqrt(calc_curr);
|
||||||
|
if (s == -1) calc_error = true;
|
||||||
|
else calc_curr = s * 1000;
|
||||||
|
calc_new_entry = true;
|
||||||
|
} else if (lbl == '=') {
|
||||||
|
do_op();
|
||||||
|
calc_curr = calc_acc; calc_op = 0; calc_new_entry = true; calc_decimal_mode = false;
|
||||||
|
} else {
|
||||||
|
if (!calc_new_entry) {
|
||||||
|
if (calc_op) do_op();
|
||||||
|
else calc_acc = calc_curr;
|
||||||
|
}
|
||||||
|
calc_op = lbl; calc_new_entry = true; calc_decimal_mode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
win_calculator = ui_window_create("Calculator", 200, 200, 180, 230);
|
win_calculator = ui_window_create("Calculator", 200, 200, 180, 230);
|
||||||
|
calc_ctx.user_data = (void *)win_calculator;
|
||||||
|
|
||||||
|
int bw = 35, bh = 25, gap = 5, start_x = 10, start_y = 40;
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
int r = i / 4;
|
||||||
|
int c = i % 4;
|
||||||
|
widget_button_init(&buttons[i], start_x + c*(bw+gap), start_y + r*(bh+gap), bw, bh, labels[i]);
|
||||||
|
}
|
||||||
|
|
||||||
calc_curr = 0;
|
calc_curr = 0;
|
||||||
calc_acc = 0;
|
calc_acc = 0;
|
||||||
calc_op = 0;
|
calc_op = 0;
|
||||||
@@ -249,8 +246,21 @@ int main(void) {
|
|||||||
if (ev.type == GUI_EVENT_PAINT) {
|
if (ev.type == GUI_EVENT_PAINT) {
|
||||||
calculator_paint();
|
calculator_paint();
|
||||||
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
||||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
} else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_MOUSE_UP) {
|
||||||
calculator_click(ev.arg1, ev.arg2);
|
bool needs_paint = false;
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
if (widget_button_handle_mouse(&buttons[i], ev.arg1, ev.arg2, ev.type == GUI_EVENT_MOUSE_DOWN, ev.type == GUI_EVENT_CLICK, NULL)) {
|
||||||
|
needs_paint = true;
|
||||||
|
if (ev.type == GUI_EVENT_CLICK) {
|
||||||
|
handle_button_click(i);
|
||||||
|
update_display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needs_paint) {
|
||||||
|
calculator_paint();
|
||||||
|
ui_mark_dirty(win_calculator, 0, 0, 180, 230);
|
||||||
|
}
|
||||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||||
sys_exit(0);
|
sys_exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
1768
src/userland/gui/grapher.c
Normal file
1768
src/userland/gui/grapher.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,9 @@
|
|||||||
#define COLOR_BLACK 0xFF000000
|
#define COLOR_BLACK 0xFF000000
|
||||||
#define COLOR_WHITE 0xFFFFFFFF
|
#define COLOR_WHITE 0xFFFFFFFF
|
||||||
#define COLOR_RED 0xFFFF0000
|
#define COLOR_RED 0xFFFF0000
|
||||||
#define COLOR_APPLE_GREEN 0xFF4CD964
|
#define COLOR_GREEN 0xFF4CD964
|
||||||
#define COLOR_APPLE_BLUE 0xFF007AFF
|
#define COLOR_BLUE 0xFF007AFF
|
||||||
#define COLOR_APPLE_YELLOW 0xFFFFCC00
|
#define COLOR_YELLOW 0xFFFFCC00
|
||||||
|
|
||||||
#define COLOR_DARK_BG 0xFF121212
|
#define COLOR_DARK_BG 0xFF121212
|
||||||
#define COLOR_DARK_PANEL 0xFF202020
|
#define COLOR_DARK_PANEL 0xFF202020
|
||||||
@@ -58,7 +58,7 @@ static void paint_paint(ui_window_t win) {
|
|||||||
ui_draw_rounded_rect_filled(win, canvas_x - 2, canvas_y - 2, CANVAS_W + 4, CANVAS_H + 4, 4, COLOR_DARK_BG);
|
ui_draw_rounded_rect_filled(win, canvas_x - 2, canvas_y - 2, CANVAS_W + 4, CANVAS_H + 4, 4, COLOR_DARK_BG);
|
||||||
ui_draw_rounded_rect_filled(win, 10, 0, 40, 230, 6, COLOR_DARK_PANEL);
|
ui_draw_rounded_rect_filled(win, 10, 0, 40, 230, 6, COLOR_DARK_PANEL);
|
||||||
|
|
||||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_WHITE};
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
int cy = 10 + (i * 25);
|
int cy = 10 + (i * 25);
|
||||||
ui_draw_rounded_rect_filled(win, 15, cy, 30, 20, 3, colors[i]);
|
ui_draw_rounded_rect_filled(win, 15, cy, 30, 20, 3, colors[i]);
|
||||||
@@ -202,7 +202,7 @@ static void paint_click(ui_window_t win, int x, int y) {
|
|||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
int cy = 10 + (i * 25);
|
int cy = 10 + (i * 25);
|
||||||
if (y >= cy && y < cy + 20) {
|
if (y >= cy && y < cy + 20) {
|
||||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_WHITE};
|
||||||
current_color = colors[i];
|
current_color = colors[i];
|
||||||
paint_paint(win);
|
paint_paint(win);
|
||||||
ui_mark_dirty(win, 0, 0, 380, 230);
|
ui_mark_dirty(win, 0, 0, 380, 230);
|
||||||
|
|||||||
@@ -9,16 +9,38 @@
|
|||||||
#include "stb_image_write.h"
|
#include "stb_image_write.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define GUI_CMD_GET_SCREEN_SIZE 17
|
#define GUI_CMD_GET_SCREEN_SIZE 50
|
||||||
#define GUI_CMD_GET_SCREENBUFFER 18
|
#define GUI_CMD_GET_SCREENBUFFER 51
|
||||||
#define GUI_CMD_SHOW_NOTIFICATION 19
|
#define GUI_CMD_SHOW_NOTIFICATION 52
|
||||||
#define GUI_CMD_GET_DATETIME 20
|
#define GUI_CMD_GET_DATETIME 53
|
||||||
|
|
||||||
|
#define PNG_WRITE_BUF_SIZE (8 * 1024 * 1024) // 8MB buffer to hold entire PNG
|
||||||
|
static uint8_t *png_output_buf = NULL;
|
||||||
|
static int png_output_idx = 0;
|
||||||
|
|
||||||
void png_write_func(void *context, void *data, int size) {
|
void png_write_func(void *context, void *data, int size) {
|
||||||
int fd = *(int*)context;
|
(void)context;
|
||||||
sys_write_fs(fd, data, size);
|
if (!png_output_buf) return;
|
||||||
|
|
||||||
|
if (png_output_idx + size < PNG_WRITE_BUF_SIZE) {
|
||||||
|
memcpy(png_output_buf + png_output_idx, data, size);
|
||||||
|
png_output_idx += size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global filename for helper functions
|
||||||
|
static char g_filename[128];
|
||||||
|
|
||||||
|
void append_num(int num, int digits) {
|
||||||
|
int len = 0; while (g_filename[len]) len++;
|
||||||
|
if (digits == 4) {
|
||||||
|
g_filename[len++] = '0' + (num / 1000) % 10;
|
||||||
|
g_filename[len++] = '0' + (num / 100) % 10;
|
||||||
|
}
|
||||||
|
g_filename[len++] = '0' + (num / 10) % 10;
|
||||||
|
g_filename[len++] = '0' + (num % 10);
|
||||||
|
g_filename[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
(void)argc;
|
(void)argc;
|
||||||
@@ -29,28 +51,25 @@ int main(int argc, char **argv) {
|
|||||||
syscall3(SYS_GUI, GUI_CMD_GET_SCREEN_SIZE, (uint64_t)&w, (uint64_t)&h);
|
syscall3(SYS_GUI, GUI_CMD_GET_SCREEN_SIZE, (uint64_t)&w, (uint64_t)&h);
|
||||||
|
|
||||||
if (w == 0 || h == 0 || w > 4096 || h > 4096) {
|
if (w == 0 || h == 0 || w > 4096 || h > 4096) {
|
||||||
printf("Failed to get screen size %d x %d\n", (int)w, (int)h);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Allocate buffer for 0xAARRGGBB
|
// 2. Allocate buffers
|
||||||
uint32_t *pixels = (uint32_t *)malloc(w * h * sizeof(uint32_t));
|
uint32_t *pixels = (uint32_t *)malloc(w * h * sizeof(uint32_t));
|
||||||
if (!pixels) {
|
uint8_t *rgb_pixels = (uint8_t *)malloc(w * h * 3);
|
||||||
printf("Failed to allocate memory for %d x %d pixels\n", (int)w, (int)h);
|
png_output_buf = (uint8_t *)malloc(PNG_WRITE_BUF_SIZE);
|
||||||
|
|
||||||
|
if (!pixels || !rgb_pixels || !png_output_buf) {
|
||||||
|
if (pixels) free(pixels);
|
||||||
|
if (rgb_pixels) free(rgb_pixels);
|
||||||
|
if (png_output_buf) free(png_output_buf);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Request screenbuffer
|
// 3. Request screenbuffer
|
||||||
syscall2(SYS_GUI, GUI_CMD_GET_SCREENBUFFER, (uint64_t)pixels);
|
syscall2(SYS_GUI, GUI_CMD_GET_SCREENBUFFER, (uint64_t)pixels);
|
||||||
|
|
||||||
// 4. Convert 0xAARRGGBB to RGB for stb_image_write
|
// 4. Convert 0xAARRGGBB to RGB
|
||||||
uint8_t *rgb_pixels = (uint8_t *)malloc(w * h * 3);
|
|
||||||
if (!rgb_pixels) {
|
|
||||||
printf("Failed to allocate RGB buffer\n");
|
|
||||||
free(pixels);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = 0; y < (int)h; y++) {
|
for (int y = 0; y < (int)h; y++) {
|
||||||
for (int x = 0; x < (int)w; x++) {
|
for (int x = 0; x < (int)w; x++) {
|
||||||
uint32_t px = pixels[y * w + x];
|
uint32_t px = pixels[y * w + x];
|
||||||
@@ -61,62 +80,50 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Get Datetime for filename
|
// 5. Get Datetime and construct filename
|
||||||
uint64_t dt[6] = {0};
|
uint64_t dt[6] = {0};
|
||||||
syscall2(SYS_GUI, GUI_CMD_GET_DATETIME, (uint64_t)dt);
|
syscall2(SYS_GUI, GUI_CMD_GET_DATETIME, (uint64_t)dt);
|
||||||
|
|
||||||
char filename[128] = "A:/Desktop/screenshot-";
|
strcpy(g_filename, "/Desktop/screenshot-");
|
||||||
|
append_num((int)dt[0], 4); // Year
|
||||||
|
append_num((int)dt[1], 2); // Month
|
||||||
|
append_num((int)dt[2], 2); // Day
|
||||||
|
|
||||||
// Quick helper to append 4-digit and 2-digit numbers
|
int len = strlen(g_filename);
|
||||||
auto void append_num(int num, int digits);
|
g_filename[len++] = '-'; g_filename[len] = '\0';
|
||||||
void append_num(int num, int digits) {
|
|
||||||
int len = 0; while (filename[len]) len++;
|
append_num((int)dt[3], 2); // Hour
|
||||||
if (digits == 4) {
|
append_num((int)dt[4], 2); // Min
|
||||||
filename[len++] = '0' + (num / 1000) % 10;
|
append_num((int)dt[5], 2); // Sec
|
||||||
filename[len++] = '0' + (num / 100) % 10;
|
strcat(g_filename, ".png");
|
||||||
|
|
||||||
|
// 6. Generate PNG in memory
|
||||||
|
png_output_idx = 0;
|
||||||
|
int res = stbi_write_png_to_func(png_write_func, NULL, (int)w, (int)h, 3, rgb_pixels, (int)w * 3);
|
||||||
|
|
||||||
|
// 7. Perform a SINGLE write to the filesystem
|
||||||
|
if (res && png_output_idx > 0) {
|
||||||
|
int fd = sys_open(g_filename, "w");
|
||||||
|
if (fd >= 0) {
|
||||||
|
sys_write_fs(fd, png_output_buf, png_output_idx);
|
||||||
|
sys_close(fd);
|
||||||
|
|
||||||
|
// Show notification
|
||||||
|
char notif[256] = "Saved ";
|
||||||
|
strcat(notif, g_filename + 9); // Skip "/Desktop/"
|
||||||
|
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)notif);
|
||||||
|
} else {
|
||||||
|
res = 0;
|
||||||
}
|
}
|
||||||
filename[len++] = '0' + (num / 10) % 10;
|
|
||||||
filename[len++] = '0' + (num % 10);
|
|
||||||
filename[len] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
append_num((int)dt[0], 4);
|
if (!res) {
|
||||||
append_num((int)dt[1], 2);
|
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)"Failed to save screenshot");
|
||||||
append_num((int)dt[2], 2);
|
|
||||||
int len = 0; while (filename[len]) len++;
|
|
||||||
filename[len++] = '-'; filename[len] = '\0';
|
|
||||||
append_num((int)dt[3], 2);
|
|
||||||
append_num((int)dt[4], 2);
|
|
||||||
append_num((int)dt[5], 2);
|
|
||||||
len = 0; while (filename[len]) len++;
|
|
||||||
filename[len++] = '.'; filename[len++] = 'p'; filename[len++] = 'n'; filename[len++] = 'g'; filename[len] = '\0';
|
|
||||||
|
|
||||||
// 6. Write to PNG
|
|
||||||
int fd = sys_open(filename, "w"); // Open file
|
|
||||||
int res = 0;
|
|
||||||
if (fd >= 0) {
|
|
||||||
res = stbi_write_png_to_func(png_write_func, &fd, (int)w, (int)h, 3, rgb_pixels, (int)w * 3);
|
|
||||||
sys_close(fd); // Close file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(png_output_buf);
|
||||||
free(rgb_pixels);
|
free(rgb_pixels);
|
||||||
free(pixels);
|
free(pixels);
|
||||||
|
|
||||||
if (res) {
|
return res ? 0 : 1;
|
||||||
char notif[256] = "Saved ";
|
|
||||||
int nlen = 6;
|
|
||||||
int flen = 0;
|
|
||||||
while (filename[11 + flen]) {
|
|
||||||
notif[nlen + flen] = filename[11 + flen];
|
|
||||||
flen++;
|
|
||||||
}
|
|
||||||
notif[nlen + flen] = '\0';
|
|
||||||
|
|
||||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)notif);
|
|
||||||
} else {
|
|
||||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)"Failed to save screenshot");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -49,29 +49,28 @@ static void update_proc_list(void) {
|
|||||||
proc_count = sys_system(SYSTEM_CMD_PROCESS_LIST, (uint64_t)proc_list, 32, 0, 0);
|
proc_count = sys_system(SYSTEM_CMD_PROCESS_LIST, (uint64_t)proc_list, 32, 0, 0);
|
||||||
|
|
||||||
uint64_t uptime_now = sys_system(SYSTEM_CMD_UPTIME, 0, 0, 0, 0);
|
uint64_t uptime_now = sys_system(SYSTEM_CMD_UPTIME, 0, 0, 0, 0);
|
||||||
uint64_t kernel_ticks_now = 0;
|
uint64_t user_ticks_now = 0;
|
||||||
|
|
||||||
for (int i = 0; i < proc_count; i++) {
|
for (int i = 0; i < proc_count; i++) {
|
||||||
if (proc_list[i].pid == 0) {
|
if (proc_list[i].pid != 0) {
|
||||||
kernel_ticks_now = proc_list[i].ticks;
|
user_ticks_now += proc_list[i].ticks;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uptime_prev > 0) {
|
if (uptime_prev > 0) {
|
||||||
uint64_t total_delta = uptime_now - uptime_prev;
|
uint64_t total_delta = uptime_now - uptime_prev;
|
||||||
if (total_delta > 0) {
|
if (total_delta > 0) {
|
||||||
uint64_t kernel_delta = kernel_ticks_now - kernel_ticks_prev;
|
uint64_t used_delta = user_ticks_now - kernel_ticks_prev; // Reusing the global state variable for prev user_ticks
|
||||||
if (kernel_delta > total_delta) kernel_delta = total_delta;
|
|
||||||
|
|
||||||
uint64_t used_delta = total_delta - kernel_delta;
|
// On a 4 CPU system, theoretically used_delta can be 4x total_delta
|
||||||
int usage = (int)((used_delta * 100) / total_delta);
|
int usage = (int)((used_delta * 100) / (total_delta * 4));
|
||||||
|
if (usage > 100) usage = 100;
|
||||||
cpu_history[history_idx] = usage;
|
cpu_history[history_idx] = usage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uptime_prev = uptime_now;
|
uptime_prev = uptime_now;
|
||||||
kernel_ticks_prev = kernel_ticks_now;
|
kernel_ticks_prev = user_ticks_now;
|
||||||
|
|
||||||
MemStats stats;
|
MemStats stats;
|
||||||
sys_system(SYSTEM_CMD_MEMINFO, (uint64_t)&stats, 0, 0, 0);
|
sys_system(SYSTEM_CMD_MEMINFO, (uint64_t)&stats, 0, 0, 0);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define VIEWER_MAX_W 800
|
#define VIEWER_MAX_W 4096
|
||||||
#define VIEWER_MAX_H 600
|
#define VIEWER_MAX_H 4096
|
||||||
|
|
||||||
static uint32_t *viewer_pixels = NULL;
|
static uint32_t *viewer_pixels = NULL;
|
||||||
static uint32_t **viewer_frames = NULL;
|
static uint32_t **viewer_frames = NULL;
|
||||||
@@ -24,6 +24,8 @@ static int viewer_img_h = 0;
|
|||||||
static char viewer_title[64] = "Viewer";
|
static char viewer_title[64] = "Viewer";
|
||||||
static bool viewer_has_image = false;
|
static bool viewer_has_image = false;
|
||||||
static char viewer_file_path[256];
|
static char viewer_file_path[256];
|
||||||
|
static bool resize_pending = false;
|
||||||
|
static uint64_t last_resize_tick = 0;
|
||||||
|
|
||||||
static int win_w = 500;
|
static int win_w = 500;
|
||||||
static int win_h = 400;
|
static int win_h = 400;
|
||||||
@@ -80,11 +82,13 @@ static void viewer_scale_rgba_to_argb(const unsigned char *rgba, int src_w, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void viewer_paint(ui_window_t win) {
|
static void viewer_paint(ui_window_t win) {
|
||||||
int cx = 4;
|
int cx = 0;
|
||||||
int cy = 0;
|
int cy = 0;
|
||||||
int cw = win_w - 8;
|
int cw = win_w;
|
||||||
int ch = win_h - 28;
|
int ch = win_h - 20; // 20px header
|
||||||
|
|
||||||
|
// Clear background
|
||||||
|
ui_draw_rect(win, 0, 0, win_w, win_h, 0xFF000000); // Black background
|
||||||
|
|
||||||
if (!viewer_has_image) {
|
if (!viewer_has_image) {
|
||||||
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
||||||
@@ -94,46 +98,42 @@ static void viewer_paint(ui_window_t win) {
|
|||||||
uint32_t *pixels = viewer_pixels;
|
uint32_t *pixels = viewer_pixels;
|
||||||
if (viewer_frames) pixels = viewer_frames[viewer_current_frame];
|
if (viewer_frames) pixels = viewer_frames[viewer_current_frame];
|
||||||
|
|
||||||
|
// Maintain aspect ratio while fitting to window
|
||||||
int disp_w = viewer_img_w;
|
int disp_w = viewer_img_w;
|
||||||
int disp_h = viewer_img_h;
|
int disp_h = viewer_img_h;
|
||||||
|
|
||||||
if (disp_w > cw - 8) {
|
float sw = (float)cw / (float)viewer_img_w;
|
||||||
disp_h = disp_h * (cw - 8) / disp_w;
|
float sh = (float)ch / (float)viewer_img_h;
|
||||||
disp_w = cw - 8;
|
float scale = (sw < sh) ? sw : sh;
|
||||||
}
|
|
||||||
if (disp_h > ch - 40) {
|
disp_w = (int)(viewer_img_w * scale);
|
||||||
disp_w = disp_w * (ch - 40) / disp_h;
|
disp_h = (int)(viewer_img_h * scale);
|
||||||
disp_h = ch - 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ox = cx + (cw - disp_w) / 2;
|
int ox = cx + (cw - disp_w) / 2;
|
||||||
int oy = cy + (ch - disp_h) / 2;
|
int oy = cy + (ch - disp_h) / 2;
|
||||||
|
|
||||||
|
if (disp_w <= 0 || disp_h <= 0) return;
|
||||||
|
|
||||||
uint32_t *temp_buf = malloc(disp_w * disp_h * sizeof(uint32_t));
|
uint32_t *temp_buf = malloc(disp_w * disp_h * sizeof(uint32_t));
|
||||||
if (temp_buf) {
|
if (temp_buf) {
|
||||||
if (disp_w == viewer_img_w && disp_h == viewer_img_h) {
|
// Fixed-point 16.16
|
||||||
// Fast path: 1:1
|
uint32_t step_x = (viewer_img_w << 16) / disp_w;
|
||||||
for (int i = 0; i < disp_w * disp_h; i++) temp_buf[i] = pixels[i];
|
uint32_t step_y = (viewer_img_h << 16) / disp_h;
|
||||||
} else {
|
uint32_t curr_y = 0;
|
||||||
// Fixed-point 16.16
|
|
||||||
uint32_t step_x = (viewer_img_w << 16) / disp_w;
|
|
||||||
uint32_t step_y = (viewer_img_h << 16) / disp_h;
|
|
||||||
uint32_t curr_y = 0;
|
|
||||||
|
|
||||||
for (int y = 0; y < disp_h; y++) {
|
for (int y = 0; y < disp_h; y++) {
|
||||||
uint32_t src_y = curr_y >> 16;
|
uint32_t src_y = curr_y >> 16;
|
||||||
if (src_y >= (uint32_t)viewer_img_h) src_y = viewer_img_h - 1;
|
if (src_y >= (uint32_t)viewer_img_h) src_y = viewer_img_h - 1;
|
||||||
uint32_t curr_x = 0;
|
uint32_t curr_x = 0;
|
||||||
uint32_t src_row_off = src_y * viewer_img_w;
|
uint32_t src_row_off = src_y * viewer_img_w;
|
||||||
uint32_t dst_row_off = y * disp_w;
|
uint32_t dst_row_off = y * disp_w;
|
||||||
for (int x = 0; x < disp_w; x++) {
|
for (int x = 0; x < disp_w; x++) {
|
||||||
uint32_t src_x = curr_x >> 16;
|
uint32_t src_x = curr_x >> 16;
|
||||||
if (src_x >= (uint32_t)viewer_img_w) src_x = viewer_img_w - 1;
|
if (src_x >= (uint32_t)viewer_img_w) src_x = viewer_img_w - 1;
|
||||||
temp_buf[dst_row_off + x] = pixels[src_row_off + src_x];
|
temp_buf[dst_row_off + x] = pixels[src_row_off + src_x];
|
||||||
curr_x += step_x;
|
curr_x += step_x;
|
||||||
}
|
|
||||||
curr_y += step_y;
|
|
||||||
}
|
}
|
||||||
|
curr_y += step_y;
|
||||||
}
|
}
|
||||||
ui_draw_image(win, ox, oy, disp_w, disp_h, temp_buf);
|
ui_draw_image(win, ox, oy, disp_w, disp_h, temp_buf);
|
||||||
free(temp_buf);
|
free(temp_buf);
|
||||||
@@ -271,8 +271,13 @@ void viewer_open_file(const char *path) {
|
|||||||
viewer_title[ti] = 0;
|
viewer_title[ti] = 0;
|
||||||
|
|
||||||
win_w = fit_w + 16;
|
win_w = fit_w + 16;
|
||||||
if (win_w < 200) win_w = 200;
|
|
||||||
win_h = fit_h + 34;
|
win_h = fit_h + 34;
|
||||||
|
|
||||||
|
// Cap initial window size to 1024x768 (or image size if smaller)
|
||||||
|
if (win_w > 1024) win_w = 1024;
|
||||||
|
if (win_h > 768) win_h = 768;
|
||||||
|
|
||||||
|
if (win_w < 200) win_w = 200;
|
||||||
if (win_h < 100) win_h = 100;
|
if (win_h < 100) win_h = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,18 +289,37 @@ int main(int argc, char **argv) {
|
|||||||
ui_window_t win = ui_window_create(viewer_title, 100, 50, win_w, win_h);
|
ui_window_t win = ui_window_create(viewer_title, 100, 50, win_w, win_h);
|
||||||
if (!win) return 1;
|
if (!win) return 1;
|
||||||
|
|
||||||
|
ui_window_set_resizable(win, true);
|
||||||
|
|
||||||
gui_event_t ev;
|
gui_event_t ev;
|
||||||
while (1) {
|
while (1) {
|
||||||
if (ui_get_event(win, &ev)) {
|
if (ui_get_event(win, &ev)) {
|
||||||
if (ev.type == GUI_EVENT_PAINT) {
|
if (ev.type == GUI_EVENT_PAINT) {
|
||||||
viewer_paint(win);
|
viewer_paint(win);
|
||||||
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||||
|
} else if (ev.type == GUI_EVENT_RESIZE) {
|
||||||
|
win_w = ev.arg1;
|
||||||
|
win_h = ev.arg2;
|
||||||
|
resize_pending = true;
|
||||||
|
last_resize_tick = sys_system(16, 0, 0, 0, 0);
|
||||||
|
// Fast background clear during active resize
|
||||||
|
ui_draw_rect(win, 0, 0, win_w, win_h, 0xFF000000);
|
||||||
|
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||||
// No actions currently
|
// No actions currently
|
||||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||||
sys_exit(0);
|
sys_exit(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (resize_pending) {
|
||||||
|
uint64_t now = sys_system(16, 0, 0, 0, 0);
|
||||||
|
if (now > last_resize_tick + 10) {
|
||||||
|
viewer_paint(win);
|
||||||
|
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||||
|
resize_pending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (viewer_has_image && viewer_frame_count > 1) {
|
if (viewer_has_image && viewer_frame_count > 1) {
|
||||||
uint64_t now = sys_system(16, 0, 0, 0, 0);
|
uint64_t now = sys_system(16, 0, 0, 0, 0);
|
||||||
if (now >= viewer_next_frame_tick) {
|
if (now >= viewer_next_frame_tick) {
|
||||||
|
|||||||
157
src/userland/libc/libmath.c
Normal file
157
src/userland/libc/libmath.c
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "math.h"
|
||||||
|
static double _pow_int(double b, int e) {
|
||||||
|
if (e == 0) return 1.0;
|
||||||
|
if (e < 0) { return 1.0 / _pow_int(b, -e); }
|
||||||
|
double r = 1.0;
|
||||||
|
while (e > 0) {
|
||||||
|
if (e & 1) r *= b;
|
||||||
|
b *= b;
|
||||||
|
e >>= 1;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
double fabs(double x) {
|
||||||
|
return x < 0.0 ? -x : x;
|
||||||
|
}
|
||||||
|
double fmod(double x, double y) {
|
||||||
|
if (y == 0.0) return 0.0;
|
||||||
|
return x - (int)(x / y) * y;
|
||||||
|
}
|
||||||
|
|
||||||
|
double floor(double x) {
|
||||||
|
int i = (int)x;
|
||||||
|
return (x < 0.0 && (double)i != x) ? (double)(i - 1) : (double)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ceil(double x) {
|
||||||
|
int i = (int)x;
|
||||||
|
return (x > 0.0 && (double)i != x) ? (double)(i + 1) : (double)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sin(double x) {
|
||||||
|
x = fmod(x, 2.0 * M_PI);
|
||||||
|
if (x > M_PI) x -= 2.0 * M_PI;
|
||||||
|
if (x < -M_PI) x += 2.0 * M_PI;
|
||||||
|
|
||||||
|
double x2 = x * x;
|
||||||
|
double term = x;
|
||||||
|
double sum = x;
|
||||||
|
for (int i = 1; i <= 8; i++) {
|
||||||
|
term *= -x2 / ((2*i) * (2*i + 1));
|
||||||
|
sum += term;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
double cos(double x) {
|
||||||
|
return sin(x + M_PI / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double tan(double x) {
|
||||||
|
double c = cos(x);
|
||||||
|
if (fabs(c) < 1e-10) return 1e15;
|
||||||
|
return sin(x) / c;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sqrt(double x) {
|
||||||
|
if (x <= 0.0) return 0.0;
|
||||||
|
double g = x * 0.5;
|
||||||
|
for (int i = 0; i < 25; i++) {
|
||||||
|
g = (g + x / g) * 0.5;
|
||||||
|
}
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
double log(double x) {
|
||||||
|
if (x <= 0.0) return -1e30;
|
||||||
|
|
||||||
|
int e = 0;
|
||||||
|
while (x > 2.0) { x /= 2.0; e++; }
|
||||||
|
while (x < 0.5) { x *= 2.0; e--; }
|
||||||
|
|
||||||
|
double t = (x - 1.0) / (x + 1.0);
|
||||||
|
double t2 = t * t;
|
||||||
|
double sum = t, term = t;
|
||||||
|
for (int i = 1; i <= 20; i++) {
|
||||||
|
term *= t2;
|
||||||
|
sum += term / (2*i + 1);
|
||||||
|
}
|
||||||
|
return 2.0 * sum + e * M_LN2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double log2(double x) {
|
||||||
|
// log2(x) = ln(x) / ln(2)
|
||||||
|
return log(x) / M_LN2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double log10(double x) {
|
||||||
|
return log(x) / 2.302585092994046;
|
||||||
|
}
|
||||||
|
|
||||||
|
double exp(double x) {
|
||||||
|
if (x > 700.0) return 1e300;
|
||||||
|
if (x < -700.0) return 0.0;
|
||||||
|
|
||||||
|
int k = (int)(x / M_LN2);
|
||||||
|
if (x < 0.0 && (double)k * M_LN2 > x) k--;
|
||||||
|
double r = x - (double)k * M_LN2;
|
||||||
|
|
||||||
|
double sum = 1.0, term = 1.0;
|
||||||
|
for (int i = 1; i <= 20; i++) {
|
||||||
|
term *= r / (double)i;
|
||||||
|
sum += term;
|
||||||
|
}
|
||||||
|
|
||||||
|
double result = sum;
|
||||||
|
if (k >= 0) { for (int i = 0; i < k; i++) result *= 2.0; }
|
||||||
|
else { for (int i = 0; i < -k; i++) result /= 2.0; }
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
double pow(double base, double exponent) {
|
||||||
|
if (base == 0.0) return 0.0;
|
||||||
|
if (exponent == 0.0) return 1.0;
|
||||||
|
int ie = (int)exponent;
|
||||||
|
if ((double)ie == exponent) return _pow_int(base, ie);
|
||||||
|
|
||||||
|
if (base < 0.0) return 0.0;
|
||||||
|
return exp(exponent * log(base));
|
||||||
|
}
|
||||||
|
|
||||||
|
double sinh(double x) {
|
||||||
|
double ep = exp(x);
|
||||||
|
double em = exp(-x);
|
||||||
|
return (ep - em) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
double cosh(double x) {
|
||||||
|
double ep = exp(x);
|
||||||
|
double em = exp(-x);
|
||||||
|
return (ep + em) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
double tanh(double x) {
|
||||||
|
double e2 = exp(2.0 * x);
|
||||||
|
return (e2 - 1.0) / (e2 + 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double hypot(double x, double y) {
|
||||||
|
return sqrt(x*x + y*y);
|
||||||
|
}
|
||||||
|
|
||||||
|
double fmin(double a, double b) {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
double fmax(double a, double b) {
|
||||||
|
return a > b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
double fclamp(double x, double lo, double hi) {
|
||||||
|
if (x < lo) return lo;
|
||||||
|
if (x > hi) return hi;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
extern uint64_t syscall3(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3);
|
extern uint64_t syscall3(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3);
|
||||||
extern uint64_t syscall4(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);
|
extern uint64_t syscall4(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);
|
||||||
extern uint64_t syscall5(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5);
|
extern uint64_t syscall5(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5);
|
||||||
|
extern uint64_t syscall6(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5, uint64_t arg6);
|
||||||
|
|
||||||
// sys_gui uses syscall #3
|
// sys_gui uses syscall #3
|
||||||
#define SYS_GUI 3
|
#define SYS_GUI 3
|
||||||
@@ -71,6 +72,16 @@ void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint3
|
|||||||
syscall5(SYS_GUI, GUI_CMD_DRAW_STRING_SCALED, (uint64_t)win, coords, (uint64_t)str, packed_arg5);
|
syscall5(SYS_GUI, GUI_CMD_DRAW_STRING_SCALED, (uint64_t)win, coords, (uint64_t)str, packed_arg5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ui_draw_string_scaled_sloped(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale, float slope) {
|
||||||
|
uint64_t coords = ((uint64_t)x & 0xFFFFFFFF) | ((uint64_t)y << 32);
|
||||||
|
// Pack color into lower 32, scale (as uint32_t representation) into upper 32
|
||||||
|
uint32_t scale_bits = *(uint32_t*)&scale;
|
||||||
|
uint32_t slope_bits = *(uint32_t*)&slope;
|
||||||
|
uint64_t packed_arg5 = ((uint64_t)scale_bits << 32) | (color & 0xFFFFFFFF);
|
||||||
|
|
||||||
|
syscall6(SYS_GUI, GUI_CMD_DRAW_STRING_SCALED_SLOPED, (uint64_t)win, coords, (uint64_t)str, packed_arg5, (uint64_t)slope_bits);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t ui_get_string_width_scaled(const char *str, float scale) {
|
uint32_t ui_get_string_width_scaled(const char *str, float scale) {
|
||||||
uint32_t scale_bits = *(uint32_t*)&scale;
|
uint32_t scale_bits = *(uint32_t*)&scale;
|
||||||
return (uint32_t)syscall4(SYS_GUI, GUI_CMD_GET_STRING_WIDTH_SCALED, (uint64_t)str, (uint64_t)scale_bits, 0);
|
return (uint32_t)syscall4(SYS_GUI, GUI_CMD_GET_STRING_WIDTH_SCALED, (uint64_t)str, (uint64_t)scale_bits, 0);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#define GUI_CMD_GET_FONT_HEIGHT_SCALED 13
|
#define GUI_CMD_GET_FONT_HEIGHT_SCALED 13
|
||||||
#define GUI_CMD_WINDOW_SET_TITLE 15
|
#define GUI_CMD_WINDOW_SET_TITLE 15
|
||||||
#define GUI_CMD_SET_FONT 16
|
#define GUI_CMD_SET_FONT 16
|
||||||
|
#define GUI_CMD_DRAW_STRING_SCALED_SLOPED 18
|
||||||
|
|
||||||
// Event Types
|
// Event Types
|
||||||
#define GUI_EVENT_NONE 0
|
#define GUI_EVENT_NONE 0
|
||||||
@@ -62,6 +63,7 @@ void ui_get_screen_size(uint64_t *out_w, uint64_t *out_h);
|
|||||||
void ui_draw_string_bitmap(ui_window_t win, int x, int y, const char *str, uint32_t color);
|
void ui_draw_string_bitmap(ui_window_t win, int x, int y, const char *str, uint32_t color);
|
||||||
|
|
||||||
void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale);
|
void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale);
|
||||||
|
void ui_draw_string_scaled_sloped(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale, float slope);
|
||||||
uint32_t ui_get_string_width_scaled(const char *str, float scale);
|
uint32_t ui_get_string_width_scaled(const char *str, float scale);
|
||||||
uint32_t ui_get_font_height_scaled(float scale);
|
uint32_t ui_get_font_height_scaled(float scale);
|
||||||
void ui_window_set_title(ui_window_t win, const char *title);
|
void ui_window_set_title(ui_window_t win, const char *title);
|
||||||
|
|||||||
40
src/userland/libc/math.h
Normal file
40
src/userland/libc/math.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef MATH_H
|
||||||
|
#define MATH_H
|
||||||
|
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#define M_E 2.71828182845904523536
|
||||||
|
#define M_LN2 0.69314718055994530942
|
||||||
|
#define M_SQRT2 1.41421356237309504880
|
||||||
|
#define HUGE_VAL (1e300 * 1e300)
|
||||||
|
|
||||||
|
double fabs(double x);
|
||||||
|
|
||||||
|
double fmod(double x, double y);
|
||||||
|
|
||||||
|
double floor(double x);
|
||||||
|
|
||||||
|
double ceil(double x);
|
||||||
|
|
||||||
|
double sin(double x);
|
||||||
|
double cos(double x);
|
||||||
|
double tan(double x);
|
||||||
|
double sqrt(double x);
|
||||||
|
|
||||||
|
double log(double x);
|
||||||
|
double log2(double x);
|
||||||
|
double log10(double x);
|
||||||
|
double exp(double x);
|
||||||
|
double pow(double base, double exponent);
|
||||||
|
double sinh(double x);
|
||||||
|
double cosh(double x);
|
||||||
|
double tanh(double x);
|
||||||
|
double hypot(double x, double y);
|
||||||
|
double fmin(double a, double b);
|
||||||
|
double fmax(double a, double b);
|
||||||
|
double fclamp(double x, double lo, double hi);
|
||||||
|
|
||||||
|
#endif /* MATH_H */
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
uint64_t syscall0(uint64_t sys_num) {
|
uint64_t syscall0(uint64_t sys_num) {
|
||||||
uint64_t ret;
|
uint64_t ret;
|
||||||
asm volatile("syscall"
|
asm volatile("int $0x80"
|
||||||
: "=a"(ret)
|
: "=a"(ret)
|
||||||
: "a"(sys_num)
|
: "a"(sys_num)
|
||||||
: "rcx", "r11", "memory");
|
: "rcx", "r11", "memory");
|
||||||
@@ -13,7 +13,7 @@ uint64_t syscall0(uint64_t sys_num) {
|
|||||||
|
|
||||||
uint64_t syscall1(uint64_t sys_num, uint64_t arg1) {
|
uint64_t syscall1(uint64_t sys_num, uint64_t arg1) {
|
||||||
uint64_t ret;
|
uint64_t ret;
|
||||||
asm volatile("syscall"
|
asm volatile("int $0x80"
|
||||||
: "=a"(ret)
|
: "=a"(ret)
|
||||||
: "a"(sys_num), "D"(arg1)
|
: "a"(sys_num), "D"(arg1)
|
||||||
: "rcx", "r11", "memory");
|
: "rcx", "r11", "memory");
|
||||||
@@ -22,7 +22,7 @@ uint64_t syscall1(uint64_t sys_num, uint64_t arg1) {
|
|||||||
|
|
||||||
uint64_t syscall2(uint64_t sys_num, uint64_t arg1, uint64_t arg2) {
|
uint64_t syscall2(uint64_t sys_num, uint64_t arg1, uint64_t arg2) {
|
||||||
uint64_t ret;
|
uint64_t ret;
|
||||||
asm volatile("syscall"
|
asm volatile("int $0x80"
|
||||||
: "=a"(ret)
|
: "=a"(ret)
|
||||||
: "a"(sys_num), "D"(arg1), "S"(arg2)
|
: "a"(sys_num), "D"(arg1), "S"(arg2)
|
||||||
: "rcx", "r11", "memory");
|
: "rcx", "r11", "memory");
|
||||||
@@ -31,7 +31,7 @@ uint64_t syscall2(uint64_t sys_num, uint64_t arg1, uint64_t arg2) {
|
|||||||
|
|
||||||
uint64_t syscall3(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3) {
|
uint64_t syscall3(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3) {
|
||||||
uint64_t ret;
|
uint64_t ret;
|
||||||
asm volatile("syscall"
|
asm volatile("int $0x80"
|
||||||
: "=a"(ret)
|
: "=a"(ret)
|
||||||
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3)
|
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3)
|
||||||
: "rcx", "r11", "memory", "r10", "r8", "r9");
|
: "rcx", "r11", "memory", "r10", "r8", "r9");
|
||||||
@@ -41,7 +41,7 @@ uint64_t syscall3(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3)
|
|||||||
uint64_t syscall4(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4) {
|
uint64_t syscall4(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4) {
|
||||||
uint64_t ret;
|
uint64_t ret;
|
||||||
register uint64_t r10 asm("r10") = arg4;
|
register uint64_t r10 asm("r10") = arg4;
|
||||||
asm volatile("syscall"
|
asm volatile("int $0x80"
|
||||||
: "=a"(ret)
|
: "=a"(ret)
|
||||||
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10)
|
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10)
|
||||||
: "rcx", "r11", "memory", "r8", "r9");
|
: "rcx", "r11", "memory", "r8", "r9");
|
||||||
@@ -52,13 +52,25 @@ uint64_t syscall5(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3,
|
|||||||
uint64_t ret;
|
uint64_t ret;
|
||||||
register uint64_t r10 asm("r10") = arg4;
|
register uint64_t r10 asm("r10") = arg4;
|
||||||
register uint64_t r8 asm("r8") = arg5;
|
register uint64_t r8 asm("r8") = arg5;
|
||||||
asm volatile("syscall"
|
asm volatile("int $0x80"
|
||||||
: "=a"(ret)
|
: "=a"(ret)
|
||||||
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8)
|
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8)
|
||||||
: "rcx", "r11", "memory", "r9");
|
: "rcx", "r11", "memory", "r9");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t syscall6(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5, uint64_t arg6) {
|
||||||
|
uint64_t ret;
|
||||||
|
register uint64_t r10 asm("r10") = arg4;
|
||||||
|
register uint64_t r8 asm("r8") = arg5;
|
||||||
|
register uint64_t r9 asm("r9") = arg6;
|
||||||
|
asm volatile("int $0x80"
|
||||||
|
: "=a"(ret)
|
||||||
|
: "a"(sys_num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8), "r"(r9)
|
||||||
|
: "rcx", "r11", "memory");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void sys_exit(int status) {
|
void sys_exit(int status) {
|
||||||
syscall1(SYS_EXIT, (uint64_t)status);
|
syscall1(SYS_EXIT, (uint64_t)status);
|
||||||
@@ -239,3 +251,11 @@ void sys_yield(void) {
|
|||||||
syscall1(SYS_SYSTEM, SYSTEM_CMD_YIELD);
|
syscall1(SYS_SYSTEM, SYSTEM_CMD_YIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sys_get_os_info(os_info_t *info) {
|
||||||
|
return (int)syscall5(SYS_SYSTEM, SYSTEM_CMD_GET_OS_INFO, (uint64_t)info, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_parallel_run(void (*fn)(void*), void **args, int count) {
|
||||||
|
syscall5(SYS_SYSTEM, SYSTEM_CMD_PARALLEL_RUN, (uint64_t)fn, (uint64_t)args, (uint64_t)count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,8 @@
|
|||||||
#define SYSTEM_CMD_SET_RAW_MODE 41
|
#define SYSTEM_CMD_SET_RAW_MODE 41
|
||||||
#define SYSTEM_CMD_TCP_RECV_NB 42
|
#define SYSTEM_CMD_TCP_RECV_NB 42
|
||||||
#define SYSTEM_CMD_YIELD 43
|
#define SYSTEM_CMD_YIELD 43
|
||||||
|
#define SYSTEM_CMD_GET_OS_INFO 49
|
||||||
|
#define SYSTEM_CMD_PARALLEL_RUN 50
|
||||||
|
|
||||||
// Internal assembly entry into Ring 0
|
// Internal assembly entry into Ring 0
|
||||||
extern uint64_t syscall0(uint64_t sys_num);
|
extern uint64_t syscall0(uint64_t sys_num);
|
||||||
@@ -91,6 +93,19 @@ void *sys_sbrk(int incr);
|
|||||||
void sys_kill(int pid);
|
void sys_kill(int pid);
|
||||||
int sys_system(int cmd, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);
|
int sys_system(int cmd, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char os_name[64];
|
||||||
|
char os_version[64];
|
||||||
|
char os_codename[64];
|
||||||
|
char kernel_name[64];
|
||||||
|
char kernel_version[64];
|
||||||
|
char build_date[64];
|
||||||
|
char build_time[64];
|
||||||
|
char build_arch[64];
|
||||||
|
} os_info_t;
|
||||||
|
|
||||||
|
int sys_get_os_info(os_info_t *info);
|
||||||
|
|
||||||
// FS API
|
// FS API
|
||||||
int sys_open(const char *path, const char *mode);
|
int sys_open(const char *path, const char *mode);
|
||||||
int sys_read(int fd, void *buf, uint32_t len);
|
int sys_read(int fd, void *buf, uint32_t len);
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ static void cmd_init_config_defaults(void) {
|
|||||||
shell_config.default_text_color = 0xFFFFFFFF; // White
|
shell_config.default_text_color = 0xFFFFFFFF; // White
|
||||||
shell_config.bg_color = 0xFF1E1E1E; // Dark background
|
shell_config.bg_color = 0xFF1E1E1E; // Dark background
|
||||||
shell_config.cursor_color = 0xFFFFFFFF;
|
shell_config.cursor_color = 0xFFFFFFFF;
|
||||||
shell_config.show_drive = true;
|
shell_config.show_drive = false;
|
||||||
shell_config.show_dir = true;
|
shell_config.show_dir = true;
|
||||||
shell_config.dir_color = 0xFF569CD6;
|
shell_config.dir_color = 0xFF569CD6;
|
||||||
shell_config.file_color = 0xFFFFFFFF;
|
shell_config.file_color = 0xFFFFFFFF;
|
||||||
@@ -42,6 +42,28 @@ static int dropdown_menu_item_height = 25;
|
|||||||
#define FILE_CONTEXT_MENU_HEIGHT 50
|
#define FILE_CONTEXT_MENU_HEIGHT 50
|
||||||
#define CONTEXT_MENU_ITEM_HEIGHT 25
|
#define CONTEXT_MENU_ITEM_HEIGHT 25
|
||||||
|
|
||||||
|
static void wm_draw_rect(void *user_data, int x, int y, int w, int h, uint32_t color) {
|
||||||
|
(void)user_data; draw_rect(x, y, w, h, color);
|
||||||
|
}
|
||||||
|
static void wm_draw_rounded_rect_filled(void *user_data, int x, int y, int w, int h, int r, uint32_t color) {
|
||||||
|
(void)user_data; draw_rounded_rect_filled(x, y, w, h, r, color);
|
||||||
|
}
|
||||||
|
static void wm_draw_string(void *user_data, int x, int y, const char *str, uint32_t color) {
|
||||||
|
(void)user_data; draw_string(x, y, str, color);
|
||||||
|
}
|
||||||
|
static int wm_measure_string_width(void *user_data, const char *str) {
|
||||||
|
(void)user_data;
|
||||||
|
return font_manager_get_string_width(graphics_get_current_ttf(), str);
|
||||||
|
}
|
||||||
|
static widget_context_t wm_widget_ctx = {
|
||||||
|
.user_data = NULL,
|
||||||
|
.draw_rect = wm_draw_rect,
|
||||||
|
.draw_rounded_rect_filled = wm_draw_rounded_rect_filled,
|
||||||
|
.draw_string = wm_draw_string,
|
||||||
|
.measure_string_width = wm_measure_string_width,
|
||||||
|
.mark_dirty = NULL
|
||||||
|
};
|
||||||
|
|
||||||
static char clipboard_path[FAT32_MAX_PATH] = "";
|
static char clipboard_path[FAT32_MAX_PATH] = "";
|
||||||
static int clipboard_action = 0;
|
static int clipboard_action = 0;
|
||||||
#define FILE_CONTEXT_ITEMS 2
|
#define FILE_CONTEXT_ITEMS 2
|
||||||
@@ -87,6 +109,25 @@ static int explorer_strcmp(const char *s1, const char *s2) {
|
|||||||
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
|
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int explorer_strcasecmp(const char *s1, const char *s2) {
|
||||||
|
while (*s1 && *s2) {
|
||||||
|
char c1 = *s1;
|
||||||
|
char c2 = *s2;
|
||||||
|
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
|
||||||
|
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
|
||||||
|
if (c1 != c2) {
|
||||||
|
return (unsigned char)c1 - (unsigned char)c2;
|
||||||
|
}
|
||||||
|
s1++;
|
||||||
|
s2++;
|
||||||
|
}
|
||||||
|
char c1 = *s1;
|
||||||
|
char c2 = *s2;
|
||||||
|
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
|
||||||
|
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
|
||||||
|
return (unsigned char)c1 - (unsigned char)c2;
|
||||||
|
}
|
||||||
|
|
||||||
void explorer_strcat(char *dest, const char *src) {
|
void explorer_strcat(char *dest, const char *src) {
|
||||||
while (*dest) dest++;
|
while (*dest) dest++;
|
||||||
explorer_strcpy(dest, src);
|
explorer_strcpy(dest, src);
|
||||||
@@ -367,7 +408,7 @@ bool explorer_delete_recursive(const char *path) {
|
|||||||
for (int k = i + 1; k < len; k++) filename[j++] = path[k];
|
for (int k = i + 1; k < len; k++) filename[j++] = path[k];
|
||||||
filename[j] = 0;
|
filename[j] = 0;
|
||||||
|
|
||||||
char drive_prefix[3] = "A:";
|
char drive_prefix[3] = "/";
|
||||||
if (path[0] && path[1] == ':') {
|
if (path[0] && path[1] == ':') {
|
||||||
drive_prefix[0] = path[0];
|
drive_prefix[0] = path[0];
|
||||||
}
|
}
|
||||||
@@ -696,12 +737,30 @@ static void explorer_load_directory(Window *win, const char *path) {
|
|||||||
explorer_strcpy(state->items[temp_count].name, entries[i].name);
|
explorer_strcpy(state->items[temp_count].name, entries[i].name);
|
||||||
state->items[temp_count].is_directory = entries[i].is_directory;
|
state->items[temp_count].is_directory = entries[i].is_directory;
|
||||||
state->items[temp_count].size = entries[i].size;
|
state->items[temp_count].size = entries[i].size;
|
||||||
state->items[temp_count].color = entries[i].is_directory ? COLOR_APPLE_BLUE : COLOR_APPLE_YELLOW;
|
state->items[temp_count].color = entries[i].is_directory ? COLOR_BLUE : COLOR_YELLOW;
|
||||||
temp_count++;
|
temp_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->item_count = temp_count;
|
state->item_count = temp_count;
|
||||||
|
|
||||||
|
for (int i = 0; i < temp_count - 1; i++) {
|
||||||
|
for (int j = 0; j < temp_count - i - 1; j++) {
|
||||||
|
bool swap = false;
|
||||||
|
if (state->items[j].is_directory == state->items[j+1].is_directory) {
|
||||||
|
if (explorer_strcasecmp(state->items[j].name, state->items[j+1].name) > 0) {
|
||||||
|
swap = true;
|
||||||
|
}
|
||||||
|
} else if (!state->items[j].is_directory && state->items[j+1].is_directory) {
|
||||||
|
swap = true;
|
||||||
|
}
|
||||||
|
if (swap) {
|
||||||
|
ExplorerItem temp = state->items[j];
|
||||||
|
state->items[j] = state->items[j+1];
|
||||||
|
state->items[j+1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kfree(entries);
|
kfree(entries);
|
||||||
if (path_changed) {
|
if (path_changed) {
|
||||||
state->selected_item = -1;
|
state->selected_item = -1;
|
||||||
@@ -750,14 +809,16 @@ static void explorer_open_target(const char *path) {
|
|||||||
} else {
|
} else {
|
||||||
if (explorer_str_ends_with(path, ".elf")) {
|
if (explorer_str_ends_with(path, ".elf")) {
|
||||||
process_create_elf(path, NULL);
|
process_create_elf(path, NULL);
|
||||||
|
} else if (explorer_str_ends_with(path, ".pdf")) {
|
||||||
|
process_create_elf("/bin/boredword.elf", path);
|
||||||
} else if (explorer_is_markdown_file(path)) {
|
} else if (explorer_is_markdown_file(path)) {
|
||||||
process_create_elf("A:/bin/markdown.elf", path);
|
process_create_elf("/bin/markdown.elf", path);
|
||||||
} else if (explorer_str_ends_with(path, ".pnt")) {
|
} else if (explorer_str_ends_with(path, ".pnt")) {
|
||||||
process_create_elf("A:/bin/paint.elf", path);
|
process_create_elf("/bin/paint.elf", path);
|
||||||
} else if (explorer_is_image_file(path)) {
|
} else if (explorer_is_image_file(path)) {
|
||||||
process_create_elf("A:/bin/viewer.elf", path);
|
process_create_elf("/bin/viewer.elf", path);
|
||||||
} else {
|
} else {
|
||||||
process_create_elf("A:/bin/txtedit.elf", path);
|
process_create_elf("/bin/txtedit.elf", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -842,6 +903,8 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c
|
|||||||
if (full_path[explorer_strlen(full_path) - 1] != '/') explorer_strcat(full_path, "/");
|
if (full_path[explorer_strlen(full_path) - 1] != '/') explorer_strcat(full_path, "/");
|
||||||
explorer_strcat(full_path, filename);
|
explorer_strcat(full_path, filename);
|
||||||
draw_image_icon(x + 5, y + 5, full_path);
|
draw_image_icon(x + 5, y + 5, full_path);
|
||||||
|
} else if (explorer_str_ends_with(filename, ".pdf")) {
|
||||||
|
draw_pdf_icon(x + 5, y + 5, "");
|
||||||
} else if (explorer_str_ends_with(filename, ".elf")) {
|
} else if (explorer_str_ends_with(filename, ".elf")) {
|
||||||
draw_elf_icon(x + 5, y + 5, "");
|
draw_elf_icon(x + 5, y + 5, "");
|
||||||
} else {
|
} else {
|
||||||
@@ -857,7 +920,7 @@ static void explorer_paint(Window *win) {
|
|||||||
DirtyRect dirty = graphics_get_dirty_rect();
|
DirtyRect dirty = graphics_get_dirty_rect();
|
||||||
|
|
||||||
|
|
||||||
graphics_set_clipping(offset_x, offset_y, win->w - 8, win->h - 28);
|
graphics_push_clipping(offset_x, offset_y, win->w - 8, win->h - 28);
|
||||||
|
|
||||||
draw_rect(offset_x, offset_y, win->w - 8, win->h - 28, COLOR_DARK_BG);
|
draw_rect(offset_x, offset_y, win->w - 8, win->h - 28, COLOR_DARK_BG);
|
||||||
|
|
||||||
@@ -909,20 +972,19 @@ static void explorer_paint(Window *win) {
|
|||||||
draw_string(path_x + 6 + path_label_w + 6, offset_y + 8, state->current_path, COLOR_DARK_TEXT);
|
draw_string(path_x + 6 + path_label_w + 6, offset_y + 8, state->current_path, COLOR_DARK_TEXT);
|
||||||
|
|
||||||
int dropdown_btn_x = win->x + win->w - 90;
|
int dropdown_btn_x = win->x + win->w - 90;
|
||||||
draw_rounded_rect_filled(dropdown_btn_x, offset_y + 3, 35, 22, 5, COLOR_DARK_PANEL);
|
widget_button_init(&state->btn_dropdown, dropdown_btn_x, offset_y + 3, 35, 22, "...");
|
||||||
draw_string(dropdown_btn_x + 10, offset_y + 8, "...", COLOR_DARK_TEXT);
|
widget_button_init(&state->btn_back, win->x + win->w - 40, offset_y + 3, 30, 22, "<");
|
||||||
|
widget_button_init(&state->btn_up, win->x + win->w - 160, offset_y + 3, 30, 22, "^");
|
||||||
|
widget_button_init(&state->btn_fwd, win->x + win->w - 125, offset_y + 3, 30, 22, "v");
|
||||||
|
|
||||||
draw_rounded_rect_filled(win->x + win->w - 40, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
|
widget_button_draw(&wm_widget_ctx, &state->btn_dropdown);
|
||||||
draw_string(win->x + win->w - 32, offset_y + 8, "<", COLOR_DARK_TEXT);
|
widget_button_draw(&wm_widget_ctx, &state->btn_back);
|
||||||
|
widget_button_draw(&wm_widget_ctx, &state->btn_up);
|
||||||
draw_rounded_rect_filled(win->x + win->w - 160, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
|
widget_button_draw(&wm_widget_ctx, &state->btn_fwd);
|
||||||
draw_string(win->x + win->w - 150, offset_y + 8, "^", COLOR_DARK_TEXT);
|
|
||||||
draw_rounded_rect_filled(win->x + win->w - 125, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
|
|
||||||
draw_string(win->x + win->w - 115, offset_y + 8, "v", COLOR_DARK_TEXT);
|
|
||||||
|
|
||||||
int content_start_y = offset_y + 30;
|
int content_start_y = offset_y + 30;
|
||||||
|
|
||||||
graphics_set_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 54 - 4);
|
graphics_push_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 54 - 4);
|
||||||
|
|
||||||
for (int i = 0; i < state->item_count; i++) {
|
for (int i = 0; i < state->item_count; i++) {
|
||||||
int row = i / EXPLORER_COLS;
|
int row = i / EXPLORER_COLS;
|
||||||
@@ -948,11 +1010,8 @@ static void explorer_paint(Window *win) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (dirty.active) {
|
graphics_pop_clipping(); // Pop content clipping
|
||||||
graphics_set_clipping(dirty.x, dirty.y, dirty.w, dirty.h);
|
graphics_pop_clipping(); // Pop main window clipping
|
||||||
} else {
|
|
||||||
graphics_clear_clipping();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state->drive_menu_visible) {
|
if (state->drive_menu_visible) {
|
||||||
int menu_x = win->x + 4;
|
int menu_x = win->x + 4;
|
||||||
@@ -1002,25 +1061,15 @@ static void explorer_paint(Window *win) {
|
|||||||
|
|
||||||
draw_string(dlg_x + 10, dlg_y + 10, "Create New File", COLOR_WHITE);
|
draw_string(dlg_x + 10, dlg_y + 10, "Create New File", COLOR_WHITE);
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
|
||||||
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE);
|
state->dialog_textbox.focused = true;
|
||||||
{ int max_w = 265;
|
state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
|
||||||
ttf_font_t *ttf_ = graphics_get_current_ttf();
|
widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
|
||||||
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
|
|
||||||
int scroll_x = 0;
|
|
||||||
if (total_w > max_w) scroll_x = total_w - max_w;
|
|
||||||
char sub_[128]; int k_=0;
|
|
||||||
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
|
|
||||||
sub_[k_]=0;
|
|
||||||
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
|
|
||||||
if (cx_ < 0) cx_ = 0;
|
|
||||||
if (cx_ > max_w) cx_ = max_w;
|
|
||||||
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
|
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Create");
|
||||||
draw_string(dlg_x + 70, dlg_y + 72, "Create", COLOR_WHITE);
|
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||||
} else if (state->dialog_state == DIALOG_CREATE_FOLDER) {
|
} else if (state->dialog_state == DIALOG_CREATE_FOLDER) {
|
||||||
int dlg_x = win->x + win->w / 2 - 150;
|
int dlg_x = win->x + win->w / 2 - 150;
|
||||||
int dlg_y = win->y + win->h / 2 - 60;
|
int dlg_y = win->y + win->h / 2 - 60;
|
||||||
@@ -1029,25 +1078,15 @@ static void explorer_paint(Window *win) {
|
|||||||
|
|
||||||
draw_string(dlg_x + 10, dlg_y + 10, "Create New Folder", COLOR_WHITE);
|
draw_string(dlg_x + 10, dlg_y + 10, "Create New Folder", COLOR_WHITE);
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
|
||||||
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE);
|
state->dialog_textbox.focused = true;
|
||||||
{ int max_w = 265;
|
state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
|
||||||
ttf_font_t *ttf_ = graphics_get_current_ttf();
|
widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
|
||||||
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
|
|
||||||
int scroll_x = 0;
|
|
||||||
if (total_w > max_w) scroll_x = total_w - max_w;
|
|
||||||
char sub_[128]; int k_=0;
|
|
||||||
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
|
|
||||||
sub_[k_]=0;
|
|
||||||
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
|
|
||||||
if (cx_ < 0) cx_ = 0;
|
|
||||||
if (cx_ > max_w) cx_ = max_w;
|
|
||||||
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
|
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Create");
|
||||||
draw_string(dlg_x + 70, dlg_y + 72, "Create", COLOR_WHITE);
|
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||||
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
|
||||||
int dlg_x = win->x + win->w / 2 - 150;
|
int dlg_x = win->x + win->w / 2 - 150;
|
||||||
int dlg_y = win->y + win->h / 2 - 60;
|
int dlg_y = win->y + win->h / 2 - 60;
|
||||||
@@ -1066,8 +1105,14 @@ static void explorer_paint(Window *win) {
|
|||||||
}
|
}
|
||||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020);
|
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020);
|
||||||
draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE);
|
draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE);
|
||||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
|
||||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
// Use libwidget only for the cancel button, or do we want to use libwidget for the delete button too?
|
||||||
|
// Let's use libwidget but the delete button needs red styling so maybe just keep it manual or make it secondary.
|
||||||
|
// Actually wait, I will use libwidget for both and let the text dictate the action, we can't style individual buttons yet.
|
||||||
|
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Delete");
|
||||||
|
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||||
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
|
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||||
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
|
||||||
int dlg_x = win->x + win->w / 2 - 150;
|
int dlg_x = win->x + win->w / 2 - 150;
|
||||||
int dlg_y = win->y + win->h / 2 - 60;
|
int dlg_y = win->y + win->h / 2 - 60;
|
||||||
@@ -1079,10 +1124,10 @@ static void explorer_paint(Window *win) {
|
|||||||
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA);
|
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA);
|
||||||
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
|
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Replace");
|
||||||
draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE);
|
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
|
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||||
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
|
||||||
int dlg_x = win->x + win->w / 2 - 150;
|
int dlg_x = win->x + win->w / 2 - 150;
|
||||||
int dlg_y = win->y + win->h / 2 - 60;
|
int dlg_y = win->y + win->h / 2 - 60;
|
||||||
@@ -1109,10 +1154,10 @@ static void explorer_paint(Window *win) {
|
|||||||
draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA);
|
draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA);
|
||||||
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
|
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Overwrite");
|
||||||
draw_string(dlg_x + 57, dlg_y + 77, "Overwrite", COLOR_WHITE);
|
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
|
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||||
} else if (state->dialog_state == DIALOG_ERROR) {
|
} else if (state->dialog_state == DIALOG_ERROR) {
|
||||||
int dlg_x = win->x + win->w / 2 - 150;
|
int dlg_x = win->x + win->w / 2 - 150;
|
||||||
int dlg_y = win->y + win->h / 2 - 60;
|
int dlg_y = win->y + win->h / 2 - 60;
|
||||||
@@ -1122,32 +1167,23 @@ static void explorer_paint(Window *win) {
|
|||||||
draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B);
|
draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B);
|
||||||
draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA);
|
draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA);
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x + 110, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
|
widget_button_init(&state->btn_primary, dlg_x + 110, dlg_y + 70, 80, 25, "OK");
|
||||||
draw_string(dlg_x + 138, dlg_y + 77, "OK", COLOR_WHITE);
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
} else if (state->dialog_state == DIALOG_RENAME) {
|
} else if (state->dialog_state == DIALOG_RENAME) {
|
||||||
int dlg_x = win->x + win->w / 2 - 150;
|
int dlg_x = win->x + win->w / 2 - 150;
|
||||||
int dlg_y = win->y + win->h / 2 - 60;
|
int dlg_y = win->y + win->h / 2 - 60;
|
||||||
|
|
||||||
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
|
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
|
||||||
draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_WHITE);
|
draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_WHITE);
|
||||||
draw_rounded_rect_filled(dlg_x + 10, dlg_y + 35, 280, 20, 4, COLOR_DARK_BG);
|
widget_textbox_init(&state->dialog_textbox, dlg_x + 10, dlg_y + 35, 280, 25, state->dialog_input, DIALOG_INPUT_MAX);
|
||||||
draw_string(dlg_x + 15, dlg_y + 40, state->dialog_input, COLOR_WHITE);
|
state->dialog_textbox.focused = true;
|
||||||
{ int max_w = 265;
|
state->dialog_textbox.cursor_pos = state->dialog_input_cursor;
|
||||||
ttf_font_t *ttf_ = graphics_get_current_ttf();
|
widget_textbox_draw(&wm_widget_ctx, &state->dialog_textbox);
|
||||||
int total_w = font_manager_get_string_width(ttf_, state->dialog_input);
|
|
||||||
int scroll_x = 0;
|
widget_button_init(&state->btn_primary, dlg_x + 50, dlg_y + 70, 80, 25, "Rename");
|
||||||
if (total_w > max_w) scroll_x = total_w - max_w;
|
widget_button_init(&state->btn_secondary, dlg_x + 170, dlg_y + 70, 80, 25, "Cancel");
|
||||||
char sub_[128]; int k_=0;
|
widget_button_draw(&wm_widget_ctx, &state->btn_primary);
|
||||||
for(k_=0; k_<state->dialog_input_cursor && state->dialog_input[k_]; k_++) sub_[k_]=state->dialog_input[k_];
|
widget_button_draw(&wm_widget_ctx, &state->btn_secondary);
|
||||||
sub_[k_]=0;
|
|
||||||
int cx_ = font_manager_get_string_width(ttf_, sub_) - scroll_x;
|
|
||||||
if (cx_ < 0) cx_ = 0;
|
|
||||||
if (cx_ > max_w) cx_ = max_w;
|
|
||||||
draw_rect(dlg_x+15+cx_, dlg_y+39, 2, 12, COLOR_WHITE); }
|
|
||||||
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
|
||||||
draw_string(dlg_x + 68, dlg_y + 72, "Rename", COLOR_WHITE);
|
|
||||||
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
|
|
||||||
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state->file_context_menu_visible) {
|
if (state->file_context_menu_visible) {
|
||||||
@@ -1179,6 +1215,9 @@ static void explorer_paint(Window *win) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define WIDGET_CLICKED(btn, cx, cy) ((cx) >= (btn)->x && (cx) < (btn)->x + (btn)->w && (cy) >= (btn)->y && (cy) < (btn)->y + (btn)->h)
|
||||||
|
#define TEXTBOX_CLICKED(tb, cx, cy) ((cx) >= (tb)->x && (cx) < (tb)->x + (tb)->w && (cy) >= (tb)->y && (cy) < (tb)->y + (tb)->h)
|
||||||
|
|
||||||
static void explorer_handle_click(Window *win, int x, int y) {
|
static void explorer_handle_click(Window *win, int x, int y) {
|
||||||
ExplorerState *state = (ExplorerState*)win->data;
|
ExplorerState *state = (ExplorerState*)win->data;
|
||||||
if (state->file_context_menu_visible) {
|
if (state->file_context_menu_visible) {
|
||||||
@@ -1187,106 +1226,90 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state->dialog_state == DIALOG_CREATE_FILE || state->dialog_state == DIALOG_CREATE_FOLDER) {
|
if (state->dialog_state == DIALOG_CREATE_FILE || state->dialog_state == DIALOG_CREATE_FOLDER) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
if (state->dialog_state == DIALOG_CREATE_FILE) dialog_confirm_create_file(win);
|
||||||
if (x >= dlg_x + 50 && x < dlg_x + 130 &&
|
else dialog_confirm_create_folder(win);
|
||||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
|
||||||
if (state->dialog_state == DIALOG_CREATE_FILE) {
|
|
||||||
dialog_confirm_create_file(win);
|
|
||||||
} else {
|
|
||||||
dialog_confirm_create_folder(win);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 170 && x < dlg_x + 250 &&
|
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y)) {
|
||||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
state->btn_secondary.pressed = false;
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 10 && x < dlg_x + 290 &&
|
if (TEXTBOX_CLICKED(&state->dialog_textbox, win->x + x, win->y + y)) {
|
||||||
y >= dlg_y + 35 && y < dlg_y + 55) {
|
state->dialog_input_cursor = (win->x + x - state->dialog_textbox.x - 5) / 8;
|
||||||
state->dialog_input_cursor = (x - dlg_x - 15) / 8;
|
|
||||||
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
|
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
|
||||||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||||
}
|
}
|
||||||
|
if (state->dialog_input_cursor < 0) state->dialog_input_cursor = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_DELETE_CONFIRM) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
|
||||||
if (x >= dlg_x + 50 && x < dlg_x + 130 &&
|
|
||||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
|
||||||
dialog_confirm_delete(win);
|
dialog_confirm_delete(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 170 && x < dlg_x + 250 &&
|
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y)) {
|
||||||
y >= dlg_y + 65 && y < dlg_y + 90) {
|
state->btn_secondary.pressed = false;
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
|
||||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
|
||||||
dialog_confirm_replace(win);
|
dialog_confirm_replace(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||||
|
state->btn_secondary.pressed = false;
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
|
||||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
|
||||||
dialog_confirm_replace_move(win);
|
dialog_confirm_replace_move(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||||
|
state->btn_secondary.pressed = false;
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) {
|
} else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
|
||||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
|
||||||
dialog_force_create_file(win);
|
dialog_force_create_file(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||||
|
state->btn_secondary.pressed = false;
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (state->dialog_state == DIALOG_ERROR) {
|
} else if (state->dialog_state == DIALOG_ERROR) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
|
||||||
if (x >= dlg_x + 110 && x < dlg_x + 190 && y >= dlg_y + 70 && y < dlg_y + 95) {
|
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (state->dialog_state == DIALOG_RENAME) {
|
} else if (state->dialog_state == DIALOG_RENAME) {
|
||||||
int dlg_x = win->w / 2 - 150;
|
if (WIDGET_CLICKED(&state->btn_primary, win->x + x, win->y + y + 20)) {
|
||||||
int dlg_y = win->h / 2 - 80;
|
state->btn_primary.pressed = false;
|
||||||
|
|
||||||
if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 65 && y < dlg_y + 90) {
|
|
||||||
char new_path[FAT32_MAX_PATH];
|
char new_path[FAT32_MAX_PATH];
|
||||||
explorer_strcpy(new_path, state->current_path);
|
explorer_strcpy(new_path, state->current_path);
|
||||||
if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/");
|
if (new_path[explorer_strlen(new_path)-1] != '/') explorer_strcat(new_path, "/");
|
||||||
@@ -1297,16 +1320,17 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 65 && y < dlg_y + 90) {
|
if (WIDGET_CLICKED(&state->btn_secondary, win->x + x, win->y + y + 20)) {
|
||||||
|
state->btn_secondary.pressed = false;
|
||||||
dialog_close(win);
|
dialog_close(win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (TEXTBOX_CLICKED(&state->dialog_textbox, win->x + x, win->y + y + 20)) {
|
||||||
if (x >= dlg_x + 10 && x < dlg_x + 290 && y >= dlg_y + 35 && y < dlg_y + 55) {
|
state->dialog_input_cursor = (win->x + x - state->dialog_textbox.x - 5) / 8;
|
||||||
state->dialog_input_cursor = (x - dlg_x - 15) / 8;
|
|
||||||
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
|
if (state->dialog_input_cursor > (int)explorer_strlen(state->dialog_input)) {
|
||||||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||||
}
|
}
|
||||||
|
if (state->dialog_input_cursor < 0) state->dialog_input_cursor = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1379,27 +1403,27 @@ static void explorer_handle_click(Window *win, int x, int y) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= win->w - 90 && x < win->w - 55 &&
|
if (WIDGET_CLICKED(&state->btn_dropdown, win->x + x, win->y + y + 20)) {
|
||||||
y >= button_y && y < button_y + 22) {
|
state->btn_dropdown.pressed = false;
|
||||||
dropdown_menu_toggle(win);
|
dropdown_menu_toggle(win);
|
||||||
state->drive_menu_visible = false;
|
state->drive_menu_visible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= win->w - 40 && x < win->w - 10 &&
|
if (WIDGET_CLICKED(&state->btn_back, win->x + x, win->y + y + 20)) {
|
||||||
y >= button_y && y < button_y + 22) {
|
state->btn_back.pressed = false;
|
||||||
explorer_navigate_to(win, "..");
|
explorer_navigate_to(win, "..");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= win->w - 160 && x < win->w - 130 &&
|
if (WIDGET_CLICKED(&state->btn_up, win->x + x, win->y + y + 20)) {
|
||||||
y >= button_y && y < button_y + 22) {
|
state->btn_up.pressed = false;
|
||||||
if (state->explorer_scroll_row > 0) state->explorer_scroll_row--;
|
if (state->explorer_scroll_row > 0) state->explorer_scroll_row--;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x >= win->w - 125 && x < win->w - 95 &&
|
if (WIDGET_CLICKED(&state->btn_fwd, win->x + x, win->y + y + 20)) {
|
||||||
y >= button_y && y < button_y + 22) {
|
state->btn_fwd.pressed = false;
|
||||||
int total_rows = (state->item_count + EXPLORER_COLS - 1) / EXPLORER_COLS;
|
int total_rows = (state->item_count + EXPLORER_COLS - 1) / EXPLORER_COLS;
|
||||||
if (total_rows == 0) total_rows = 1;
|
if (total_rows == 0) total_rows = 1;
|
||||||
if (state->explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) state->explorer_scroll_row++;
|
if (state->explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) state->explorer_scroll_row++;
|
||||||
@@ -1675,7 +1699,7 @@ static void explorer_handle_file_context_menu_click(Window *win, int x, int y) {
|
|||||||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||||
explorer_strcpy(state->dialog_target_path, full_path);
|
explorer_strcpy(state->dialog_target_path, full_path);
|
||||||
} else if (clicked_action == 110) { // Open with Text Editor
|
} else if (clicked_action == 110) { // Open with Text Editor
|
||||||
process_create_elf("A:/bin/txtedit.elf", full_path);
|
process_create_elf("/bin/txtedit.elf", full_path);
|
||||||
} else if (clicked_action == ACTION_RESTORE) {
|
} else if (clicked_action == ACTION_RESTORE) {
|
||||||
explorer_restore_file(win, state->file_context_menu_item);
|
explorer_restore_file(win, state->file_context_menu_item);
|
||||||
} else if (clicked_action == ACTION_CREATE_SHORTCUT) {
|
} else if (clicked_action == ACTION_CREATE_SHORTCUT) {
|
||||||
@@ -1859,12 +1883,12 @@ Window* explorer_create_window(const char *path) {
|
|||||||
state->explorer_scroll_row = 0;
|
state->explorer_scroll_row = 0;
|
||||||
state->dialog_state = DIALOG_NONE;
|
state->dialog_state = DIALOG_NONE;
|
||||||
|
|
||||||
if (explorer_strcmp(path, "/") == 0) explorer_load_directory(win, "A:/");
|
if (explorer_strcmp(path, "/") == 0) explorer_load_directory(win, "/");
|
||||||
else explorer_load_directory(win, path);
|
else explorer_load_directory(win, path);
|
||||||
|
|
||||||
explorer_wins[explorer_win_count++] = win;
|
explorer_wins[explorer_win_count++] = win;
|
||||||
wm_add_window(win);
|
wm_add_window_locked(win);
|
||||||
wm_bring_to_front(win);
|
// wm_add_window_locked already calls wm_bring_to_front_locked!
|
||||||
|
|
||||||
return win;
|
return win;
|
||||||
}
|
}
|
||||||
@@ -1896,11 +1920,11 @@ void explorer_init(void) {
|
|||||||
state->dialog_state = DIALOG_NONE;
|
state->dialog_state = DIALOG_NONE;
|
||||||
|
|
||||||
explorer_wins[explorer_win_count++] = &win_explorer;
|
explorer_wins[explorer_win_count++] = &win_explorer;
|
||||||
explorer_load_directory(&win_explorer, "A:/");
|
explorer_load_directory(&win_explorer, "/");
|
||||||
}
|
}
|
||||||
void explorer_reset(void) {
|
void explorer_reset(void) {
|
||||||
ExplorerState *state = (ExplorerState*)win_explorer.data;
|
ExplorerState *state = (ExplorerState*)win_explorer.data;
|
||||||
explorer_load_directory(&win_explorer, "A:/");
|
explorer_load_directory(&win_explorer, "/");
|
||||||
state->explorer_scroll_row = 0;
|
state->explorer_scroll_row = 0;
|
||||||
win_explorer.focused = false;
|
win_explorer.focused = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "wm.h"
|
#include "wm.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include "libwidget.h"
|
||||||
|
|
||||||
// External windows references (for opening other apps)
|
// External windows references (for opening other apps)
|
||||||
extern Window win_explorer;
|
extern Window win_explorer;
|
||||||
@@ -55,6 +56,16 @@ typedef struct {
|
|||||||
int file_context_menu_y;
|
int file_context_menu_y;
|
||||||
int file_context_menu_item;
|
int file_context_menu_item;
|
||||||
|
|
||||||
|
// GUI widgets
|
||||||
|
widget_button_t btn_primary;
|
||||||
|
widget_button_t btn_secondary;
|
||||||
|
widget_button_t btn_dropdown;
|
||||||
|
widget_button_t btn_back;
|
||||||
|
widget_button_t btn_up;
|
||||||
|
widget_button_t btn_fwd;
|
||||||
|
|
||||||
|
widget_textbox_t dialog_textbox;
|
||||||
|
|
||||||
} ExplorerState;
|
} ExplorerState;
|
||||||
|
|
||||||
void explorer_init(void);
|
void explorer_init(void);
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ float kfabsf(float x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float kpowf(float b, float e) {
|
float kpowf(float b, float e) {
|
||||||
// Very simplified pow for stb_truetype's needs
|
|
||||||
if (e == 0) return 1.0f;
|
if (e == 0) return 1.0f;
|
||||||
if (e == 1) return b;
|
if (e == 1) return b;
|
||||||
if (e == 0.5f) return ksqrtf(b);
|
if (e == 0.5f) return ksqrtf(b);
|
||||||
|
|
||||||
// Fallback/log-based would be complex, let's see if this suffices
|
|
||||||
float res = 1.0f;
|
float res = 1.0f;
|
||||||
for (int i = 0; i < (int)e; i++) res *= b;
|
for (int i = 0; i < (int)e; i++) res *= b;
|
||||||
return res;
|
return res;
|
||||||
@@ -33,13 +31,11 @@ float kfmodf(float x, float y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float kcosf(float x) {
|
float kcosf(float x) {
|
||||||
// Taylor series for cos(x) around 0
|
|
||||||
float x2 = x * x;
|
float x2 = x * x;
|
||||||
return 1.0f - (x2 / 2.0f) + (x2 * x2 / 24.0f) - (x2 * x2 * x2 / 720.0f);
|
return 1.0f - (x2 / 2.0f) + (x2 * x2 / 24.0f) - (x2 * x2 * x2 / 720.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float kacosf(float x) {
|
float kacosf(float x) {
|
||||||
// Very rough approximation for acos(x)
|
|
||||||
if (x >= 1.0f) return 0;
|
if (x >= 1.0f) return 0;
|
||||||
if (x <= -1.0f) return 3.14159f;
|
if (x <= -1.0f) return 3.14159f;
|
||||||
return 1.57079f - x - (x*x*x)/6.0f;
|
return 1.57079f - x - (x*x*x)/6.0f;
|
||||||
@@ -58,24 +54,46 @@ static inline uint32_t alpha_blend(uint32_t bg, uint32_t fg, uint8_t alpha) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static ttf_font_t *default_font = NULL;
|
static ttf_font_t *default_font = NULL;
|
||||||
|
static ttf_font_t *fallback_font = NULL;
|
||||||
|
|
||||||
|
void font_manager_set_fallback_font(ttf_font_t *font) {
|
||||||
|
fallback_font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAX_LOADED_FONTS 8
|
||||||
|
typedef struct {
|
||||||
|
char path[128];
|
||||||
|
ttf_font_t *font;
|
||||||
|
} loaded_font_t;
|
||||||
|
|
||||||
|
static loaded_font_t loaded_fonts[MAX_LOADED_FONTS];
|
||||||
|
static int loaded_font_count = 0;
|
||||||
|
|
||||||
#define FONT_CACHE_SIZE 2048
|
#define FONT_CACHE_SIZE 2048
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char c;
|
uint32_t codepoint;
|
||||||
float pixel_height;
|
float scale;
|
||||||
|
void *font;
|
||||||
int w, h, xoff, yoff;
|
int w, h, xoff, yoff;
|
||||||
unsigned char *bitmap;
|
unsigned char *bitmap;
|
||||||
} font_cache_entry_t;
|
} font_cache_entry_t;
|
||||||
|
static font_cache_entry_t font_cache[FONT_CACHE_SIZE] = {0};
|
||||||
// Cache is disabled for now due to race conditions and collisions
|
|
||||||
// static font_cache_entry_t g_font_cache[FONT_CACHE_SIZE];
|
|
||||||
|
|
||||||
bool font_manager_init(void) {
|
bool font_manager_init(void) {
|
||||||
// We'll load a default font later if available
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ttf_font_t* font_manager_load(const char *path, float size) {
|
ttf_font_t* font_manager_load(const char *path, float size) {
|
||||||
|
(void)size;
|
||||||
|
|
||||||
|
for(int i=0; i<loaded_font_count; i++) {
|
||||||
|
int match = 1;
|
||||||
|
for(int j=0; path[j] || loaded_fonts[i].path[j]; j++) {
|
||||||
|
if (path[j] != loaded_fonts[i].path[j]) { match = 0; break; }
|
||||||
|
}
|
||||||
|
if (match) return loaded_fonts[i].font;
|
||||||
|
}
|
||||||
|
|
||||||
FAT32_FileHandle *fh = fat32_open(path, "r");
|
FAT32_FileHandle *fh = fat32_open(path, "r");
|
||||||
if (!fh || !fh->valid) {
|
if (!fh || !fh->valid) {
|
||||||
serial_write("[FONT] Failed to open font file: ");
|
serial_write("[FONT] Failed to open font file: ");
|
||||||
@@ -126,37 +144,90 @@ ttf_font_t* font_manager_load(const char *path, float size) {
|
|||||||
stbtt_GetFontVMetrics(info, &font->ascent, &font->descent, &font->line_gap);
|
stbtt_GetFontVMetrics(info, &font->ascent, &font->descent, &font->line_gap);
|
||||||
|
|
||||||
if (!default_font) default_font = font;
|
if (!default_font) default_font = font;
|
||||||
|
|
||||||
|
if (loaded_font_count < MAX_LOADED_FONTS) {
|
||||||
|
int i=0; while(path[i] && i<127) { loaded_fonts[loaded_font_count].path[i] = path[i]; i++; }
|
||||||
|
loaded_fonts[loaded_font_count].path[i] = 0;
|
||||||
|
loaded_fonts[loaded_font_count].font = font;
|
||||||
|
loaded_font_count++;
|
||||||
|
}
|
||||||
|
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_manager_render_char(ttf_font_t *font, int x, int y, char c, uint32_t color, void (*put_pixel_fn)(int, int, uint32_t)) {
|
uint32_t utf8_decode(const char **s) {
|
||||||
if (!font) font = default_font;
|
const unsigned char *u = (const unsigned char *)*s;
|
||||||
if (!font) return;
|
if (!*u) return 0;
|
||||||
font_manager_render_char_scaled(font, x, y, c, color, font->pixel_height, put_pixel_fn);
|
|
||||||
|
if (u[0] < 0x80) {
|
||||||
|
*s = (const char *)(u + 1);
|
||||||
|
return u[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((u[0] & 0xE0) == 0xC0 && (u[1] & 0xC0) == 0x80) {
|
||||||
|
*s = (const char *)(u + 2);
|
||||||
|
return ((u[0] & 0x1F) << 6) | (u[1] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((u[0] & 0xF0) == 0xE0 && (u[1] & 0xC0) == 0x80 && (u[2] & 0xC0) == 0x80) {
|
||||||
|
*s = (const char *)(u + 3);
|
||||||
|
return ((u[0] & 0x0F) << 12) | ((u[1] & 0x3F) << 6) | (u[2] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((u[0] & 0xF8) == 0xF0 && (u[1] & 0xC0) == 0x80 && (u[2] & 0xC0) == 0x80 && (u[3] & 0xC0) == 0x80) {
|
||||||
|
*s = (const char *)(u + 4);
|
||||||
|
return ((u[0] & 0x07) << 18) | ((u[1] & 0x3F) << 12) | ((u[2] & 0x3F) << 6) | (u[3] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t codepoint = u[0];
|
||||||
|
*s = (const char *)(u + 1);
|
||||||
|
if (codepoint == 128) return 0x2014;
|
||||||
|
if (codepoint == 129) return 0x2013;
|
||||||
|
if (codepoint == 130) return 0x2022;
|
||||||
|
if (codepoint == 131) return 0x2026;
|
||||||
|
if (codepoint == 132) return 0x2122;
|
||||||
|
if (codepoint == 133) return 0x20AC;
|
||||||
|
if (codepoint == 134) return 0x00B7;
|
||||||
|
return codepoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, void (*put_pixel_fn)(int, int, uint32_t)) {
|
void font_manager_render_char(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, void (*put_pixel_fn)(int, int, uint32_t)) {
|
||||||
|
if (!font) font = default_font;
|
||||||
|
if (!font) return;
|
||||||
|
font_manager_render_char_scaled(font, x, y, codepoint, color, font->pixel_height, put_pixel_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, void (*put_pixel_fn)(int, int, uint32_t)) {
|
||||||
if (!font) font = default_font;
|
if (!font) font = default_font;
|
||||||
if (!font) return;
|
if (!font) return;
|
||||||
|
|
||||||
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
uint32_t hash = (codepoint * 31 + (uint32_t)scale * 73) % FONT_CACHE_SIZE;
|
||||||
|
font_cache_entry_t *entry = &font_cache[hash];
|
||||||
|
|
||||||
unsigned char *bitmap = NULL;
|
unsigned char *bitmap = NULL;
|
||||||
int w, h, xoff, yoff;
|
int w, h, xoff, yoff;
|
||||||
|
|
||||||
float real_scale = stbtt_ScaleForPixelHeight(info, scale); // Convert pixel size back to stbtt scale
|
if (entry->bitmap && entry->codepoint == codepoint && entry->scale == scale && entry->font == font) {
|
||||||
|
bitmap = entry->bitmap;
|
||||||
int codepoint = (unsigned char)c;
|
w = entry->w; h = entry->h; xoff = entry->xoff; yoff = entry->yoff;
|
||||||
if (codepoint == 128) codepoint = 0x2014; // — (—)
|
} else {
|
||||||
if (codepoint == 129) codepoint = 0x2013; // – (–)
|
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||||
if (codepoint == 130) codepoint = 0x2022; // • (•)
|
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||||
if (codepoint == 131) codepoint = 0x2026; // … (…)
|
|
||||||
if (codepoint == 132) codepoint = 0x2122; // ™ (™)
|
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||||
if (codepoint == 133) codepoint = 0x20AC; // € (€)
|
info = (stbtt_fontinfo *)fallback_font->info;
|
||||||
if (codepoint == 134) codepoint = 0x00B7; // · (·)
|
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||||
|
}
|
||||||
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
|
||||||
|
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
||||||
|
|
||||||
|
if (entry->bitmap) stbtt_FreeBitmap(entry->bitmap, NULL);
|
||||||
|
entry->codepoint = codepoint;
|
||||||
|
entry->scale = scale;
|
||||||
|
entry->font = font;
|
||||||
|
entry->w = w; entry->h = h; entry->xoff = xoff; entry->yoff = yoff;
|
||||||
|
entry->bitmap = bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
if (bitmap) {
|
if (bitmap) {
|
||||||
for (int row = 0; row < h; row++) {
|
for (int row = 0; row < h; row++) {
|
||||||
@@ -170,7 +241,55 @@ void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, char c, uin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stbtt_FreeBitmap(bitmap, NULL);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t)) {
|
||||||
|
if (!font) font = default_font;
|
||||||
|
if (!font) return;
|
||||||
|
|
||||||
|
uint32_t hash = (codepoint * 31 + (uint32_t)scale * 73) % FONT_CACHE_SIZE;
|
||||||
|
font_cache_entry_t *entry = &font_cache[hash];
|
||||||
|
|
||||||
|
unsigned char *bitmap = NULL;
|
||||||
|
int w, h, xoff, yoff;
|
||||||
|
|
||||||
|
if (entry->bitmap && entry->codepoint == codepoint && entry->scale == scale && entry->font == font) {
|
||||||
|
bitmap = entry->bitmap;
|
||||||
|
w = entry->w; h = entry->h; xoff = entry->xoff; yoff = entry->yoff;
|
||||||
|
} else {
|
||||||
|
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||||
|
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||||
|
|
||||||
|
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||||
|
info = (stbtt_fontinfo *)fallback_font->info;
|
||||||
|
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap = stbtt_GetCodepointBitmap(info, 0, real_scale, codepoint, &w, &h, &xoff, &yoff);
|
||||||
|
|
||||||
|
if (entry->bitmap) stbtt_FreeBitmap(entry->bitmap, NULL);
|
||||||
|
entry->codepoint = codepoint;
|
||||||
|
entry->scale = scale;
|
||||||
|
entry->font = font;
|
||||||
|
entry->w = w; entry->h = h; entry->xoff = xoff; entry->yoff = yoff;
|
||||||
|
entry->bitmap = bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmap) {
|
||||||
|
for (int row = 0; row < h; row++) {
|
||||||
|
int slant_offset = (int)((h - row) * slope);
|
||||||
|
|
||||||
|
for (int col = 0; col < w; col++) {
|
||||||
|
unsigned char alpha = bitmap[row * w + col];
|
||||||
|
if (alpha > 0) {
|
||||||
|
int px = x + col + xoff + slant_offset;
|
||||||
|
int py = y + row + yoff;
|
||||||
|
uint32_t bg = graphics_get_pixel(px, py);
|
||||||
|
put_pixel_fn(px, py, alpha_blend(bg, color, alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,18 +329,33 @@ int font_manager_get_string_width_scaled(ttf_font_t *font, const char *s, float
|
|||||||
int width = 0;
|
int width = 0;
|
||||||
while (*s) {
|
while (*s) {
|
||||||
int advance, lsb;
|
int advance, lsb;
|
||||||
int codepoint = (unsigned char)*s;
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
if (codepoint == 128) codepoint = 0x2014; // — (—)
|
stbtt_fontinfo *current_info = info;
|
||||||
if (codepoint == 129) codepoint = 0x2013; // – (–)
|
float current_scale = real_scale;
|
||||||
if (codepoint == 130) codepoint = 0x2022; // • (•)
|
|
||||||
if (codepoint == 131) codepoint = 0x2026; // … (…)
|
if (stbtt_FindGlyphIndex(current_info, codepoint) == 0 && fallback_font) {
|
||||||
if (codepoint == 132) codepoint = 0x2122; // ™ (™)
|
current_info = (stbtt_fontinfo *)fallback_font->info;
|
||||||
if (codepoint == 133) codepoint = 0x20AC; // € (€)
|
current_scale = stbtt_ScaleForPixelHeight(current_info, scale);
|
||||||
if (codepoint == 134) codepoint = 0x00B7; // · (·)
|
}
|
||||||
stbtt_GetCodepointHMetrics(info, codepoint, &advance, &lsb);
|
|
||||||
// Round per-character to match draw_string's accumulation
|
stbtt_GetCodepointHMetrics(current_info, codepoint, &advance, &lsb);
|
||||||
width += (int)(advance * real_scale + 0.5f);
|
width += (int)(advance * current_scale + 0.5f);
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int font_manager_get_codepoint_width_scaled(ttf_font_t *font, uint32_t codepoint, float scale) {
|
||||||
|
if (!font) font = default_font;
|
||||||
|
if (!font) return 0;
|
||||||
|
stbtt_fontinfo *info = (stbtt_fontinfo *)font->info;
|
||||||
|
float real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||||
|
|
||||||
|
if (stbtt_FindGlyphIndex(info, codepoint) == 0 && fallback_font) {
|
||||||
|
info = (stbtt_fontinfo *)fallback_font->info;
|
||||||
|
real_scale = stbtt_ScaleForPixelHeight(info, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
int advance, lsb;
|
||||||
|
stbtt_GetCodepointHMetrics(info, codepoint, &advance, &lsb);
|
||||||
|
return (int)(advance * real_scale + 0.5f);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
// stb_truetype math stubs
|
|
||||||
extern float ksqrtf(float x);
|
extern float ksqrtf(float x);
|
||||||
extern float kpowf(float b, float e);
|
extern float kpowf(float b, float e);
|
||||||
extern float kfmodf(float x, float y);
|
extern float kfmodf(float x, float y);
|
||||||
@@ -24,21 +23,18 @@ extern float kfabsf(float x);
|
|||||||
|
|
||||||
#define STBTT_assert(x) ((void)0)
|
#define STBTT_assert(x) ((void)0)
|
||||||
|
|
||||||
// Memory management
|
|
||||||
#define STBTT_malloc(x,u) kmalloc(x)
|
#define STBTT_malloc(x,u) kmalloc(x)
|
||||||
#define STBTT_free(x,u) kfree(x)
|
#define STBTT_free(x,u) kfree(x)
|
||||||
|
|
||||||
// String functions
|
|
||||||
#define STBTT_memcpy mem_memcpy
|
#define STBTT_memcpy mem_memcpy
|
||||||
#define STBTT_memset mem_memset
|
#define STBTT_memset mem_memset
|
||||||
|
|
||||||
// Data types
|
|
||||||
typedef uint64_t STBTT_ptrsize;
|
typedef uint64_t STBTT_ptrsize;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *data;
|
void *data;
|
||||||
size_t size;
|
size_t size;
|
||||||
void *info; // stbtt_fontinfo
|
void *info;
|
||||||
float scale;
|
float scale;
|
||||||
float pixel_height;
|
float pixel_height;
|
||||||
int ascent;
|
int ascent;
|
||||||
@@ -48,10 +44,14 @@ typedef struct {
|
|||||||
|
|
||||||
bool font_manager_init(void);
|
bool font_manager_init(void);
|
||||||
ttf_font_t* font_manager_load(const char *path, float size);
|
ttf_font_t* font_manager_load(const char *path, float size);
|
||||||
void font_manager_render_char(ttf_font_t *font, int x, int y, char c, uint32_t color, void (*put_pixel_fn)(int, int, uint32_t));
|
void font_manager_set_fallback_font(ttf_font_t *font);
|
||||||
void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, char c, uint32_t color, float scale, void (*put_pixel_fn)(int, int, uint32_t));
|
uint32_t utf8_decode(const char **s);
|
||||||
|
void font_manager_render_char(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, void (*put_pixel_fn)(int, int, uint32_t));
|
||||||
|
void font_manager_render_char_scaled(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, void (*put_pixel_fn)(int, int, uint32_t));
|
||||||
|
void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t));
|
||||||
int font_manager_get_string_width(ttf_font_t *font, const char *s);
|
int font_manager_get_string_width(ttf_font_t *font, const char *s);
|
||||||
int font_manager_get_string_width_scaled(ttf_font_t *font, const char *s, float scale);
|
int font_manager_get_string_width_scaled(ttf_font_t *font, const char *s, float scale);
|
||||||
|
int font_manager_get_codepoint_width_scaled(ttf_font_t *font, uint32_t codepoint, float scale);
|
||||||
|
|
||||||
int font_manager_get_font_height_scaled(ttf_font_t *font, float scale);
|
int font_manager_get_font_height_scaled(ttf_font_t *font, float scale);
|
||||||
int font_manager_get_font_ascent_scaled(ttf_font_t *font, float scale);
|
int font_manager_get_font_ascent_scaled(ttf_font_t *font, float scale);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "font_manager.h"
|
#include "font_manager.h"
|
||||||
|
#include "../mem/memory_manager.h"
|
||||||
|
|
||||||
static struct limine_framebuffer *g_fb = NULL;
|
static struct limine_framebuffer *g_fb = NULL;
|
||||||
static uint32_t g_bg_color = 0xFF696969;
|
static uint32_t g_bg_color = 0xFF696969;
|
||||||
@@ -30,12 +31,19 @@ static DirtyRect g_dirty = {0, 0, 0, 0, false};
|
|||||||
#define MAX_FB_HEIGHT 2048
|
#define MAX_FB_HEIGHT 2048
|
||||||
static uint32_t g_back_buffer[MAX_FB_WIDTH * MAX_FB_HEIGHT] __attribute__((aligned(4096)));
|
static uint32_t g_back_buffer[MAX_FB_WIDTH * MAX_FB_HEIGHT] __attribute__((aligned(4096)));
|
||||||
|
|
||||||
static int g_clip_x = 0, g_clip_y = 0, g_clip_w = 0, g_clip_h = 0;
|
#define MAX_RENDER_CPUS 32
|
||||||
static bool g_clip_enabled = false;
|
#define CLIP_STACK_DEPTH 8
|
||||||
|
static int g_clip_stack_x[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||||
|
static int g_clip_stack_y[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||||
|
static int g_clip_stack_w[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||||
|
static int g_clip_stack_h[MAX_RENDER_CPUS][CLIP_STACK_DEPTH];
|
||||||
|
static int g_clip_stack_ptr[MAX_RENDER_CPUS] = {0};
|
||||||
|
static bool g_clip_enabled[MAX_RENDER_CPUS] = {false};
|
||||||
|
|
||||||
static uint32_t *g_render_target = NULL;
|
extern uint32_t smp_this_cpu_id(void);
|
||||||
static int g_rt_width = 0;
|
static uint32_t *g_render_target[MAX_RENDER_CPUS] = {0};
|
||||||
static int g_rt_height = 0;
|
static int g_rt_width[MAX_RENDER_CPUS] = {0};
|
||||||
|
static int g_rt_height[MAX_RENDER_CPUS] = {0};
|
||||||
|
|
||||||
static ttf_font_t *g_current_ttf = NULL;
|
static ttf_font_t *g_current_ttf = NULL;
|
||||||
|
|
||||||
@@ -54,6 +62,7 @@ void graphics_init_fonts(void) {
|
|||||||
if (!g_current_ttf) {
|
if (!g_current_ttf) {
|
||||||
serial_write("[FONT] Falling back to bitmap font\n");
|
serial_write("[FONT] Falling back to bitmap font\n");
|
||||||
}
|
}
|
||||||
|
font_manager_set_fallback_font(font_manager_load("/Library/Fonts/Emoji/NotoEmoji-VariableFont_wght.ttf", 15.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_update_resolution(int width, int height, int bpp, void* fb_addr, int color_mode) {
|
void graphics_update_resolution(int width, int height, int bpp, void* fb_addr, int color_mode) {
|
||||||
@@ -127,10 +136,6 @@ static void merge_dirty_rect(int x, int y, int w, int h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void graphics_mark_dirty(int x, int y, int w, int h) {
|
void graphics_mark_dirty(int x, int y, int w, int h) {
|
||||||
uint64_t rflags;
|
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
|
||||||
|
|
||||||
// Clamp to screen bounds
|
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
w += x;
|
w += x;
|
||||||
x = 0;
|
x = 0;
|
||||||
@@ -147,12 +152,10 @@ void graphics_mark_dirty(int x, int y, int w, int h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (w <= 0 || h <= 0) {
|
if (w <= 0 || h <= 0) {
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge_dirty_rect(x, y, w, h);
|
merge_dirty_rect(x, y, w, h);
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_mark_screen_dirty(void) {
|
void graphics_mark_screen_dirty(void) {
|
||||||
@@ -168,22 +171,31 @@ DirtyRect graphics_get_dirty_rect(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void graphics_clear_dirty(void) {
|
void graphics_clear_dirty(void) {
|
||||||
uint64_t rflags;
|
extern uint64_t wm_lock_acquire(void);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
extern void wm_lock_release(uint64_t);
|
||||||
|
uint64_t rflags = wm_lock_acquire();
|
||||||
|
g_dirty.active = false;
|
||||||
|
wm_lock_release(rflags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphics_clear_dirty_no_lock(void) {
|
||||||
g_dirty.active = false;
|
g_dirty.active = false;
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_set_render_target(uint32_t *buffer, int w, int h) {
|
void graphics_set_render_target(uint32_t *buffer, int w, int h) {
|
||||||
g_render_target = buffer;
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
g_rt_width = w;
|
if (cpu < MAX_RENDER_CPUS) {
|
||||||
g_rt_height = h;
|
g_render_target[cpu] = buffer;
|
||||||
|
g_rt_width[cpu] = w;
|
||||||
|
g_rt_height[cpu] = h;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void put_pixel(int x, int y, uint32_t color) {
|
void put_pixel(int x, int y, uint32_t color) {
|
||||||
if (g_render_target) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
if (x >= 0 && x < g_rt_width && y >= 0 && y < g_rt_height) {
|
if (cpu < MAX_RENDER_CPUS && g_render_target[cpu]) {
|
||||||
g_render_target[y * g_rt_width + x] = color;
|
if (x >= 0 && x < g_rt_width[cpu] && y >= 0 && y < g_rt_height[cpu]) {
|
||||||
|
g_render_target[cpu][y * g_rt_width[cpu] + x] = color;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -191,9 +203,10 @@ void put_pixel(int x, int y, uint32_t color) {
|
|||||||
if (!g_fb) return;
|
if (!g_fb) return;
|
||||||
if (x < 0 || x >= (int)g_fb->width || y < 0 || y >= (int)g_fb->height) return;
|
if (x < 0 || x >= (int)g_fb->width || y < 0 || y >= (int)g_fb->height) return;
|
||||||
|
|
||||||
if (g_clip_enabled) {
|
if (g_clip_enabled[cpu]) {
|
||||||
if (x < g_clip_x || x >= g_clip_x + g_clip_w ||
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
y < g_clip_y || y >= g_clip_y + g_clip_h) {
|
if (x < g_clip_stack_x[cpu][ptr] || x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr] ||
|
||||||
|
y < g_clip_stack_y[cpu][ptr] || y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,9 +216,10 @@ void put_pixel(int x, int y, uint32_t color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t graphics_get_pixel(int x, int y) {
|
uint32_t graphics_get_pixel(int x, int y) {
|
||||||
if (g_render_target) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
if (x >= 0 && x < g_rt_width && y >= 0 && y < g_rt_height) {
|
if (cpu < MAX_RENDER_CPUS && g_render_target[cpu]) {
|
||||||
return g_render_target[y * g_rt_width + x];
|
if (x >= 0 && x < g_rt_width[cpu] && y >= 0 && y < g_rt_height[cpu]) {
|
||||||
|
return g_render_target[cpu][y * g_rt_width[cpu] + x];
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -219,15 +233,16 @@ uint32_t graphics_get_pixel(int x, int y) {
|
|||||||
void draw_rect(int x, int y, int w, int h, uint32_t color) {
|
void draw_rect(int x, int y, int w, int h, uint32_t color) {
|
||||||
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
|
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
|
||||||
|
|
||||||
if (g_render_target) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
|
if (cpu < MAX_RENDER_CPUS && g_render_target[cpu]) {
|
||||||
if (x1 < 0) x1 = 0;
|
if (x1 < 0) x1 = 0;
|
||||||
if (y1 < 0) y1 = 0;
|
if (y1 < 0) y1 = 0;
|
||||||
if (x2 > g_rt_width) x2 = g_rt_width;
|
if (x2 > g_rt_width[cpu]) x2 = g_rt_width[cpu];
|
||||||
if (y2 > g_rt_height) y2 = g_rt_height;
|
if (y2 > g_rt_height[cpu]) y2 = g_rt_height[cpu];
|
||||||
if (x1 >= x2 || y1 >= y2) return;
|
if (x1 >= x2 || y1 >= y2) return;
|
||||||
|
|
||||||
for (int i = y1; i < y2; i++) {
|
for (int i = y1; i < y2; i++) {
|
||||||
uint32_t *row = &g_render_target[i * g_rt_width + x1];
|
uint32_t *row = &g_render_target[cpu][i * g_rt_width[cpu] + x1];
|
||||||
int len = x2 - x1;
|
int len = x2 - x1;
|
||||||
for (int j = 0; j < len; j++) {
|
for (int j = 0; j < len; j++) {
|
||||||
row[j] = color;
|
row[j] = color;
|
||||||
@@ -238,11 +253,12 @@ void draw_rect(int x, int y, int w, int h, uint32_t color) {
|
|||||||
|
|
||||||
if (!g_fb) return;
|
if (!g_fb) return;
|
||||||
|
|
||||||
if (g_clip_enabled) {
|
if (g_clip_enabled[cpu]) {
|
||||||
if (x1 < g_clip_x) x1 = g_clip_x;
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
if (y1 < g_clip_y) y1 = g_clip_y;
|
if (x1 < g_clip_stack_x[cpu][ptr]) x1 = g_clip_stack_x[cpu][ptr];
|
||||||
if (x2 > g_clip_x + g_clip_w) x2 = g_clip_x + g_clip_w;
|
if (y1 < g_clip_stack_y[cpu][ptr]) y1 = g_clip_stack_y[cpu][ptr];
|
||||||
if (y2 > g_clip_y + g_clip_h) y2 = g_clip_y + g_clip_h;
|
if (x2 > g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]) x2 = g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr];
|
||||||
|
if (y2 > g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) y2 = g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x1 < 0) x1 = 0;
|
if (x1 < 0) x1 = 0;
|
||||||
@@ -278,55 +294,191 @@ static int isqrt(int n) {
|
|||||||
void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) {
|
void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color) {
|
||||||
if (radius > w / 2) radius = w / 2;
|
if (radius > w / 2) radius = w / 2;
|
||||||
if (radius > h / 2) radius = h / 2;
|
if (radius > h / 2) radius = h / 2;
|
||||||
if (radius < 1) radius = 1;
|
if (radius < 1) {
|
||||||
|
// Draw a simple rect outline if no radius
|
||||||
|
draw_rect(x, y, w, 1, color);
|
||||||
|
draw_rect(x, y + h - 1, w, 1, color);
|
||||||
|
draw_rect(x, y + 1, 1, h - 2, color);
|
||||||
|
draw_rect(x + w - 1, y + 1, 1, h - 2, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Draw top and bottom edges
|
// Draw top and bottom straight edges
|
||||||
draw_rect(x + radius, y, w - 2*radius, 1, color);
|
draw_rect(x + radius, y, w - 2*radius, 1, color);
|
||||||
draw_rect(x + radius, y + h - 1, w - 2*radius, 1, color);
|
draw_rect(x + radius, y + h - 1, w - 2*radius, 1, color);
|
||||||
|
|
||||||
// Draw left and right edges
|
// Draw left and right straight edges
|
||||||
draw_rect(x, y + radius, 1, h - 2*radius, color);
|
draw_rect(x, y + radius, 1, h - 2*radius, color);
|
||||||
draw_rect(x + w - 1, y + radius, 1, h - 2*radius, color);
|
draw_rect(x + w - 1, y + radius, 1, h - 2*radius, color);
|
||||||
|
|
||||||
// Draw corner circles using integer approximation
|
// Draw four corner arcs
|
||||||
for (int i = 0; i < radius; i++) {
|
for (int dy = 0; dy < radius; dy++) {
|
||||||
int j = isqrt(radius*radius - i*i);
|
int y_dist = radius - 1 - dy;
|
||||||
|
int dx = isqrt(radius*radius - y_dist*y_dist);
|
||||||
|
int next_dx = (dy < radius - 1) ? isqrt(radius*radius - (y_dist - 1)*(y_dist - 1)) : radius;
|
||||||
|
|
||||||
// Top-left corner
|
for (int i = dx; i < next_dx && i <= radius; i++) {
|
||||||
put_pixel(x + radius - i - 1, y + radius - j, color);
|
// Top-left
|
||||||
// Top-right corner
|
put_pixel(x + radius - 1 - i, y + dy, color);
|
||||||
put_pixel(x + w - radius + i, y + radius - j, color);
|
// Top-right
|
||||||
// Bottom-left corner
|
put_pixel(x + w - radius + i, y + dy, color);
|
||||||
put_pixel(x + radius - i - 1, y + h - radius + j - 1, color);
|
// Bottom-left
|
||||||
// Bottom-right corner
|
put_pixel(x + radius - 1 - i, y + h - 1 - dy, color);
|
||||||
put_pixel(x + w - radius + i, y + h - radius + j - 1, color);
|
// Bottom-right
|
||||||
|
put_pixel(x + w - radius + i, y + h - 1 - dy, color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw filled rounded rectangle
|
// Draw filled rounded rectangle
|
||||||
void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color) {
|
void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color) {
|
||||||
|
if (radius > w / 2) radius = w / 2;
|
||||||
|
if (radius > h / 2) radius = h / 2;
|
||||||
|
if (radius < 1) {
|
||||||
|
draw_rect(x, y, w, h, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw main rectangle body
|
||||||
|
draw_rect(x, y + radius, w, h - 2*radius, color);
|
||||||
|
|
||||||
|
// Draw rounded top and bottom caps
|
||||||
|
for (int dy = 0; dy < radius; dy++) {
|
||||||
|
int y_dist = radius - 1 - dy;
|
||||||
|
int dx = isqrt(radius*radius - y_dist*y_dist);
|
||||||
|
|
||||||
|
draw_rect(x + radius - dx, y + dy, w - 2*radius + 2*dx, 1, color);
|
||||||
|
draw_rect(x + radius - dx, y + h - 1 - dy, w - 2*radius + 2*dx, 1, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t blend_color_alpha(uint32_t bottom, uint32_t top, int alpha) {
|
||||||
|
if (alpha <= 0) return bottom;
|
||||||
|
if (alpha >= 255) return top;
|
||||||
|
|
||||||
|
int rb = (bottom >> 16) & 0xFF;
|
||||||
|
int gb = (bottom >> 8) & 0xFF;
|
||||||
|
int bb = bottom & 0xFF;
|
||||||
|
|
||||||
|
int rt = (top >> 16) & 0xFF;
|
||||||
|
int gt = (top >> 8) & 0xFF;
|
||||||
|
int bt = top & 0xFF;
|
||||||
|
|
||||||
|
int rr = rb + (((rt - rb) * alpha) >> 8);
|
||||||
|
int gg = gb + (((gt - gb) * alpha) >> 8);
|
||||||
|
int bb_new = bb + (((bt - bb) * alpha) >> 8);
|
||||||
|
|
||||||
|
return (rr << 16) | (gg << 8) | bb_new;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_rounded_rect_blurred(int x, int y, int w, int h, int radius, uint32_t tint_color, int blur_radius, int alpha) {
|
||||||
|
if (!g_fb) return;
|
||||||
|
int sw = get_screen_width();
|
||||||
|
int sh = get_screen_height();
|
||||||
|
|
||||||
|
if (x < 0) { w += x; x = 0; }
|
||||||
|
if (y < 0) { h += y; y = 0; }
|
||||||
|
if (x + w > sw) w = sw - x;
|
||||||
|
if (y + h > sh) h = sh - y;
|
||||||
|
if (w <= 0 || h <= 0) return;
|
||||||
|
|
||||||
if (radius > w / 2) radius = w / 2;
|
if (radius > w / 2) radius = w / 2;
|
||||||
if (radius > h / 2) radius = h / 2;
|
if (radius > h / 2) radius = h / 2;
|
||||||
if (radius < 1) radius = 1;
|
if (radius < 1) radius = 1;
|
||||||
|
|
||||||
// Draw main rectangle body (center part without corners)
|
uint32_t *tmp_buf = (uint32_t *)kmalloc(w * h * sizeof(uint32_t));
|
||||||
draw_rect(x + radius, y, w - 2*radius, h, color);
|
if (!tmp_buf) {
|
||||||
draw_rect(x, y + radius, radius, h - 2*radius, color);
|
draw_rounded_rect_filled(x, y, w, h, radius, tint_color);
|
||||||
draw_rect(x + w - radius, y + radius, radius, h - 2*radius, color);
|
return;
|
||||||
|
|
||||||
for (int dy = 0; dy < radius; dy++) {
|
|
||||||
int dx_top = isqrt(radius*radius - (radius - dy) * (radius - dy));
|
|
||||||
|
|
||||||
int dx_bottom = isqrt(radius*radius - dy*dy);
|
|
||||||
|
|
||||||
draw_rect(x + radius - dx_top, y + dy, dx_top, 1, color);
|
|
||||||
|
|
||||||
draw_rect(x + w - radius, y + dy, dx_top, 1, color);
|
|
||||||
|
|
||||||
draw_rect(x + radius - dx_bottom, y + h - radius + dy, dx_bottom, 1, color);
|
|
||||||
|
|
||||||
draw_rect(x + w - radius, y + h - radius + dy, dx_bottom, 1, color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int r = 0; r < h; r++) {
|
||||||
|
int g_y = y + r;
|
||||||
|
for (int c = 0; c < w; c++) {
|
||||||
|
int g_x = x + c;
|
||||||
|
|
||||||
|
int r_sum = 0, g_sum = 0, b_sum = 0, count = 0;
|
||||||
|
int start_kx = g_x - blur_radius;
|
||||||
|
int end_kx = g_x + blur_radius;
|
||||||
|
if (start_kx < 0) start_kx = 0;
|
||||||
|
if (end_kx >= sw) end_kx = sw - 1;
|
||||||
|
|
||||||
|
for (int kx = start_kx; kx <= end_kx; kx++) {
|
||||||
|
uint32_t pixel = g_back_buffer[g_y * sw + kx];
|
||||||
|
r_sum += (pixel >> 16) & 0xFF;
|
||||||
|
g_sum += (pixel >> 8) & 0xFF;
|
||||||
|
b_sum += pixel & 0xFF;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if(count == 0) count = 1;
|
||||||
|
uint32_t out_pixel = ((r_sum / count) << 16) | ((g_sum / count) << 8) | (b_sum / count);
|
||||||
|
tmp_buf[r * w + c] = out_pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int r = 0; r < h; r++) {
|
||||||
|
int g_y = y + r;
|
||||||
|
if (g_y < 0 || g_y >= sh) continue;
|
||||||
|
|
||||||
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
|
if (g_clip_enabled[cpu]) {
|
||||||
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
|
if (g_y < g_clip_stack_y[cpu][ptr] || g_y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int c = 0; c < w; c++) {
|
||||||
|
int g_x = x + c;
|
||||||
|
if (g_x < 0 || g_x >= sw) continue;
|
||||||
|
|
||||||
|
if (g_clip_enabled[cpu]) {
|
||||||
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
|
if (g_x < g_clip_stack_x[cpu][ptr] || g_x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool in_corner = false;
|
||||||
|
int dx = 0, dy = 0;
|
||||||
|
if (c < radius && r < radius) {
|
||||||
|
dx = radius - c - 1; dy = radius - r - 1;
|
||||||
|
in_corner = true;
|
||||||
|
} else if (c >= w - radius && r < radius) {
|
||||||
|
dx = c - (w - radius); dy = radius - r - 1;
|
||||||
|
in_corner = true;
|
||||||
|
} else if (c < radius && r >= h - radius) {
|
||||||
|
dx = radius - c - 1; dy = r - (h - radius);
|
||||||
|
in_corner = true;
|
||||||
|
} else if (c >= w - radius && r >= h - radius) {
|
||||||
|
dx = c - (w - radius); dy = r - (h - radius);
|
||||||
|
in_corner = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_corner) {
|
||||||
|
if (dx*dx + dy*dy >= radius*radius) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int r_sum = 0, g_sum = 0, b_sum = 0, count = 0;
|
||||||
|
int start_kr = r - blur_radius;
|
||||||
|
int end_kr = r + blur_radius;
|
||||||
|
if (start_kr < 0) start_kr = 0;
|
||||||
|
if (end_kr >= h) end_kr = h - 1;
|
||||||
|
|
||||||
|
for (int kr = start_kr; kr <= end_kr; kr++) {
|
||||||
|
uint32_t pixel = tmp_buf[kr * w + c];
|
||||||
|
r_sum += (pixel >> 16) & 0xFF;
|
||||||
|
g_sum += (pixel >> 8) & 0xFF;
|
||||||
|
b_sum += pixel & 0xFF;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if(count == 0) count = 1;
|
||||||
|
uint32_t blurred_pixel = ((r_sum / count) << 16) | ((g_sum / count) << 8) | (b_sum / count);
|
||||||
|
|
||||||
|
uint32_t final_pixel = blend_color_alpha(blurred_pixel, tint_color, alpha);
|
||||||
|
g_back_buffer[g_y * sw + g_x] = final_pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(tmp_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_char(int x, int y, char c, uint32_t color) {
|
void draw_char(int x, int y, char c, uint32_t color) {
|
||||||
@@ -338,9 +490,12 @@ void draw_char(int x, int y, char c, uint32_t color) {
|
|||||||
unsigned char uc = (unsigned char)c;
|
unsigned char uc = (unsigned char)c;
|
||||||
if (uc > 127) return;
|
if (uc > 127) return;
|
||||||
|
|
||||||
if (g_clip_enabled && !g_render_target) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
if (x + 8 <= g_clip_x || x >= g_clip_x + g_clip_w ||
|
bool has_rt = (cpu < MAX_RENDER_CPUS && g_render_target[cpu]);
|
||||||
y + 8 <= g_clip_y || y >= g_clip_y + g_clip_h) {
|
if (g_clip_enabled[cpu] && !has_rt) {
|
||||||
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
|
if (x + 8 <= g_clip_stack_x[cpu][ptr] || x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr] ||
|
||||||
|
y + 8 <= g_clip_stack_y[cpu][ptr] || y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,14 +511,16 @@ void draw_char(int x, int y, char c, uint32_t color) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitmap-only version for terminal — always uses 8x8 bitmap font regardless of TTF
|
|
||||||
void draw_char_bitmap(int x, int y, char c, uint32_t color) {
|
void draw_char_bitmap(int x, int y, char c, uint32_t color) {
|
||||||
unsigned char uc = (unsigned char)c;
|
unsigned char uc = (unsigned char)c;
|
||||||
if (uc > 127) return;
|
if (uc > 127) return;
|
||||||
|
|
||||||
if (g_clip_enabled && !g_render_target) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
if (x + 8 <= g_clip_x || x >= g_clip_x + g_clip_w ||
|
bool has_rt = (cpu < MAX_RENDER_CPUS && g_render_target[cpu]);
|
||||||
y + 8 <= g_clip_y || y >= g_clip_y + g_clip_h) {
|
if (g_clip_enabled[cpu] && !has_rt) {
|
||||||
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
|
if (x + 8 <= g_clip_stack_x[cpu][ptr] || x >= g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr] ||
|
||||||
|
y + 8 <= g_clip_stack_y[cpu][ptr] || y >= g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,7 +574,10 @@ int graphics_get_string_width_scaled(const char *s, float scale) {
|
|||||||
return font_manager_get_string_width_scaled(g_current_ttf, s, scale);
|
return font_manager_get_string_width_scaled(g_current_ttf, s, scale);
|
||||||
}
|
}
|
||||||
int len = 0;
|
int len = 0;
|
||||||
while (s && s[len]) len++;
|
while (s && *s) {
|
||||||
|
utf8_decode(&s);
|
||||||
|
len++;
|
||||||
|
}
|
||||||
return len * 8; // Fallback bitmap width
|
return len * 8; // Fallback bitmap width
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,47 +591,76 @@ void draw_string_scaled(int x, int y, const char *s, uint32_t color, float scale
|
|||||||
int cur_x = x;
|
int cur_x = x;
|
||||||
|
|
||||||
if (g_current_ttf) {
|
if (g_current_ttf) {
|
||||||
// We let the font manager handle the stbtt scale internally to avoid bringing stb_truetype into graphics.c
|
|
||||||
int baseline = y + font_manager_get_font_ascent_scaled(g_current_ttf, scale) - 2;
|
int baseline = y + font_manager_get_font_ascent_scaled(g_current_ttf, scale) - 2;
|
||||||
int line_height = font_manager_get_font_line_height_scaled(g_current_ttf, scale);
|
int line_height = font_manager_get_font_line_height_scaled(g_current_ttf, scale);
|
||||||
|
|
||||||
while (*s) {
|
while (*s) {
|
||||||
if (*s == '\n') {
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
|
if (codepoint == '\n') {
|
||||||
cur_x = x;
|
cur_x = x;
|
||||||
baseline += line_height;
|
baseline += line_height;
|
||||||
} else {
|
} else {
|
||||||
font_manager_render_char_scaled(g_current_ttf, cur_x, baseline, *s, color, scale, put_pixel);
|
font_manager_render_char_scaled(g_current_ttf, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||||
// Advance by same rounded width that font_manager_get_string_width uses
|
cur_x += font_manager_get_codepoint_width_scaled(g_current_ttf, codepoint, scale);
|
||||||
char buf[2] = {*s, 0};
|
|
||||||
cur_x += font_manager_get_string_width_scaled(g_current_ttf, buf, scale);
|
|
||||||
}
|
}
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cur_y = y;
|
int cur_y = y;
|
||||||
while (*s) {
|
while (*s) {
|
||||||
if (*s == '\n') {
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
|
if (codepoint == '\n') {
|
||||||
cur_x = x;
|
cur_x = x;
|
||||||
cur_y += 10;
|
cur_y += 10;
|
||||||
} else {
|
} else {
|
||||||
draw_char(cur_x, cur_y, *s, color);
|
draw_char(cur_x, cur_y, (codepoint < 128) ? (char)codepoint : '?', color);
|
||||||
cur_x += 8;
|
cur_x += 8;
|
||||||
}
|
}
|
||||||
s++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void draw_string_sloped(int x, int y, const char *s, uint32_t color, float slope) {
|
||||||
|
if (g_current_ttf) draw_string_scaled_sloped(x, y, s, color, g_current_ttf->pixel_height, slope);
|
||||||
|
else draw_string_scaled(x, y, s, color, 15.0f); // Fast fallback if no ttf
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_string_scaled_sloped(int x, int y, const char *s, uint32_t color, float scale, float slope) {
|
||||||
|
if (!s) return;
|
||||||
|
int cur_x = x;
|
||||||
|
|
||||||
|
if (g_current_ttf) {
|
||||||
|
int baseline = y + font_manager_get_font_ascent_scaled(g_current_ttf, scale) - 2;
|
||||||
|
int line_height = font_manager_get_font_line_height_scaled(g_current_ttf, scale);
|
||||||
|
|
||||||
|
while (*s) {
|
||||||
|
uint32_t codepoint = utf8_decode(&s);
|
||||||
|
if (codepoint == '\n') {
|
||||||
|
cur_x = x;
|
||||||
|
baseline += line_height;
|
||||||
|
} else {
|
||||||
|
font_manager_render_char_sloped(g_current_ttf, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||||
|
cur_x += font_manager_get_codepoint_width_scaled(g_current_ttf, codepoint, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to normal draw_string_scaled if no TTF
|
||||||
|
draw_string_scaled(x, y, s, color, scale);
|
||||||
|
}
|
||||||
|
|
||||||
void draw_desktop_background(void) {
|
void draw_desktop_background(void) {
|
||||||
if (!g_fb) return;
|
if (!g_fb) return;
|
||||||
|
|
||||||
if (g_use_image && g_bg_image) {
|
if (g_use_image && g_bg_image) {
|
||||||
// Draw wallpaper image (stretch/scale to screen)
|
// Draw wallpaper image (stretch/scale to screen)
|
||||||
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
|
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
|
||||||
if (g_clip_enabled) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
x1 = g_clip_x; y1 = g_clip_y;
|
if (g_clip_enabled[cpu]) {
|
||||||
x2 = g_clip_x + g_clip_w; y2 = g_clip_y + g_clip_h;
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
|
x1 = g_clip_stack_x[cpu][ptr]; y1 = g_clip_stack_y[cpu][ptr];
|
||||||
|
x2 = g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]; y2 = g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr];
|
||||||
}
|
}
|
||||||
for (int y = y1; y < y2; y++) {
|
for (int y = y1; y < y2; y++) {
|
||||||
int src_y = y * g_bg_image_h / (int)g_fb->height;
|
int src_y = y * g_bg_image_h / (int)g_fb->height;
|
||||||
@@ -486,9 +675,11 @@ void draw_desktop_background(void) {
|
|||||||
} else if (g_use_pattern) {
|
} else if (g_use_pattern) {
|
||||||
// Optimized tiled pattern: only draw within the clipping/dirty rect
|
// Optimized tiled pattern: only draw within the clipping/dirty rect
|
||||||
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
|
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
|
||||||
if (g_clip_enabled) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
x1 = g_clip_x; y1 = g_clip_y;
|
if (g_clip_enabled[cpu]) {
|
||||||
x2 = g_clip_x + g_clip_w; y2 = g_clip_y + g_clip_h;
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
|
x1 = g_clip_stack_x[cpu][ptr]; y1 = g_clip_stack_y[cpu][ptr];
|
||||||
|
x2 = g_clip_stack_x[cpu][ptr] + g_clip_stack_w[cpu][ptr]; y2 = g_clip_stack_y[cpu][ptr] + g_clip_stack_h[cpu][ptr];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = y1; y < y2; y++) {
|
for (int y = y1; y < y2; y++) {
|
||||||
@@ -604,10 +795,9 @@ void graphics_flip_buffer(void) {
|
|||||||
uint32_t *src_row = &g_back_buffer[curr_y * g_fb->width + x];
|
uint32_t *src_row = &g_back_buffer[curr_y * g_fb->width + x];
|
||||||
|
|
||||||
if (g_fb->bpp == 32) {
|
if (g_fb->bpp == 32) {
|
||||||
|
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||||
uint32_t *dst_row = (uint32_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x;
|
uint32_t *dst_row = (uint32_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x;
|
||||||
for (int j = 0; j < w; j++) {
|
mem_memcpy(dst_row, src_row, w * 4);
|
||||||
dst_row[j] = src_row[j];
|
|
||||||
}
|
|
||||||
} else if (g_fb->bpp == 16) {
|
} else if (g_fb->bpp == 16) {
|
||||||
uint16_t *dst_row = (uint16_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x;
|
uint16_t *dst_row = (uint16_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x;
|
||||||
for (int j = 0; j < w; j++) {
|
for (int j = 0; j < w; j++) {
|
||||||
@@ -640,12 +830,6 @@ void graphics_flip_buffer(void) {
|
|||||||
|
|
||||||
int gray = (r * 77 + g * 150 + b * 29) >> 8;
|
int gray = (r * 77 + g * 150 + b * 29) >> 8;
|
||||||
|
|
||||||
// Boost contrast by 2x to separate the dark UI colors:
|
|
||||||
// Background (~30) -> 60
|
|
||||||
// Panel (~40) -> 80
|
|
||||||
// With thresholds {0, 64, 128, 192}:
|
|
||||||
// BG > 0 (1/4 white), Panel > 64 (2/4 white - checkerboard)
|
|
||||||
// Text (~170) -> 255 (solid white)
|
|
||||||
gray = gray * 2;
|
gray = gray * 2;
|
||||||
if (gray > 255) gray = 255;
|
if (gray > 255) gray = 255;
|
||||||
|
|
||||||
@@ -670,19 +854,49 @@ void graphics_flip_buffer(void) {
|
|||||||
void graphics_copy_screenbuffer(uint32_t *dest) {
|
void graphics_copy_screenbuffer(uint32_t *dest) {
|
||||||
if (!g_fb || !dest) return;
|
if (!g_fb || !dest) return;
|
||||||
|
|
||||||
uint64_t rflags;
|
extern uint64_t wm_lock_acquire(void);
|
||||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
extern void wm_lock_release(uint64_t);
|
||||||
int sw = g_fb->width;
|
uint64_t rflags = wm_lock_acquire();
|
||||||
int sh = g_fb->height;
|
|
||||||
|
int sw = (int)g_fb->width;
|
||||||
|
int sh = (int)g_fb->height;
|
||||||
|
|
||||||
// Copy the internal back object to the dest directly
|
// Copy from the composition back buffer, applying color mode transformations if necessary
|
||||||
for (int y = 0; y < sh; y++) {
|
for (int y = 0; y < sh; y++) {
|
||||||
uint32_t *src_row = &g_back_buffer[y * sw];
|
uint32_t *src_row = &g_back_buffer[y * sw];
|
||||||
for (int x = 0; x < sw; x++) {
|
for (int x = 0; x < sw; x++) {
|
||||||
dest[y * sw + x] = src_row[x];
|
uint32_t px = src_row[x];
|
||||||
|
|
||||||
|
if (g_color_mode == 1) { // 8-bit Grayscale
|
||||||
|
uint8_t r = (px >> 16) & 0xFF;
|
||||||
|
uint8_t g = (px >> 8) & 0xFF;
|
||||||
|
uint8_t b = px & 0xFF;
|
||||||
|
uint8_t gray = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
|
||||||
|
dest[y * sw + x] = 0xFF000000 | (gray << 16) | (gray << 8) | gray;
|
||||||
|
} else if (g_color_mode == 2) { // 1-bit Monochrome (Dithered)
|
||||||
|
static const uint8_t bayer2[2][2] = {
|
||||||
|
{ 0, 128 },
|
||||||
|
{192, 64 }
|
||||||
|
};
|
||||||
|
uint8_t r = (px >> 16) & 0xFF;
|
||||||
|
uint8_t g = (px >> 8) & 0xFF;
|
||||||
|
uint8_t b = px & 0xFF;
|
||||||
|
int gray = (r * 77 + g * 150 + b * 29) >> 8;
|
||||||
|
|
||||||
|
// Boost contrast (matches graphics_flip_buffer logic)
|
||||||
|
gray = gray * 2;
|
||||||
|
if (gray > 255) gray = 255;
|
||||||
|
|
||||||
|
uint8_t threshold = bayer2[y & 1][x & 1];
|
||||||
|
dest[y * sw + x] = (gray > threshold) ? 0xFFFFFFFF : 0xFF000000;
|
||||||
|
} else {
|
||||||
|
// 32-bit (Standard)
|
||||||
|
dest[y * sw + x] = px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
|
||||||
|
wm_lock_release(rflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_set_clipping(int x, int y, int w, int h) {
|
void graphics_set_clipping(int x, int y, int w, int h) {
|
||||||
@@ -695,33 +909,94 @@ void graphics_set_clipping(int x, int y, int w, int h) {
|
|||||||
if (w < 0) w = 0;
|
if (w < 0) w = 0;
|
||||||
if (h < 0) h = 0;
|
if (h < 0) h = 0;
|
||||||
|
|
||||||
g_clip_x = x;
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
g_clip_y = y;
|
g_clip_stack_x[cpu][0] = x;
|
||||||
g_clip_w = w;
|
g_clip_stack_y[cpu][0] = y;
|
||||||
g_clip_h = h;
|
g_clip_stack_w[cpu][0] = w;
|
||||||
g_clip_enabled = true;
|
g_clip_stack_h[cpu][0] = h;
|
||||||
|
g_clip_stack_ptr[cpu] = 0; // Reset to base
|
||||||
|
g_clip_enabled[cpu] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphics_push_clipping(int x, int y, int w, int h) {
|
||||||
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
|
int cur_ptr = g_clip_stack_ptr[cpu];
|
||||||
|
if (cur_ptr + 1 >= CLIP_STACK_DEPTH) return; // Stack overflow
|
||||||
|
|
||||||
|
// Intersect with current top
|
||||||
|
int cx1 = g_clip_stack_x[cpu][cur_ptr];
|
||||||
|
int cy1 = g_clip_stack_y[cpu][cur_ptr];
|
||||||
|
int cx2 = cx1 + g_clip_stack_w[cpu][cur_ptr];
|
||||||
|
int cy2 = cy1 + g_clip_stack_h[cpu][cur_ptr];
|
||||||
|
|
||||||
|
int nx1 = x;
|
||||||
|
int ny1 = y;
|
||||||
|
int nx2 = x + w;
|
||||||
|
int ny2 = y + h;
|
||||||
|
|
||||||
|
if (nx1 < cx1) nx1 = cx1;
|
||||||
|
if (ny1 < cy1) ny1 = cy1;
|
||||||
|
if (nx2 > cx2) nx2 = cx2;
|
||||||
|
if (ny2 > cy2) ny2 = cy2;
|
||||||
|
|
||||||
|
int nw = nx2 - nx1;
|
||||||
|
int nh = ny2 - ny1;
|
||||||
|
if (nw < 0) nw = 0;
|
||||||
|
if (nh < 0) nh = 0;
|
||||||
|
|
||||||
|
g_clip_stack_ptr[cpu]++;
|
||||||
|
g_clip_stack_x[cpu][cur_ptr + 1] = nx1;
|
||||||
|
g_clip_stack_y[cpu][cur_ptr + 1] = ny1;
|
||||||
|
g_clip_stack_w[cpu][cur_ptr + 1] = nw;
|
||||||
|
g_clip_stack_h[cpu][cur_ptr + 1] = nh;
|
||||||
|
g_clip_enabled[cpu] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphics_pop_clipping(void) {
|
||||||
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
|
if (g_clip_stack_ptr[cpu] > 0) {
|
||||||
|
g_clip_stack_ptr[cpu]--;
|
||||||
|
} else {
|
||||||
|
g_clip_enabled[cpu] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_clear_clipping(void) {
|
void graphics_clear_clipping(void) {
|
||||||
g_clip_enabled = false;
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
|
g_clip_stack_ptr[cpu] = 0;
|
||||||
|
g_clip_enabled[cpu] = false;
|
||||||
}
|
}
|
||||||
void graphics_blit_buffer(uint32_t *src, int dst_x, int dst_y, int w, int h) {
|
void graphics_blit_buffer(uint32_t *src, int dst_x, int dst_y, int w, int h) {
|
||||||
if (!g_fb || !src) return;
|
if (!g_fb || !src) return;
|
||||||
int sw = get_screen_width();
|
int sw = get_screen_width();
|
||||||
int sh = get_screen_height();
|
int sh = get_screen_height();
|
||||||
|
|
||||||
for (int y = 0; y < h; y++) {
|
uint32_t cpu = smp_this_cpu_id();
|
||||||
int vy = dst_y + y;
|
int cx1 = 0, cy1 = 0, cx2 = sw, cy2 = sh;
|
||||||
if (vy < 0 || vy >= sh) continue;
|
if (g_clip_enabled[cpu]) {
|
||||||
|
int ptr = g_clip_stack_ptr[cpu];
|
||||||
for (int x = 0; x < w; x++) {
|
cx1 = g_clip_stack_x[cpu][ptr];
|
||||||
int vx = dst_x + x;
|
cy1 = g_clip_stack_y[cpu][ptr];
|
||||||
if (vx < 0 || vx >= sw) continue;
|
cx2 = cx1 + g_clip_stack_w[cpu][ptr];
|
||||||
|
cy2 = cy1 + g_clip_stack_h[cpu][ptr];
|
||||||
uint32_t pcol = src[y * w + x];
|
}
|
||||||
|
|
||||||
|
int x1 = dst_x, y1 = dst_y, x2 = dst_x + w, y2 = dst_y + h;
|
||||||
|
if (x1 < cx1) x1 = cx1;
|
||||||
|
if (y1 < cy1) y1 = cy1;
|
||||||
|
if (x2 > cx2) x2 = cx2;
|
||||||
|
if (y2 > cy2) y2 = cy2;
|
||||||
|
|
||||||
|
if (x1 >= x2 || y1 >= y2) return;
|
||||||
|
|
||||||
|
for (int y = y1; y < y2; y++) {
|
||||||
|
uint32_t *dst_row = &g_back_buffer[y * sw + x1];
|
||||||
|
uint32_t *src_row = &src[(y - dst_y) * w + (x1 - dst_x)];
|
||||||
|
int len = x2 - x1;
|
||||||
|
for (int x = 0; x < len; x++) {
|
||||||
|
uint32_t pcol = src_row[x];
|
||||||
if ((pcol & 0xFF000000) != 0 || (pcol & 0xFFFFFF) != 0) {
|
if ((pcol & 0xFF000000) != 0 || (pcol & 0xFFFFFF) != 0) {
|
||||||
g_back_buffer[vy * sw + vx] = pcol;
|
dst_row[x] = pcol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ uint32_t graphics_get_pixel(int x, int y);
|
|||||||
void draw_rect(int x, int y, int w, int h, uint32_t color);
|
void draw_rect(int x, int y, int w, int h, uint32_t color);
|
||||||
void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color);
|
void draw_rounded_rect(int x, int y, int w, int h, int radius, uint32_t color);
|
||||||
void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color);
|
void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t color);
|
||||||
|
void draw_rounded_rect_blurred(int x, int y, int w, int h, int radius, uint32_t tint_color, int blur_radius, int alpha);
|
||||||
void draw_char(int x, int y, char c, uint32_t color);
|
void draw_char(int x, int y, char c, uint32_t color);
|
||||||
void draw_char_bitmap(int x, int y, char c, uint32_t color);
|
void draw_char_bitmap(int x, int y, char c, uint32_t color);
|
||||||
void draw_string(int x, int y, const char *s, uint32_t color);
|
void draw_string(int x, int y, const char *s, uint32_t color);
|
||||||
void draw_string_scaled(int x, int y, const char *s, uint32_t color, float scale);
|
void draw_string_scaled(int x, int y, const char *s, uint32_t color, float scale);
|
||||||
|
void draw_string_sloped(int x, int y, const char *s, uint32_t color, float slope);
|
||||||
|
void draw_string_scaled_sloped(int x, int y, const char *s, uint32_t color, float scale, float slope);
|
||||||
void draw_desktop_background(void);
|
void draw_desktop_background(void);
|
||||||
void graphics_set_bg_color(uint32_t color);
|
void graphics_set_bg_color(uint32_t color);
|
||||||
void graphics_set_bg_pattern(const uint32_t *pattern); // 128x128 pattern
|
void graphics_set_bg_pattern(const uint32_t *pattern); // 128x128 pattern
|
||||||
@@ -46,6 +49,7 @@ void graphics_mark_dirty(int x, int y, int w, int h);
|
|||||||
void graphics_mark_screen_dirty(void);
|
void graphics_mark_screen_dirty(void);
|
||||||
DirtyRect graphics_get_dirty_rect(void);
|
DirtyRect graphics_get_dirty_rect(void);
|
||||||
void graphics_clear_dirty(void);
|
void graphics_clear_dirty(void);
|
||||||
|
void graphics_clear_dirty_no_lock(void);
|
||||||
|
|
||||||
// Double buffering
|
// Double buffering
|
||||||
void graphics_flip_buffer(void);
|
void graphics_flip_buffer(void);
|
||||||
@@ -53,6 +57,8 @@ void graphics_clear_back_buffer(uint32_t color);
|
|||||||
|
|
||||||
// Clipping
|
// Clipping
|
||||||
void graphics_set_clipping(int x, int y, int w, int h);
|
void graphics_set_clipping(int x, int y, int w, int h);
|
||||||
|
void graphics_push_clipping(int x, int y, int w, int h);
|
||||||
|
void graphics_pop_clipping(void);
|
||||||
void graphics_clear_clipping(void);
|
void graphics_clear_clipping(void);
|
||||||
|
|
||||||
// Font access (requires font_manager.h for ttf_font_t)
|
// Font access (requires font_manager.h for ttf_font_t)
|
||||||
|
|||||||
@@ -14,10 +14,11 @@
|
|||||||
#define GUI_CMD_GET_STRING_WIDTH 8
|
#define GUI_CMD_GET_STRING_WIDTH 8
|
||||||
#define GUI_CMD_GET_FONT_HEIGHT 9
|
#define GUI_CMD_GET_FONT_HEIGHT 9
|
||||||
#define GUI_CMD_WINDOW_SET_RESIZABLE 14
|
#define GUI_CMD_WINDOW_SET_RESIZABLE 14
|
||||||
#define GUI_CMD_GET_SCREEN_SIZE 17
|
// Remapped Screenshot API Commands to avoid collisions (Originals 17, 18, 19 conflicted with magic numbers)
|
||||||
#define GUI_CMD_GET_SCREENBUFFER 18
|
#define GUI_CMD_GET_SCREEN_SIZE 50
|
||||||
#define GUI_CMD_SHOW_NOTIFICATION 19
|
#define GUI_CMD_GET_SCREENBUFFER 51
|
||||||
#define GUI_CMD_GET_DATETIME 20
|
#define GUI_CMD_SHOW_NOTIFICATION 52
|
||||||
|
#define GUI_CMD_GET_DATETIME 53
|
||||||
|
|
||||||
#define GUI_EVENT_NONE 0
|
#define GUI_EVENT_NONE 0
|
||||||
#define GUI_EVENT_PAINT 1
|
#define GUI_EVENT_PAINT 1
|
||||||
|
|||||||
455
src/wm/libwidget.c
Normal file
455
src/wm/libwidget.c
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
#include "libwidget.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define COLOR_GRAY 0xFFC0C0C0
|
||||||
|
#define COLOR_LTGRAY 0xFFDFDFDF
|
||||||
|
#define COLOR_DKGRAY 0xFF808080
|
||||||
|
#define COLOR_WHITE 0xFFFFFFFF
|
||||||
|
#define COLOR_BLACK 0xFF000000
|
||||||
|
|
||||||
|
#define COLOR_SCROLLBAR_BG 0xFF2A2A2A
|
||||||
|
#define COLOR_SCROLLBAR_THUMB 0xFF505050
|
||||||
|
#define COLOR_SCROLLBAR_THUMB_HOVER 0xFF707070
|
||||||
|
#define COLOR_SCROLLBAR_THUMB_DRAG 0xFF909090
|
||||||
|
|
||||||
|
static size_t string_len(const char *str) {
|
||||||
|
size_t l = 0;
|
||||||
|
while(str && str[l]) l++;
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAC_BTN_BORDER 0xFF4A4A4C
|
||||||
|
#define MAC_BTN_BG_NORMAL 0xFF353537
|
||||||
|
#define MAC_BTN_BG_HOVER 0xFF454547
|
||||||
|
#define MAC_BTN_BG_PRESSED 0xFF555557
|
||||||
|
|
||||||
|
// --- Button Implementation ---
|
||||||
|
|
||||||
|
void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text) {
|
||||||
|
btn->x = x;
|
||||||
|
btn->y = y;
|
||||||
|
btn->w = w;
|
||||||
|
btn->h = h;
|
||||||
|
btn->text = text;
|
||||||
|
btn->pressed = false;
|
||||||
|
btn->hovered = false;
|
||||||
|
btn->on_click = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn) {
|
||||||
|
uint32_t border_color = MAC_BTN_BORDER;
|
||||||
|
uint32_t normal_bg = MAC_BTN_BG_NORMAL;
|
||||||
|
uint32_t hover_bg = MAC_BTN_BG_HOVER;
|
||||||
|
uint32_t pressed_bg = MAC_BTN_BG_PRESSED;
|
||||||
|
uint32_t text_color = COLOR_WHITE;
|
||||||
|
|
||||||
|
if (ctx->use_light_theme) {
|
||||||
|
border_color = 0xFFB0B0B0;
|
||||||
|
normal_bg = 0xFFEAEAEA;
|
||||||
|
hover_bg = 0xFFD0D0D0;
|
||||||
|
pressed_bg = 0xFFB0B0B0;
|
||||||
|
text_color = COLOR_BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t bg_color = normal_bg;
|
||||||
|
if (btn->pressed) {
|
||||||
|
bg_color = pressed_bg;
|
||||||
|
} else if (btn->hovered) {
|
||||||
|
bg_color = hover_bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->draw_rounded_rect_filled) {
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x, btn->y, btn->w, btn->h, 6, border_color);
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, btn->x + 1, btn->y + 1, btn->w - 2, btn->h - 2, 5, bg_color);
|
||||||
|
} else if (ctx->draw_rect) {
|
||||||
|
ctx->draw_rect(ctx->user_data, btn->x, btn->y, btn->w, btn->h, border_color);
|
||||||
|
ctx->draw_rect(ctx->user_data, btn->x + 1, btn->y + 1, btn->w - 2, btn->h - 2, bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn->text && ctx->draw_string) {
|
||||||
|
int len = string_len(btn->text);
|
||||||
|
int tx = btn->x + (btn->w - (len * 8)) / 2;
|
||||||
|
int ty = btn->y + (btn->h - 8) / 2;
|
||||||
|
ctx->draw_string(ctx->user_data, tx, ty, btn->text, text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data) {
|
||||||
|
bool in_bounds = (mx >= btn->x && mx < btn->x + btn->w && my >= btn->y && my < btn->y + btn->h);
|
||||||
|
|
||||||
|
btn->hovered = in_bounds;
|
||||||
|
|
||||||
|
if (mouse_clicked && in_bounds) {
|
||||||
|
btn->pressed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mouse_down && btn->pressed) {
|
||||||
|
btn->pressed = false;
|
||||||
|
if (in_bounds && btn->on_click) {
|
||||||
|
btn->on_click(user_data);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Scrollbar Implementation ---
|
||||||
|
|
||||||
|
void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h) {
|
||||||
|
sb->x = x;
|
||||||
|
sb->y = y;
|
||||||
|
sb->w = w;
|
||||||
|
sb->h = h;
|
||||||
|
sb->content_height = h;
|
||||||
|
sb->scroll_y = 0;
|
||||||
|
sb->is_dragging = false;
|
||||||
|
sb->on_scroll = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y) {
|
||||||
|
sb->content_height = content_height;
|
||||||
|
sb->scroll_y = scroll_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb) {
|
||||||
|
// Only draw scrollbar if content is larger than view
|
||||||
|
if (sb->content_height > sb->h) {
|
||||||
|
// Draw the track background
|
||||||
|
uint32_t track_color = ctx->use_light_theme ? 0xFFE0E0E0 : 0xFF2A2A2A;
|
||||||
|
if (ctx->draw_rounded_rect_filled) {
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, sb->x, sb->y, sb->w, sb->h, 4, track_color);
|
||||||
|
} else if (ctx->draw_rect) {
|
||||||
|
ctx->draw_rect(ctx->user_data, sb->x, sb->y, sb->w, sb->h, track_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
int thumb_h = (sb->h * sb->h) / sb->content_height;
|
||||||
|
if (thumb_h < 20) thumb_h = 20;
|
||||||
|
|
||||||
|
int max_scroll = sb->content_height - sb->h;
|
||||||
|
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
|
||||||
|
if (sb->scroll_y < 0) sb->scroll_y = 0;
|
||||||
|
|
||||||
|
int thumb_y = sb->y + (sb->scroll_y * (sb->h - thumb_h)) / max_scroll;
|
||||||
|
|
||||||
|
uint32_t color = 0xFF888888; // Subtle gray thumb for mac style
|
||||||
|
if (sb->is_dragging) color = 0xFF666666;
|
||||||
|
|
||||||
|
if (ctx->draw_rounded_rect_filled) {
|
||||||
|
// Pill shaped thumb with margin
|
||||||
|
int margin = 2;
|
||||||
|
int radius = (sb->w - margin*2) / 2;
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, sb->x + margin, thumb_y + margin, sb->w - margin*2, thumb_h - margin*2, radius, color);
|
||||||
|
} else if (ctx->draw_rect) {
|
||||||
|
ctx->draw_rect(ctx->user_data, sb->x, thumb_y, sb->w, thumb_h, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data) {
|
||||||
|
if (sb->content_height <= sb->h) return false;
|
||||||
|
|
||||||
|
int thumb_h = (sb->h * sb->h) / sb->content_height;
|
||||||
|
if (thumb_h < 20) thumb_h = 20;
|
||||||
|
|
||||||
|
int max_scroll = sb->content_height - sb->h;
|
||||||
|
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
|
||||||
|
if (sb->scroll_y < 0) sb->scroll_y = 0;
|
||||||
|
|
||||||
|
int thumb_y = sb->y + (sb->scroll_y * (sb->h - thumb_h)) / max_scroll;
|
||||||
|
|
||||||
|
bool in_thumb = (mx >= sb->x && mx < sb->x + sb->w && my >= thumb_y && my < thumb_y + thumb_h);
|
||||||
|
bool in_track = (mx >= sb->x && mx < sb->x + sb->w && my >= sb->y && my < sb->y + sb->h);
|
||||||
|
|
||||||
|
if (mouse_down && !sb->is_dragging) {
|
||||||
|
if (in_thumb) {
|
||||||
|
sb->is_dragging = true;
|
||||||
|
sb->drag_start_my = my;
|
||||||
|
sb->drag_start_scroll_y = sb->scroll_y;
|
||||||
|
return true;
|
||||||
|
} else if (in_track) {
|
||||||
|
// Page scroll
|
||||||
|
if (my < thumb_y) {
|
||||||
|
sb->scroll_y -= sb->h;
|
||||||
|
} else {
|
||||||
|
sb->scroll_y += sb->h;
|
||||||
|
}
|
||||||
|
if (sb->scroll_y < 0) sb->scroll_y = 0;
|
||||||
|
if (sb->scroll_y > max_scroll) sb->scroll_y = max_scroll;
|
||||||
|
if (sb->on_scroll) sb->on_scroll(user_data, sb->scroll_y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (!mouse_down) {
|
||||||
|
sb->is_dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb->is_dragging && mouse_down) {
|
||||||
|
int dy = my - sb->drag_start_my;
|
||||||
|
int track_h = sb->h - thumb_h;
|
||||||
|
if (track_h > 0) {
|
||||||
|
float ratio = (float)max_scroll / (float)track_h;
|
||||||
|
int new_scroll = sb->drag_start_scroll_y + (int)(dy * ratio);
|
||||||
|
|
||||||
|
if (new_scroll < 0) new_scroll = 0;
|
||||||
|
if (new_scroll > max_scroll) new_scroll = max_scroll;
|
||||||
|
|
||||||
|
if (new_scroll != sb->scroll_y) {
|
||||||
|
sb->scroll_y = new_scroll;
|
||||||
|
if (sb->on_scroll) sb->on_scroll(user_data, sb->scroll_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_track || sb->is_dragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TextBox Implementation ---
|
||||||
|
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len) {
|
||||||
|
tb->x = x; tb->y = y; tb->w = w; tb->h = h;
|
||||||
|
tb->text = buffer;
|
||||||
|
tb->max_len = max_len;
|
||||||
|
tb->cursor_pos = string_len(buffer);
|
||||||
|
tb->focused = false;
|
||||||
|
tb->on_change = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
|
||||||
|
uint32_t border_color = MAC_BTN_BORDER;
|
||||||
|
uint32_t bg_color = COLOR_BLACK;
|
||||||
|
uint32_t text_color = COLOR_WHITE;
|
||||||
|
|
||||||
|
if (ctx->use_light_theme) {
|
||||||
|
border_color = 0xFFA0A0A0;
|
||||||
|
bg_color = 0xFFFFFFFF;
|
||||||
|
text_color = COLOR_BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background and border
|
||||||
|
if (ctx->draw_rounded_rect_filled) {
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x, tb->y, tb->w, tb->h, 4, border_color);
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, 3, bg_color); // background
|
||||||
|
} else if (ctx->draw_rect) {
|
||||||
|
ctx->draw_rect(ctx->user_data, tb->x, tb->y, tb->w, tb->h, border_color);
|
||||||
|
ctx->draw_rect(ctx->user_data, tb->x + 1, tb->y + 1, tb->w - 2, tb->h - 2, bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->draw_string && tb->text) {
|
||||||
|
int max_w = tb->w - 15;
|
||||||
|
int scroll_x = 0;
|
||||||
|
int text_w = 0;
|
||||||
|
|
||||||
|
if (ctx->measure_string_width) {
|
||||||
|
text_w = ctx->measure_string_width(ctx->user_data, tb->text);
|
||||||
|
} else {
|
||||||
|
text_w = string_len(tb->text) * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text_w > max_w) scroll_x = text_w - max_w;
|
||||||
|
|
||||||
|
// Very basic simple drawing, without proper clipping since context lacks it
|
||||||
|
ctx->draw_string(ctx->user_data, tb->x + 5, tb->y + (tb->h - 8) / 2, tb->text, text_color);
|
||||||
|
|
||||||
|
if (tb->focused && ctx->draw_rect) {
|
||||||
|
int cx = 0;
|
||||||
|
if (ctx->measure_string_width) {
|
||||||
|
// measure up to cursor
|
||||||
|
char tmp[256];
|
||||||
|
int k = 0;
|
||||||
|
for (k = 0; k < tb->cursor_pos && tb->text[k]; k++) {
|
||||||
|
tmp[k] = tb->text[k];
|
||||||
|
}
|
||||||
|
tmp[k] = 0;
|
||||||
|
cx = ctx->measure_string_width(ctx->user_data, tmp);
|
||||||
|
} else {
|
||||||
|
cx = tb->cursor_pos * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cx > max_w) cx = max_w; // clamped to visible end
|
||||||
|
|
||||||
|
ctx->draw_rect(ctx->user_data, tb->x + 5 + cx, tb->y + (tb->h - 12) / 2, 2, 12, text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data) {
|
||||||
|
bool in_bounds = (mx >= tb->x && mx < tb->x + tb->w && my >= tb->y && my < tb->y + tb->h);
|
||||||
|
if (mouse_clicked) {
|
||||||
|
tb->focused = in_bounds;
|
||||||
|
if (in_bounds && tb->text) {
|
||||||
|
int rel_x = mx - (tb->x + 5);
|
||||||
|
if (rel_x < 0) rel_x = 0;
|
||||||
|
// Rough estimation for fixed-width font 8px chars
|
||||||
|
int new_pos = rel_x / 8;
|
||||||
|
int len = string_len(tb->text);
|
||||||
|
if (new_pos > len) new_pos = len;
|
||||||
|
tb->cursor_pos = new_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in_bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data) {
|
||||||
|
if (!tb->focused || !tb->text) return false;
|
||||||
|
|
||||||
|
int len = string_len(tb->text);
|
||||||
|
if (c == 19) { // LEFT
|
||||||
|
if (tb->cursor_pos > 0) tb->cursor_pos--;
|
||||||
|
return true;
|
||||||
|
} else if (c == 20) { // RIGHT
|
||||||
|
if (tb->cursor_pos < len) tb->cursor_pos++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\b') { // backspace
|
||||||
|
if (tb->cursor_pos > 0) {
|
||||||
|
for (int i = tb->cursor_pos - 1; i < len; i++) {
|
||||||
|
tb->text[i] = tb->text[i + 1];
|
||||||
|
}
|
||||||
|
tb->cursor_pos--;
|
||||||
|
if (tb->on_change) tb->on_change(user_data);
|
||||||
|
}
|
||||||
|
} else if (c >= 32 && c < 127) {
|
||||||
|
if (len < tb->max_len - 1) {
|
||||||
|
for (int i = len; i >= tb->cursor_pos; i--) {
|
||||||
|
tb->text[i + 1] = tb->text[i];
|
||||||
|
}
|
||||||
|
tb->text[tb->cursor_pos] = c;
|
||||||
|
tb->cursor_pos++;
|
||||||
|
if (tb->on_change) tb->on_change(user_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Dropdown Implementation ---
|
||||||
|
void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count) {
|
||||||
|
dd->x = x; dd->y = y; dd->w = w; dd->h = h;
|
||||||
|
dd->items = items;
|
||||||
|
dd->item_count = count;
|
||||||
|
dd->selected_idx = 0;
|
||||||
|
dd->is_open = false;
|
||||||
|
dd->on_select = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd) {
|
||||||
|
uint32_t border_color = MAC_BTN_BORDER;
|
||||||
|
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||||
|
uint32_t text_color = COLOR_WHITE;
|
||||||
|
|
||||||
|
if (ctx->use_light_theme) {
|
||||||
|
border_color = 0xFFB0B0B0;
|
||||||
|
bg_color = 0xFFE0E0E0;
|
||||||
|
text_color = COLOR_BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->draw_rounded_rect_filled) {
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x, dd->y, dd->w, dd->h, 4, border_color);
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, 3, bg_color);
|
||||||
|
} else if (ctx->draw_rect) {
|
||||||
|
ctx->draw_rect(ctx->user_data, dd->x, dd->y, dd->w, dd->h, border_color);
|
||||||
|
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + 1, dd->w - 2, dd->h - 2, bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->draw_string && dd->items && dd->item_count > 0 && dd->selected_idx >= 0 && dd->selected_idx < dd->item_count) {
|
||||||
|
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + (dd->h - 8) / 2, dd->items[dd->selected_idx], text_color);
|
||||||
|
ctx->draw_string(ctx->user_data, dd->x + dd->w - 12, dd->y + (dd->h - 8) / 2, "v", text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dd->is_open && ctx->draw_rect && dd->items) {
|
||||||
|
int menu_h = dd->item_count * dd->h;
|
||||||
|
ctx->draw_rect(ctx->user_data, dd->x, dd->y + dd->h, dd->w, menu_h, border_color);
|
||||||
|
ctx->draw_rect(ctx->user_data, dd->x + 1, dd->y + dd->h + 1, dd->w - 2, menu_h - 2, bg_color);
|
||||||
|
for (int i = 0; i < dd->item_count; i++) {
|
||||||
|
if (ctx->draw_string) {
|
||||||
|
ctx->draw_string(ctx->user_data, dd->x + 5, dd->y + dd->h + i * dd->h + (dd->h - 8)/2, dd->items[i], text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data) {
|
||||||
|
if (!mouse_clicked) return false;
|
||||||
|
|
||||||
|
if (dd->is_open) {
|
||||||
|
int menu_h = dd->item_count * dd->h;
|
||||||
|
if (mx >= dd->x && mx < dd->x + dd->w && my >= dd->y + dd->h && my < dd->y + dd->h + menu_h) {
|
||||||
|
int clicked_idx = (my - (dd->y + dd->h)) / dd->h;
|
||||||
|
if (clicked_idx >= 0 && clicked_idx < dd->item_count) {
|
||||||
|
dd->selected_idx = clicked_idx;
|
||||||
|
dd->is_open = false;
|
||||||
|
if (dd->on_select) dd->on_select(user_data, clicked_idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dd->is_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mx >= dd->x && mx < dd->x + dd->w && my >= dd->y && my < dd->y + dd->h) {
|
||||||
|
dd->is_open = !dd->is_open;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Checkbox / Radio Implementation ---
|
||||||
|
void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio) {
|
||||||
|
cb->x = x; cb->y = y; cb->w = w; cb->h = h;
|
||||||
|
cb->text = text;
|
||||||
|
cb->checked = false;
|
||||||
|
cb->is_radio = is_radio;
|
||||||
|
cb->on_toggle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb) {
|
||||||
|
int box_size = 14;
|
||||||
|
int box_y = cb->y + (cb->h - box_size) / 2;
|
||||||
|
|
||||||
|
uint32_t border_color = MAC_BTN_BORDER;
|
||||||
|
uint32_t bg_color = MAC_BTN_BG_NORMAL;
|
||||||
|
uint32_t inner_color = COLOR_WHITE;
|
||||||
|
uint32_t text_color = COLOR_WHITE;
|
||||||
|
|
||||||
|
if (ctx->use_light_theme) {
|
||||||
|
border_color = 0xFF909090;
|
||||||
|
bg_color = 0xFFFFFFFF;
|
||||||
|
inner_color = 0xFF404040;
|
||||||
|
text_color = COLOR_BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->draw_rounded_rect_filled) {
|
||||||
|
int radius = cb->is_radio ? box_size / 2 : 3;
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x, box_y, box_size, box_size, radius, border_color);
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, radius - 1, bg_color);
|
||||||
|
|
||||||
|
if (cb->checked) {
|
||||||
|
int inner = box_size - 6;
|
||||||
|
int inner_rad = cb->is_radio ? inner / 2 : 2;
|
||||||
|
ctx->draw_rounded_rect_filled(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_rad, inner_color);
|
||||||
|
}
|
||||||
|
} else if (ctx->draw_rect) {
|
||||||
|
ctx->draw_rect(ctx->user_data, cb->x, box_y, box_size, box_size, border_color);
|
||||||
|
ctx->draw_rect(ctx->user_data, cb->x + 1, box_y + 1, box_size - 2, box_size - 2, bg_color);
|
||||||
|
if (cb->checked) {
|
||||||
|
int inner = box_size - 6;
|
||||||
|
ctx->draw_rect(ctx->user_data, cb->x + 3, box_y + 3, inner, inner, inner_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->draw_string && cb->text) {
|
||||||
|
ctx->draw_string(ctx->user_data, cb->x + box_size + 8, cb->y + (cb->h - 8) / 2, cb->text, text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data) {
|
||||||
|
if (!mouse_clicked) return false;
|
||||||
|
|
||||||
|
if (mx >= cb->x && mx < cb->x + cb->w && my >= cb->y && my < cb->y + cb->h) {
|
||||||
|
cb->checked = !cb->checked;
|
||||||
|
if (cb->on_toggle) cb->on_toggle(user_data, cb->checked);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
93
src/wm/libwidget.h
Normal file
93
src/wm/libwidget.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef LIBWIDGET_H
|
||||||
|
#define LIBWIDGET_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// Widget Context for abstract drawing backend
|
||||||
|
typedef struct {
|
||||||
|
void *user_data;
|
||||||
|
void (*draw_rect)(void *user_data, int x, int y, int w, int h, uint32_t color);
|
||||||
|
void (*draw_rounded_rect_filled)(void *user_data, int x, int y, int w, int h, int r, uint32_t color);
|
||||||
|
void (*draw_string)(void *user_data, int x, int y, const char *str, uint32_t color);
|
||||||
|
int (*measure_string_width)(void *user_data, const char *str);
|
||||||
|
void (*mark_dirty)(void *user_data, int x, int y, int w, int h);
|
||||||
|
bool use_light_theme;
|
||||||
|
} widget_context_t;
|
||||||
|
|
||||||
|
// --- Button ---
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
const char *text;
|
||||||
|
bool pressed;
|
||||||
|
bool hovered;
|
||||||
|
void (*on_click)(void *user_data);
|
||||||
|
} widget_button_t;
|
||||||
|
|
||||||
|
void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text);
|
||||||
|
void widget_button_draw(widget_context_t *ctx, widget_button_t *btn);
|
||||||
|
// Returns true if event was consumed
|
||||||
|
bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Scrollbar ---
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
int content_height;
|
||||||
|
int scroll_y;
|
||||||
|
bool is_dragging;
|
||||||
|
int drag_start_my;
|
||||||
|
int drag_start_scroll_y;
|
||||||
|
void (*on_scroll)(void *user_data, int new_scroll_y);
|
||||||
|
} widget_scrollbar_t;
|
||||||
|
|
||||||
|
void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h);
|
||||||
|
void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y);
|
||||||
|
void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb);
|
||||||
|
// Returns true if event was consumed
|
||||||
|
bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data);
|
||||||
|
|
||||||
|
|
||||||
|
// --- TextBox ---
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
char *text;
|
||||||
|
int max_len;
|
||||||
|
int cursor_pos;
|
||||||
|
bool focused;
|
||||||
|
void (*on_change)(void *user_data);
|
||||||
|
} widget_textbox_t;
|
||||||
|
|
||||||
|
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len);
|
||||||
|
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb);
|
||||||
|
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);
|
||||||
|
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data);
|
||||||
|
|
||||||
|
// --- Dropdown ---
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
const char **items;
|
||||||
|
int item_count;
|
||||||
|
int selected_idx;
|
||||||
|
bool is_open;
|
||||||
|
void (*on_select)(void *user_data, int new_idx);
|
||||||
|
} widget_dropdown_t;
|
||||||
|
|
||||||
|
void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count);
|
||||||
|
void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd);
|
||||||
|
bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data);
|
||||||
|
|
||||||
|
// --- Checkbox / Radio ---
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
const char *text;
|
||||||
|
bool checked;
|
||||||
|
bool is_radio;
|
||||||
|
void (*on_toggle)(void *user_data, bool new_state);
|
||||||
|
} widget_checkbox_t;
|
||||||
|
|
||||||
|
void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio);
|
||||||
|
void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb);
|
||||||
|
bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -151,8 +151,8 @@ int wallpaper_get_height(void) { return wp_height; }
|
|||||||
void wallpaper_init(void) {
|
void wallpaper_init(void) {
|
||||||
// We expect Limine modules to have been copied to /Library/images/Wallpapers/ by main.c
|
// We expect Limine modules to have been copied to /Library/images/Wallpapers/ by main.c
|
||||||
// Set a default wallpaper if one exists
|
// Set a default wallpaper if one exists
|
||||||
if (fat32_exists("/Library/images/Wallpapers/mountain.jpg")) {
|
if (fat32_exists("/Library/images/Wallpapers/bored.jpg")) {
|
||||||
wallpaper_request_set_from_file("/Library/images/Wallpapers/mountain.jpg");
|
wallpaper_request_set_from_file("/Library/images/Wallpapers/bored.jpg");
|
||||||
} else if (fat32_exists("/Library/images/Wallpapers/moon.jpg")) {
|
} else if (fat32_exists("/Library/images/Wallpapers/moon.jpg")) {
|
||||||
wallpaper_request_set_from_file("/Library/images/Wallpapers/moon.jpg");
|
wallpaper_request_set_from_file("/Library/images/Wallpapers/moon.jpg");
|
||||||
}
|
}
|
||||||
|
|||||||
932
src/wm/wm.c
932
src/wm/wm.c
File diff suppressed because it is too large
Load Diff
19
src/wm/wm.h
19
src/wm/wm.h
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include "../sys/spinlock.h"
|
||||||
|
|
||||||
|
uint64_t wm_lock_acquire(void);
|
||||||
|
void wm_lock_release(uint64_t flags);
|
||||||
|
|
||||||
// --- Constants ---
|
// --- Constants ---
|
||||||
#define COLOR_TEAL 0xFF008080
|
#define COLOR_TEAL 0xFF008080
|
||||||
@@ -15,14 +19,13 @@
|
|||||||
#define COLOR_BLUE 0xFF000080
|
#define COLOR_BLUE 0xFF000080
|
||||||
#define COLOR_LTGRAY 0xFFDFDFDF
|
#define COLOR_LTGRAY 0xFFDFDFDF
|
||||||
#define COLOR_DKGRAY 0xFF808080
|
#define COLOR_DKGRAY 0xFF808080
|
||||||
#define COLOR_RED 0xFFFF0000
|
|
||||||
#define COLOR_PURPLE 0xFF800080
|
#define COLOR_PURPLE 0xFF800080
|
||||||
#define COLOR_COFFEE 0xFF6B4423
|
#define COLOR_COFFEE 0xFF6B4423
|
||||||
#define COLOR_APPLE_RED 0xFFFF0000
|
#define COLOR_RED 0xFFFF0000
|
||||||
#define COLOR_APPLE_ORANGE 0xFFFF7F00
|
#define COLOR_ORANGE 0xFFFF7F00
|
||||||
#define COLOR_APPLE_YELLOW 0xFFFFFF00
|
#define COLOR_YELLOW 0xFFFFFF00
|
||||||
#define COLOR_APPLE_GREEN 0xFF00FF00
|
#define COLOR_GREEN 0xFF00FF00
|
||||||
#define COLOR_APPLE_BLUE 0xFF0000FF
|
#define COLOR_BLUE 0xFF0000FF
|
||||||
#define COLOR_APPLE_INDIGO 0xFF4B0082
|
#define COLOR_APPLE_INDIGO 0xFF4B0082
|
||||||
#define COLOR_APPLE_VIOLET 0xFF9400D3
|
#define COLOR_APPLE_VIOLET 0xFF9400D3
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ struct Window {
|
|||||||
uint32_t *pixels;
|
uint32_t *pixels;
|
||||||
uint32_t *comp_pixels;
|
uint32_t *comp_pixels;
|
||||||
void *font;
|
void *font;
|
||||||
|
spinlock_t lock;
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
void (*paint)(Window *win);
|
void (*paint)(Window *win);
|
||||||
@@ -71,8 +75,10 @@ void wm_handle_click(int x, int y);
|
|||||||
void wm_handle_right_click(int x, int y);
|
void wm_handle_right_click(int x, int y);
|
||||||
void wm_process_input(void);
|
void wm_process_input(void);
|
||||||
void wm_process_deferred_thumbs(void);
|
void wm_process_deferred_thumbs(void);
|
||||||
|
void wm_add_window_locked(Window *win);
|
||||||
void wm_add_window(Window *win);
|
void wm_add_window(Window *win);
|
||||||
void wm_remove_window(Window *win);
|
void wm_remove_window(Window *win);
|
||||||
|
void wm_bring_to_front_locked(Window *win);
|
||||||
void wm_bring_to_front(Window *win);
|
void wm_bring_to_front(Window *win);
|
||||||
Window* wm_find_window_by_title(const char *title);
|
Window* wm_find_window_by_title(const char *title);
|
||||||
|
|
||||||
@@ -99,6 +105,7 @@ void draw_traffic_light(int x, int y);
|
|||||||
void draw_icon(int x, int y, const char *label);
|
void draw_icon(int x, int y, const char *label);
|
||||||
void draw_folder_icon(int x, int y, const char *label);
|
void draw_folder_icon(int x, int y, const char *label);
|
||||||
void draw_document_icon(int x, int y, const char *label);
|
void draw_document_icon(int x, int y, const char *label);
|
||||||
|
void draw_pdf_icon(int x, int y, const char *label);
|
||||||
void draw_elf_icon(int x, int y, const char *label);
|
void draw_elf_icon(int x, int y, const char *label);
|
||||||
void draw_image_icon(int x, int y, const char *label);
|
void draw_image_icon(int x, int y, const char *label);
|
||||||
void draw_notepad_icon(int x, int y, const char *label);
|
void draw_notepad_icon(int x, int y, const char *label);
|
||||||
|
|||||||
Reference in New Issue
Block a user