mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
Porting of apps to userspace
This commit is contained in:
parent
c2ead0d6a7
commit
786eac0345
84 changed files with 2887 additions and 2392 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
BIN
boredos.iso
BIN
boredos.iso
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/cmd.o
BIN
build/cmd.o
Binary file not shown.
Binary file not shown.
BIN
build/editor.o
BIN
build/editor.o
Binary file not shown.
BIN
build/explorer.o
BIN
build/explorer.o
Binary file not shown.
BIN
build/fat32.o
BIN
build/fat32.o
Binary file not shown.
BIN
build/graphics.o
BIN
build/graphics.o
Binary file not shown.
BIN
build/idt.o
BIN
build/idt.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/markdown.o
BIN
build/markdown.o
Binary file not shown.
Binary file not shown.
BIN
build/paint.o
BIN
build/paint.o
Binary file not shown.
BIN
build/viewer.o
BIN
build/viewer.o
Binary file not shown.
BIN
build/vm.o
BIN
build/vm.o
Binary file not shown.
BIN
build/wm.o
BIN
build/wm.o
Binary file not shown.
BIN
disk.img
BIN
disk.img
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
iso_root/bin/markdown.elf
Executable file
BIN
iso_root/bin/markdown.elf
Executable file
Binary file not shown.
BIN
iso_root/bin/minesweeper.elf
Executable file
BIN
iso_root/bin/minesweeper.elf
Executable file
Binary file not shown.
Binary file not shown.
BIN
iso_root/bin/paint.elf
Executable file
BIN
iso_root/bin/paint.elf
Executable file
Binary file not shown.
BIN
iso_root/bin/settings.elf
Executable file
BIN
iso_root/bin/settings.elf
Executable file
Binary file not shown.
BIN
iso_root/bin/txtedit.elf
Executable file
BIN
iso_root/bin/txtedit.elf
Executable file
Binary file not shown.
BIN
iso_root/bin/viewer.elf
Executable file
BIN
iso_root/bin/viewer.elf
Executable file
Binary file not shown.
Binary file not shown.
|
|
@ -8,4 +8,10 @@ timeout: 3
|
|||
module_path: boot():/bin/calculator.elf
|
||||
module_path: boot():/bin/crash.elf
|
||||
module_path: boot():/bin/hello.elf
|
||||
module_path: boot():/bin/markdown.elf
|
||||
module_path: boot():/bin/minesweeper.elf
|
||||
module_path: boot():/bin/notepad.elf
|
||||
module_path: boot():/bin/paint.elf
|
||||
module_path: boot():/bin/settings.elf
|
||||
module_path: boot():/bin/txtedit.elf
|
||||
module_path: boot():/bin/viewer.elf
|
||||
|
|
|
|||
|
|
@ -7,15 +7,11 @@ extern kmain
|
|||
bits 64
|
||||
|
||||
_start:
|
||||
; Ensure interrupts are disabled
|
||||
cli
|
||||
|
||||
; (Limine guarantees 16-byte alignment)
|
||||
|
||||
; Call the C kernel entry point
|
||||
call kmain
|
||||
|
||||
; Halt if kmain returns
|
||||
hlt
|
||||
.loop:
|
||||
jmp .loop
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
#include "cli_utils.h"
|
||||
#include "fat32.h"
|
||||
#include "wm.h"
|
||||
|
||||
// Forward declarations from editor.h and wm.c
|
||||
extern void editor_open_file(const char *filename);
|
||||
extern void editor_init(void);
|
||||
extern Window win_editor;
|
||||
extern Window win_explorer;
|
||||
extern Window win_cmd;
|
||||
|
||||
void cli_cmd_txtedit(char *args) {
|
||||
// Parse the file path argument
|
||||
char filepath[256];
|
||||
int i = 0;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (args && args[i] && (args[i] == ' ' || args[i] == '\t')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Extract filepath
|
||||
int j = 0;
|
||||
while (args && args[i] && args[i] != ' ' && args[i] != '\t' && j < 255) {
|
||||
filepath[j++] = args[i++];
|
||||
}
|
||||
filepath[j] = 0;
|
||||
|
||||
// If no filepath provided, create a new empty file
|
||||
if (j == 0) {
|
||||
cli_write("Usage: txtedit <filename>\n");
|
||||
cli_write("Example: txtedit myfile.txt\n");
|
||||
cli_write(" txtedit /document.txt\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize the path (handles relative and absolute paths)
|
||||
char normalized_path[256];
|
||||
fat32_normalize_path(filepath, normalized_path);
|
||||
|
||||
// Open the file in the GUI editor
|
||||
editor_open_file(normalized_path);
|
||||
|
||||
// Make editor window visible and focused, bring to front
|
||||
win_editor.visible = true;
|
||||
win_editor.focused = true;
|
||||
|
||||
// Calculate max z_index to bring window to front
|
||||
int max_z = 0;
|
||||
if (win_explorer.z_index > max_z) max_z = win_explorer.z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
win_editor.z_index = max_z + 1;
|
||||
|
||||
cli_write("Opening: ");
|
||||
cli_write(normalized_path);
|
||||
cli_write("\n");
|
||||
}
|
||||
|
|
@ -513,7 +513,7 @@ static void internal_cmd_txtedit(char *args) {
|
|||
i++;
|
||||
}
|
||||
|
||||
// Extract filepath (args now includes drive prefix from cmd_exec_single)
|
||||
// Extract filepath
|
||||
int j = 0;
|
||||
while (args && args[i] && args[i] != ' ' && args[i] != '\t' && j < 255) {
|
||||
filepath[j++] = args[i++];
|
||||
|
|
@ -523,50 +523,19 @@ static void internal_cmd_txtedit(char *args) {
|
|||
// If no filepath provided, show usage
|
||||
if (j == 0) {
|
||||
cmd_write("Usage: txtedit <filename>\n");
|
||||
cmd_write("Example: txtedit myfile.txt\n");
|
||||
cmd_write(" txtedit /document.txt\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize the path (filepath already includes drive from cmd_exec_single)
|
||||
// Normalize the path
|
||||
char normalized_path[256];
|
||||
fat32_normalize_path(filepath, normalized_path);
|
||||
|
||||
// Extract drive from normalized path to set it as current temporarily
|
||||
char drive = 'A';
|
||||
if (normalized_path[1] == ':') {
|
||||
drive = normalized_path[0];
|
||||
if (drive >= 'a' && drive <= 'z') drive -= 32;
|
||||
}
|
||||
|
||||
// Set global drive temporarily to match file's drive
|
||||
char saved_drive = fat32_get_current_drive();
|
||||
fat32_change_drive(drive);
|
||||
|
||||
// Open the file in the GUI editor
|
||||
extern void editor_open_file(const char *filename);
|
||||
extern Window win_editor;
|
||||
editor_open_file(normalized_path);
|
||||
|
||||
// Restore the drive
|
||||
fat32_change_drive(saved_drive);
|
||||
|
||||
// Make editor window visible and focused, bring to front
|
||||
extern Window win_explorer;
|
||||
extern Window win_cmd;
|
||||
|
||||
win_editor.visible = true;
|
||||
win_editor.focused = true;
|
||||
|
||||
// Calculate max z_index to bring window to front
|
||||
int max_z = 0;
|
||||
if (win_explorer.z_index > max_z) max_z = win_explorer.z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
win_editor.z_index = max_z + 1;
|
||||
|
||||
cmd_write("Opening: ");
|
||||
cmd_write(normalized_path);
|
||||
cmd_write("\n");
|
||||
|
||||
cmd_is_waiting_for_process = true;
|
||||
process_create_elf("A:/bin/txtedit.elf", normalized_path);
|
||||
}
|
||||
|
||||
static void internal_cmd_ls(char *args) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +0,0 @@
|
|||
#ifndef CONTROL_PANEL_H
|
||||
#define CONTROL_PANEL_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_control_panel;
|
||||
|
||||
void control_panel_init(void);
|
||||
void control_panel_reset(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#ifndef EDITOR_H
|
||||
#define EDITOR_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_editor;
|
||||
|
||||
void editor_init(void);
|
||||
void editor_open_file(const char *filename);
|
||||
|
||||
#endif
|
||||
|
|
@ -4,29 +4,17 @@
|
|||
#include "disk.h"
|
||||
#include "wm.h"
|
||||
#include "memory_manager.h"
|
||||
#include "editor.h"
|
||||
#include "markdown.h"
|
||||
#include "cmd.h"
|
||||
|
||||
#include "process.h"
|
||||
#include "minesweeper.h"
|
||||
#include "viewer.h"
|
||||
#include "control_panel.h"
|
||||
#include "about.h"
|
||||
#include "paint.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// === File Explorer State ===
|
||||
Window win_explorer;
|
||||
|
||||
#define EXPLORER_MAX_FILES 64
|
||||
#define EXPLORER_ITEM_HEIGHT 80
|
||||
#define EXPLORER_ITEM_WIDTH 120
|
||||
#define EXPLORER_COLS 4
|
||||
#define EXPLORER_ROWS 4
|
||||
#define EXPLORER_PADDING 15
|
||||
|
||||
Window win_explorer;
|
||||
|
||||
// Dialog states
|
||||
#define DIALOG_NONE 0
|
||||
#define DIALOG_CREATE_FILE 1
|
||||
|
|
@ -819,16 +807,13 @@ static void explorer_open_target(const char *path) {
|
|||
if (explorer_str_ends_with(path, ".elf")) {
|
||||
process_create_elf(path, NULL);
|
||||
} else if (explorer_is_markdown_file(path)) {
|
||||
wm_bring_to_front(&win_markdown);
|
||||
markdown_open_file(path);
|
||||
process_create_elf("A:/bin/markdown.elf", path);
|
||||
} else if (explorer_str_ends_with(path, ".pnt")) {
|
||||
paint_load(path);
|
||||
wm_bring_to_front(&win_paint);
|
||||
process_create_elf("A:/bin/paint.elf", path);
|
||||
} else if (explorer_str_ends_with(path, ".jpg") || explorer_str_ends_with(path, ".JPG")) {
|
||||
viewer_open_file(path);
|
||||
process_create_elf("A:/bin/viewer.elf", path);
|
||||
} else {
|
||||
wm_bring_to_front(&win_editor);
|
||||
editor_open_file(path);
|
||||
process_create_elf("A:/bin/txtedit.elf", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -859,9 +844,9 @@ static void explorer_open_item(Window *win, int index) {
|
|||
} else if (explorer_strcmp(state->items[index].name, "Terminal.shortcut") == 0) {
|
||||
target = &win_cmd; cmd_reset();
|
||||
} else if (explorer_strcmp(state->items[index].name, "Minesweeper.shortcut") == 0) {
|
||||
target = &win_minesweeper;
|
||||
} else if (explorer_strcmp(state->items[index].name, "Control Panel.shortcut") == 0) {
|
||||
target = &win_control_panel;
|
||||
process_create_elf("/bin/minesweeper.elf", NULL); return;
|
||||
} else if (explorer_strcmp(state->items[index].name, "Control Panel.shortcut") == 0 || explorer_strcmp(state->items[index].name, "Settings.shortcut") == 0) {
|
||||
process_create_elf("/bin/settings.elf", NULL); return;
|
||||
} else if (explorer_strcmp(state->items[index].name, "About.shortcut") == 0) {
|
||||
target = &win_about;
|
||||
} else if (explorer_strcmp(state->items[index].name, "Explorer.shortcut") == 0) {
|
||||
|
|
@ -1781,17 +1766,7 @@ static void explorer_handle_file_context_menu_click(Window *win, int x, int y) {
|
|||
state->dialog_input_cursor = explorer_strlen(state->dialog_input);
|
||||
explorer_strcpy(state->dialog_target_path, full_path);
|
||||
} else if (clicked_action == 110) { // Open with Text Editor
|
||||
win_editor.visible = true; win_editor.focused = true;
|
||||
int max_z = 0;
|
||||
for (int i = 0; i < explorer_win_count; i++) if (explorer_wins[i]->z_index > max_z) max_z = explorer_wins[i]->z_index;
|
||||
if (win_cmd.z_index > max_z) max_z = win_cmd.z_index;
|
||||
if (win_editor.z_index > max_z) max_z = win_editor.z_index;
|
||||
if (win_markdown.z_index > max_z) max_z = win_markdown.z_index;
|
||||
if (win_control_panel.z_index > max_z) max_z = win_control_panel.z_index;
|
||||
if (win_about.z_index > max_z) max_z = win_about.z_index;
|
||||
if (win_minesweeper.z_index > max_z) max_z = win_minesweeper.z_index;
|
||||
win_editor.z_index = max_z + 1;
|
||||
editor_open_file(full_path);
|
||||
process_create_elf("A:/bin/txtedit.elf", full_path);
|
||||
} else if (clicked_action == ACTION_RESTORE) {
|
||||
explorer_restore_file(win, state->file_context_menu_item);
|
||||
} else if (clicked_action == ACTION_CREATE_SHORTCUT) {
|
||||
|
|
@ -2024,6 +1999,6 @@ void explorer_reset(void) {
|
|||
ExplorerState *state = (ExplorerState*)win_explorer.data;
|
||||
// Reset explorer to root directory on close/reopen
|
||||
explorer_load_directory(&win_explorer, "A:/");
|
||||
win_explorer.focused = false;
|
||||
state->explorer_scroll_row = 0;
|
||||
win_explorer.focused = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m
|
|||
if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Volume label
|
||||
|
||||
// Compare name (simplistic 8.3 matching)
|
||||
char name[12];
|
||||
char name[13];
|
||||
int n = 0;
|
||||
for (int k = 0; k < 8 && entry[e].filename[k] != ' '; k++) name[n++] = entry[e].filename[k];
|
||||
if (entry[e].extension[0] != ' ') {
|
||||
|
|
@ -973,7 +973,7 @@ static bool realfs_delete(char drive, const char *path) {
|
|||
if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Skip volume label
|
||||
|
||||
// Format name and compare
|
||||
char name[12];
|
||||
char name[13];
|
||||
int n = 0;
|
||||
for (int k = 0; k < 8 && entry[e].filename[k] != ' '; k++) name[n++] = entry[e].filename[k];
|
||||
if (entry[e].extension[0] != ' ') {
|
||||
|
|
|
|||
|
|
@ -4,34 +4,27 @@
|
|||
#include "io.h"
|
||||
|
||||
static struct limine_framebuffer *g_fb = NULL;
|
||||
static uint32_t g_bg_color = 0xFF696969; // Dark gray background
|
||||
static uint32_t g_bg_color = 0xFF696969;
|
||||
|
||||
// Pattern support
|
||||
#define PATTERN_SIZE 128
|
||||
static uint32_t g_bg_pattern[PATTERN_SIZE * PATTERN_SIZE];
|
||||
static bool g_use_pattern = false;
|
||||
|
||||
// Wallpaper image support
|
||||
static uint32_t *g_bg_image = NULL;
|
||||
static int g_bg_image_w = 0;
|
||||
static int g_bg_image_h = 0;
|
||||
static bool g_use_image = false;
|
||||
|
||||
// Dirty rectangle tracking
|
||||
static DirtyRect g_dirty = {0, 0, 0, 0, false};
|
||||
|
||||
// Double buffering - allocate a back buffer
|
||||
// Max screen size: 2048x2048 @ 32bpp = 16MB, but allocate for common sizes
|
||||
// Using a simple approach: allocate max size buffer
|
||||
|
||||
#define MAX_FB_WIDTH 2048
|
||||
#define MAX_FB_HEIGHT 2048
|
||||
static uint32_t g_back_buffer[MAX_FB_WIDTH * MAX_FB_HEIGHT] __attribute__((aligned(4096)));
|
||||
|
||||
// Clipping state
|
||||
static int g_clip_x = 0, g_clip_y = 0, g_clip_w = 0, g_clip_h = 0;
|
||||
static bool g_clip_enabled = false;
|
||||
|
||||
// Custom Render Target
|
||||
static uint32_t *g_render_target = NULL;
|
||||
static int g_rt_width = 0;
|
||||
static int g_rt_height = 0;
|
||||
|
|
@ -256,24 +249,17 @@ void draw_rounded_rect_filled(int x, int y, int w, int h, int radius, uint32_t c
|
|||
draw_rect(x, y + radius, radius, h - 2*radius, color);
|
||||
draw_rect(x + w - radius, y + radius, radius, h - 2*radius, color);
|
||||
|
||||
// Draw rounded corners using scanline approach (fills gaps properly)
|
||||
for (int dy = 0; dy < radius; dy++) {
|
||||
// For top corners: distance formula inverted (narrow at top, wide at junction)
|
||||
int dx_top = isqrt(radius*radius - (radius - dy) * (radius - dy));
|
||||
|
||||
// For bottom corners: distance formula normal (wide at junction, narrow at bottom)
|
||||
int dx_bottom = isqrt(radius*radius - dy*dy);
|
||||
|
||||
// Top-left corner - horizontal scanline
|
||||
draw_rect(x + radius - dx_top, y + dy, dx_top, 1, color);
|
||||
|
||||
// Top-right corner - horizontal scanline
|
||||
draw_rect(x + w - radius, y + dy, dx_top, 1, color);
|
||||
|
||||
// Bottom-left corner - horizontal scanline
|
||||
draw_rect(x + radius - dx_bottom, y + h - radius + dy, dx_bottom, 1, color);
|
||||
|
||||
// Bottom-right corner - horizontal scanline
|
||||
draw_rect(x + w - radius, y + h - radius + dy, dx_bottom, 1, color);
|
||||
}
|
||||
}
|
||||
|
|
@ -282,7 +268,6 @@ void draw_char(int x, int y, char c, uint32_t color) {
|
|||
unsigned char uc = (unsigned char)c;
|
||||
if (uc > 127) return;
|
||||
|
||||
// Fast rejection: if the character is entirely outside the clipping/dirty rect, skip it
|
||||
if (g_clip_enabled && !g_render_target) {
|
||||
if (x + 8 <= g_clip_x || x >= g_clip_x + g_clip_w ||
|
||||
y + 8 <= g_clip_y || y >= g_clip_y + g_clip_h) {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,15 @@
|
|||
#define GUI_EVENT_RIGHT_CLICK 3
|
||||
#define GUI_EVENT_CLOSE 4
|
||||
#define GUI_EVENT_KEY 5
|
||||
#define GUI_EVENT_MOUSE_DOWN 6
|
||||
#define GUI_EVENT_MOUSE_UP 7
|
||||
#define GUI_EVENT_MOUSE_MOVE 8
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
int arg1; // For click: x
|
||||
int arg2; // For click: y
|
||||
int arg3; // For click: button state
|
||||
} gui_event_t;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include "memory_manager.h"
|
||||
#include "platform.h"
|
||||
#include "wallpaper.h"
|
||||
#include "viewer.h"
|
||||
|
||||
// --- Limine Requests ---
|
||||
__attribute__((used, section(".requests")))
|
||||
|
|
@ -183,7 +182,6 @@ void kmain(void) {
|
|||
while (1) {
|
||||
wm_process_input();
|
||||
wallpaper_process_pending();
|
||||
viewer_process_pending();
|
||||
asm("hlt");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
#ifndef MARKDOWN_H
|
||||
#define MARKDOWN_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_markdown;
|
||||
|
||||
void markdown_init(void);
|
||||
void markdown_open_file(const char *filename);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#ifndef MINESWEEPER_H
|
||||
#define MINESWEEPER_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_minesweeper;
|
||||
|
||||
void minesweeper_init(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
#include "paint.h"
|
||||
#include "graphics.h"
|
||||
#include "wm.h"
|
||||
#include "memory_manager.h"
|
||||
#include "fat32.h"
|
||||
|
||||
#define CANVAS_W 300
|
||||
#define CANVAS_H 200
|
||||
#define PAINT_MAGIC 0x544E5042 // 'BPNT'
|
||||
|
||||
Window win_paint;
|
||||
static uint32_t *canvas_buffer = NULL;
|
||||
static uint32_t current_color = COLOR_BLACK;
|
||||
static int last_mx = -1;
|
||||
static int last_my = -1;
|
||||
static char current_file_path[256] = "/Desktop/drawing.pnt";
|
||||
|
||||
static void paint_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static void paint_paint(Window *win) {
|
||||
// Toolbar area - dark mode
|
||||
draw_rounded_rect_filled(win->x + 10, win->y + 30, 40, win->h - 40, 6, COLOR_DARK_PANEL);
|
||||
|
||||
// Color Palette with rounded corners
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int cy = win->y + 40 + (i * 25);
|
||||
draw_rounded_rect_filled(win->x + 15, cy, 30, 20, 3, colors[i]);
|
||||
|
||||
// Highlight selected color with border
|
||||
if (current_color == colors[i]) {
|
||||
draw_rounded_rect(win->x + 13, cy - 2, 34, 24, 4, COLOR_DARK_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
// Toolbar Buttons - dark mode with rounded corners
|
||||
draw_rounded_rect_filled(win->x + 12, win->y + win->h - 65, 36, 20, 4, COLOR_DARK_BORDER);
|
||||
draw_string(win->x + 18, win->y + win->h - 58, "CLR", COLOR_DARK_TEXT);
|
||||
|
||||
draw_rounded_rect_filled(win->x + 12, win->y + win->h - 40, 36, 20, 4, COLOR_DARK_BORDER);
|
||||
draw_string(win->x + 18, win->y + win->h - 33, "SAV", COLOR_DARK_TEXT);
|
||||
|
||||
// Canvas Area with dark background and rounded corners
|
||||
int canvas_x = win->x + 60;
|
||||
int canvas_y = win->y + 30;
|
||||
draw_rounded_rect_filled(canvas_x - 2, canvas_y - 2, CANVAS_W + 4, CANVAS_H + 4, 4, COLOR_DARK_BG);
|
||||
|
||||
if (canvas_buffer) {
|
||||
for (int y = 0; y < CANVAS_H; y++) {
|
||||
for (int x = 0; x < CANVAS_W; x++) {
|
||||
uint32_t color = canvas_buffer[y * CANVAS_W + x];
|
||||
put_pixel(canvas_x + x, canvas_y + y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void paint_put_brush(int cx, int cy) {
|
||||
if (!canvas_buffer) return;
|
||||
for (int dy = 0; dy < 2; dy++) {
|
||||
for (int dx = 0; dx < 2; dx++) {
|
||||
int px = cx + dx;
|
||||
int py = cy + dy;
|
||||
if (px >= 0 && px < CANVAS_W && py >= 0 && py < CANVAS_H) {
|
||||
canvas_buffer[py * CANVAS_W + px] = current_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
wm_mark_dirty(win_paint.x + 60 + cx, win_paint.y + 30 + cy, 2, 2);
|
||||
}
|
||||
|
||||
void paint_handle_mouse(int x, int y) {
|
||||
int cx = x - 60;
|
||||
int cy = y - 30;
|
||||
|
||||
if (cx < 0 || cx >= CANVAS_W || cy < 0 || cy >= CANVAS_H) {
|
||||
last_mx = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_mx == -1) {
|
||||
paint_put_brush(cx, cy);
|
||||
} else {
|
||||
// Bresenham's line algorithm to fill gaps
|
||||
int x0 = last_mx, y0 = last_my;
|
||||
int x1 = cx, y1 = cy;
|
||||
int dx = (x1 - x0 > 0) ? (x1 - x0) : (x0 - x1);
|
||||
int dy = (y1 - y0 > 0) ? (y1 - y0) : (y0 - y1);
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (1) {
|
||||
paint_put_brush(x0, y0);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
last_mx = cx;
|
||||
last_my = cy;
|
||||
}
|
||||
|
||||
void paint_reset_last_pos(void) {
|
||||
last_mx = -1;
|
||||
last_my = -1;
|
||||
}
|
||||
|
||||
static void paint_save(const char *path) {
|
||||
FAT32_FileHandle *fh = fat32_open(path, "w");
|
||||
if (fh) {
|
||||
uint32_t header[3] = {PAINT_MAGIC, CANVAS_W, CANVAS_H};
|
||||
fat32_write(fh, header, sizeof(header));
|
||||
fat32_write(fh, canvas_buffer, CANVAS_W * CANVAS_H * sizeof(uint32_t));
|
||||
fat32_close(fh);
|
||||
wm_show_message("Paint", "Image saved.");
|
||||
}
|
||||
}
|
||||
|
||||
void paint_load(const char *path) {
|
||||
paint_strcpy(current_file_path, path);
|
||||
FAT32_FileHandle *fh = fat32_open(path, "r");
|
||||
if (fh) {
|
||||
uint32_t header[3];
|
||||
if (fat32_read(fh, header, sizeof(header)) == sizeof(header)) {
|
||||
if (header[0] == PAINT_MAGIC) {
|
||||
fat32_read(fh, canvas_buffer, CANVAS_W * CANVAS_H * sizeof(uint32_t));
|
||||
win_paint.visible = true;
|
||||
win_paint.focused = true;
|
||||
}
|
||||
}
|
||||
fat32_close(fh);
|
||||
}
|
||||
}
|
||||
|
||||
static void paint_click(Window *win, int x, int y) {
|
||||
(void)win;
|
||||
// Check Buttons
|
||||
if (x >= 12 && x < 48) {
|
||||
if (y >= win->h - 65 && y < win->h - 45) {
|
||||
paint_reset();
|
||||
wm_mark_dirty(win->x, win->y, win->w, win->h);
|
||||
return;
|
||||
}
|
||||
if (y >= win->h - 40 && y < win->h - 20) {
|
||||
paint_save(current_file_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check Palette
|
||||
if (x >= 15 && x < 45) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int cy = 40 + (i * 25);
|
||||
if (y >= cy && y < cy + 20) {
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
||||
current_color = colors[i];
|
||||
wm_mark_dirty(win->x, win->y, win->w, win->h);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
paint_handle_mouse(x, y);
|
||||
}
|
||||
|
||||
static void paint_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
||||
if (buttons & 0x01) { // Left button down
|
||||
paint_handle_mouse(x, y);
|
||||
wm_mark_dirty(win->x, win->y, win->w, win->h);
|
||||
} else {
|
||||
paint_reset_last_pos();
|
||||
}
|
||||
}
|
||||
|
||||
void paint_init(void) {
|
||||
win_paint.title = "Paint";
|
||||
win_paint.x = 150;
|
||||
win_paint.y = 100;
|
||||
win_paint.w = 380;
|
||||
win_paint.h = 260;
|
||||
win_paint.visible = false;
|
||||
win_paint.focused = false;
|
||||
win_paint.z_index = 0;
|
||||
win_paint.paint = paint_paint;
|
||||
win_paint.handle_click = paint_click;
|
||||
win_paint.handle_right_click = NULL;
|
||||
win_paint.handle_key = NULL;
|
||||
|
||||
if (!canvas_buffer) {
|
||||
canvas_buffer = (uint32_t*)kmalloc(CANVAS_W * CANVAS_H * sizeof(uint32_t));
|
||||
paint_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void paint_reset(void) {
|
||||
if (canvas_buffer) {
|
||||
for (int i = 0; i < CANVAS_W * CANVAS_H; i++) {
|
||||
canvas_buffer[i] = COLOR_WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#ifndef PAINT_H
|
||||
#define PAINT_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_paint;
|
||||
|
||||
void paint_init(void);
|
||||
void paint_reset(void);
|
||||
void paint_handle_mouse(int x, int y);
|
||||
void paint_load(const char *path);
|
||||
void paint_reset_last_pos(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -119,6 +119,8 @@ void process_create_elf(const char* filepath, const char* args_str) {
|
|||
new_proc->gui_event_head = 0;
|
||||
new_proc->gui_event_tail = 0;
|
||||
new_proc->ui_window = NULL;
|
||||
new_proc->heap_start = 0x20000000; // 512MB mark
|
||||
new_proc->heap_end = 0x20000000;
|
||||
|
||||
// 2. Load ELF executable
|
||||
uint64_t entry_point = elf_load(filepath, new_proc->pml4_phys);
|
||||
|
|
@ -352,7 +354,6 @@ void process_push_gui_event(process_t *proc, gui_event_t *ev) {
|
|||
// Drop event if queue is full
|
||||
if (next_tail == proc->gui_event_head) {
|
||||
extern void serial_write(const char *str);
|
||||
serial_write("PROC: GUI event queue full, dropping event!\n");
|
||||
return;
|
||||
}
|
||||
proc->gui_events[proc->gui_event_tail] = *ev;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ typedef struct process {
|
|||
int gui_event_tail;
|
||||
void *ui_window; // Pointer to the active Window
|
||||
|
||||
uint64_t heap_start;
|
||||
uint64_t heap_end;
|
||||
|
||||
void *fds[MAX_PROCESS_FDS];
|
||||
|
||||
struct process *next;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
#include "process.h"
|
||||
#include "wm.h"
|
||||
#include "fat32.h"
|
||||
#include "fat32.h"
|
||||
#include "paging.h"
|
||||
#include "platform.h"
|
||||
|
||||
// Read MSR
|
||||
static inline uint64_t rdmsr(uint32_t msr) {
|
||||
|
|
@ -69,6 +70,50 @@ static void user_window_click(Window *win, int x, int y) {
|
|||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_right_click(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_RIGHT_CLICK, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_mouse_down(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_DOWN, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_mouse_up(Window *win, int x, int y) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_UP, .arg1 = x, .arg2 = y };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
static void user_window_mouse_move(Window *win, int x, int y, uint8_t buttons) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
if (!proc) return;
|
||||
gui_event_t ev = { .type = GUI_EVENT_MOUSE_MOVE, .arg1 = x, .arg2 = y, .arg3 = buttons };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
|
||||
// Helper function for WM to send mouse events
|
||||
void syscall_send_mouse_move_event(Window *win, int x, int y, uint8_t buttons) {
|
||||
if (!win || !win->data) return;
|
||||
user_window_mouse_move(win, x, y, buttons);
|
||||
}
|
||||
|
||||
void syscall_send_mouse_down_event(Window *win, int x, int y) {
|
||||
if (!win || !win->data) return;
|
||||
user_window_mouse_down(win, x, y);
|
||||
}
|
||||
|
||||
void syscall_send_mouse_up_event(Window *win, int x, int y) {
|
||||
if (!win || !win->data) return;
|
||||
user_window_mouse_up(win, x, y);
|
||||
}
|
||||
|
||||
static void user_window_key(Window *win, char c) {
|
||||
process_t *proc = (process_t *)win->data;
|
||||
if (!proc) return;
|
||||
|
|
@ -185,9 +230,9 @@ uint64_t syscall_handler_c(uint64_t syscall_num, uint64_t arg1, uint64_t arg2, u
|
|||
// Set callbacks
|
||||
win->paint = user_window_paint;
|
||||
win->handle_click = user_window_click;
|
||||
win->handle_right_click = user_window_right_click;
|
||||
win->handle_close = user_window_close;
|
||||
win->handle_key = user_window_key;
|
||||
win->handle_right_click = NULL;
|
||||
|
||||
proc->ui_window = win;
|
||||
wm_add_window(win);
|
||||
|
|
@ -398,6 +443,110 @@ uint64_t syscall_handler_c(uint64_t syscall_num, uint64_t arg1, uint64_t arg2, u
|
|||
extern void serial_write(const char *str);
|
||||
serial_write((const char *)arg2);
|
||||
return 0;
|
||||
} else if (syscall_num == 9) { // SYS_SBRK
|
||||
int incr = (int)arg1;
|
||||
process_t *proc = process_get_current();
|
||||
if (!proc || !proc->is_user) return (uint64_t)-1;
|
||||
|
||||
uint64_t old_end = proc->heap_end;
|
||||
if (incr == 0) return old_end;
|
||||
|
||||
uint64_t new_end = old_end + incr;
|
||||
|
||||
// If expanding, we might need to map new pages
|
||||
if (incr > 0) {
|
||||
uint64_t start_page = (old_end + 0xFFF) & ~0xFFF;
|
||||
uint64_t end_page = (new_end + 0xFFF) & ~0xFFF;
|
||||
|
||||
for (uint64_t page = start_page; page < end_page; page += 4096) {
|
||||
void *phys = kmalloc_aligned(4096, 4096);
|
||||
if (!phys) return (uint64_t)-1; // Out of memory
|
||||
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
mem_memset(phys, 0, 4096);
|
||||
|
||||
paging_map_page(proc->pml4_phys, page, v2p((uint64_t)phys), 0x07); // PT_PRESENT | PT_RW | PT_USER
|
||||
}
|
||||
}
|
||||
|
||||
proc->heap_end = new_end;
|
||||
return old_end;
|
||||
} else if (syscall_num == 5) { // SYS_SYSTEM
|
||||
int cmd = (int)arg1;
|
||||
if (cmd == 1) { // SYSTEM_CMD_SET_BG_COLOR
|
||||
uint32_t color = (uint32_t)arg2;
|
||||
extern void graphics_set_bg_color(uint32_t color);
|
||||
graphics_set_bg_color(color);
|
||||
return 0;
|
||||
} else if (cmd == 2) { // SYSTEM_CMD_SET_BG_PATTERN
|
||||
uint32_t *user_pat = (uint32_t *)arg2;
|
||||
if (!user_pat) {
|
||||
extern void graphics_set_bg_pattern(uint32_t *pattern);
|
||||
graphics_set_bg_pattern(NULL);
|
||||
} else {
|
||||
static uint32_t global_bg_pattern[128*128];
|
||||
for (int i=0; i<128*128; i++) {
|
||||
global_bg_pattern[i] = user_pat[i];
|
||||
}
|
||||
extern void graphics_set_bg_pattern(uint32_t *pattern);
|
||||
graphics_set_bg_pattern(global_bg_pattern);
|
||||
}
|
||||
extern void wm_refresh(void);
|
||||
wm_refresh();
|
||||
return 0;
|
||||
} else if (cmd == 3) { // SYSTEM_CMD_SET_WALLPAPER
|
||||
int wp_id = (int)arg2;
|
||||
extern void wallpaper_request_set(int index);
|
||||
wallpaper_request_set(wp_id);
|
||||
return 0;
|
||||
} else if (cmd == 4) { // SYSTEM_CMD_SET_DESKTOP_PROP
|
||||
int prop = (int)arg2;
|
||||
int val = (int)arg3;
|
||||
extern _Bool desktop_snap_to_grid;
|
||||
extern _Bool desktop_auto_align;
|
||||
extern int desktop_max_rows_per_col;
|
||||
extern int desktop_max_cols;
|
||||
if (prop == 1) desktop_snap_to_grid = val;
|
||||
if (prop == 2) desktop_auto_align = val;
|
||||
if (prop == 3) desktop_max_rows_per_col = val;
|
||||
if (prop == 4) desktop_max_cols = val;
|
||||
extern void wm_refresh_desktop(void);
|
||||
wm_refresh_desktop();
|
||||
return 0;
|
||||
} else if (cmd == 5) { // SYSTEM_CMD_SET_MOUSE_SPEED
|
||||
extern int mouse_speed;
|
||||
mouse_speed = (int)arg2;
|
||||
return 0;
|
||||
} else if (cmd == 6) { // SYSTEM_CMD_NETWORK_INIT
|
||||
extern int network_init(void);
|
||||
return network_init();
|
||||
} else if (cmd == 7) { // SYSTEM_CMD_GET_DESKTOP_PROP
|
||||
int prop = (int)arg2;
|
||||
extern _Bool desktop_snap_to_grid;
|
||||
extern _Bool desktop_auto_align;
|
||||
extern int desktop_max_rows_per_col;
|
||||
extern int desktop_max_cols;
|
||||
if (prop == 1) return desktop_snap_to_grid;
|
||||
if (prop == 2) return desktop_auto_align;
|
||||
if (prop == 3) return desktop_max_rows_per_col;
|
||||
if (prop == 4) return desktop_max_cols;
|
||||
return 0;
|
||||
} else if (cmd == 8) { // SYSTEM_CMD_GET_MOUSE_SPEED
|
||||
extern int mouse_speed;
|
||||
return mouse_speed;
|
||||
} else if (cmd == 9) { // SYSTEM_CMD_GET_WALLPAPER_THUMB
|
||||
int id = (int)arg2;
|
||||
uint32_t *dest = (uint32_t *)arg3;
|
||||
if (!dest) return -1;
|
||||
extern uint32_t* wallpaper_get_thumb(int index);
|
||||
extern _Bool wallpaper_thumb_valid(int index);
|
||||
if (!wallpaper_thumb_valid(id)) return -1;
|
||||
uint32_t *thumb = wallpaper_get_thumb(id);
|
||||
if (!thumb) return -1;
|
||||
for (int i=0; i<100*60; i++) dest[i] = thumb[i];
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
// Forward declarations
|
||||
typedef struct Window Window;
|
||||
|
||||
// MSRs used for syscalls in x86_64
|
||||
#define MSR_EFER 0xC0000080
|
||||
#define MSR_STAR 0xC0000081
|
||||
|
|
@ -32,4 +35,9 @@
|
|||
void syscall_init(void);
|
||||
uint64_t syscall_handler_c(uint64_t syscall_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5);
|
||||
|
||||
// Mouse event helpers for WM
|
||||
void syscall_send_mouse_move_event(Window *win, int x, int y, uint8_t buttons);
|
||||
void syscall_send_mouse_down_event(Window *win, int x, int y);
|
||||
void syscall_send_mouse_up_event(Window *win, int x, int y);
|
||||
|
||||
#endif // SYSCALL_H
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ LDFLAGS = -m elf_x86_64 -nostdlib -static -no-pie -Ttext=0x40000000 --no-dynamic
|
|||
LIBC_SOURCES = $(wildcard libc/*.c)
|
||||
LIBC_OBJS = $(LIBC_SOURCES:.c=.o) crt0.o
|
||||
|
||||
APP_SOURCES = $(wildcard *.c)
|
||||
APP_SOURCES = $(filter-out nanojpeg.c, $(wildcard *.c))
|
||||
APP_OBJS = $(APP_SOURCES:.c=.o)
|
||||
APP_ELFS = $(APP_SOURCES:.c=.elf)
|
||||
|
||||
|
|
@ -23,6 +23,9 @@ libc/%.o: libc/%.c
|
|||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
viewer.elf: $(LIBC_OBJS) viewer.o nanojpeg.o
|
||||
$(LD) $(LDFLAGS) $^ -o $@
|
||||
|
||||
%.elf: $(LIBC_OBJS) %.o
|
||||
$(LD) $(LDFLAGS) $^ -o $@
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#define SCALE 1000000LL
|
||||
|
||||
// Dark Mode Colors mapping to libui
|
||||
#define COLOR_DARK_BG 0xFF1E1E1E
|
||||
#define COLOR_DARK_PANEL 0xFF2D2D2D
|
||||
#define COLOR_DARK_TEXT 0xFFF0F0F0
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -4,7 +4,7 @@ int main() {
|
|||
const char* msg = "Attempting to crash via null dereference...\n";
|
||||
sys_write(1, msg, 45);
|
||||
|
||||
// Null pointer dereference
|
||||
// Null pointer dereference (should not crash the system and instead get this process killed)
|
||||
volatile int* p = (int*)0;
|
||||
*p = 123;
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -20,10 +20,15 @@
|
|||
#define GUI_EVENT_CLOSE 4
|
||||
#define GUI_EVENT_KEY 5
|
||||
|
||||
#define GUI_EVENT_MOUSE_DOWN 6
|
||||
#define GUI_EVENT_MOUSE_UP 7
|
||||
#define GUI_EVENT_MOUSE_MOVE 8
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
int arg1; // For click: x
|
||||
int arg2; // For click: y
|
||||
int arg3; // For click: button state
|
||||
} gui_event_t;
|
||||
|
||||
// Window Handle
|
||||
|
|
|
|||
BIN
src/kernel/userland/libc/libui.o
Normal file
BIN
src/kernel/userland/libc/libui.o
Normal file
Binary file not shown.
140
src/kernel/userland/libc/stdlib.c
Normal file
140
src/kernel/userland/libc/stdlib.c
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#include "stdlib.h"
|
||||
#include "syscall.h"
|
||||
|
||||
// Simple block allocator over sys_sbrk
|
||||
typedef struct BlockMeta {
|
||||
size_t size;
|
||||
int free;
|
||||
struct BlockMeta *next;
|
||||
} BlockMeta;
|
||||
|
||||
#define META_SIZE sizeof(BlockMeta)
|
||||
|
||||
static void *global_base = NULL;
|
||||
|
||||
static BlockMeta *find_free_block(BlockMeta **last, size_t size) {
|
||||
BlockMeta *current = global_base;
|
||||
while (current && !(current->free && current->size >= size)) {
|
||||
*last = current;
|
||||
current = current->next;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
static BlockMeta *request_space(BlockMeta* last, size_t size) {
|
||||
BlockMeta *block;
|
||||
block = (BlockMeta *)sys_sbrk(0);
|
||||
// Ask for space, ensuring everything stays 8-byte aligned if sizes are odd.
|
||||
// For simplicity, we just request exactly what is needed,
|
||||
// but typically `size` should be aligned.
|
||||
size_t align = 8;
|
||||
if (size % align != 0) {
|
||||
size += align - (size % align);
|
||||
}
|
||||
|
||||
void *request = sys_sbrk(size + META_SIZE);
|
||||
if (request == (void*)-1) {
|
||||
return NULL; // sbrk failed
|
||||
}
|
||||
|
||||
if (last) { // NULL on first request
|
||||
last->next = block;
|
||||
}
|
||||
block->size = size;
|
||||
block->next = NULL;
|
||||
block->free = 0;
|
||||
return block;
|
||||
}
|
||||
|
||||
void *malloc(size_t size) {
|
||||
BlockMeta *block;
|
||||
if (size <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Align size to 8 bytes for safety
|
||||
size_t align = 8;
|
||||
if (size % align != 0) {
|
||||
size += align - (size % align);
|
||||
}
|
||||
|
||||
if (!global_base) { // First call
|
||||
block = request_space(NULL, size);
|
||||
if (!block) return NULL;
|
||||
global_base = block;
|
||||
} else {
|
||||
BlockMeta *last = global_base;
|
||||
block = find_free_block(&last, size);
|
||||
if (!block) { // Failed to find free block
|
||||
block = request_space(last, size);
|
||||
if (!block) return NULL;
|
||||
} else { // Found free block
|
||||
block->free = 0;
|
||||
// We could split the block here if block->size is much larger than size...
|
||||
}
|
||||
}
|
||||
|
||||
return (block + 1);
|
||||
}
|
||||
|
||||
void free(void *ptr) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockMeta *block = (BlockMeta*)ptr - 1;
|
||||
block->free = 1;
|
||||
}
|
||||
|
||||
void *calloc(size_t nelem, size_t elsize) {
|
||||
size_t size = nelem * elsize;
|
||||
void *ptr = malloc(size);
|
||||
if (ptr) {
|
||||
char *p = ptr;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *realloc(void *ptr, size_t size) {
|
||||
if (!ptr) {
|
||||
return malloc(size);
|
||||
}
|
||||
if (size == 0) {
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BlockMeta *block = (BlockMeta*)ptr - 1;
|
||||
if (block->size >= size) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *new_ptr = malloc(size);
|
||||
if (!new_ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *src = ptr;
|
||||
char *dst = new_ptr;
|
||||
for (size_t i = 0; i < block->size; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
free(ptr);
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
void *memset(void *s, int c, size_t n) {
|
||||
unsigned char *p = s;
|
||||
while (n--) *p++ = (unsigned char)c;
|
||||
return s;
|
||||
}
|
||||
|
||||
void *memcpy(void *dest, const void *src, size_t n) {
|
||||
unsigned char *d = dest;
|
||||
const unsigned char *s = src;
|
||||
while (n--) *d++ = *s++;
|
||||
return dest;
|
||||
}
|
||||
14
src/kernel/userland/libc/stdlib.h
Normal file
14
src/kernel/userland/libc/stdlib.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef STDLIB_H
|
||||
#define STDLIB_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void* malloc(size_t size);
|
||||
void free(void* ptr);
|
||||
void* calloc(size_t nmemb, size_t size);
|
||||
void* realloc(void* ptr, size_t size);
|
||||
void *memset(void *s, int c, size_t n);
|
||||
void *memcpy(void *dest, const void *src, size_t n);
|
||||
|
||||
#endif
|
||||
BIN
src/kernel/userland/libc/stdlib.o
Normal file
BIN
src/kernel/userland/libc/stdlib.o
Normal file
Binary file not shown.
|
|
@ -70,6 +70,14 @@ int sys_write(int fd, const char *buf, int len) {
|
|||
return (int)syscall3(SYS_WRITE, (uint64_t)fd, (uint64_t)buf, (uint64_t)len);
|
||||
}
|
||||
|
||||
void *sys_sbrk(int incr) {
|
||||
return (void *)syscall1(SYS_SBRK, (uint64_t)incr);
|
||||
}
|
||||
|
||||
int sys_system(int cmd, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4) {
|
||||
return (int)syscall5(SYS_SYSTEM, (uint64_t)cmd, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
int sys_open(const char *path, const char *mode) {
|
||||
return (int)syscall3(SYS_FS, FS_CMD_OPEN, (uint64_t)path, (uint64_t)mode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#define SYS_WRITE 1
|
||||
#define SYS_GUI 3
|
||||
#define SYS_FS 4
|
||||
#define SYS_SYSTEM 5
|
||||
#define SYS_SBRK 9
|
||||
|
||||
// FS Commands
|
||||
#define FS_CMD_OPEN 1
|
||||
|
|
@ -33,6 +35,8 @@ extern uint64_t syscall5(uint64_t sys_num, uint64_t arg1, uint64_t arg2, uint64_
|
|||
// Public API
|
||||
void sys_exit(int status);
|
||||
int sys_write(int fd, const char *buf, int len);
|
||||
void *sys_sbrk(int incr);
|
||||
int sys_system(int cmd, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);
|
||||
|
||||
// FS API
|
||||
int sys_open(const char *path, const char *mode);
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,20 +1,19 @@
|
|||
#include "markdown.h"
|
||||
#include "graphics.h"
|
||||
#include "fat32.h"
|
||||
#include "wm.h"
|
||||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// === Markdown Viewer State ===
|
||||
Window win_markdown;
|
||||
#include <stdint.h>
|
||||
|
||||
#define MD_MAX_CONTENT 16384
|
||||
#define MD_MAX_LINES 256
|
||||
#define MD_CHAR_WIDTH 8
|
||||
#define MD_LINE_HEIGHT 16
|
||||
#define MD_CONTENT_Y 40
|
||||
#define MD_PADDING_X 12
|
||||
#define MD_CONTENT_WIDTH 400
|
||||
|
||||
#define COLOR_DARK_PANEL 0xFF202020
|
||||
#define COLOR_DARK_BG 0xFF121212
|
||||
#define COLOR_DARK_TEXT 0xFFE0E0E0
|
||||
#define COLOR_DARK_TITLEBAR 0xFF303030
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
|
||||
typedef enum {
|
||||
MD_LINE_NORMAL,
|
||||
|
|
@ -40,7 +39,8 @@ static int line_count = 0;
|
|||
static int scroll_top = 0;
|
||||
static char open_filename[256] = "";
|
||||
|
||||
// === Helper Functions ===
|
||||
static int win_w = 600;
|
||||
static int win_h = 400;
|
||||
|
||||
static size_t md_strlen(const char *str) {
|
||||
size_t len = 0;
|
||||
|
|
@ -63,64 +63,42 @@ static int md_strncpy(char *dest, const char *src, int n) {
|
|||
return i;
|
||||
}
|
||||
|
||||
static int md_strcmp(const char *s1, const char *s2) {
|
||||
(void)s1; // Suppress unused warning
|
||||
(void)s2; // Suppress unused warning
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if string starts with pattern
|
||||
static bool md_starts_with(const char *str, const char *pattern) {
|
||||
(void)str; // Suppress unused warning
|
||||
(void)pattern; // Suppress unused warning
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse markdown line and extract formatted text
|
||||
static void md_parse_line(const char *raw_line, char *output, MDLineType *type, int *indent) {
|
||||
int i = 0;
|
||||
int out_idx = 0;
|
||||
*indent = 0;
|
||||
*type = MD_LINE_NORMAL;
|
||||
|
||||
// Skip leading whitespace and count indentation
|
||||
while (raw_line[i] == ' ' || raw_line[i] == '\t') {
|
||||
if (raw_line[i] == '\t') *indent += 2;
|
||||
else *indent += 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
// Detect line type
|
||||
if (raw_line[i] == '#') {
|
||||
// Heading
|
||||
int hash_count = 0;
|
||||
while (raw_line[i] == '#') {
|
||||
hash_count++;
|
||||
i++;
|
||||
}
|
||||
// Skip space after hashes
|
||||
if (raw_line[i] == ' ') i++;
|
||||
|
||||
if (hash_count == 1) *type = MD_LINE_HEADING1;
|
||||
else if (hash_count == 2) *type = MD_LINE_HEADING2;
|
||||
else if (hash_count <= 6) *type = MD_LINE_HEADING3;
|
||||
} else if (raw_line[i] == '-' || raw_line[i] == '*') {
|
||||
// Could be list or horizontal rule
|
||||
if ((raw_line[i] == '-' || raw_line[i] == '*') && (raw_line[i+1] == ' ' || raw_line[i+1] == '\t')) {
|
||||
*type = MD_LINE_LIST;
|
||||
i += 2; // Skip '- ' or '* '
|
||||
while (raw_line[i] == ' ' || raw_line[i] == '\t') i++; // Skip extra spaces
|
||||
i += 2;
|
||||
while (raw_line[i] == ' ' || raw_line[i] == '\t') i++;
|
||||
}
|
||||
} else if (raw_line[i] == '>') {
|
||||
// Blockquote
|
||||
*type = MD_LINE_BLOCKQUOTE;
|
||||
i++;
|
||||
if (raw_line[i] == ' ') i++;
|
||||
}
|
||||
|
||||
// Parse inline formatting and copy content
|
||||
while (raw_line[i] && out_idx < 255) {
|
||||
// Handle bold **text**
|
||||
if (raw_line[i] == '*' && raw_line[i+1] == '*') {
|
||||
i += 2;
|
||||
while (raw_line[i] && !(raw_line[i] == '*' && raw_line[i+1] == '*') && out_idx < 255) {
|
||||
|
|
@ -129,8 +107,6 @@ static void md_parse_line(const char *raw_line, char *output, MDLineType *type,
|
|||
if (raw_line[i] == '*' && raw_line[i+1] == '*') i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle italic *text* or _text_
|
||||
if ((raw_line[i] == '*' || raw_line[i] == '_') && out_idx > 0 && raw_line[i-1] != '\\') {
|
||||
char delim = raw_line[i];
|
||||
i++;
|
||||
|
|
@ -140,8 +116,6 @@ static void md_parse_line(const char *raw_line, char *output, MDLineType *type,
|
|||
if (raw_line[i] == delim) i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle inline code `code`
|
||||
if (raw_line[i] == '`') {
|
||||
i++;
|
||||
while (raw_line[i] && raw_line[i] != '`' && out_idx < 255) {
|
||||
|
|
@ -150,29 +124,23 @@ static void md_parse_line(const char *raw_line, char *output, MDLineType *type,
|
|||
if (raw_line[i] == '`') i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle links [text](url) - keep only text
|
||||
if (raw_line[i] == '[') {
|
||||
i++;
|
||||
while (raw_line[i] && raw_line[i] != ']' && out_idx < 255) {
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
if (raw_line[i] == ']') i++;
|
||||
// Skip (url)
|
||||
if (raw_line[i] == '(') {
|
||||
while (raw_line[i] && raw_line[i] != ')') i++;
|
||||
if (raw_line[i] == ')') i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
output[out_idx++] = raw_line[i++];
|
||||
}
|
||||
|
||||
output[out_idx] = 0;
|
||||
}
|
||||
|
||||
// Clear all markdown lines
|
||||
static void md_clear_all(void) {
|
||||
for (int i = 0; i < MD_MAX_LINES; i++) {
|
||||
lines[i].content[0] = 0;
|
||||
|
|
@ -185,29 +153,20 @@ static void md_clear_all(void) {
|
|||
open_filename[0] = 0;
|
||||
}
|
||||
|
||||
// Load and parse markdown file
|
||||
void markdown_open_file(const char *filename) {
|
||||
md_clear_all();
|
||||
md_strcpy(open_filename, filename);
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(filename, "r");
|
||||
if (!fh) {
|
||||
// File not found
|
||||
return;
|
||||
}
|
||||
int fd = sys_open(filename, "r");
|
||||
if (fd < 0) return;
|
||||
|
||||
// Read file content
|
||||
char buffer[MD_MAX_CONTENT];
|
||||
int bytes_read = fat32_read(fh, buffer, sizeof(buffer) - 1);
|
||||
fat32_close(fh);
|
||||
|
||||
if (bytes_read <= 0) {
|
||||
return;
|
||||
}
|
||||
static char buffer[MD_MAX_CONTENT];
|
||||
int bytes_read = sys_read(fd, buffer, sizeof(buffer) - 1);
|
||||
sys_close(fd);
|
||||
|
||||
if (bytes_read <= 0) return;
|
||||
buffer[bytes_read] = 0;
|
||||
|
||||
// Parse into markdown lines
|
||||
int line = 0;
|
||||
int col = 0;
|
||||
char raw_line[256] = "";
|
||||
|
|
@ -215,10 +174,8 @@ void markdown_open_file(const char *filename) {
|
|||
|
||||
for (int i = 0; i < bytes_read && line < MD_MAX_LINES; i++) {
|
||||
char ch = buffer[i];
|
||||
|
||||
if (ch == '\n') {
|
||||
raw_line[col] = 0;
|
||||
|
||||
if (raw_line[0] == '`' && raw_line[1] == '`' && raw_line[2] == '`') {
|
||||
in_code_block = !in_code_block;
|
||||
} else {
|
||||
|
|
@ -240,7 +197,6 @@ void markdown_open_file(const char *filename) {
|
|||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
col = 0;
|
||||
raw_line[0] = 0;
|
||||
} else if (col < 255) {
|
||||
|
|
@ -248,7 +204,6 @@ void markdown_open_file(const char *filename) {
|
|||
}
|
||||
}
|
||||
|
||||
// Handle last line if no trailing newline
|
||||
if (col > 0 && line < MD_MAX_LINES) {
|
||||
raw_line[col] = 0;
|
||||
if (raw_line[0] == '`' && raw_line[1] == '`' && raw_line[2] == '`') {
|
||||
|
|
@ -270,48 +225,38 @@ void markdown_open_file(const char *filename) {
|
|||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
line_count = line;
|
||||
}
|
||||
|
||||
// === Paint Function ===
|
||||
|
||||
// Helper to draw text with emphasis (bold effect by overlaying)
|
||||
static void md_draw_text_bold(int x, int y, const char *text, uint32_t color) {
|
||||
draw_string(x, y, text, color);
|
||||
draw_string(x + 1, y, text, color);
|
||||
static void md_draw_text_bold(ui_window_t win, int x, int y, const char *text, uint32_t color) {
|
||||
ui_draw_string(win, x, y, text, color);
|
||||
ui_draw_string(win, x + 1, y, text, color);
|
||||
}
|
||||
|
||||
static void md_paint(Window *win) {
|
||||
int offset_x = win->x + 4;
|
||||
int offset_y = win->y + 24;
|
||||
int content_width = win->w - 8;
|
||||
int content_height = win->h - 28;
|
||||
static void md_paint(ui_window_t win) {
|
||||
int offset_x = 4;
|
||||
int offset_y = 24;
|
||||
int content_width = win_w - 8;
|
||||
int content_height = win_h - 28;
|
||||
|
||||
// Draw filename bar below title (dark mode)
|
||||
draw_rounded_rect_filled(offset_x, offset_y, content_width, 20, 6, COLOR_DARK_PANEL);
|
||||
draw_string(offset_x + 4, offset_y + 4, "File", COLOR_DARK_TEXT);
|
||||
draw_string(offset_x + 50, offset_y + 4, open_filename, COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y, content_width, 20, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 4, offset_y + 4, "File", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 50, offset_y + 4, open_filename, COLOR_DARK_TEXT);
|
||||
|
||||
// Draw scroll buttons on top right with rounded style
|
||||
int btn_x_up = offset_x + content_width - 50;
|
||||
int btn_y = offset_y + 2;
|
||||
draw_rounded_rect_filled(btn_x_up, btn_y, 20, 16, 4, COLOR_DARK_TITLEBAR);
|
||||
draw_string(btn_x_up + 6, btn_y, "^", COLOR_DARK_TEXT);
|
||||
draw_rounded_rect_filled(btn_x_up + 24, btn_y, 20, 16, 4, COLOR_DARK_TITLEBAR);
|
||||
draw_string(btn_x_up + 30, btn_y, "v", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, btn_x_up, btn_y, 20, 16, 4, COLOR_DARK_TITLEBAR);
|
||||
ui_draw_string(win, btn_x_up + 6, btn_y, "^", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, btn_x_up + 24, btn_y, 20, 16, 4, COLOR_DARK_TITLEBAR);
|
||||
ui_draw_string(win, btn_x_up + 30, btn_y, "v", COLOR_DARK_TEXT);
|
||||
|
||||
// Content area - starts below filename bar
|
||||
int content_start_y = offset_y + 24;
|
||||
int content_start_x = offset_x + 4;
|
||||
int usable_content_width = content_width - 8 - 20; // Reserved space for scroll button
|
||||
int usable_content_width = content_width - 8 - 20;
|
||||
int usable_content_height = content_height - 28;
|
||||
int max_display_lines = usable_content_height / MD_LINE_HEIGHT;
|
||||
|
||||
// Draw content background (dark mode)
|
||||
draw_rounded_rect_filled(win->x + 4, content_start_y, win->w - 24, usable_content_height, 6, COLOR_DARK_BG);
|
||||
|
||||
|
||||
ui_draw_rounded_rect_filled(win, 4, content_start_y, win_w - 24, usable_content_height, 6, COLOR_DARK_BG);
|
||||
|
||||
int display_line = 0;
|
||||
int i = scroll_top;
|
||||
|
|
@ -319,7 +264,6 @@ static void md_paint(Window *win) {
|
|||
while (i < line_count && display_line < max_display_lines) {
|
||||
MDLine *line = &lines[i];
|
||||
|
||||
// Determine spacing and text properties based on heading level (dark mode colors)
|
||||
int line_height = MD_LINE_HEIGHT;
|
||||
int extra_spacing = 0;
|
||||
uint32_t text_color = COLOR_DARK_TEXT;
|
||||
|
|
@ -327,148 +271,117 @@ static void md_paint(Window *win) {
|
|||
|
||||
switch (line->type) {
|
||||
case MD_LINE_HEADING1:
|
||||
line_height = MD_LINE_HEIGHT * 2; // Double height
|
||||
text_color = 0xFF87CEEB; // Light blue
|
||||
line_height = MD_LINE_HEIGHT * 2;
|
||||
text_color = 0xFF87CEEB;
|
||||
use_bold = true;
|
||||
extra_spacing = 4;
|
||||
break;
|
||||
case MD_LINE_HEADING2:
|
||||
line_height = MD_LINE_HEIGHT + 6; // 1.5x height
|
||||
text_color = 0xFF4A90E2; // Medium blue
|
||||
line_height = MD_LINE_HEIGHT + 6;
|
||||
text_color = 0xFF4A90E2;
|
||||
use_bold = true;
|
||||
extra_spacing = 2;
|
||||
break;
|
||||
case MD_LINE_HEADING3:
|
||||
line_height = MD_LINE_HEIGHT + 2; // Slightly larger
|
||||
text_color = 0xFF87CEEB; // Light blue
|
||||
line_height = MD_LINE_HEIGHT + 2;
|
||||
text_color = 0xFF87CEEB;
|
||||
use_bold = false;
|
||||
break;
|
||||
case MD_LINE_BLOCKQUOTE:
|
||||
text_color = 0xFFA0A0A0; // Light gray
|
||||
text_color = 0xFFA0A0A0;
|
||||
break;
|
||||
case MD_LINE_CODE:
|
||||
text_color = 0xFF90EE90; // Light green for code
|
||||
text_color = 0xFF90EE90;
|
||||
break;
|
||||
default:
|
||||
text_color = COLOR_DARK_TEXT;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this heading will fit on the screen
|
||||
if (display_line + (line_height / MD_LINE_HEIGHT) > max_display_lines) {
|
||||
break; // Stop rendering if heading won't fit
|
||||
}
|
||||
if (display_line + (line_height / MD_LINE_HEIGHT) > max_display_lines) break;
|
||||
|
||||
// Adjust X position based on indentation
|
||||
int x_offset = content_start_x + (line->indent_level * 4);
|
||||
int available_width = usable_content_width - (line->indent_level * 4);
|
||||
int max_chars_per_line = available_width / MD_CHAR_WIDTH;
|
||||
|
||||
if (max_chars_per_line < 1) max_chars_per_line = 1;
|
||||
|
||||
// Handle line wrapping (word-based)
|
||||
const char *text = line->content;
|
||||
int text_len = line->length;
|
||||
int char_idx = 0;
|
||||
int local_display_line = 0;
|
||||
int wrapped_line_count = 0;
|
||||
|
||||
while (char_idx < text_len) {
|
||||
while (char_idx < text_len || (text_len == 0 && local_display_line == 0)) {
|
||||
int line_y = content_start_y + display_line * MD_LINE_HEIGHT + (local_display_line * MD_LINE_HEIGHT);
|
||||
|
||||
// Extract line segment - copy up to max_chars_per_line characters
|
||||
char line_segment[256];
|
||||
int segment_len = 0;
|
||||
int segment_start = char_idx;
|
||||
|
||||
// Copy characters up to max_chars_per_line OR until end of string
|
||||
while (char_idx < text_len && segment_len < max_chars_per_line) {
|
||||
line_segment[segment_len++] = text[char_idx++];
|
||||
}
|
||||
line_segment[segment_len] = 0;
|
||||
|
||||
if (char_idx < text_len && segment_len > 0) {
|
||||
// Look for the last space in the segment
|
||||
int last_space = -1;
|
||||
for (int i = segment_len - 1; i >= 0; i--) {
|
||||
if (line_segment[i] == ' ') {
|
||||
last_space = i;
|
||||
break;
|
||||
for (int j = segment_len - 1; j >= 0; j--) {
|
||||
if (line_segment[j] == ' ') {
|
||||
last_space = j; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_space > 0) {
|
||||
segment_len = last_space;
|
||||
line_segment[segment_len] = 0;
|
||||
// Backtrack char_idx to position after the space
|
||||
char_idx = segment_start + last_space + 1;
|
||||
// Skip any additional spaces at the start of next line
|
||||
while (char_idx < text_len && text[char_idx] == ' ') {
|
||||
char_idx++;
|
||||
}
|
||||
while (char_idx < text_len && text[char_idx] == ' ') char_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
if (line->type == MD_LINE_CODE && segment_len > 0) {
|
||||
draw_rect(x_offset - 2, line_y - 2, (segment_len * MD_CHAR_WIDTH) + 4, 12, COLOR_BLACK);
|
||||
ui_draw_rect(win, x_offset - 2, line_y - 2, (segment_len * MD_CHAR_WIDTH) + 4, 12, COLOR_BLACK);
|
||||
}
|
||||
|
||||
// Draw special elements for first wrapped line of this markdown line
|
||||
if (local_display_line == 0) {
|
||||
switch (line->type) {
|
||||
case MD_LINE_LIST:
|
||||
// Draw bullet point
|
||||
draw_rect(x_offset, line_y + MD_LINE_HEIGHT/2 - 1, 2, 2, COLOR_BLACK);
|
||||
ui_draw_rect(win, x_offset, line_y + MD_LINE_HEIGHT/2 - 1, 2, 2, COLOR_BLACK);
|
||||
x_offset += 12;
|
||||
// Redraw segment without leading space
|
||||
if (segment_len > 0 && line_segment[0] == ' ') {
|
||||
for (int j = 0; j < segment_len - 1; j++) {
|
||||
line_segment[j] = line_segment[j + 1];
|
||||
}
|
||||
for (int j = 0; j < segment_len - 1; j++) line_segment[j] = line_segment[j + 1];
|
||||
segment_len--;
|
||||
}
|
||||
break;
|
||||
case MD_LINE_BLOCKQUOTE:
|
||||
// Draw left border
|
||||
draw_rect(x_offset - 4, line_y, 2, line_height, 0xFF404080);
|
||||
break;
|
||||
default:
|
||||
ui_draw_rect(win, x_offset - 4, line_y, 2, line_height, 0xFF404080);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the text segment with appropriate styling
|
||||
if (segment_len > 0) {
|
||||
if (use_bold) {
|
||||
md_draw_text_bold(x_offset, line_y + extra_spacing, line_segment, text_color);
|
||||
md_draw_text_bold(win, x_offset, line_y + extra_spacing, line_segment, text_color);
|
||||
} else {
|
||||
draw_string(x_offset, line_y, line_segment, text_color);
|
||||
ui_draw_string(win, x_offset, line_y, line_segment, text_color);
|
||||
}
|
||||
}
|
||||
|
||||
local_display_line++;
|
||||
wrapped_line_count++;
|
||||
|
||||
if (char_idx >= text_len) break;
|
||||
}
|
||||
|
||||
|
||||
display_line += wrapped_line_count;
|
||||
|
||||
display_line += (wrapped_line_count > 0 ? wrapped_line_count : 1);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// === Input Handling ===
|
||||
|
||||
static void md_handle_key(Window *win, char c) {
|
||||
(void)win; // Suppress unused warning
|
||||
|
||||
|
||||
if (c == 'w' || c == 'W' || c == 17) { // Page up or UP arrow
|
||||
static void md_handle_key(char c) {
|
||||
if (c == 'w' || c == 'W' || c == 17) {
|
||||
scroll_top -= 3;
|
||||
if (scroll_top < 0) scroll_top = 0;
|
||||
} else if (c == 's' || c == 'S' || c == 18) { // Page down or DOWN arrow
|
||||
} else if (c == 's' || c == 'S' || c == 18) {
|
||||
scroll_top += 3;
|
||||
int max_scroll = line_count - 10;
|
||||
if (scroll_top > max_scroll) scroll_top = max_scroll;
|
||||
|
|
@ -476,24 +389,18 @@ static void md_handle_key(Window *win, char c) {
|
|||
}
|
||||
}
|
||||
|
||||
static void md_handle_click(Window *win, int x, int y) {
|
||||
// x and y are relative to window origin
|
||||
int content_width = win->w - 8;
|
||||
|
||||
// Top right up button: 4 + content_width - 50, 24 + 2, 20x16
|
||||
static void md_handle_click(int x, int y) {
|
||||
int content_width = win_w - 8;
|
||||
int btn_x_up = 4 + content_width - 50;
|
||||
int btn_y = 24 + 2;
|
||||
if (x >= btn_x_up && x < btn_x_up + 20 && y >= btn_y && y < btn_y + 16) {
|
||||
// Scroll up
|
||||
scroll_top -= 3;
|
||||
if (scroll_top < 0) scroll_top = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Top right down button: 4 + content_width - 50 + 24, 24 + 2, 20x16
|
||||
int btn_x_down_top = 4 + content_width - 50 + 24;
|
||||
if (x >= btn_x_down_top && x < btn_x_down_top + 20 && y >= btn_y && y < btn_y + 16) {
|
||||
// Scroll down
|
||||
scroll_top += 3;
|
||||
int max_scroll = line_count - 10;
|
||||
if (scroll_top > max_scroll) scroll_top = max_scroll;
|
||||
|
|
@ -502,21 +409,33 @@ static void md_handle_click(Window *win, int x, int y) {
|
|||
}
|
||||
}
|
||||
|
||||
// === Initialization ===
|
||||
|
||||
void markdown_init(void) {
|
||||
win_markdown.title = "Markdown Viewer";
|
||||
win_markdown.x = 150;
|
||||
win_markdown.y = 180;
|
||||
win_markdown.w = 600;
|
||||
win_markdown.h = 400;
|
||||
win_markdown.visible = false;
|
||||
win_markdown.focused = false;
|
||||
win_markdown.z_index = 0;
|
||||
win_markdown.paint = md_paint;
|
||||
win_markdown.handle_key = md_handle_key;
|
||||
win_markdown.handle_click = md_handle_click;
|
||||
win_markdown.handle_right_click = NULL;
|
||||
int main(int argc, char **argv) {
|
||||
ui_window_t win = ui_window_create("Markdown Viewer", 150, 180, win_w, win_h);
|
||||
if (!win) return 1;
|
||||
|
||||
md_clear_all();
|
||||
if (argc > 1) {
|
||||
markdown_open_file(argv[1]);
|
||||
}
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
md_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
md_handle_click(ev.arg1, ev.arg2);
|
||||
md_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_KEY) {
|
||||
md_handle_key((char)ev.arg1);
|
||||
md_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
BIN
src/kernel/userland/markdown.elf
Executable file
BIN
src/kernel/userland/markdown.elf
Executable file
Binary file not shown.
|
|
@ -1,10 +1,23 @@
|
|||
#include "minesweeper.h"
|
||||
#include "graphics.h"
|
||||
#include "wm.h"
|
||||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
Window win_minesweeper;
|
||||
#define COLOR_DARK_BG 0xFF121212
|
||||
#define COLOR_DARK_PANEL 0xFF202020
|
||||
#define COLOR_DARK_BORDER 0xFF404040
|
||||
#define COLOR_DARK_TEXT 0xFFE0E0E0
|
||||
#define COLOR_TRAFFIC_RED 0xFFFF6B6B
|
||||
|
||||
// Debugging helper
|
||||
static void debug_print(const char *msg) {
|
||||
sys_write(1, msg, 0);
|
||||
int i = 0;
|
||||
while (msg[i]) i++;
|
||||
sys_write(1, msg, i);
|
||||
sys_write(1, "\n", 1);
|
||||
}
|
||||
|
||||
// Game constants
|
||||
#define GRID_WIDTH 10
|
||||
|
|
@ -73,9 +86,6 @@ static void init_game(void) {
|
|||
revealed_count = 0;
|
||||
}
|
||||
|
||||
static void reveal_cell(int x, int y);
|
||||
|
||||
// Flood fill for empty cells
|
||||
static void flood_fill(int x, int y) {
|
||||
if (x < 0 || x >= GRID_WIDTH || y < 0 || y >= GRID_HEIGHT) return;
|
||||
if (revealed[y][x] || flagged[y][x]) return;
|
||||
|
|
@ -131,41 +141,24 @@ static void flag_cell(int x, int y) {
|
|||
flagged[y][x] = !flagged[y][x];
|
||||
}
|
||||
|
||||
static void minesweeper_right_click(Window *win, int x, int y) {
|
||||
// x and y are relative to window content (0,0 is top-left of window)
|
||||
int grid_start_x = 10;
|
||||
int grid_start_y = 50;
|
||||
static void minesweeper_paint(ui_window_t win) {
|
||||
int win_w = 240, win_h = 340;
|
||||
|
||||
// Check grid cells
|
||||
if (x >= grid_start_x && x < grid_start_x + GRID_WIDTH * CELL_SIZE &&
|
||||
y >= grid_start_y && y < grid_start_y + GRID_HEIGHT * CELL_SIZE) {
|
||||
|
||||
if (game_over || game_won) return;
|
||||
|
||||
int cell_x = (x - grid_start_x) / CELL_SIZE;
|
||||
int cell_y = (y - grid_start_y) / CELL_SIZE;
|
||||
|
||||
flag_cell(cell_x, cell_y);
|
||||
wm_mark_dirty(win->x, win->y, win->w, win->h);
|
||||
}
|
||||
}
|
||||
|
||||
static void minesweeper_paint(Window *win) {
|
||||
// Background - dark mode
|
||||
draw_rect(win->x + 4, win->y + 30, win->w - 8, win->h - 34, COLOR_DARK_BG);
|
||||
ui_draw_rect(win, 4, 30, win_w - 8, win_h - 34, COLOR_DARK_BG);
|
||||
|
||||
// Game status
|
||||
if (game_over) {
|
||||
draw_string(win->x + 10, win->y + 36, "Game Over!", COLOR_TRAFFIC_RED);
|
||||
ui_draw_string(win, 10, 36, "Game Over!", COLOR_TRAFFIC_RED);
|
||||
} else if (game_won) {
|
||||
draw_string(win->x + 10, win->y + 36, "You Won!", 0xFF00FF00); // Bright green
|
||||
ui_draw_string(win, 10, 36, "You Won!", 0xFF00FF00); // Bright green
|
||||
} else {
|
||||
draw_string(win->x + 10, win->y + 36, "", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, 10, 36, "", COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
// Draw grid
|
||||
int grid_start_x = win->x + 10;
|
||||
int grid_start_y = win->y + 56;
|
||||
int grid_start_x = 10;
|
||||
int grid_start_y = 56;
|
||||
|
||||
for (int y = 0; y < GRID_HEIGHT; y++) {
|
||||
for (int x = 0; x < GRID_WIDTH; x++) {
|
||||
|
|
@ -174,45 +167,42 @@ static void minesweeper_paint(Window *win) {
|
|||
|
||||
if (revealed[y][x]) {
|
||||
// Revealed cell - dark mode
|
||||
draw_rounded_rect_filled(px, py, CELL_SIZE, CELL_SIZE, 2, COLOR_DARK_PANEL);
|
||||
ui_draw_rounded_rect_filled(win, px, py, CELL_SIZE, CELL_SIZE, 2, COLOR_DARK_PANEL);
|
||||
|
||||
if (grid[y][x] == -1) {
|
||||
// Mine
|
||||
draw_string(px + 8, py + 6, "*", COLOR_TRAFFIC_RED);
|
||||
ui_draw_string(win, px + 8, py + 6, "*", COLOR_TRAFFIC_RED);
|
||||
} else if (grid[y][x] > 0) {
|
||||
// Number
|
||||
char num[2] = { '0' + grid[y][x], 0 };
|
||||
draw_string(px + 8, py + 6, num, COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, px + 8, py + 6, num, COLOR_DARK_TEXT);
|
||||
}
|
||||
// 0 = empty, nothing to draw
|
||||
} else {
|
||||
// Unrevealed cell - raised/button look
|
||||
draw_rounded_rect_filled(px, py, CELL_SIZE, CELL_SIZE, 2, COLOR_DARK_BORDER);
|
||||
// Unrevealed cell
|
||||
ui_draw_rounded_rect_filled(win, px, py, CELL_SIZE, CELL_SIZE, 2, COLOR_DARK_BORDER);
|
||||
|
||||
if (flagged[y][x]) {
|
||||
draw_string(px + 7, py + 6, "F", COLOR_TRAFFIC_RED);
|
||||
ui_draw_string(win, px + 7, py + 6, "F", COLOR_TRAFFIC_RED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw new game button (narrower, less dead space)
|
||||
// Draw new game button
|
||||
int btn_y = grid_start_y + GRID_HEIGHT * CELL_SIZE + 10;
|
||||
draw_rounded_rect_filled(grid_start_x, btn_y, 70, 24, 4, COLOR_DARK_BORDER);
|
||||
draw_string(grid_start_x + 6, btn_y + 8, "New Game", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, grid_start_x, btn_y, 70, 24, 4, COLOR_DARK_BORDER);
|
||||
ui_draw_string(win, grid_start_x + 6, btn_y + 8, "New Game", COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
static void minesweeper_click(Window *win, int x, int y) {
|
||||
// x and y are relative to window content (0,0 is top-left of window)
|
||||
static void minesweeper_handle_click(ui_window_t win, int x, int y, int button) {
|
||||
int grid_start_x = 10;
|
||||
int grid_start_y = 50;
|
||||
int grid_start_y = 56;
|
||||
int btn_y = grid_start_y + GRID_HEIGHT * CELL_SIZE + 10;
|
||||
|
||||
// Check "New Game" button
|
||||
if (x >= grid_start_x && x < grid_start_x + 90 &&
|
||||
if (x >= grid_start_x && x < grid_start_x + 70 &&
|
||||
y >= btn_y && y < btn_y + 24) {
|
||||
init_game();
|
||||
wm_mark_dirty(win->x, win->y, win->w, win->h);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -225,25 +215,46 @@ static void minesweeper_click(Window *win, int x, int y) {
|
|||
int cell_x = (x - grid_start_x) / CELL_SIZE;
|
||||
int cell_y = (y - grid_start_y) / CELL_SIZE;
|
||||
|
||||
if (button == GUI_EVENT_RIGHT_CLICK) {
|
||||
debug_print("[MINESWEEPER] Flagging cell");
|
||||
flag_cell(cell_x, cell_y);
|
||||
} else {
|
||||
debug_print("[MINESWEEPER] Revealing cell");
|
||||
reveal_cell(cell_x, cell_y);
|
||||
|
||||
wm_mark_dirty(win->x, win->y, win->w, win->h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void minesweeper_init(void) {
|
||||
win_minesweeper.title = "Minesweeper";
|
||||
win_minesweeper.x = 250;
|
||||
win_minesweeper.y = 100;
|
||||
win_minesweeper.w = 240;
|
||||
win_minesweeper.h = 340;
|
||||
win_minesweeper.visible = false;
|
||||
win_minesweeper.focused = false;
|
||||
win_minesweeper.z_index = 0;
|
||||
win_minesweeper.paint = minesweeper_paint;
|
||||
win_minesweeper.handle_click = minesweeper_click;
|
||||
win_minesweeper.handle_right_click = minesweeper_right_click;
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
ui_window_t win = ui_window_create("Minesweeper", 250, 100, 240, 340);
|
||||
if (!win) return 1;
|
||||
|
||||
// Initialize game
|
||||
// Use current time or something for seed? No syscall for time right now.
|
||||
random_seed = 987654321;
|
||||
init_game();
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
minesweeper_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 240, 340);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
debug_print("[MINESWEEPER] LEFT CLICK");
|
||||
minesweeper_handle_click(win, ev.arg1, ev.arg2, ev.type);
|
||||
minesweeper_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 240, 340);
|
||||
} else if (ev.type == GUI_EVENT_RIGHT_CLICK) {
|
||||
debug_print("[MINESWEEPER] RIGHT CLICK DETECTED");
|
||||
minesweeper_handle_click(win, ev.arg1, ev.arg2, ev.type);
|
||||
minesweeper_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 240, 340);
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
BIN
src/kernel/userland/minesweeper.elf
Executable file
BIN
src/kernel/userland/minesweeper.elf
Executable file
Binary file not shown.
967
src/kernel/userland/nanojpeg.c
Normal file
967
src/kernel/userland/nanojpeg.c
Normal file
|
|
@ -0,0 +1,967 @@
|
|||
// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder
|
||||
// version 1.3 (2012-03-05)
|
||||
// by Martin J. Fiedler <martin.fiedler@gmx.net>
|
||||
//
|
||||
// This software is published under the terms of KeyJ's Research License,
|
||||
// version 0.2. Usage of this software is subject to the following conditions:
|
||||
// 0. There's no warranty whatsoever. The author(s) of this software can not
|
||||
// be held liable for any damages that occur when using this software.
|
||||
// 1. This software may be used freely for both non-commercial and commercial
|
||||
// purposes.
|
||||
// 2. This software may be redistributed freely as long as no fees are charged
|
||||
// for the distribution and this license information is included.
|
||||
// 3. This software may be modified freely except for this license information,
|
||||
// which must not be changed in any way.
|
||||
// 4. If anything other than configuration, indentation or comments have been
|
||||
// altered in the code, the original author(s) must receive a copy of the
|
||||
// modified code.
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DOCUMENTATION SECTION //
|
||||
// read this if you want to know what this is all about //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// INTRODUCTION
|
||||
// ============
|
||||
//
|
||||
// This is a minimal decoder for baseline JPEG images. It accepts memory dumps
|
||||
// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit
|
||||
// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files
|
||||
// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are
|
||||
// not supported. All YCbCr subsampling schemes with power-of-two ratios are
|
||||
// supported, as are restart intervals. Progressive or lossless JPEG is not
|
||||
// supported.
|
||||
// Summed up, NanoJPEG should be able to decode all images from digital cameras
|
||||
// and most common forms of other non-progressive JPEG images.
|
||||
// The decoder is not optimized for speed, it's optimized for simplicity and
|
||||
// small code. Image quality should be at a reasonable level. A bicubic chroma
|
||||
// upsampling filter ensures that subsampled YCbCr images are rendered in
|
||||
// decent quality. The decoder is not meant to deal with broken JPEG files in
|
||||
// a graceful manner; if anything is wrong with the bitstream, decoding will
|
||||
// simply fail.
|
||||
// The code should work with every modern C compiler without problems and
|
||||
// should not emit any warnings. It uses only (at least) 32-bit integer
|
||||
// arithmetic and is supposed to be endianness independent and 64-bit clean.
|
||||
// However, it is not thread-safe.
|
||||
|
||||
|
||||
// COMPILE-TIME CONFIGURATION
|
||||
// ==========================
|
||||
//
|
||||
// The following aspects of NanoJPEG can be controlled with preprocessor
|
||||
// defines:
|
||||
//
|
||||
// _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example
|
||||
// program.
|
||||
// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header
|
||||
// file for NanoJPEG. Example:
|
||||
// #define _NJ_INCLUDE_HEADER_ONLY
|
||||
// #include "nanojpeg.c"
|
||||
// int main(void) {
|
||||
// njInit();
|
||||
// // your code here
|
||||
// njDone();
|
||||
// }
|
||||
// NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy()
|
||||
// functions from the standard C library (default).
|
||||
// NJ_USE_LIBC=0 = Don't use the standard C library. In this mode,
|
||||
// external functions njAlloc(), njFreeMem(),
|
||||
// njFillMem() and njCopyMem() need to be defined
|
||||
// and implemented somewhere.
|
||||
// NJ_USE_WIN32=0 = Normal mode (default).
|
||||
// NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and
|
||||
// NJ_USE_LIBC=0, NanoJPEG will use its own
|
||||
// implementations of the required C library
|
||||
// functions (default if compiling with MSVC and
|
||||
// NJ_USE_LIBC=0).
|
||||
// NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter
|
||||
// (default). // 图像resize的一种算法
|
||||
// NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling
|
||||
// (bad quality, but faster and less code).
|
||||
|
||||
|
||||
// API
|
||||
// ===
|
||||
//
|
||||
// For API documentation, read the "header section" below.
|
||||
|
||||
|
||||
// EXAMPLE
|
||||
// =======
|
||||
//
|
||||
// A few pages below, you can find an example program that uses NanoJPEG to
|
||||
// convert JPEG files into PGM or PPM. To compile it, use something like
|
||||
// gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c
|
||||
// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :)
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// HEADER SECTION //
|
||||
// copy and pase this into nanojpeg.h if you want //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _NANOJPEG_H
|
||||
#define _NANOJPEG_H
|
||||
|
||||
// nj_result_t: Result codes for njDecode().
|
||||
typedef enum _nj_result {
|
||||
NJ_OK = 0, // no error, decoding successful
|
||||
NJ_NO_JPEG, // not a JPEG file
|
||||
NJ_UNSUPPORTED, // unsupported format
|
||||
NJ_OUT_OF_MEM, // out of memory
|
||||
NJ_INTERNAL_ERR, // internal error
|
||||
NJ_SYNTAX_ERROR, // syntax error
|
||||
__NJ_FINISHED, // used internally, will never be reported
|
||||
} nj_result_t;
|
||||
|
||||
// njInit: Initialize NanoJPEG.
|
||||
// For safety reasons, this should be called at least one time before using
|
||||
// using any of the other NanoJPEG functions.
|
||||
void njInit(void);
|
||||
|
||||
// njDecode: Decode a JPEG image.
|
||||
// Decodes a memory dump of a JPEG file into internal buffers.
|
||||
// Parameters:
|
||||
// jpeg = The pointer to the memory dump.
|
||||
// size = The size of the JPEG file.
|
||||
// Return value: The error code in case of failure, or NJ_OK (zero) on success.
|
||||
nj_result_t njDecode(const void* jpeg, const int size);
|
||||
|
||||
// njGetWidth: Return the width (in pixels) of the most recently decoded
|
||||
// image. If njDecode() failed, the result of njGetWidth() is undefined.
|
||||
int njGetWidth(void);
|
||||
|
||||
// njGetHeight: Return the height (in pixels) of the most recently decoded
|
||||
// image. If njDecode() failed, the result of njGetHeight() is undefined.
|
||||
int njGetHeight(void);
|
||||
|
||||
// njIsColor: Return 1 if the most recently decoded image is a color image
|
||||
// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result
|
||||
// of njGetWidth() is undefined.
|
||||
int njIsColor(void);
|
||||
|
||||
// njGetImage: Returns the decoded image data.
|
||||
// Returns a pointer to the most recently image. The memory layout it byte-
|
||||
// oriented, top-down, without any padding between lines. Pixels of color
|
||||
// images will be stored as three consecutive bytes for the red, green and
|
||||
// blue channels. This data format is thus compatible with the PGM or PPM
|
||||
// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8.
|
||||
// If njDecode() failed, the result of njGetImage() is undefined.
|
||||
unsigned char* njGetImage(void);
|
||||
|
||||
// njGetImageSize: Returns the size (in bytes) of the image data returned
|
||||
// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is
|
||||
// undefined.
|
||||
int njGetImageSize(void);
|
||||
|
||||
// njDone: Uninitialize NanoJPEG.
|
||||
// Resets NanoJPEG's internal state and frees all memory that has been
|
||||
// allocated at run-time by NanoJPEG. It is still possible to decode another
|
||||
// image after a njDone() call.
|
||||
void njDone(void);
|
||||
|
||||
#endif//_NANOJPEG_H
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CONFIGURATION SECTION //
|
||||
// adjust the default settings for the NJ_ defines here //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef NJ_USE_LIBC
|
||||
#define NJ_USE_LIBC 1
|
||||
#endif
|
||||
|
||||
#ifndef NJ_USE_WIN32
|
||||
#ifdef _MSC_VER
|
||||
#define NJ_USE_WIN32 (!NJ_USE_LIBC)
|
||||
#else
|
||||
#define NJ_USE_WIN32 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NJ_CHROMA_FILTER
|
||||
#define NJ_CHROMA_FILTER 1
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// EXAMPLE PROGRAM //
|
||||
// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef _NJ_EXAMPLE_PROGRAM
|
||||
|
||||
#include <stdio.h>
|
||||
#include "libc/stdlib.h"
|
||||
#include "libc/string.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int size;
|
||||
char *buf;
|
||||
FILE *f;
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <input.jpg> [<output.ppm>]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
f = fopen(argv[1], "rb");
|
||||
if (!f) {
|
||||
printf("Error opening the input file.\n");
|
||||
return 1;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = (int) ftell(f); // 字节
|
||||
buf = malloc(size);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
size = (int) fread(buf, 1, size, f); // 读取整个文件内容到buf
|
||||
fclose(f);
|
||||
|
||||
njInit(); // 初始化nj_context_t
|
||||
if (njDecode(buf, size)) {
|
||||
printf("Error decoding the input file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb");
|
||||
if (!f) {
|
||||
printf("Error opening the output file.\n");
|
||||
return 1;
|
||||
}
|
||||
fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight());
|
||||
fwrite(njGetImage(), 1, njGetImageSize(), f);
|
||||
fclose(f);
|
||||
njDone();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// 解释什么是stride http://msdn.microsoft.com/en-us/library/windows/desktop/aa473780(v=vs.85).aspx
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// IMPLEMENTATION SECTION //
|
||||
// you may stop reading here //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _NJ_INCLUDE_HEADER_ONLY
|
||||
|
||||
#include <stddef.h> // For NULL in freestanding mode
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define NJ_INLINE static __inline
|
||||
#define NJ_FORCE_INLINE static __forceinline
|
||||
#else
|
||||
#define NJ_INLINE static inline
|
||||
#define NJ_FORCE_INLINE static inline
|
||||
#endif
|
||||
|
||||
#if NJ_USE_LIBC
|
||||
#include "libc/stdlib.h"
|
||||
#define njAllocMem malloc
|
||||
#define njFreeMem free
|
||||
#define njFillMem memset
|
||||
#define njCopyMem memcpy
|
||||
#elif NJ_USE_WIN32
|
||||
#include <windows.h>
|
||||
#define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size)))
|
||||
#define njFreeMem(block) ((void) LocalFree((HLOCAL) block))
|
||||
NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm {
|
||||
mov edi, block
|
||||
mov al, value
|
||||
mov ecx, count
|
||||
rep stosb
|
||||
} }
|
||||
NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm {
|
||||
mov edi, dest
|
||||
mov esi, src
|
||||
mov ecx, count
|
||||
rep movsb
|
||||
} }
|
||||
#else
|
||||
extern void* njAllocMem(int size);
|
||||
extern void njFreeMem(void* block);
|
||||
extern void njFillMem(void* block, unsigned char byte, int size);
|
||||
extern void njCopyMem(void* dest, const void* src, int size);
|
||||
#endif
|
||||
|
||||
typedef struct _nj_code {
|
||||
unsigned char bits, code;
|
||||
} nj_vlc_code_t;
|
||||
|
||||
typedef struct _nj_cmp {
|
||||
int cid;
|
||||
int ssx, ssy; // 水平/垂直因子
|
||||
int width, height;
|
||||
int stride;
|
||||
int qtsel; // Quantization Table量化表
|
||||
int actabsel, dctabsel; // AC/DC Huffman Table
|
||||
int dcpred; // DC prediction
|
||||
unsigned char *pixels;
|
||||
} nj_component_t; // 颜色分量
|
||||
|
||||
typedef struct _nj_ctx {
|
||||
nj_result_t error;
|
||||
const unsigned char *pos; // 待解码数据指针(按字节来)
|
||||
int size; // 整个数据的长度
|
||||
int length; // 某一个marker内容的长度
|
||||
int width, height; // 图片宽和高度
|
||||
int mbwidth, mbheight; // MCU水平/垂直个数
|
||||
int mbsizex, mbsizey; // MCU宽/高
|
||||
int ncomp; // 颜色分量数
|
||||
nj_component_t comp[3]; // YCbCr
|
||||
int qtused, qtavail; // 这两个目前看不出来很大用处
|
||||
unsigned char qtab[4][64]; // 但是目前似乎只有2个
|
||||
nj_vlc_code_t vlctab[4][65536]; // 构造所有16位数的Huffman基数
|
||||
// 目前基本上是4个(直/交/0/1)
|
||||
int buf, bufbits; // 这是用来做什么的 buf是存放内容的 bufbits是计数器,存放了多少个bits
|
||||
int block[64];
|
||||
int rstinterval;
|
||||
unsigned char *rgb; // 解析出来的RGB所要占用的内存 // 每1个点包含3个字节,按找RGB的顺序
|
||||
} nj_context_t;
|
||||
|
||||
static nj_context_t nj;
|
||||
|
||||
static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18,
|
||||
11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35,
|
||||
42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
|
||||
38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 };
|
||||
|
||||
/*
|
||||
0 1 2 3 4 5 6 7
|
||||
|
||||
8 9 10 11 12 13 14 15
|
||||
|
||||
16 17 18 19 20 21 22 23
|
||||
|
||||
24 25 26 27 28 29 30 31
|
||||
|
||||
32 33 34 35 36 37 38 39
|
||||
|
||||
40 41 42 43 44 45 46 47
|
||||
|
||||
48 49 50 51 52 53 54 55
|
||||
|
||||
56 57 58 59 60 61 62 63
|
||||
*/
|
||||
|
||||
NJ_FORCE_INLINE unsigned char njClip(const int x) { // 限定范围是0 ~ 255之间
|
||||
return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x);
|
||||
}
|
||||
|
||||
#define W1 2841
|
||||
#define W2 2676
|
||||
#define W3 2408
|
||||
#define W5 1609
|
||||
#define W6 1108
|
||||
#define W7 565
|
||||
|
||||
NJ_INLINE void njRowIDCT(int* blk) { // 按行来操作的 0 ~ 7 // 8 ~ 15
|
||||
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
|
||||
if (!((x1 = blk[4] << 11)
|
||||
| (x2 = blk[6])
|
||||
| (x3 = blk[2])
|
||||
| (x4 = blk[1])
|
||||
| (x5 = blk[7])
|
||||
| (x6 = blk[5])
|
||||
| (x7 = blk[3])))
|
||||
{
|
||||
blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3;
|
||||
return;
|
||||
}
|
||||
x0 = (blk[0] << 11) + 128;
|
||||
x8 = W7 * (x4 + x5);
|
||||
x4 = x8 + (W1 - W7) * x4;
|
||||
x5 = x8 - (W1 + W7) * x5;
|
||||
x8 = W3 * (x6 + x7);
|
||||
x6 = x8 - (W3 - W5) * x6;
|
||||
x7 = x8 - (W3 + W5) * x7;
|
||||
x8 = x0 + x1;
|
||||
x0 -= x1;
|
||||
x1 = W6 * (x3 + x2);
|
||||
x2 = x1 - (W2 + W6) * x2;
|
||||
x3 = x1 + (W2 - W6) * x3;
|
||||
x1 = x4 + x6;
|
||||
x4 -= x6;
|
||||
x6 = x5 + x7;
|
||||
x5 -= x7;
|
||||
x7 = x8 + x3;
|
||||
x8 -= x3;
|
||||
x3 = x0 + x2;
|
||||
x0 -= x2;
|
||||
x2 = (181 * (x4 + x5) + 128) >> 8;
|
||||
x4 = (181 * (x4 - x5) + 128) >> 8;
|
||||
blk[0] = (x7 + x1) >> 8;
|
||||
blk[1] = (x3 + x2) >> 8;
|
||||
blk[2] = (x0 + x4) >> 8;
|
||||
blk[3] = (x8 + x6) >> 8;
|
||||
blk[4] = (x8 - x6) >> 8;
|
||||
blk[5] = (x0 - x4) >> 8;
|
||||
blk[6] = (x3 - x2) >> 8;
|
||||
blk[7] = (x7 - x1) >> 8;
|
||||
}
|
||||
|
||||
NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) {
|
||||
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
|
||||
if (!((x1 = blk[8*4] << 8)
|
||||
| (x2 = blk[8*6])
|
||||
| (x3 = blk[8*2])
|
||||
| (x4 = blk[8*1])
|
||||
| (x5 = blk[8*7])
|
||||
| (x6 = blk[8*5])
|
||||
| (x7 = blk[8*3])))
|
||||
{
|
||||
x1 = njClip(((blk[0] + 32) >> 6) + 128);
|
||||
for (x0 = 8; x0; --x0) {
|
||||
*out = (unsigned char) x1;
|
||||
out += stride;
|
||||
}
|
||||
return;
|
||||
}
|
||||
x0 = (blk[0] << 8) + 8192;
|
||||
x8 = W7 * (x4 + x5) + 4;
|
||||
x4 = (x8 + (W1 - W7) * x4) >> 3;
|
||||
x5 = (x8 - (W1 + W7) * x5) >> 3;
|
||||
x8 = W3 * (x6 + x7) + 4;
|
||||
x6 = (x8 - (W3 - W5) * x6) >> 3;
|
||||
x7 = (x8 - (W3 + W5) * x7) >> 3;
|
||||
x8 = x0 + x1;
|
||||
x0 -= x1;
|
||||
x1 = W6 * (x3 + x2) + 4;
|
||||
x2 = (x1 - (W2 + W6) * x2) >> 3;
|
||||
x3 = (x1 + (W2 - W6) * x3) >> 3;
|
||||
x1 = x4 + x6;
|
||||
x4 -= x6;
|
||||
x6 = x5 + x7;
|
||||
x5 -= x7;
|
||||
x7 = x8 + x3;
|
||||
x8 -= x3;
|
||||
x3 = x0 + x2;
|
||||
x0 -= x2;
|
||||
x2 = (181 * (x4 + x5) + 128) >> 8; // Y,Cb和Cr的值都范围都是-128 ~ 127,并且在FDCT的时候有先减去128,所以现在要IDCT之后再加上128
|
||||
x4 = (181 * (x4 - x5) + 128) >> 8;
|
||||
*out = njClip(((x7 + x1) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x3 + x2) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x0 + x4) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x8 + x6) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x8 - x6) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x0 - x4) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x3 - x2) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x7 - x1) >> 14) + 128);
|
||||
}
|
||||
|
||||
#define njThrow(e) do { nj.error = e; return; } while (0)
|
||||
#define njCheckError() do { if (nj.error) return; } while (0)
|
||||
|
||||
static int njShowBits(int bits) { // 能放得下大于32位的值么?
|
||||
unsigned char newbyte;
|
||||
if (!bits) return 0;
|
||||
while (nj.bufbits < bits) { // 也就是说要buf的位数小于已经buf的位数的时候,就直接读出来?
|
||||
if (nj.size <= 0) {
|
||||
nj.buf = (nj.buf << 8) | 0xFF;
|
||||
nj.bufbits += 8;
|
||||
continue;
|
||||
}
|
||||
newbyte = *nj.pos++; // 数据指针是按字节
|
||||
nj.size--;
|
||||
nj.bufbits += 8;
|
||||
nj.buf = (nj.buf << 8) | newbyte; // 高位最终会被覆盖掉,比如我要buf一个64位的值怎么办?
|
||||
if (newbyte == 0xFF) {
|
||||
if (nj.size) {
|
||||
unsigned char marker = *nj.pos++;
|
||||
nj.size--;
|
||||
switch (marker) {
|
||||
case 0x00:
|
||||
case 0xFF:
|
||||
break;
|
||||
case 0xD9: nj.size = 0; break;
|
||||
default:
|
||||
if ((marker & 0xF8) != 0xD0)
|
||||
nj.error = NJ_SYNTAX_ERROR;
|
||||
else {
|
||||
nj.buf = (nj.buf << 8) | marker;
|
||||
nj.bufbits += 8;
|
||||
}
|
||||
}
|
||||
} else
|
||||
nj.error = NJ_SYNTAX_ERROR;
|
||||
}
|
||||
}
|
||||
return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1);
|
||||
}
|
||||
|
||||
NJ_INLINE void njSkipBits(int bits) {
|
||||
if (nj.bufbits < bits)
|
||||
(void) njShowBits(bits);
|
||||
nj.bufbits -= bits;
|
||||
}
|
||||
|
||||
NJ_INLINE int njGetBits(int bits) {
|
||||
int res = njShowBits(bits);
|
||||
njSkipBits(bits);
|
||||
return res;
|
||||
}
|
||||
|
||||
NJ_INLINE void njByteAlign(void) {
|
||||
nj.bufbits &= 0xF8; // (1111 1000)8的倍数,不满8的部分丢弃
|
||||
}
|
||||
|
||||
static void njSkip(int count) {
|
||||
nj.pos += count; // 数据指针增加
|
||||
nj.size -= count; // 总体数据大小减去count
|
||||
nj.length -= count; // 当前marker长度减去count
|
||||
if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
NJ_INLINE unsigned short njDecode16(const unsigned char *pos) {
|
||||
return (pos[0] << 8) | pos[1]; // 00000000 00001101
|
||||
}
|
||||
|
||||
static void njDecodeLength(void) { // decode长度字段,这个方法调用一般都是已经进入到特定的marker之后
|
||||
if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.length = njDecode16(nj.pos); // 该marker的长度(除去marker名字所占用的2个字节)
|
||||
if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR);
|
||||
njSkip(2);
|
||||
}
|
||||
|
||||
NJ_INLINE void njSkipMarker(void) {
|
||||
njDecodeLength();
|
||||
njSkip(nj.length);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeSOF(void) { // 解析Start of Frame的时候就会把所需要的内存都分配好
|
||||
int i, ssxmax = 0, ssymax = 0;
|
||||
nj_component_t* c;
|
||||
njDecodeLength(); // 解析长度并移动数据指针
|
||||
if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); // 样本精度,一般都是8
|
||||
nj.height = njDecode16(nj.pos + 1); // 图片高度/宽度
|
||||
nj.width = njDecode16(nj.pos + 3);
|
||||
nj.ncomp = nj.pos[5]; // 颜色分量数据,一般都是3
|
||||
njSkip(6); // 之前共6个字节数据,所以移动数据指针6个字节
|
||||
switch (nj.ncomp) { // 目前只支持1和3这两种
|
||||
case 1:
|
||||
case 3:
|
||||
break;
|
||||
default:
|
||||
njThrow(NJ_UNSUPPORTED);
|
||||
}
|
||||
if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); // 数据量肯定是要大于颜色分量数 multiply 3,因为接着存颜色分量信息的每个结构占3个字节
|
||||
// 颜色分量ID占用1个字节,水平/垂直因子占用1个字节(高4位水平,低4位垂直),量化表占用1个字节
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
c->cid = nj.pos[0]; // 颜色分量ID
|
||||
if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); // 高4位(水平因子)
|
||||
if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
|
||||
if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); // (00001111)低4位(垂直因子)
|
||||
if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
|
||||
if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (11111101) 这里0xFC是用在这里干什么的?
|
||||
njSkip(3); // 移动数据指针到下一个颜色分量
|
||||
nj.qtused |= 1 << c->qtsel; // 这里是做什么用的?看不出来
|
||||
if (c->ssx > ssxmax) ssxmax = c->ssx; // 记录最大水平因子
|
||||
if (c->ssy > ssymax) ssymax = c->ssy; // 记录最大垂直因子
|
||||
}
|
||||
if (nj.ncomp == 1) { // 只有一种颜色分量的时候就简单啦
|
||||
c = nj.comp;
|
||||
c->ssx = c->ssy = ssxmax = ssymax = 1;
|
||||
}
|
||||
nj.mbsizex = ssxmax << 3; // MCU宽 是 水平采样因子最大值 multiply 8
|
||||
nj.mbsizey = ssymax << 3; // MCU高 是 垂直采样因子最大值 multiply 8
|
||||
nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; // 分子采用+ nj.mbsizex - 1就取到大于但是最接近(等于)宽度的值,
|
||||
// 并且这个值是MCU宽度整数倍 // 这里是水平方向MCU的个数
|
||||
nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; // 这里是垂直方向MCU的个数
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; // 采样宽度? 最大水平/垂直因子的值就是图片原来的值,否则就会根据因子做相应的减少
|
||||
c->stride = (c->width + 7) & 0x7FFFFFF8; // (0111 1111 1111 1111 1111 1111 1111 1000) 做什么?以1234567结尾的都省略掉?
|
||||
// 变成8的整数
|
||||
// 补齐8位,注意前面有加7,所以总是不会比原来的少,比如原来是227,那么这里就会变成232
|
||||
// 这是按照数据单元计算的,所以不对
|
||||
|
||||
c->height = (nj.height * c->ssy + ssymax - 1) / ssymax;
|
||||
c->stride = nj.mbwidth * nj.mbsizex * c->ssx / ssxmax; // 再计算一遍stride有什么用?前面计算的是错误的,没有考虑MCU宽度
|
||||
// 这里都已经是round过的了,所以直接计算
|
||||
|
||||
if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED);
|
||||
if (!(c->pixels = njAllocMem(c->stride * (nj.mbheight * nj.mbsizey * c->ssy / ssymax)))) njThrow(NJ_OUT_OF_MEM); // 为分量分配内存
|
||||
// 大小是所有MCU的
|
||||
// 可能比图片实际
|
||||
// 尺寸大
|
||||
}
|
||||
if (nj.ncomp == 3) { // 只有有3个颜色分量的时候才需要
|
||||
nj.rgb = njAllocMem(nj.width * nj.height * nj.ncomp);
|
||||
if (!nj.rgb) njThrow(NJ_OUT_OF_MEM);
|
||||
}
|
||||
njSkip(nj.length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
NJ_INLINE void njDecodeDHT(void) {
|
||||
int codelen, currcnt, remain, spread, i, j;
|
||||
nj_vlc_code_t *vlc;
|
||||
static unsigned char counts[16]; // 码字
|
||||
njDecodeLength();
|
||||
while (nj.length >= 17) { // 码字的数量(16) + 类型和ID(1)
|
||||
i = nj.pos[0]; // 类型和ID
|
||||
if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); // (11101100)
|
||||
if (i & 0x02) njThrow(NJ_UNSUPPORTED); // (00000010)
|
||||
i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value
|
||||
// 直流0,直流1,交流0,交流1
|
||||
for (codelen = 1; codelen <= 16; ++codelen) // 码字长度
|
||||
counts[codelen - 1] = nj.pos[codelen]; // 读取码字 DHT 当中的16个字节 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00
|
||||
njSkip(17);
|
||||
|
||||
vlc = &nj.vlctab[i][0];
|
||||
remain = spread = 65536;
|
||||
for (codelen = 1; codelen <= 16; ++codelen) {
|
||||
spread >>= 1; // 干什么? // 65536 >> 16 = 1 每个category所包含的编码个数
|
||||
currcnt = counts[codelen - 1];
|
||||
if (!currcnt) continue; // 如果该位数没有码字
|
||||
if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR);
|
||||
remain -= currcnt << (16 - codelen); // 干什么? 计算当前size的码字占用多少VLC表的空间,得到剩下的空间
|
||||
if (remain < 0) njThrow(NJ_SYNTAX_ERROR);
|
||||
for (i = 0; i < currcnt; ++i) { // 码字个数,同样位数的码字可以有多个
|
||||
register unsigned char code = nj.pos[i]; // 有多少个就,读多少个字节
|
||||
for (j = spread; j; --j) { // 保存这么多个有什么作用?
|
||||
vlc->bits = (unsigned char) codelen; // 码字位数
|
||||
vlc->code = code; // 码字值(这个读取出来的到底是什么?00 01 02 03 04 05 06 07 08 09 0A 0B,是值,还是权重?)
|
||||
++vlc;
|
||||
}
|
||||
}
|
||||
njSkip(currcnt);
|
||||
}
|
||||
while (remain--) { // 16位都填充完成,剩下的就用0填(1位码字XX个,2位码字XX个,...)
|
||||
// printf("i'm nothing vlc id %d\n", tblid);
|
||||
vlc->bits = 0;
|
||||
++vlc;
|
||||
}
|
||||
|
||||
// for debug
|
||||
// printf("Huffman vlc id %d\n", tblid);
|
||||
// njPrintHT(tblid);
|
||||
}
|
||||
|
||||
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeDQT(void) {
|
||||
int i;
|
||||
unsigned char *t;
|
||||
njDecodeLength();
|
||||
while (nj.length >= 65) {
|
||||
i = nj.pos[0]; // QT信息,高4位为QT精度,低4位为QT号
|
||||
if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (1111 1110)这个用来检测QT号码是否正确的吗?目前精度好像都为0,所以这么写?
|
||||
nj.qtavail |= 1 << i; // XXX 直接通过这里转换为数量?
|
||||
t = &nj.qtab[i][0];
|
||||
for (i = 0; i < 64; ++i)
|
||||
t[i] = nj.pos[i + 1]; // 读取到QT数组当中,但应该还是按照文件流当中的排列
|
||||
njSkip(65);
|
||||
}
|
||||
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeDRI(void) {
|
||||
njDecodeLength();
|
||||
if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.rstinterval = njDecode16(nj.pos);
|
||||
njSkip(nj.length);
|
||||
}
|
||||
|
||||
static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { // Variable Length Coding
|
||||
int value = njShowBits(16); // 为什么是2个字节? 这又是什么? 或许是这里的Huffman编码的码字永远是少于16位的
|
||||
int bits = vlc[value].bits;
|
||||
|
||||
if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; }
|
||||
njSkipBits(bits);
|
||||
value = vlc[value].code;
|
||||
|
||||
if (code) *code = (unsigned char) value;
|
||||
bits = value & 15; // 这个value必须是0~15之间?
|
||||
if (!bits) {
|
||||
return 0;
|
||||
}
|
||||
value = njGetBits(bits); // 如果这里需要读取的值的位数超过之前njShowBits剩余的值,这里会重新读取
|
||||
if (value < (1 << (bits - 1)))
|
||||
value += ((-1) << bits) + 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { // 8 x 8
|
||||
unsigned char code = 0;
|
||||
int value, coef = 0;
|
||||
njFillMem(nj.block, 0, sizeof(nj.block));
|
||||
int dcvlcval = njGetVLC(&nj.vlctab[c->dctabsel][0], NULL);
|
||||
c->dcpred += dcvlcval;
|
||||
nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; // DC // 这里是反量化?
|
||||
do {
|
||||
value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); // DC 2/3
|
||||
if (!code) break; // EOB
|
||||
if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); // 这是什么字段?(难道是为了兼容这个过程中可以遇到0xF0这样的数据)
|
||||
coef += (code >> 4) + 1; // coefficient 系数
|
||||
if (coef > 63) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; // AC 这里是反量化?
|
||||
} while (coef < 63);
|
||||
for (coef = 0; coef < 64; coef += 8)
|
||||
njRowIDCT(&nj.block[coef]); // 上面先Huffman解码/反量化,这里行(反DCT)
|
||||
for (coef = 0; coef < 8; ++coef)
|
||||
njColIDCT(&nj.block[coef], &out[coef], c->stride);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeScan(void) {
|
||||
// njPrintHT(0);
|
||||
// njPrintHT(2);
|
||||
// njPrintHT(1);
|
||||
// njPrintHT(3);
|
||||
|
||||
int i, mbx, mby, sbx, sby;
|
||||
int rstcount = nj.rstinterval, nextrst = 0;
|
||||
nj_component_t* c;
|
||||
njDecodeLength();
|
||||
if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED);
|
||||
njSkip(1); // 颜色分量数量
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); // 颜色分量ID
|
||||
if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR);
|
||||
c->dctabsel = nj.pos[1] >> 4; // 高4位为直流表DC Table
|
||||
c->actabsel = (nj.pos[1] & 1) | 2; // 低4位为交流表AC Table(这里有做特殊处理,所以AC的表名不会和DC相同)
|
||||
|
||||
|
||||
|
||||
njSkip(2);
|
||||
}
|
||||
if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED);
|
||||
njSkip(nj.length); // 忽略3个字节 通常为 00 3F 00
|
||||
// 2 + 1 + 6 + 3为12字节,这个marker的长度刚好为12字节
|
||||
// 接下来都是编码过的图像数据
|
||||
for (mbx = mby = 0;;) {
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) // 每个分量都要decode
|
||||
for (sby = 0; sby < c->ssy; ++sby) // 水平/垂直因子
|
||||
for (sbx = 0; sbx < c->ssx; ++sbx) {
|
||||
njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]); // 读取原始编码过
|
||||
// 的图片数据到block中
|
||||
// 并反量化,反离散余弦变换
|
||||
njCheckError();
|
||||
}
|
||||
if (++mbx >= nj.mbwidth) { // 读完所有的MCU,到达最右就返回从下一行开始
|
||||
mbx = 0;
|
||||
if (++mby >= nj.mbheight) break; // 到达最底行的时候推出,decode结束
|
||||
}
|
||||
if (nj.rstinterval && !(--rstcount)) { // restart marker
|
||||
njByteAlign();
|
||||
i = njGetBits(16);
|
||||
if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR);
|
||||
nextrst = (nextrst + 1) & 7;
|
||||
rstcount = nj.rstinterval;
|
||||
for (i = 0; i < 3; ++i)
|
||||
nj.comp[i].dcpred = 0;
|
||||
}
|
||||
}
|
||||
nj.error = __NJ_FINISHED;
|
||||
}
|
||||
|
||||
#if NJ_CHROMA_FILTER
|
||||
|
||||
#define CF4A (-9)
|
||||
#define CF4B (111)
|
||||
#define CF4C (29)
|
||||
#define CF4D (-3)
|
||||
#define CF3A (28)
|
||||
#define CF3B (109)
|
||||
#define CF3C (-9)
|
||||
#define CF3X (104)
|
||||
#define CF3Y (27)
|
||||
#define CF3Z (-3)
|
||||
#define CF2A (139)
|
||||
#define CF2B (-11)
|
||||
#define CF(x) njClip(((x) + 64) >> 7)
|
||||
|
||||
// 通常我们放大图片的时候就需要upsampling,缩小的时候就downsampling,通称为resampling
|
||||
// 这里Cb/Cr分量的会少些,所以需要upsampling
|
||||
|
||||
NJ_INLINE void njUpsampleH(nj_component_t* c) {
|
||||
|
||||
const int xmax = c->width - 3;
|
||||
unsigned char *out, *lin, *lout;
|
||||
int x, y;
|
||||
out = njAllocMem((c->width * c->height) << 1);
|
||||
if (!out) njThrow(NJ_OUT_OF_MEM);
|
||||
lin = c->pixels;
|
||||
lout = out;
|
||||
for (y = c->height; y; --y) {
|
||||
lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]);
|
||||
lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]);
|
||||
lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]);
|
||||
for (x = 0; x < xmax; ++x) {
|
||||
lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]);
|
||||
lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]);
|
||||
}
|
||||
lin += c->stride;
|
||||
lout += c->width << 1;
|
||||
lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]);
|
||||
lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]);
|
||||
lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]);
|
||||
}
|
||||
c->width <<= 1;
|
||||
c->stride = c->width;
|
||||
njFreeMem(c->pixels);
|
||||
c->pixels = out;
|
||||
}
|
||||
|
||||
NJ_INLINE void njUpsampleV(nj_component_t* c) {
|
||||
|
||||
const int w = c->width, s1 = c->stride, s2 = s1 + s1;
|
||||
unsigned char *out, *cin, *cout;
|
||||
int x, y;
|
||||
out = njAllocMem((c->width * c->height) << 1);
|
||||
if (!out) njThrow(NJ_OUT_OF_MEM);
|
||||
for (x = 0; x < w; ++x) {
|
||||
cin = &c->pixels[x];
|
||||
cout = &out[x];
|
||||
*cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w;
|
||||
*cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w;
|
||||
*cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w;
|
||||
cin += s1;
|
||||
for (y = c->height - 3; y; --y) {
|
||||
*cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w;
|
||||
*cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w;
|
||||
cin += s1;
|
||||
}
|
||||
cin += s1;
|
||||
*cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w;
|
||||
*cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w;
|
||||
*cout = CF(CF2A * cin[0] + CF2B * cin[-s1]);
|
||||
}
|
||||
c->height <<= 1;
|
||||
c->stride = c->width;
|
||||
njFreeMem(c->pixels);
|
||||
c->pixels = out;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
NJ_INLINE void njUpsample(nj_component_t* c) {
|
||||
|
||||
int x, y, xshift = 0, yshift = 0;
|
||||
unsigned char *out, *lin, *lout;
|
||||
while (c->width < nj.width) { c->width <<= 1; ++xshift; }
|
||||
while (c->height < nj.height) { c->height <<= 1; ++yshift; }
|
||||
out = njAllocMem(c->width * c->height); // 放大后的尺寸
|
||||
if (!out) njThrow(NJ_OUT_OF_MEM);
|
||||
lin = c->pixels;
|
||||
lout = out;
|
||||
for (y = 0; y < c->height; ++y) {
|
||||
lin = &c->pixels[(y >> yshift) * c->stride];
|
||||
for (x = 0; x < c->width; ++x)
|
||||
lout[x] = lin[x >> xshift];
|
||||
lout += c->width;
|
||||
}
|
||||
c->stride = c->width;
|
||||
njFreeMem(c->pixels);
|
||||
c->pixels = out;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
NJ_INLINE void njConvert() {
|
||||
int i;
|
||||
nj_component_t* c;
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { // 如果需要的话就upsampling
|
||||
#if NJ_CHROMA_FILTER
|
||||
while ((c->width < nj.width) || (c->height < nj.height)) {
|
||||
if (c->width < nj.width) njUpsampleH(c);
|
||||
njCheckError();
|
||||
if (c->height < nj.height) njUpsampleV(c);
|
||||
njCheckError();
|
||||
}
|
||||
#else
|
||||
if ((c->width < nj.width) || (c->height < nj.height))
|
||||
njUpsample(c);
|
||||
#endif
|
||||
if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR);
|
||||
}
|
||||
if (nj.ncomp == 3) { // SEE njGetImage()
|
||||
// convert to RGB
|
||||
int x, yy;
|
||||
unsigned char *prgb = nj.rgb;
|
||||
const unsigned char *py = nj.comp[0].pixels;
|
||||
const unsigned char *pcb = nj.comp[1].pixels;
|
||||
const unsigned char *pcr = nj.comp[2].pixels;
|
||||
// 多余的数据(编/解码是对齐用的)会被丢弃吗?
|
||||
for (yy = nj.height; yy; --yy) { // 列
|
||||
for (x = 0; x < nj.width; ++x) { // 行
|
||||
register int y = py[x] << 8; // 这是为什么? 色彩空间转换公式计算需要
|
||||
register int cb = pcb[x] - 128; // YCbCr的Cb和Cr一般都是有符号数,但是在JPEG当中都是无符号数
|
||||
register int cr = pcr[x] - 128;
|
||||
*prgb++ = njClip((y + 359 * cr + 128) >> 8); // 色彩空间转换,YCbCr到RGB
|
||||
*prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8);
|
||||
*prgb++ = njClip((y + 454 * cb + 128) >> 8);
|
||||
}
|
||||
py += nj.comp[0].stride; // 移动YCbCr数据指针,每一行都是有stride的,所以当需要的数据都得到时,后面的就不管,直接丢弃,移动到下一行
|
||||
pcb += nj.comp[1].stride;
|
||||
pcr += nj.comp[2].stride;
|
||||
}
|
||||
} else if (nj.comp[0].width != nj.comp[0].stride) { // 如果宽度和stride都一样,什么都不用做
|
||||
// grayscale -> only remove stride
|
||||
unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride];
|
||||
unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width];
|
||||
int y;
|
||||
for (y = nj.comp[0].height - 1; y; --y) {
|
||||
njCopyMem(pout, pin, nj.comp[0].width);
|
||||
pin += nj.comp[0].stride;
|
||||
pout += nj.comp[0].width;
|
||||
}
|
||||
nj.comp[0].stride = nj.comp[0].width;
|
||||
}
|
||||
}
|
||||
|
||||
void njInit(void) {
|
||||
njFillMem(&nj, 0, sizeof(nj_context_t)); // 初始化nj_context_t
|
||||
}
|
||||
|
||||
void njDone(void) {
|
||||
int i;
|
||||
for (i = 0; i < 3; ++i)
|
||||
if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels);
|
||||
if (nj.rgb) njFreeMem((void*) nj.rgb);
|
||||
njInit();
|
||||
}
|
||||
|
||||
nj_result_t njDecode(const void* jpeg, const int size) {
|
||||
njDone();
|
||||
nj.pos = (const unsigned char*) jpeg;
|
||||
nj.size = size & 0x7FFFFFFF; // ?
|
||||
if (nj.size < 2) return NJ_NO_JPEG;
|
||||
if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; // 不以0xFFD8打头(为什么要用异或来判断?)
|
||||
njSkip(2);
|
||||
while (!nj.error) { // 有“错误”的时候离开
|
||||
if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; // 太小,或者不以0xFF打头
|
||||
njSkip(2); // 移动到标签的后面(长度字段的前面)
|
||||
switch (nj.pos[-1]) {
|
||||
case 0xC0: njDecodeSOF(); break;
|
||||
case 0xC4: njDecodeDHT(); break;
|
||||
case 0xDB: njDecodeDQT(); break;
|
||||
case 0xDD: njDecodeDRI(); break;
|
||||
case 0xDA: njDecodeScan(); break;
|
||||
case 0xFE: njSkipMarker(); break;
|
||||
default:
|
||||
if ((nj.pos[-1] & 0xF0) == 0xE0) // JPG0和APP0字段,目前都忽略
|
||||
njSkipMarker();
|
||||
else
|
||||
return NJ_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
if (nj.error != __NJ_FINISHED) return nj.error;
|
||||
nj.error = NJ_OK;
|
||||
njConvert();
|
||||
return nj.error;
|
||||
}
|
||||
|
||||
int njGetWidth(void) { return nj.width; }
|
||||
int njGetHeight(void) { return nj.height; }
|
||||
int njIsColor(void) { return (nj.ncomp != 1); }
|
||||
unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } // 一/三个分量
|
||||
int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; }
|
||||
|
||||
#endif // _NJ_INCLUDE_HEADER_ONLY
|
||||
10
src/kernel/userland/nanojpeg.h
Normal file
10
src/kernel/userland/nanojpeg.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// nanojpeg.h - Header for NanoJPEG decoder (freestanding kernel use)
|
||||
#ifndef NANOJPEG_H
|
||||
#define NANOJPEG_H
|
||||
|
||||
// Include naojpeg.c in header-only mode to get the type/function declarations
|
||||
#define _NJ_INCLUDE_HEADER_ONLY
|
||||
#include "nanojpeg.c"
|
||||
#undef _NJ_INCLUDE_HEADER_ONLY
|
||||
|
||||
#endif // NANOJPEG_H
|
||||
BIN
src/kernel/userland/nanojpeg.o
Normal file
BIN
src/kernel/userland/nanojpeg.o
Normal file
Binary file not shown.
Binary file not shown.
251
src/kernel/userland/paint.c
Normal file
251
src/kernel/userland/paint.c
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include "libc/stdlib.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define CANVAS_W 300
|
||||
#define CANVAS_H 200
|
||||
#define PAINT_MAGIC 0x544E5042 // 'BPNT'
|
||||
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
#define COLOR_RED 0xFFFF0000
|
||||
#define COLOR_APPLE_GREEN 0xFF4CD964
|
||||
#define COLOR_APPLE_BLUE 0xFF007AFF
|
||||
#define COLOR_APPLE_YELLOW 0xFFFFCC00
|
||||
|
||||
#define COLOR_DARK_BG 0xFF121212
|
||||
#define COLOR_DARK_PANEL 0xFF202020
|
||||
#define COLOR_DARK_BORDER 0xFF404040
|
||||
#define COLOR_DARK_TEXT 0xFFE0E0E0
|
||||
|
||||
static uint32_t *canvas_buffer = NULL;
|
||||
static uint32_t current_color = COLOR_BLACK;
|
||||
static int last_mx = -1;
|
||||
static int last_my = -1;
|
||||
static char current_file_path[256] = "/Desktop/drawing.pnt";
|
||||
|
||||
static void paint_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static void debug_print(const char *msg) {
|
||||
sys_write(1, msg, 0);
|
||||
int i = 0;
|
||||
while (msg[i]) i++;
|
||||
sys_write(1, msg, i);
|
||||
sys_write(1, "\n", 1);
|
||||
}
|
||||
|
||||
static void paint_reset(void) {
|
||||
if (canvas_buffer) {
|
||||
for (int i = 0; i < CANVAS_W * CANVAS_H; i++) {
|
||||
canvas_buffer[i] = COLOR_WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void paint_paint(ui_window_t win) {
|
||||
int canvas_x = 60;
|
||||
int canvas_y = 30;
|
||||
|
||||
// Canvas Area with dark background and rounded corners (draw first so it's behind everything)
|
||||
ui_draw_rounded_rect_filled(win, canvas_x - 2, canvas_y - 2, CANVAS_W + 4, CANVAS_H + 4, 4, COLOR_DARK_BG);
|
||||
|
||||
// Toolbar area - dark mode
|
||||
ui_draw_rounded_rect_filled(win, 10, 30, 40, 260 - 40, 6, COLOR_DARK_PANEL);
|
||||
|
||||
// Color Palette with rounded corners
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int cy = 40 + (i * 25);
|
||||
ui_draw_rounded_rect_filled(win, 15, cy, 30, 20, 3, colors[i]);
|
||||
|
||||
// Highlight selected color with border
|
||||
if (current_color == colors[i]) {
|
||||
// Note: libui might not have draw_rounded_rect (hollow), so we just draw four lines simulating it
|
||||
// or we use ui_draw_rect for hollow border
|
||||
ui_draw_rect(win, 13, cy - 2, 34, 1, COLOR_DARK_TEXT);
|
||||
ui_draw_rect(win, 13, cy - 2 + 24, 34, 1, COLOR_DARK_TEXT);
|
||||
ui_draw_rect(win, 13, cy - 2, 1, 24, COLOR_DARK_TEXT);
|
||||
ui_draw_rect(win, 13 + 34, cy - 2, 1, 24, COLOR_DARK_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
// Toolbar Buttons - dark mode with rounded corners
|
||||
ui_draw_rounded_rect_filled(win, 12, 260 - 65, 36, 20, 4, COLOR_DARK_BORDER);
|
||||
ui_draw_string(win, 18, 260 - 58, "CLR", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, 12, 260 - 40, 36, 20, 4, COLOR_DARK_BORDER);
|
||||
ui_draw_string(win, 18, 260 - 33, "SAV", COLOR_DARK_TEXT);
|
||||
|
||||
// Draw canvas content
|
||||
if (canvas_buffer) {
|
||||
for (int y = 0; y < CANVAS_H; y++) {
|
||||
for (int x = 0; x < CANVAS_W; x++) {
|
||||
uint32_t color = canvas_buffer[y * CANVAS_W + x];
|
||||
ui_draw_rect(win, canvas_x + x, canvas_y + y, 1, 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void paint_put_brush(ui_window_t win, int cx, int cy) {
|
||||
if (!canvas_buffer) return;
|
||||
for (int dy = 0; dy < 2; dy++) {
|
||||
for (int dx = 0; dx < 2; dx++) {
|
||||
int px = cx + dx;
|
||||
int py = cy + dy;
|
||||
if (px >= 0 && px < CANVAS_W && py >= 0 && py < CANVAS_H) {
|
||||
canvas_buffer[py * CANVAS_W + px] = current_color;
|
||||
ui_draw_rect(win, 60 + px, 30 + py, 1, 1, current_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
ui_mark_dirty(win, 60 + cx, 30 + cy, 2, 2);
|
||||
}
|
||||
|
||||
void paint_handle_mouse(ui_window_t win, int x, int y) {
|
||||
int cx = x - 60;
|
||||
int cy = y - 30;
|
||||
|
||||
if (cx < 0 || cx >= CANVAS_W || cy < 0 || cy >= CANVAS_H) {
|
||||
last_mx = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_mx == -1) {
|
||||
paint_put_brush(win, cx, cy);
|
||||
} else {
|
||||
// Bresenham's line algorithm to fill gaps between points
|
||||
int x0 = last_mx, y0 = last_my;
|
||||
int x1 = cx, y1 = cy;
|
||||
int dx = (x1 - x0 > 0) ? (x1 - x0) : (x0 - x1);
|
||||
int dy = (y1 - y0 > 0) ? (y1 - y0) : (y0 - y1);
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (1) {
|
||||
paint_put_brush(win, x0, y0);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
last_mx = cx;
|
||||
last_my = cy;
|
||||
}
|
||||
|
||||
void paint_reset_last_pos(void) {
|
||||
last_mx = -1;
|
||||
last_my = -1;
|
||||
}
|
||||
|
||||
// Simple window message dialog wrapper using syscall
|
||||
static void wm_show_message(const char *title, const char *msg) {
|
||||
// Wait, userland doesn't have wm_show_message syscall available yet, or maybe it does?
|
||||
// We didn't add it or GUI_EVENT doesn't support it directly.
|
||||
// For now we do nothing, or just open a small window.
|
||||
}
|
||||
|
||||
static void paint_save(const char *path) {
|
||||
int fd = sys_open(path, "w");
|
||||
if (fd >= 0) {
|
||||
uint32_t header[3] = {PAINT_MAGIC, CANVAS_W, CANVAS_H};
|
||||
sys_write_fs(fd, (char*)header, sizeof(header));
|
||||
sys_write_fs(fd, (char*)canvas_buffer, CANVAS_W * CANVAS_H * sizeof(uint32_t));
|
||||
sys_close(fd);
|
||||
wm_show_message("Paint", "Image saved.");
|
||||
}
|
||||
}
|
||||
|
||||
void paint_load(const char *path) {
|
||||
paint_strcpy(current_file_path, path);
|
||||
int fd = sys_open(path, "r");
|
||||
if (fd >= 0) {
|
||||
uint32_t header[3];
|
||||
if (sys_read(fd, (char*)header, sizeof(header)) == sizeof(header)) {
|
||||
if (header[0] == PAINT_MAGIC) {
|
||||
sys_read(fd, (char*)canvas_buffer, CANVAS_W * CANVAS_H * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
sys_close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void paint_click(ui_window_t win, int x, int y) {
|
||||
// Check Buttons
|
||||
if (x >= 12 && x < 48) {
|
||||
if (y >= 260 - 65 && y < 260 - 45) {
|
||||
paint_reset();
|
||||
paint_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 380, 260);
|
||||
return;
|
||||
}
|
||||
if (y >= 260 - 40 && y < 260 - 20) {
|
||||
paint_save(current_file_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check Palette
|
||||
if (x >= 15 && x < 45) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int cy = 40 + (i * 25);
|
||||
if (y >= cy && y < cy + 20) {
|
||||
uint32_t colors[] = {COLOR_BLACK, COLOR_RED, COLOR_APPLE_GREEN, COLOR_APPLE_BLUE, COLOR_APPLE_YELLOW, COLOR_WHITE};
|
||||
current_color = colors[i];
|
||||
paint_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 380, 260);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
paint_handle_mouse(win, x, y);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
ui_window_t win = ui_window_create("Paint", 150, 100, 380, 260);
|
||||
if (!win) return 1;
|
||||
|
||||
canvas_buffer = malloc(CANVAS_W * CANVAS_H * sizeof(uint32_t));
|
||||
if (!canvas_buffer) return 1;
|
||||
|
||||
paint_reset();
|
||||
|
||||
if (argc > 1) {
|
||||
paint_load(argv[1]);
|
||||
}
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
paint_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 380, 260);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
paint_click(win, ev.arg1, ev.arg2);
|
||||
paint_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 380, 260);
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_DOWN) {
|
||||
paint_handle_mouse(win, ev.arg1, ev.arg2);
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_UP) {
|
||||
paint_reset_last_pos();
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_MOVE) {
|
||||
if (ev.arg3 & 0x01) { // Left button down
|
||||
paint_handle_mouse(win, ev.arg1, ev.arg2);
|
||||
} else {
|
||||
paint_reset_last_pos();
|
||||
}
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
BIN
src/kernel/userland/paint.elf
Executable file
BIN
src/kernel/userland/paint.elf
Executable file
Binary file not shown.
691
src/kernel/userland/settings.c
Normal file
691
src/kernel/userland/settings.c
Normal file
|
|
@ -0,0 +1,691 @@
|
|||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#define COLOR_COFFEE 0xFF6B4423
|
||||
#define COLOR_TEAL 0xFF008080
|
||||
#define COLOR_GREEN 0xFF008000
|
||||
#define COLOR_BLUE_BG 0xFF000080
|
||||
#define COLOR_PURPLE 0xFF800080
|
||||
#define COLOR_GREY 0xFF454545
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
|
||||
#define COLOR_DARK_PANEL 0xFF2D2D2D
|
||||
#define COLOR_DARK_TEXT 0xFFE0E0E0
|
||||
#define COLOR_DARK_BORDER 0xFF404040
|
||||
#define COLOR_DKGRAY 0xFFAAAAAA
|
||||
#define COLOR_DARK_BG 0xFF1E1E1E
|
||||
|
||||
// Control panel state
|
||||
#define VIEW_MAIN 0
|
||||
#define VIEW_WALLPAPER 1
|
||||
#define VIEW_NETWORK 2
|
||||
#define VIEW_DESKTOP 3
|
||||
#define VIEW_MOUSE 4
|
||||
|
||||
static int current_view = VIEW_MAIN;
|
||||
static char rgb_r[4] = "";
|
||||
static char rgb_g[4] = "";
|
||||
static char rgb_b[4] = "";
|
||||
static int focused_field = -1;
|
||||
static int input_cursor = 0;
|
||||
|
||||
static char net_status[64] = "";
|
||||
|
||||
// Pattern buffers (128x128)
|
||||
#define PATTERN_SIZE 128
|
||||
static uint32_t pattern_lumberjack[PATTERN_SIZE * PATTERN_SIZE];
|
||||
static uint32_t pattern_blue_diamond[PATTERN_SIZE * PATTERN_SIZE];
|
||||
|
||||
#define WALLPAPER_THUMB_W 100
|
||||
#define WALLPAPER_THUMB_H 60
|
||||
static uint32_t moon_thumb[WALLPAPER_THUMB_W * WALLPAPER_THUMB_H];
|
||||
static uint32_t mtn_thumb[WALLPAPER_THUMB_W * WALLPAPER_THUMB_H];
|
||||
static _Bool moon_thumb_valid = 0;
|
||||
static _Bool mtn_thumb_valid = 0;
|
||||
|
||||
static _Bool desktop_snap_to_grid = 1;
|
||||
static _Bool desktop_auto_align = 1;
|
||||
static int desktop_max_rows_per_col = 10;
|
||||
static int desktop_max_cols = 10;
|
||||
static int mouse_speed = 10;
|
||||
|
||||
static void cli_itoa(int num, char *str) {
|
||||
if (num == 0) {
|
||||
str[0] = '0';
|
||||
str[1] = '\0';
|
||||
return;
|
||||
}
|
||||
int t = num;
|
||||
int len = 0;
|
||||
while (t > 0) { len++; t /= 10; }
|
||||
str[len] = '\0';
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
str[i] = (num % 10) + '0';
|
||||
num /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_lumberjack_pattern(void) {
|
||||
uint32_t red = 0xFFDC143C;
|
||||
uint32_t dark_grey = 0xFF404040;
|
||||
uint32_t black = 0xFF000000;
|
||||
int scale = 5;
|
||||
|
||||
for (int y = 0; y < PATTERN_SIZE; y++) {
|
||||
for (int x = 0; x < PATTERN_SIZE; x++) {
|
||||
int cell_x = (x / scale) % 3;
|
||||
int cell_y = (y / scale) % 3;
|
||||
uint32_t color;
|
||||
if (cell_x == 1 && cell_y == 1) {
|
||||
color = black;
|
||||
} else if (cell_x == 1 || cell_y == 1) {
|
||||
color = dark_grey;
|
||||
} else {
|
||||
color = red;
|
||||
}
|
||||
pattern_lumberjack[y * PATTERN_SIZE + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_blue_diamond_pattern(void) {
|
||||
uint32_t bg_color = 0xFFADD8E6;
|
||||
uint32_t diamond_color = 0xFF0000CD;
|
||||
|
||||
for (int y = 0; y < PATTERN_SIZE; y++) {
|
||||
for (int x = 0; x < PATTERN_SIZE; x++) {
|
||||
pattern_blue_diamond[y * PATTERN_SIZE + x] = bg_color;
|
||||
}
|
||||
}
|
||||
for (int dy = -24; dy <= 24; dy++) {
|
||||
for (int dx = -24; dx <= 24; dx++) {
|
||||
int abs_dx = dx < 0 ? -dx : dx;
|
||||
int abs_dy = dy < 0 ? -dy : dy;
|
||||
if (abs_dx + abs_dy <= 24) {
|
||||
int x1 = 32 + dx;
|
||||
int y1 = 32 + dy;
|
||||
if (x1 >= 0 && x1 < PATTERN_SIZE && y1 >= 0 && y1 < PATTERN_SIZE) {
|
||||
pattern_blue_diamond[y1 * PATTERN_SIZE + x1] = diamond_color;
|
||||
}
|
||||
int x2 = 96 + dx;
|
||||
int y2 = 96 + dy;
|
||||
if (x2 >= 0 && x2 < PATTERN_SIZE && y2 >= 0 && y2 < PATTERN_SIZE) {
|
||||
pattern_blue_diamond[y2 * PATTERN_SIZE + x2] = diamond_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t parse_rgb_separate(const char *r, const char *g, const char *b) {
|
||||
int rv = 0, gv = 0, bv = 0;
|
||||
for (int i = 0; r[i] && i < 3; i++) {
|
||||
if (r[i] >= '0' && r[i] <= '9') rv = rv * 10 + (r[i] - '0');
|
||||
}
|
||||
for (int i = 0; g[i] && i < 3; i++) {
|
||||
if (g[i] >= '0' && g[i] <= '9') gv = gv * 10 + (g[i] - '0');
|
||||
}
|
||||
for (int i = 0; b[i] && i < 3; i++) {
|
||||
if (b[i] >= '0' && b[i] <= '9') bv = bv * 10 + (b[i] - '0');
|
||||
}
|
||||
if (rv > 255) rv = 255;
|
||||
if (gv > 255) gv = 255;
|
||||
if (bv > 255) bv = 255;
|
||||
return 0xFF000000 | (rv << 16) | (gv << 8) | bv;
|
||||
}
|
||||
|
||||
static void control_panel_paint_main(ui_window_t win) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
int win_w = 350;
|
||||
|
||||
int item_y = 15;
|
||||
int item_h = 60;
|
||||
int item_spacing = 10;
|
||||
|
||||
// Wallpaper
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + item_y, win_w - 16, item_h, 8, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, offset_x + 12, offset_y + item_y + 8, 40, 40, 0xFF87CEEB);
|
||||
ui_draw_rect(win, offset_x + 12, offset_y + item_y + 28, 40, 20, 0xFF90EE90);
|
||||
ui_draw_rect(win, offset_x + 24, offset_y + item_y + 22, 3, 6, 0xFF654321);
|
||||
ui_draw_rect(win, offset_x + 21, offset_y + item_y + 18, 9, 8, 0xFF228B22);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 15, "Wallpaper", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 35, "Choose wallpaper", COLOR_DKGRAY);
|
||||
|
||||
// Network
|
||||
item_y += item_h + item_spacing;
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + item_y, win_w - 16, item_h, 8, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, offset_x + 18, offset_y + item_y + 12, 24, 24, 0xFF4169E1);
|
||||
ui_draw_rect(win, offset_x + 22, offset_y + item_y + 16, 16, 16, 0xFF87CEEB);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 15, "Network", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 35, "Internet and connectivity", COLOR_DKGRAY);
|
||||
|
||||
// Desktop
|
||||
item_y += item_h + item_spacing;
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + item_y, win_w - 16, item_h, 8, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, offset_x + 12, offset_y + item_y + 10, 36, 8, 0xFFE0C060);
|
||||
ui_draw_rect(win, offset_x + 12, offset_y + item_y + 18, 36, 22, 0xFFD4A574);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 15, "Desktop", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 35, "Desktop alignment", COLOR_DKGRAY);
|
||||
|
||||
// Mouse
|
||||
item_y += item_h + item_spacing;
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + item_y, win_w - 16, item_h, 8, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, offset_x + 18, offset_y + item_y + 8, 20, 28, 0xFFD3D3D3);
|
||||
ui_draw_rect(win, offset_x + 20, offset_y + item_y + 10, 16, 10, 0xFFB0B0B0);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 15, "Mouse", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 60, offset_y + item_y + 35, "Pointer settings", COLOR_DKGRAY);
|
||||
}
|
||||
|
||||
static void control_panel_paint_wallpaper(ui_window_t win) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + 5, 80, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 10, offset_y + 13, "< Back", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_string(win, offset_x, offset_y + 40, "Presets:", COLOR_DARK_TEXT);
|
||||
|
||||
int button_y = offset_y + 65;
|
||||
int button_x = offset_x;
|
||||
|
||||
// Colors
|
||||
ui_draw_rounded_rect_filled(win, button_x, button_y, 91, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, button_x + 8, button_y + 6, 18, 13, COLOR_COFFEE);
|
||||
ui_draw_string(win, button_x + 35, button_y + 8, "Coffee", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x + 100, button_y, 91, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, button_x + 108, button_y + 6, 18, 13, COLOR_TEAL);
|
||||
ui_draw_string(win, button_x + 135, button_y + 8, "Teal", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x + 200, button_y, 91, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, button_x + 208, button_y + 6, 18, 13, COLOR_GREEN);
|
||||
ui_draw_string(win, button_x + 235, button_y + 8, "Green", COLOR_DARK_TEXT);
|
||||
|
||||
button_y += 35;
|
||||
ui_draw_rounded_rect_filled(win, button_x, button_y, 91, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, button_x + 8, button_y + 6, 18, 13, COLOR_BLUE_BG);
|
||||
ui_draw_string(win, button_x + 35, button_y + 8, "Blue", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x + 100, button_y, 91, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, button_x + 108, button_y + 6, 18, 13, COLOR_PURPLE);
|
||||
ui_draw_string(win, button_x + 132, button_y + 8, "Purple", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x + 200, button_y, 91, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_rect(win, button_x + 208, button_y + 6, 18, 13, COLOR_GREY);
|
||||
ui_draw_string(win, button_x + 235, button_y + 8, "Grey", COLOR_DARK_TEXT);
|
||||
|
||||
// Patterns
|
||||
button_y += 40;
|
||||
ui_draw_string(win, offset_x, button_y, "Patterns:", COLOR_DARK_TEXT);
|
||||
|
||||
button_y += 20;
|
||||
ui_draw_rounded_rect_filled(win, button_x, button_y, 132, 25, 6, COLOR_DARK_PANEL);
|
||||
for (int py = 0; py < 10; py++) {
|
||||
for (int px = 0; px < 12; px++) {
|
||||
int cell_x = px % 3;
|
||||
int cell_y = py % 3;
|
||||
uint32_t color = (cell_x == 1 && cell_y == 1) ? 0xFF000000 :
|
||||
(cell_x == 1 || cell_y == 1) ? 0xFF404040 : 0xFFDC143C;
|
||||
ui_draw_rect(win, button_x + 8 + px, button_y + 7 + py, 1, 1, color);
|
||||
}
|
||||
}
|
||||
ui_draw_string(win, button_x + 28, button_y + 8, "Lumberjack", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x + 145, button_y, 132, 25, 6, COLOR_DARK_PANEL);
|
||||
for (int py = 0; py < 8; py++) {
|
||||
for (int px = 0; px < 10; px++) {
|
||||
int cx = px - 5;
|
||||
int cy = py - 4;
|
||||
int abs_cx = cx < 0 ? -cx : cx;
|
||||
int abs_cy = cy < 0 ? -cy : cy;
|
||||
uint32_t color = (abs_cx + abs_cy <= 3) ? 0xFF0000CD : 0xFFADD8E6;
|
||||
ui_draw_rect(win, button_x + 153 + px, button_y + 8 + py, 1, 1, color);
|
||||
}
|
||||
}
|
||||
ui_draw_string(win, button_x + 165, button_y + 8, "Blue Diamond", COLOR_DARK_TEXT);
|
||||
|
||||
// Custom color
|
||||
button_y += 40;
|
||||
ui_draw_string(win, offset_x, button_y, "Custom color:", COLOR_DARK_TEXT);
|
||||
button_y += 20;
|
||||
|
||||
ui_draw_string(win, button_x, button_y + 4, "R:", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, button_x + 25, button_y, 50, 18, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, button_x + 30, button_y + 4, rgb_r, (focused_field == 0) ? 0xFFFF6B6B : COLOR_DARK_TEXT);
|
||||
if (focused_field == 0) {
|
||||
int cursor_x = button_x + 30 + input_cursor * 8;
|
||||
ui_draw_rect(win, cursor_x, button_y + 4, 1, 9, 0xFFFF6B6B);
|
||||
}
|
||||
|
||||
ui_draw_string(win, button_x + 90, button_y + 4, "G:", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, button_x + 115, button_y, 50, 18, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, button_x + 120, button_y + 4, rgb_g, (focused_field == 1) ? 0xFF90EE90 : COLOR_DARK_TEXT);
|
||||
if (focused_field == 1) {
|
||||
int cursor_x = button_x + 120 + input_cursor * 8;
|
||||
ui_draw_rect(win, cursor_x, button_y + 4, 1, 9, 0xFF90EE90);
|
||||
}
|
||||
|
||||
ui_draw_string(win, button_x + 180, button_y + 4, "B:", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, button_x + 205, button_y, 50, 18, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, button_x + 210, button_y + 4, rgb_b, (focused_field == 2) ? 0xFF87CEEB : COLOR_DARK_TEXT);
|
||||
if (focused_field == 2) {
|
||||
int cursor_x = button_x + 210 + input_cursor * 8;
|
||||
ui_draw_rect(win, cursor_x, button_y + 4, 1, 9, 0xFF87CEEB);
|
||||
}
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x, button_y + 25, 70, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, button_x + 18, button_y + 33, "Apply", COLOR_DARK_TEXT);
|
||||
|
||||
// Wallpapers section
|
||||
button_y += 60;
|
||||
ui_draw_string(win, offset_x, button_y, "Wallpapers:", COLOR_DARK_TEXT);
|
||||
button_y += 20;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, button_x, button_y, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 24, 6, COLOR_DARK_PANEL);
|
||||
if (moon_thumb_valid) {
|
||||
for (int ty = 0; ty < WALLPAPER_THUMB_H; ty++) {
|
||||
for (int tx = 0; tx < WALLPAPER_THUMB_W; tx++) {
|
||||
ui_draw_rect(win, button_x + 4 + tx, button_y + 4 + ty, 1, 1, moon_thumb[ty * WALLPAPER_THUMB_W + tx]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui_draw_string(win, button_x + 20, button_y + 30, "Error", 0xFFFF4444);
|
||||
}
|
||||
ui_draw_string(win, button_x + 30, button_y + WALLPAPER_THUMB_H + 8, "Moon", COLOR_DARK_TEXT);
|
||||
|
||||
int thumb2_x = button_x + WALLPAPER_THUMB_W + 20;
|
||||
ui_draw_rounded_rect_filled(win, thumb2_x, button_y, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 24, 6, COLOR_DARK_PANEL);
|
||||
if (mtn_thumb_valid) {
|
||||
for (int ty = 0; ty < WALLPAPER_THUMB_H; ty++) {
|
||||
for (int tx = 0; tx < WALLPAPER_THUMB_W; tx++) {
|
||||
ui_draw_rect(win, thumb2_x + 4 + tx, button_y + 4 + ty, 1, 1, mtn_thumb[ty * WALLPAPER_THUMB_W + tx]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui_draw_string(win, thumb2_x + 20, button_y + 30, "Error", 0xFFFF4444);
|
||||
}
|
||||
ui_draw_string(win, thumb2_x + 16, button_y + WALLPAPER_THUMB_H + 8, "Mountain", COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
static void control_panel_paint_network(ui_window_t win) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + 5, 80, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 10, offset_y + 13, "< Back", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_string(win, offset_x, offset_y + 40, "Network:", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + 55, 140, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 30, offset_y + 63, "Init Network", COLOR_DARK_TEXT);
|
||||
|
||||
if (net_status[0] != '\0') {
|
||||
ui_draw_string(win, offset_x + 150, offset_y + 63, net_status, 0xFF90EE90);
|
||||
}
|
||||
}
|
||||
|
||||
static void control_panel_paint_desktop(ui_window_t win) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + 5, 80, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 10, offset_y + 13, "< Back", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x, offset_y + 40, "Desktop Settings:", COLOR_DARK_TEXT);
|
||||
|
||||
int section_y = offset_y + 65;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x, section_y, 16, 16, 3, COLOR_DARK_PANEL);
|
||||
if (desktop_snap_to_grid) ui_draw_string(win, offset_x + 3, section_y + 1, "v", 0xFF90EE90);
|
||||
ui_draw_string(win, offset_x + 25, section_y + 3, "Snap to Grid", COLOR_DARK_TEXT);
|
||||
|
||||
section_y += 25;
|
||||
ui_draw_rounded_rect_filled(win, offset_x, section_y, 16, 16, 3, COLOR_DARK_PANEL);
|
||||
if (desktop_auto_align) ui_draw_string(win, offset_x + 3, section_y + 1, "v", 0xFF90EE90);
|
||||
ui_draw_string(win, offset_x + 25, section_y + 3, "Auto Align Icons", COLOR_DARK_TEXT);
|
||||
|
||||
section_y += 30;
|
||||
ui_draw_string(win, offset_x, section_y + 3, "Apps per column:", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, offset_x + 130, section_y, 20, 20, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 135, section_y + 4, "-", COLOR_DARK_TEXT);
|
||||
|
||||
char num[4];
|
||||
cli_itoa(desktop_max_rows_per_col, num);
|
||||
ui_draw_string(win, offset_x + 160, section_y + 5, num, COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x + 180, section_y, 20, 20, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 186, section_y + 4, "+", COLOR_DARK_TEXT);
|
||||
|
||||
section_y += 30;
|
||||
ui_draw_string(win, offset_x, section_y + 3, "Columns:", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, offset_x + 130, section_y, 20, 20, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 135, section_y + 4, "-", COLOR_DARK_TEXT);
|
||||
|
||||
char num_c[4];
|
||||
cli_itoa(desktop_max_cols, num_c);
|
||||
ui_draw_string(win, offset_x + 160, section_y + 5, num_c, COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x + 180, section_y, 20, 20, 4, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 186, section_y + 4, "+", COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
static void control_panel_paint_mouse(ui_window_t win) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y + 5, 80, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 10, offset_y + 13, "< Back", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x, offset_y + 40, "Mouse Settings:", COLOR_DARK_TEXT);
|
||||
|
||||
int section_y = offset_y + 65;
|
||||
ui_draw_string(win, offset_x, section_y, "Speed:", COLOR_DARK_TEXT);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, offset_x + 60, section_y + 8, 200, 8, 4, COLOR_DARK_PANEL);
|
||||
|
||||
int knob_x = offset_x + 60 + (mouse_speed - 1) * 190 / 49;
|
||||
ui_draw_rounded_rect_filled(win, knob_x, section_y + 2, 10, 14, 3, 0xFF4A90E2);
|
||||
|
||||
ui_draw_string(win, offset_x + 270, section_y + 4, "x", COLOR_DARK_TEXT);
|
||||
char speed_str[4];
|
||||
cli_itoa(mouse_speed, speed_str);
|
||||
ui_draw_string(win, offset_x + 280, section_y + 4, speed_str, COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
static void control_panel_paint(ui_window_t win) {
|
||||
// Fill background
|
||||
ui_draw_rect(win, 0, 20, 350, 480, COLOR_DARK_BG);
|
||||
|
||||
if (current_view == VIEW_MAIN) {
|
||||
control_panel_paint_main(win);
|
||||
} else if (current_view == VIEW_WALLPAPER) {
|
||||
control_panel_paint_wallpaper(win);
|
||||
} else if (current_view == VIEW_NETWORK) {
|
||||
control_panel_paint_network(win);
|
||||
} else if (current_view == VIEW_DESKTOP) {
|
||||
control_panel_paint_desktop(win);
|
||||
} else if (current_view == VIEW_MOUSE) {
|
||||
control_panel_paint_mouse(win);
|
||||
}
|
||||
}
|
||||
|
||||
static void save_desktop_config(void) {
|
||||
sys_system(4 /*SET_DESKTOP_PROP*/, 1, desktop_snap_to_grid, 0, 0);
|
||||
sys_system(4, 2, desktop_auto_align, 0, 0);
|
||||
sys_system(4, 3, desktop_max_rows_per_col, 0, 0);
|
||||
sys_system(4, 4, desktop_max_cols, 0, 0);
|
||||
}
|
||||
|
||||
static void save_mouse_config(void) {
|
||||
sys_system(5 /*SET_MOUSE_SPEED*/, mouse_speed, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void fetch_kernel_state(void) {
|
||||
desktop_snap_to_grid = sys_system(7 /*GET_DESKTOP_PROP*/, 1, 0, 0, 0);
|
||||
desktop_auto_align = sys_system(7, 2, 0, 0, 0);
|
||||
desktop_max_rows_per_col = sys_system(7, 3, 0, 0, 0);
|
||||
desktop_max_cols = sys_system(7, 4, 0, 0, 0);
|
||||
mouse_speed = sys_system(8 /*GET_MOUSE_SPEED*/, 0, 0, 0, 0);
|
||||
|
||||
if (sys_system(9, 0, (uint64_t)moon_thumb, 0, 0) == 0) {
|
||||
moon_thumb_valid = 1;
|
||||
}
|
||||
if (sys_system(9, 1, (uint64_t)mtn_thumb, 0, 0) == 0) {
|
||||
mtn_thumb_valid = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void control_panel_handle_click(int x, int y) {
|
||||
int win_w = 350;
|
||||
|
||||
if (current_view == VIEW_MAIN) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
int item_h = 60;
|
||||
int item_spacing = 10;
|
||||
|
||||
int item_y = offset_y + 15;
|
||||
if (x >= offset_x && x < win_w - 8 && y >= item_y && y < item_y + item_h) {
|
||||
current_view = VIEW_WALLPAPER;
|
||||
focused_field = -1;
|
||||
}
|
||||
item_y += item_h + item_spacing;
|
||||
if (x >= offset_x && x < win_w - 8 && y >= item_y && y < item_y + item_h) {
|
||||
current_view = VIEW_NETWORK;
|
||||
focused_field = -1;
|
||||
}
|
||||
item_y += item_h + item_spacing;
|
||||
if (x >= offset_x && x < win_w - 8 && y >= item_y && y < item_y + item_h) {
|
||||
current_view = VIEW_DESKTOP;
|
||||
}
|
||||
item_y += item_h + item_spacing;
|
||||
if (x >= offset_x && x < win_w - 8 && y >= item_y && y < item_y + item_h) {
|
||||
current_view = VIEW_MOUSE;
|
||||
}
|
||||
} else if (current_view == VIEW_WALLPAPER) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
int button_y = offset_y + 65;
|
||||
int button_x = offset_x;
|
||||
|
||||
if (x >= offset_x && x < offset_x + 80 && y >= offset_y + 5 && y < offset_y + 30) {
|
||||
current_view = VIEW_MAIN;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= button_x && x < button_x + 91 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(1, COLOR_COFFEE, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (x >= button_x + 100 && x < button_x + 191 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(1, COLOR_TEAL, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (x >= button_x + 200 && x < button_x + 291 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(1, COLOR_GREEN, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
button_y += 35;
|
||||
if (x >= button_x && x < button_x + 91 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(1, COLOR_BLUE_BG, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (x >= button_x + 100 && x < button_x + 191 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(1, COLOR_PURPLE, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (x >= button_x + 200 && x < button_x + 291 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(1, COLOR_GREY, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
button_y += 60; // 40 + 20
|
||||
if (x >= button_x && x < button_x + 132 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(2, (uint64_t)pattern_lumberjack, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (x >= button_x + 145 && x < button_x + 277 && y >= button_y && y < button_y + 25) {
|
||||
sys_system(2, (uint64_t)pattern_blue_diamond, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
button_y += 60;
|
||||
if (x >= button_x + 25 && x < button_x + 75 && y >= button_y && y < button_y + 18) {
|
||||
if (focused_field != 0) rgb_r[0] = 0;
|
||||
focused_field = 0; input_cursor = 0; return;
|
||||
}
|
||||
if (x >= button_x + 115 && x < button_x + 165 && y >= button_y && y < button_y + 18) {
|
||||
if (focused_field != 1) rgb_g[0] = 0;
|
||||
focused_field = 1; input_cursor = 0; return;
|
||||
}
|
||||
if (x >= button_x + 205 && x < button_x + 255 && y >= button_y && y < button_y + 18) {
|
||||
if (focused_field != 2) rgb_b[0] = 0;
|
||||
focused_field = 2; input_cursor = 0; return;
|
||||
}
|
||||
|
||||
if (x >= button_x && x < button_x + 70 && y >= button_y + 25 && y < button_y + 50) {
|
||||
uint32_t cust = parse_rgb_separate(rgb_r, rgb_g, rgb_b);
|
||||
sys_system(1, cust, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
button_y += 80;
|
||||
if (x >= button_x && x < button_x + WALLPAPER_THUMB_W + 8 && y >= button_y && y < button_y + WALLPAPER_THUMB_H + 24) {
|
||||
sys_system(3, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
int thumb2_x = button_x + WALLPAPER_THUMB_W + 20;
|
||||
if (x >= thumb2_x && x < thumb2_x + WALLPAPER_THUMB_W + 8 && y >= button_y && y < button_y + WALLPAPER_THUMB_H + 24) {
|
||||
sys_system(3, 1, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
} else if (current_view == VIEW_NETWORK) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
if (x >= offset_x && x < offset_x + 80 && y >= offset_y + 5 && y < offset_y + 30) {
|
||||
current_view = VIEW_MAIN;
|
||||
focused_field = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x >= offset_x && x < offset_x + 140 && y >= offset_y + 55 && y < offset_y + 80) {
|
||||
if (sys_system(6, 0, 0, 0, 0) == 0) {
|
||||
net_status[0] = 'I'; net_status[1] = 'n'; net_status[2] = 'i';
|
||||
net_status[3] = 't'; net_status[4] = 'e'; net_status[5] = 'd'; net_status[6] = 0;
|
||||
} else {
|
||||
net_status[0] = 'F'; net_status[1] = 'a'; net_status[2] = 'i';
|
||||
net_status[3] = 'l'; net_status[4] = 'e'; net_status[5] = 'd'; net_status[6] = 0;
|
||||
}
|
||||
}
|
||||
} else if (current_view == VIEW_DESKTOP) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
if (x >= offset_x && x < offset_x + 80 && y >= offset_y + 5 && y < offset_y + 30) {
|
||||
current_view = VIEW_MAIN;
|
||||
return;
|
||||
}
|
||||
|
||||
int section_y = offset_y + 65;
|
||||
if (x >= offset_x && x < offset_x + 16 && y >= section_y && y < section_y + 16) {
|
||||
desktop_snap_to_grid = !desktop_snap_to_grid;
|
||||
if (!desktop_snap_to_grid) desktop_auto_align = 0;
|
||||
save_desktop_config();
|
||||
return;
|
||||
}
|
||||
|
||||
section_y += 25;
|
||||
if (x >= offset_x && x < offset_x + 16 && y >= section_y && y < section_y + 16) {
|
||||
desktop_auto_align = !desktop_auto_align;
|
||||
if (desktop_auto_align) desktop_snap_to_grid = 1;
|
||||
save_desktop_config();
|
||||
return;
|
||||
}
|
||||
|
||||
section_y += 25;
|
||||
if (x >= offset_x + 130 && x < offset_x + 150 && y >= section_y && y < section_y + 20) {
|
||||
if (desktop_max_rows_per_col > 1) {
|
||||
desktop_max_rows_per_col--;
|
||||
save_desktop_config();
|
||||
}
|
||||
}
|
||||
if (x >= offset_x + 180 && x < offset_x + 200 && y >= section_y && y < section_y + 20) {
|
||||
if (desktop_max_rows_per_col < 15) desktop_max_rows_per_col++;
|
||||
save_desktop_config();
|
||||
}
|
||||
|
||||
section_y += 25;
|
||||
if (x >= offset_x + 130 && x < offset_x + 150 && y >= section_y && y < section_y + 20) {
|
||||
if (desktop_max_cols > 1) {
|
||||
desktop_max_cols--;
|
||||
save_desktop_config();
|
||||
}
|
||||
}
|
||||
if (x >= offset_x + 180 && x < offset_x + 200 && y >= section_y && y < section_y + 20) {
|
||||
desktop_max_cols++;
|
||||
save_desktop_config();
|
||||
}
|
||||
} else if (current_view == VIEW_MOUSE) {
|
||||
int offset_x = 8;
|
||||
int offset_y = 21;
|
||||
|
||||
if (x >= offset_x && x < offset_x + 80 && y >= offset_y + 5 && y < offset_y + 30) {
|
||||
current_view = VIEW_MAIN;
|
||||
return;
|
||||
}
|
||||
|
||||
int section_y = offset_y + 65;
|
||||
if (x >= offset_x + 60 && x <= offset_x + 260 && y >= section_y && y <= section_y + 20) {
|
||||
int new_speed = 1 + (x - (offset_x + 60)) * 49 / 200;
|
||||
if (new_speed < 1) new_speed = 1;
|
||||
if (new_speed > 50) new_speed = 50;
|
||||
mouse_speed = new_speed;
|
||||
save_mouse_config();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void control_panel_handle_key(char c) {
|
||||
if (focused_field < 0) return;
|
||||
|
||||
if (current_view == VIEW_WALLPAPER) {
|
||||
char *focused_buffer = NULL;
|
||||
int max_len = 3;
|
||||
|
||||
if (focused_field == 0) focused_buffer = rgb_r;
|
||||
else if (focused_field == 1) focused_buffer = rgb_g;
|
||||
else if (focused_field == 2) focused_buffer = rgb_b;
|
||||
else return;
|
||||
|
||||
if (c == '\b') {
|
||||
if (input_cursor > 0) {
|
||||
input_cursor--;
|
||||
focused_buffer[input_cursor] = '\0';
|
||||
}
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
if (input_cursor < max_len) {
|
||||
focused_buffer[input_cursor] = c;
|
||||
input_cursor++;
|
||||
focused_buffer[input_cursor] = '\0';
|
||||
}
|
||||
} else if (c == '\t') {
|
||||
focused_field = (focused_field + 1) % 3;
|
||||
input_cursor = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
ui_window_t win = ui_window_create("Settings", 200, 150, 350, 500);
|
||||
if (!win) return 1;
|
||||
|
||||
generate_lumberjack_pattern();
|
||||
generate_blue_diamond_pattern();
|
||||
|
||||
fetch_kernel_state();
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
control_panel_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 350, 500);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
control_panel_handle_click(ev.arg1, ev.arg2);
|
||||
control_panel_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 350, 500);
|
||||
} else if (ev.type == GUI_EVENT_KEY) {
|
||||
control_panel_handle_key((char)ev.arg1);
|
||||
control_panel_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, 350, 500);
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
BIN
src/kernel/userland/settings.elf
Executable file
BIN
src/kernel/userland/settings.elf
Executable file
Binary file not shown.
|
|
@ -1,11 +1,14 @@
|
|||
#include "editor.h"
|
||||
#include "graphics.h"
|
||||
#include "fat32.h"
|
||||
#include "wm.h"
|
||||
#include <stdbool.h>
|
||||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include <stddef.h>
|
||||
|
||||
Window win_editor;
|
||||
#define COLOR_DARK_PANEL 0xFF202020
|
||||
#define COLOR_DARK_TEXT 0xFFE0E0E0
|
||||
#define COLOR_DARK_BORDER 0xFF404040
|
||||
#define COLOR_RED 0xFFFF4444
|
||||
#define COLOR_DARK_BG 0xFF121212
|
||||
#define COLOR_DKGRAY 0xFF808080
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
|
||||
#define EDITOR_MAX_LINES 128
|
||||
#define EDITOR_MAX_LINE_LEN 256
|
||||
|
|
@ -23,37 +26,18 @@ static int cursor_line = 0;
|
|||
static int cursor_col = 0;
|
||||
static int scroll_top = 0;
|
||||
static char open_filename[256] = "";
|
||||
static bool file_modified = false;
|
||||
static _Bool file_modified = 0;
|
||||
|
||||
// === Helper Functions ===
|
||||
|
||||
static size_t editor_strlen(const char *str) {
|
||||
size_t len = 0;
|
||||
while (str[len]) len++;
|
||||
return len;
|
||||
}
|
||||
static int win_w = 700;
|
||||
static int win_h = 450;
|
||||
|
||||
static void editor_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static int editor_strcmp(const char *s1, const char *s2) {
|
||||
while (*s1 && (*s1 == *s2)) {
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
|
||||
}
|
||||
|
||||
// === Editor Logic ===
|
||||
|
||||
// Forward declaration
|
||||
static void editor_ensure_cursor_visible(void);
|
||||
|
||||
static void editor_clear_all(void) {
|
||||
for (int i = 0; i < EDITOR_MAX_LINES; i++) {
|
||||
// Zero out entire buffer to prevent ghost text from previous file
|
||||
for (int j = 0; j < EDITOR_MAX_LINE_LEN; j++) {
|
||||
lines[i].content[j] = 0;
|
||||
}
|
||||
|
|
@ -64,37 +48,43 @@ static void editor_clear_all(void) {
|
|||
cursor_col = 0;
|
||||
scroll_top = 0;
|
||||
open_filename[0] = 0;
|
||||
file_modified = false;
|
||||
file_modified = 0;
|
||||
}
|
||||
|
||||
static void editor_ensure_cursor_visible(void) {
|
||||
int visible_lines = 22;
|
||||
if (cursor_line < scroll_top) {
|
||||
scroll_top = cursor_line;
|
||||
}
|
||||
if (cursor_line >= scroll_top + visible_lines) {
|
||||
scroll_top = cursor_line - visible_lines + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void editor_open_file(const char *filename) {
|
||||
editor_clear_all();
|
||||
editor_strcpy(open_filename, filename);
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(filename, "r");
|
||||
if (!fh) {
|
||||
// New file
|
||||
file_modified = false;
|
||||
int fd = sys_open(filename, "r");
|
||||
if (fd < 0) {
|
||||
file_modified = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file content
|
||||
char buffer[16384];
|
||||
int bytes_read = fat32_read(fh, buffer, sizeof(buffer));
|
||||
fat32_close(fh);
|
||||
static char buffer[16384];
|
||||
int bytes_read = sys_read(fd, buffer, sizeof(buffer));
|
||||
sys_close(fd);
|
||||
|
||||
if (bytes_read <= 0) {
|
||||
file_modified = false;
|
||||
file_modified = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse into lines
|
||||
int line = 0;
|
||||
int col = 0;
|
||||
|
||||
for (int i = 0; i < bytes_read && line < EDITOR_MAX_LINES; i++) {
|
||||
char ch = buffer[i];
|
||||
|
||||
if (ch == '\n') {
|
||||
lines[line].content[col] = 0;
|
||||
lines[line].length = col;
|
||||
|
|
@ -115,71 +105,54 @@ void editor_open_file(const char *filename) {
|
|||
}
|
||||
|
||||
line_count = (line > 0) ? line : 1;
|
||||
file_modified = false;
|
||||
file_modified = 0;
|
||||
}
|
||||
|
||||
static void editor_save_file(void) {
|
||||
if (!open_filename[0]) {
|
||||
// No filename set
|
||||
return;
|
||||
}
|
||||
if (!open_filename[0]) return;
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(open_filename, "w");
|
||||
if (!fh) {
|
||||
return;
|
||||
}
|
||||
int fd = sys_open(open_filename, "w");
|
||||
if (fd < 0) return;
|
||||
|
||||
// Write lines
|
||||
for (int i = 0; i < line_count; i++) {
|
||||
fat32_write(fh, lines[i].content, lines[i].length);
|
||||
fat32_write(fh, "\n", 1);
|
||||
sys_write_fs(fd, lines[i].content, lines[i].length);
|
||||
sys_write_fs(fd, "\n", 1);
|
||||
}
|
||||
sys_close(fd);
|
||||
file_modified = 0;
|
||||
}
|
||||
|
||||
fat32_close(fh);
|
||||
file_modified = false;
|
||||
}
|
||||
|
||||
// Insert character at cursor position
|
||||
static void editor_insert_char(char ch) {
|
||||
if (cursor_line >= EDITOR_MAX_LINES) return;
|
||||
|
||||
EditorLine *line = &lines[cursor_line];
|
||||
|
||||
if (ch == '\n') {
|
||||
// Split line - shift all lines below down first
|
||||
if (line_count >= EDITOR_MAX_LINES) return;
|
||||
|
||||
// Shift all lines from cursor_line+1 onwards down by one position
|
||||
for (int j = line_count; j > cursor_line; j--) {
|
||||
lines[j] = lines[j - 1];
|
||||
}
|
||||
line_count++;
|
||||
|
||||
// Clear the new line completely (zero entire buffer)
|
||||
for (int k = 0; k < EDITOR_MAX_LINE_LEN; k++) {
|
||||
lines[cursor_line + 1].content[k] = 0;
|
||||
}
|
||||
lines[cursor_line + 1].length = 0;
|
||||
|
||||
// Now split the current line at cursor position
|
||||
int current_len = lines[cursor_line].length;
|
||||
int new_len = current_len - cursor_col;
|
||||
|
||||
// Copy the second part to the new line
|
||||
for (int i = 0; i < new_len; i++) {
|
||||
lines[cursor_line + 1].content[i] = lines[cursor_line].content[cursor_col + i];
|
||||
}
|
||||
lines[cursor_line + 1].content[new_len] = 0;
|
||||
lines[cursor_line + 1].length = new_len;
|
||||
|
||||
// Truncate current line
|
||||
lines[cursor_line].content[cursor_col] = 0;
|
||||
lines[cursor_line].length = cursor_col;
|
||||
|
||||
cursor_line++;
|
||||
cursor_col = 0;
|
||||
} else if (ch == '\b') {
|
||||
// Backspace
|
||||
if (cursor_col > 0) {
|
||||
for (int i = cursor_col - 1; i < line->length; i++) {
|
||||
line->content[i] = line->content[i + 1];
|
||||
|
|
@ -187,7 +160,6 @@ static void editor_insert_char(char ch) {
|
|||
line->length--;
|
||||
cursor_col--;
|
||||
} else if (cursor_line > 0) {
|
||||
// Merge with previous line
|
||||
EditorLine *prev = &lines[cursor_line - 1];
|
||||
int merge_point = prev->length;
|
||||
|
||||
|
|
@ -199,9 +171,8 @@ static void editor_insert_char(char ch) {
|
|||
prev->content[merge_point + i] = 0;
|
||||
prev->length = merge_point + i;
|
||||
|
||||
// Shift lines up
|
||||
for (int i = cursor_line; i < line_count - 1; i++) {
|
||||
lines[i] = lines[i + 1];
|
||||
for (int j = cursor_line; j < line_count - 1; j++) {
|
||||
lines[j] = lines[j + 1];
|
||||
}
|
||||
lines[line_count - 1].length = 0;
|
||||
lines[line_count - 1].content[0] = 0;
|
||||
|
|
@ -211,9 +182,7 @@ static void editor_insert_char(char ch) {
|
|||
line_count--;
|
||||
}
|
||||
} else if (ch >= 32 && ch <= 126) {
|
||||
// Regular character
|
||||
if (cursor_col < EDITOR_MAX_LINE_LEN - 1) {
|
||||
// Shift characters right
|
||||
for (int i = line->length; i > cursor_col; i--) {
|
||||
line->content[i] = line->content[i - 1];
|
||||
}
|
||||
|
|
@ -222,60 +191,39 @@ static void editor_insert_char(char ch) {
|
|||
cursor_col++;
|
||||
}
|
||||
}
|
||||
|
||||
file_modified = true;
|
||||
file_modified = 1;
|
||||
editor_ensure_cursor_visible();
|
||||
}
|
||||
|
||||
// Ensure cursor is visible by adjusting scroll position
|
||||
static void editor_ensure_cursor_visible(void) {
|
||||
int visible_lines = 22; // Allow ~24 lines to use available window space
|
||||
static void editor_paint(ui_window_t win) {
|
||||
int offset_x = 4;
|
||||
int offset_y = 24;
|
||||
int content_width = win_w - 8;
|
||||
int content_height = win_h - 28;
|
||||
|
||||
// Scroll up if cursor is above visible area
|
||||
if (cursor_line < scroll_top) {
|
||||
scroll_top = cursor_line;
|
||||
}
|
||||
// Top content bar
|
||||
ui_draw_rounded_rect_filled(win, offset_x, offset_y, content_width, 25, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 10, offset_y + 6, "File", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 55, offset_y + 6, open_filename, COLOR_DARK_TEXT);
|
||||
|
||||
// Scroll down if cursor is below visible area
|
||||
if (cursor_line >= scroll_top + visible_lines) {
|
||||
scroll_top = cursor_line - visible_lines + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// === Paint Function ===
|
||||
|
||||
static void editor_paint(Window *win) {
|
||||
int offset_x = win->x + 4;
|
||||
int offset_y = win->y + 24;
|
||||
int content_width = win->w - 8;
|
||||
int content_height = win->h - 28;
|
||||
|
||||
// Top content bar (modern dark, rounded)
|
||||
draw_rounded_rect_filled(offset_x, offset_y, content_width, 25, 6, COLOR_DARK_PANEL);
|
||||
draw_string(offset_x + 10, offset_y + 6, "File", COLOR_DARK_TEXT);
|
||||
draw_string(offset_x + 55, offset_y + 6, open_filename, COLOR_DARK_TEXT);
|
||||
|
||||
// Save button (modern rounded)
|
||||
// Save button
|
||||
int save_btn_x = offset_x + content_width - 80;
|
||||
int save_btn_y = offset_y + 3;
|
||||
draw_rounded_rect_filled(save_btn_x, save_btn_y, 70, 20, 6, COLOR_DARK_BORDER);
|
||||
draw_string(save_btn_x + 20, save_btn_y + 6, "Save", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, save_btn_x, save_btn_y, 70, 20, 6, COLOR_DARK_BORDER);
|
||||
ui_draw_string(win, save_btn_x + 20, save_btn_y + 6, "Save", COLOR_DARK_TEXT);
|
||||
|
||||
// Draw modification indicator
|
||||
if (file_modified) {
|
||||
draw_string(offset_x + content_width - 200, offset_y + 5, "[Modified]", COLOR_RED);
|
||||
ui_draw_string(win, offset_x + content_width - 200, offset_y + 5, "[Modified]", COLOR_RED);
|
||||
}
|
||||
|
||||
// Fill editor background - dark mode
|
||||
draw_rect(win->x + 4, win->y + 54, win->w - 8, win->h - 58, COLOR_DARK_BG);
|
||||
// Editor background
|
||||
ui_draw_rect(win, 4, 54, win_w - 8, win_h - 58, COLOR_DARK_BG);
|
||||
|
||||
// Calculate available width for text (accounting for line numbers)
|
||||
int text_start_x = offset_x + 40;
|
||||
int available_width = content_width - 40;
|
||||
int max_chars_per_line = available_width / EDITOR_CHAR_WIDTH;
|
||||
if (max_chars_per_line < 1) max_chars_per_line = 1;
|
||||
|
||||
// Draw line numbers and content with word wrapping
|
||||
int display_line = 0;
|
||||
int visible_lines = (content_height - 55) / EDITOR_LINE_HEIGHT;
|
||||
int max_display_lines = visible_lines;
|
||||
|
|
@ -284,9 +232,7 @@ static void editor_paint(Window *win) {
|
|||
while (line_idx < line_count && display_line < max_display_lines) {
|
||||
int display_y = offset_y + 35 + display_line * EDITOR_LINE_HEIGHT;
|
||||
|
||||
// Only draw line number for first wrapped line of this editor line
|
||||
if (display_line == 0 || line_idx < line_count) {
|
||||
// Draw line number
|
||||
char line_num_str[16];
|
||||
int temp = line_idx + 1;
|
||||
int str_len = 0;
|
||||
|
|
@ -298,7 +244,6 @@ static void editor_paint(Window *win) {
|
|||
line_num_str[str_len++] = (temp % 10) + '0';
|
||||
temp /= 10;
|
||||
}
|
||||
// Reverse
|
||||
for (int j = 0; j < str_len / 2; j++) {
|
||||
char t = line_num_str[j];
|
||||
line_num_str[j] = line_num_str[str_len - 1 - j];
|
||||
|
|
@ -306,21 +251,19 @@ static void editor_paint(Window *win) {
|
|||
}
|
||||
}
|
||||
line_num_str[str_len] = 0;
|
||||
draw_string(offset_x + 4, display_y, line_num_str, COLOR_DKGRAY);
|
||||
ui_draw_string(win, offset_x + 4, display_y, line_num_str, COLOR_DKGRAY);
|
||||
}
|
||||
|
||||
// Word-based text wrapping for this line
|
||||
const char *text = lines[line_idx].content;
|
||||
int text_len = lines[line_idx].length;
|
||||
int char_idx = 0;
|
||||
int local_display_line = 0;
|
||||
bool first_pass = true;
|
||||
_Bool first_pass = 1;
|
||||
|
||||
while ((char_idx < text_len || (text_len == 0 && first_pass)) && display_line < max_display_lines) {
|
||||
first_pass = false;
|
||||
first_pass = 0;
|
||||
int current_display_y = offset_y + 35 + display_line * EDITOR_LINE_HEIGHT;
|
||||
|
||||
// Extract segment (up to max_chars_per_line)
|
||||
char segment[256];
|
||||
int segment_len = 0;
|
||||
int segment_start = char_idx;
|
||||
|
|
@ -330,7 +273,6 @@ static void editor_paint(Window *win) {
|
|||
}
|
||||
segment[segment_len] = 0;
|
||||
|
||||
// Word-based wrapping: find last space if we didn't reach end
|
||||
if (char_idx < text_len && segment_len > 0) {
|
||||
int last_space = -1;
|
||||
for (int i = segment_len - 1; i >= 0; i--) {
|
||||
|
|
@ -339,51 +281,44 @@ static void editor_paint(Window *win) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_space > 0) {
|
||||
segment_len = last_space;
|
||||
segment[segment_len] = 0;
|
||||
char_idx = segment_start + last_space + 1;
|
||||
// Skip additional spaces
|
||||
while (char_idx < text_len && text[char_idx] == ' ') {
|
||||
char_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the text segment
|
||||
if (segment_len > 0) {
|
||||
draw_string(text_start_x, current_display_y, segment, COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, text_start_x, current_display_y, segment, COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
// Draw cursor if on this line and wrapped segment
|
||||
if (line_idx == cursor_line) {
|
||||
int segment_end = segment_start + segment_len;
|
||||
bool draw_cursor = false;
|
||||
_Bool draw_cursor = 0;
|
||||
if (cursor_col >= segment_start && cursor_col < segment_end) {
|
||||
draw_cursor = true;
|
||||
draw_cursor = 1;
|
||||
} else if (cursor_col == text_len && segment_end == text_len) {
|
||||
draw_cursor = true;
|
||||
draw_cursor = 1;
|
||||
}
|
||||
if (draw_cursor) {
|
||||
int cursor_x = text_start_x + ((cursor_col - segment_start) * EDITOR_CHAR_WIDTH);
|
||||
draw_rect(cursor_x, current_display_y, 2, 10, COLOR_WHITE);
|
||||
ui_draw_rect(win, cursor_x, current_display_y, 2, 10, COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
display_line++;
|
||||
local_display_line++;
|
||||
|
||||
if (char_idx >= text_len) break;
|
||||
}
|
||||
|
||||
line_idx++;
|
||||
}
|
||||
|
||||
// Status bar at bottom (modern dark, rounded)
|
||||
int status_y = offset_y + content_height - 20;
|
||||
draw_rounded_rect_filled(offset_x, status_y, content_width, 20, 6, COLOR_DARK_PANEL);
|
||||
draw_string(offset_x + 10, status_y + 5, "Line: ", COLOR_DARK_TEXT);
|
||||
ui_draw_rounded_rect_filled(win, offset_x, status_y, content_width, 20, 6, COLOR_DARK_PANEL);
|
||||
ui_draw_string(win, offset_x + 10, status_y + 5, "Line: ", COLOR_DARK_TEXT);
|
||||
|
||||
char line_str[32];
|
||||
int temp = cursor_line + 1;
|
||||
|
|
@ -399,8 +334,8 @@ static void editor_paint(Window *win) {
|
|||
}
|
||||
line_str[idx] = 0;
|
||||
|
||||
draw_string(offset_x + 60, status_y + 5, line_str, COLOR_DARK_TEXT);
|
||||
draw_string(offset_x + 100, status_y + 5, " Col: ", COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 60, status_y + 5, line_str, COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 100, status_y + 5, " Col: ", COLOR_DARK_TEXT);
|
||||
|
||||
char col_str[32];
|
||||
temp = cursor_col + 1;
|
||||
|
|
@ -416,100 +351,81 @@ static void editor_paint(Window *win) {
|
|||
}
|
||||
col_str[idx] = 0;
|
||||
|
||||
draw_string(offset_x + 170, status_y + 5, col_str, COLOR_DARK_TEXT);
|
||||
ui_draw_string(win, offset_x + 170, status_y + 5, col_str, COLOR_DARK_TEXT);
|
||||
}
|
||||
|
||||
// === Key Handler ===
|
||||
|
||||
static void editor_handle_key(Window *win, char c) {
|
||||
// Arrow keys - UP
|
||||
if (c == 17) {
|
||||
static void editor_handle_key(char c) {
|
||||
if (c == 17) { // UP
|
||||
if (cursor_line > 0) {
|
||||
cursor_line--;
|
||||
if (cursor_col > (int)lines[cursor_line].length) {
|
||||
cursor_col = lines[cursor_line].length;
|
||||
if (cursor_col > lines[cursor_line].length) cursor_col = lines[cursor_line].length;
|
||||
if (cursor_line < scroll_top) scroll_top = cursor_line;
|
||||
}
|
||||
if (cursor_line < scroll_top) {
|
||||
scroll_top = cursor_line;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys - DOWN
|
||||
if (c == 18) {
|
||||
} else if (c == 18) { // DOWN
|
||||
if (cursor_line < line_count - 1) {
|
||||
cursor_line++;
|
||||
if (cursor_col > (int)lines[cursor_line].length) {
|
||||
cursor_col = lines[cursor_line].length;
|
||||
}
|
||||
if (cursor_col > lines[cursor_line].length) cursor_col = lines[cursor_line].length;
|
||||
int visible_lines = 20;
|
||||
if (cursor_line >= scroll_top + visible_lines) {
|
||||
scroll_top = cursor_line - visible_lines + 1;
|
||||
if (cursor_line >= scroll_top + visible_lines) scroll_top = cursor_line - visible_lines + 1;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys - LEFT
|
||||
if (c == 19) {
|
||||
} else if (c == 19) { // LEFT
|
||||
if (cursor_col > 0) {
|
||||
cursor_col--;
|
||||
} else if (cursor_line > 0) {
|
||||
cursor_line--;
|
||||
cursor_col = lines[cursor_line].length;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys - RIGHT
|
||||
if (c == 20) {
|
||||
if (cursor_col < (int)lines[cursor_line].length) {
|
||||
} else if (c == 20) { // RIGHT
|
||||
if (cursor_col < lines[cursor_line].length) {
|
||||
cursor_col++;
|
||||
} else if (cursor_line < line_count - 1) {
|
||||
cursor_line++;
|
||||
cursor_col = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular character input
|
||||
} else {
|
||||
editor_insert_char(c);
|
||||
}
|
||||
}
|
||||
|
||||
// === Click Handler ===
|
||||
|
||||
static void editor_handle_click(Window *win, int x, int y) {
|
||||
// x and y are relative to window origin
|
||||
int content_width = win->w - 8;
|
||||
|
||||
// Check save button - position is at (4 + content_width - 80, 24 + 3) = (4 + w - 8 - 80, 27)
|
||||
// Button dimensions: 70 wide, 20 tall
|
||||
static void editor_handle_click(int x, int y) {
|
||||
int content_width = win_w - 8;
|
||||
int button_x = 4 + content_width - 80;
|
||||
int button_y = 24 + 3;
|
||||
|
||||
if (x >= button_x && x < button_x + 70 &&
|
||||
y >= button_y && y < button_y + 20) {
|
||||
if (x >= button_x && x < button_x + 70 && y >= button_y && y < button_y + 20) {
|
||||
editor_save_file();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// === Initialization ===
|
||||
|
||||
void editor_init(void) {
|
||||
win_editor.title = "Text Editor";
|
||||
win_editor.x = 100;
|
||||
win_editor.y = 150;
|
||||
win_editor.w = 700;
|
||||
win_editor.h = 450;
|
||||
win_editor.visible = false;
|
||||
win_editor.focused = false;
|
||||
win_editor.z_index = 0;
|
||||
win_editor.paint = editor_paint;
|
||||
win_editor.handle_key = editor_handle_key;
|
||||
win_editor.handle_click = editor_handle_click;
|
||||
win_editor.handle_right_click = NULL;
|
||||
int main(int argc, char **argv) {
|
||||
ui_window_t win = ui_window_create("Text Editor", 100, 150, win_w, win_h);
|
||||
if (!win) return 1;
|
||||
|
||||
editor_clear_all();
|
||||
if (argc > 1) {
|
||||
editor_open_file(argv[1]);
|
||||
} else {
|
||||
editor_strcpy(open_filename, "untitled.txt");
|
||||
}
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
editor_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
editor_handle_click(ev.arg1, ev.arg2);
|
||||
editor_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_KEY) {
|
||||
editor_handle_key((char)ev.arg1);
|
||||
editor_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
BIN
src/kernel/userland/txtedit.elf
Executable file
BIN
src/kernel/userland/txtedit.elf
Executable file
Binary file not shown.
226
src/kernel/userland/viewer.c
Normal file
226
src/kernel/userland/viewer.c
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
#include "nanojpeg.h"
|
||||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include "libc/stdlib.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define VIEWER_MAX_W 800
|
||||
#define VIEWER_MAX_H 600
|
||||
|
||||
static uint32_t *viewer_pixels = NULL;
|
||||
static int viewer_img_w = 0;
|
||||
static int viewer_img_h = 0;
|
||||
static char viewer_title[64] = "Viewer";
|
||||
static bool viewer_has_image = false;
|
||||
static char viewer_file_path[256];
|
||||
|
||||
static int win_w = 500;
|
||||
static int win_h = 400;
|
||||
|
||||
static void viewer_strcpy(char *dst, const char *src) {
|
||||
while (*src) *dst++ = *src++;
|
||||
*dst = 0;
|
||||
}
|
||||
|
||||
static int viewer_strlen(const char *s) {
|
||||
int len = 0;
|
||||
while (s[len]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
static void viewer_scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
|
||||
uint32_t *dst, int dst_w, int dst_h) {
|
||||
for (int y = 0; y < dst_h; y++) {
|
||||
int src_y = y * src_h / dst_h;
|
||||
if (src_y >= src_h) src_y = src_h - 1;
|
||||
for (int x = 0; x < dst_w; x++) {
|
||||
int src_x = x * src_w / dst_w;
|
||||
if (src_x >= src_w) src_x = src_w - 1;
|
||||
int idx = (src_y * src_w + src_x) * 3;
|
||||
unsigned char r = rgb[idx];
|
||||
unsigned char g = rgb[idx + 1];
|
||||
unsigned char b = rgb[idx + 2];
|
||||
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void viewer_paint(ui_window_t win) {
|
||||
int cx = 4;
|
||||
int cy = 24;
|
||||
int cw = win_w - 8;
|
||||
int ch = win_h - 28;
|
||||
|
||||
ui_draw_rect(win, cx, cy, cw, ch, 0xFF1A1A1A);
|
||||
|
||||
if (!viewer_has_image) {
|
||||
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
||||
return;
|
||||
}
|
||||
|
||||
int disp_w = viewer_img_w;
|
||||
int disp_h = viewer_img_h;
|
||||
|
||||
if (disp_w > cw - 8) {
|
||||
disp_h = disp_h * (cw - 8) / disp_w;
|
||||
disp_w = cw - 8;
|
||||
}
|
||||
if (disp_h > ch - 40) {
|
||||
disp_w = disp_w * (ch - 40) / disp_h;
|
||||
disp_h = ch - 40;
|
||||
}
|
||||
|
||||
int ox = cx + (cw - disp_w) / 2;
|
||||
int oy = cy + (ch - disp_h - 30) / 2;
|
||||
|
||||
for (int y = 0; y < disp_h; y++) {
|
||||
int src_y = y * viewer_img_h / disp_h;
|
||||
if (src_y >= viewer_img_h) src_y = viewer_img_h - 1;
|
||||
for (int x = 0; x < disp_w; x++) {
|
||||
int src_x = x * viewer_img_w / disp_w;
|
||||
if (src_x >= viewer_img_w) src_x = viewer_img_w - 1;
|
||||
uint32_t pixel = viewer_pixels[src_y * viewer_img_w + src_x];
|
||||
ui_draw_rect(win, ox + x, oy + y, 1, 1, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
int btn_w = 160;
|
||||
int btn_h = 22;
|
||||
int btn_x = cx + (cw - btn_w) / 2;
|
||||
int btn_y = win_h - 30;
|
||||
ui_draw_rounded_rect_filled(win, btn_x, btn_y, btn_w, btn_h, 6, 0xFF2D2D2D);
|
||||
ui_draw_string(win, btn_x + 10, btn_y + 6, "Set as Wallpaper", 0xFFF0F0F0);
|
||||
}
|
||||
|
||||
static void viewer_handle_click(ui_window_t win, int x, int y) {
|
||||
if (!viewer_has_image) return;
|
||||
|
||||
int cx = 4;
|
||||
int cw = win_w - 8;
|
||||
int btn_w = 160;
|
||||
int btn_x = cx + (cw - btn_w) / 2;
|
||||
int btn_y = win_h - 30;
|
||||
|
||||
if (x >= btn_x && x < btn_x + btn_w && y >= btn_y && y < btn_y + 22) {
|
||||
// SYSTEM_CMD_SET_WALLPAPER is 3 based on syscall.c code
|
||||
sys_system(3, (uint64_t)viewer_file_path, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void viewer_open_file(const char *path) {
|
||||
int fd = sys_open(path, "r");
|
||||
if (fd < 0) return;
|
||||
|
||||
// We can't use stat yet, so read chunks until EOF
|
||||
// Alternatively, use a large buffer if sys_read handles large chunks.
|
||||
int alloc_size = 2 * 1024 * 1024;
|
||||
unsigned char *buf = malloc(alloc_size);
|
||||
if (!buf) {
|
||||
sys_close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int total_read = 0;
|
||||
while (total_read < alloc_size) {
|
||||
int chunk = sys_read(fd, (char*)buf + total_read, alloc_size - total_read);
|
||||
if (chunk <= 0) break;
|
||||
total_read += chunk;
|
||||
}
|
||||
sys_close(fd);
|
||||
|
||||
if (total_read <= 0) {
|
||||
free(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
njInit();
|
||||
nj_result_t result = njDecode(buf, total_read);
|
||||
if (result != NJ_OK) {
|
||||
njDone();
|
||||
free(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
int img_w = njGetWidth();
|
||||
int img_h = njGetHeight();
|
||||
unsigned char *rgb = njGetImage();
|
||||
|
||||
if (!rgb || img_w <= 0 || img_h <= 0) {
|
||||
njDone();
|
||||
free(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
int fit_w = img_w;
|
||||
int fit_h = img_h;
|
||||
if (fit_w > VIEWER_MAX_W) {
|
||||
fit_h = fit_h * VIEWER_MAX_W / fit_w;
|
||||
fit_w = VIEWER_MAX_W;
|
||||
}
|
||||
if (fit_h > VIEWER_MAX_H) {
|
||||
fit_w = fit_w * VIEWER_MAX_H / fit_h;
|
||||
fit_h = VIEWER_MAX_H;
|
||||
}
|
||||
|
||||
viewer_pixels = malloc(fit_w * fit_h * sizeof(uint32_t));
|
||||
if (viewer_pixels) {
|
||||
viewer_scale_rgb_to_argb(rgb, img_w, img_h, viewer_pixels, fit_w, fit_h);
|
||||
viewer_img_w = fit_w;
|
||||
viewer_img_h = fit_h;
|
||||
viewer_has_image = true;
|
||||
}
|
||||
|
||||
njDone();
|
||||
free(buf);
|
||||
|
||||
viewer_strcpy(viewer_file_path, path);
|
||||
|
||||
const char *fname = path;
|
||||
int plen = viewer_strlen(path);
|
||||
for (int i = plen - 1; i >= 0; i--) {
|
||||
if (path[i] == '/') {
|
||||
fname = &path[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
viewer_title[0] = 'V'; viewer_title[1] = 'i'; viewer_title[2] = 'e';
|
||||
viewer_title[3] = 'w'; viewer_title[4] = 'e'; viewer_title[5] = 'r';
|
||||
viewer_title[6] = ' '; viewer_title[7] = '-'; viewer_title[8] = ' ';
|
||||
int ti = 9;
|
||||
for (int i = 0; fname[i] && ti < 60; i++) {
|
||||
viewer_title[ti++] = fname[i];
|
||||
}
|
||||
viewer_title[ti] = 0;
|
||||
|
||||
win_w = fit_w + 16;
|
||||
if (win_w < 200) win_w = 200;
|
||||
win_h = fit_h + 64;
|
||||
if (win_h < 100) win_h = 100;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1) {
|
||||
viewer_open_file(argv[1]);
|
||||
}
|
||||
|
||||
ui_window_t win = ui_window_create(viewer_title, 100, 50, win_w, win_h);
|
||||
if (!win) return 1;
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
viewer_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, win_w, win_h);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
viewer_handle_click(win, ev.arg1, ev.arg2);
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
BIN
src/kernel/userland/viewer.elf
Executable file
BIN
src/kernel/userland/viewer.elf
Executable file
Binary file not shown.
BIN
src/kernel/userland/viewer.o
Normal file
BIN
src/kernel/userland/viewer.o
Normal file
Binary file not shown.
|
|
@ -1,332 +0,0 @@
|
|||
// viewer.c - Image Viewer app for BoredOS
|
||||
// Opens .jpg files and displays the decoded image in a window
|
||||
|
||||
#include "viewer.h"
|
||||
#include "nanojpeg.h"
|
||||
#include "graphics.h"
|
||||
#include "fat32.h"
|
||||
#include "memory_manager.h"
|
||||
#include "wallpaper.h"
|
||||
#include "io.h"
|
||||
#include <stddef.h>
|
||||
|
||||
Window win_viewer;
|
||||
|
||||
// Viewer state
|
||||
#define VIEWER_MAX_W 800
|
||||
#define VIEWER_MAX_H 600
|
||||
static uint32_t viewer_pixels[VIEWER_MAX_W * VIEWER_MAX_H];
|
||||
static int viewer_img_w = 0;
|
||||
static int viewer_img_h = 0;
|
||||
static char viewer_title[64] = "Viewer";
|
||||
static bool viewer_has_image = false;
|
||||
|
||||
// Deferred open: click handler stores path, main loop decodes
|
||||
static char viewer_pending_path[256];
|
||||
static volatile bool viewer_open_pending = false;
|
||||
|
||||
// Store the file path for "Set as Wallpaper"
|
||||
static char viewer_file_path[256];
|
||||
|
||||
// String helpers
|
||||
static int viewer_strlen(const char *s) {
|
||||
int len = 0;
|
||||
while (s[len]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
static void viewer_strcpy(char *dst, const char *src) {
|
||||
while (*src) *dst++ = *src++;
|
||||
*dst = 0;
|
||||
}
|
||||
|
||||
// Simple nearest-neighbor scale from decoded RGB to ARGB pixel buffer
|
||||
static void viewer_scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
|
||||
uint32_t *dst, int dst_w, int dst_h) {
|
||||
for (int y = 0; y < dst_h; y++) {
|
||||
int src_y = y * src_h / dst_h;
|
||||
if (src_y >= src_h) src_y = src_h - 1;
|
||||
for (int x = 0; x < dst_w; x++) {
|
||||
int src_x = x * src_w / dst_w;
|
||||
if (src_x >= src_w) src_x = src_w - 1;
|
||||
int idx = (src_y * src_w + src_x) * 3;
|
||||
unsigned char r = rgb[idx];
|
||||
unsigned char g = rgb[idx + 1];
|
||||
unsigned char b = rgb[idx + 2];
|
||||
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void viewer_paint(Window *win) {
|
||||
int cx = win->x + 4;
|
||||
int cy = win->y + 24;
|
||||
int cw = win->w - 8;
|
||||
int ch = win->h - 28;
|
||||
|
||||
draw_rect(cx, cy, cw, ch, 0xFF1A1A1A);
|
||||
|
||||
if (!viewer_has_image) {
|
||||
draw_string(cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate display size (fit within window, keep aspect ratio)
|
||||
int disp_w = viewer_img_w;
|
||||
int disp_h = viewer_img_h;
|
||||
|
||||
if (disp_w > cw - 8) {
|
||||
disp_h = disp_h * (cw - 8) / disp_w;
|
||||
disp_w = cw - 8;
|
||||
}
|
||||
if (disp_h > ch - 40) {
|
||||
disp_w = disp_w * (ch - 40) / disp_h;
|
||||
disp_h = ch - 40;
|
||||
}
|
||||
|
||||
// Center in window
|
||||
int ox = cx + (cw - disp_w) / 2;
|
||||
int oy = cy + (ch - disp_h - 30) / 2;
|
||||
|
||||
// Draw the image pixel by pixel (nearest-neighbor from stored buffer)
|
||||
for (int y = 0; y < disp_h; y++) {
|
||||
int src_y = y * viewer_img_h / disp_h;
|
||||
if (src_y >= viewer_img_h) src_y = viewer_img_h - 1;
|
||||
for (int x = 0; x < disp_w; x++) {
|
||||
int src_x = x * viewer_img_w / disp_w;
|
||||
if (src_x >= viewer_img_w) src_x = viewer_img_w - 1;
|
||||
uint32_t pixel = viewer_pixels[src_y * viewer_img_w + src_x];
|
||||
put_pixel(ox + x, oy + y, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw "Set as Wallpaper" button at the bottom
|
||||
int btn_w = 160;
|
||||
int btn_h = 22;
|
||||
int btn_x = cx + (cw - btn_w) / 2;
|
||||
int btn_y = win->y + win->h - 30;
|
||||
draw_rounded_rect_filled(btn_x, btn_y, btn_w, btn_h, 6, 0xFF2D2D2D);
|
||||
draw_string(btn_x + 10, btn_y + 6, "Set as Wallpaper", 0xFFF0F0F0);
|
||||
}
|
||||
|
||||
static void viewer_handle_click(Window *win, int x, int y) {
|
||||
if (!viewer_has_image) return;
|
||||
|
||||
int cx = 4;
|
||||
int cw = win->w - 8;
|
||||
|
||||
// Check "Set as Wallpaper" button
|
||||
int btn_w = 160;
|
||||
int btn_x = cx + (cw - btn_w) / 2;
|
||||
int btn_y = win->h - 30;
|
||||
if (x >= btn_x && x < btn_x + btn_w && y >= btn_y && y < btn_y + 22) {
|
||||
// Queue wallpaper change from file (deferred to main loop)
|
||||
wallpaper_request_set_from_file(viewer_file_path);
|
||||
}
|
||||
}
|
||||
|
||||
static void viewer_handle_key(Window *win, char c) {
|
||||
(void)win;
|
||||
(void)c;
|
||||
}
|
||||
|
||||
// Simple serial output for debugging
|
||||
static void v_serial_char(char c) {
|
||||
while (!(inb(0x3F8 + 5) & 0x20));
|
||||
outb(0x3F8, c);
|
||||
}
|
||||
static void v_serial_str(const char *s) { while (*s) v_serial_char(*s++); }
|
||||
static void v_serial_num(int n) {
|
||||
if (n < 0) { v_serial_char('-'); n = -n; }
|
||||
if (n >= 10) v_serial_num(n / 10);
|
||||
v_serial_char('0' + (n % 10));
|
||||
}
|
||||
|
||||
// Called from interrupt context - just queue the path for later processing
|
||||
void viewer_open_file(const char *path) {
|
||||
v_serial_str("[VIEWER] open_file queued: ");
|
||||
v_serial_str(path);
|
||||
v_serial_str("\n");
|
||||
viewer_strcpy(viewer_pending_path, path);
|
||||
viewer_open_pending = true;
|
||||
}
|
||||
|
||||
// Process deferred viewer open (called from main loop, NOT interrupt context)
|
||||
void viewer_process_pending(void) {
|
||||
if (!viewer_open_pending) return;
|
||||
viewer_open_pending = false;
|
||||
|
||||
const char *path = viewer_pending_path;
|
||||
v_serial_str("[VIEWER] process_pending: ");
|
||||
v_serial_str(path);
|
||||
v_serial_str("\n");
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(path, "r");
|
||||
if (!fh) {
|
||||
v_serial_str("[VIEWER] fat32_open FAILED\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t file_size = fh->size;
|
||||
v_serial_str("[VIEWER] file_size=");
|
||||
v_serial_num((int)file_size);
|
||||
v_serial_str("\n");
|
||||
|
||||
if (file_size == 0 || file_size > 2 * 1024 * 1024) {
|
||||
v_serial_str("[VIEWER] file too big or empty\n");
|
||||
fat32_close(fh);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char *buf = (unsigned char*)kmalloc(file_size);
|
||||
if (!buf) {
|
||||
v_serial_str("[VIEWER] kmalloc FAILED\n");
|
||||
fat32_close(fh);
|
||||
return;
|
||||
}
|
||||
|
||||
int total_read = 0;
|
||||
while (total_read < (int)file_size) {
|
||||
int chunk = fat32_read(fh, buf + total_read, (int)file_size - total_read);
|
||||
if (chunk <= 0) break;
|
||||
total_read += chunk;
|
||||
}
|
||||
fat32_close(fh);
|
||||
|
||||
v_serial_str("[VIEWER] read ");
|
||||
v_serial_num(total_read);
|
||||
v_serial_str(" bytes\n");
|
||||
|
||||
if (total_read <= 0) {
|
||||
kfree(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode JPEG (now running in main loop, safe context)
|
||||
njInit();
|
||||
nj_result_t result = njDecode(buf, total_read);
|
||||
v_serial_str("[VIEWER] njDecode returned: ");
|
||||
v_serial_num((int)result);
|
||||
v_serial_str("\n");
|
||||
|
||||
if (result != NJ_OK) {
|
||||
njDone();
|
||||
kfree(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
int img_w = njGetWidth();
|
||||
int img_h = njGetHeight();
|
||||
unsigned char *rgb = njGetImage();
|
||||
|
||||
v_serial_str("[VIEWER] decoded ");
|
||||
v_serial_num(img_w);
|
||||
v_serial_str("x");
|
||||
v_serial_num(img_h);
|
||||
v_serial_str("\n");
|
||||
|
||||
if (!rgb || img_w <= 0 || img_h <= 0) {
|
||||
njDone();
|
||||
kfree(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Scale to fit viewer buffer
|
||||
int fit_w = img_w;
|
||||
int fit_h = img_h;
|
||||
if (fit_w > VIEWER_MAX_W) {
|
||||
fit_h = fit_h * VIEWER_MAX_W / fit_w;
|
||||
fit_w = VIEWER_MAX_W;
|
||||
}
|
||||
if (fit_h > VIEWER_MAX_H) {
|
||||
fit_w = fit_w * VIEWER_MAX_H / fit_h;
|
||||
fit_h = VIEWER_MAX_H;
|
||||
}
|
||||
|
||||
viewer_scale_rgb_to_argb(rgb, img_w, img_h, viewer_pixels, fit_w, fit_h);
|
||||
viewer_img_w = fit_w;
|
||||
viewer_img_h = fit_h;
|
||||
viewer_has_image = true;
|
||||
|
||||
njDone();
|
||||
kfree(buf);
|
||||
|
||||
// Store the file path for "Set as Wallpaper"
|
||||
viewer_strcpy(viewer_file_path, path);
|
||||
|
||||
// Update title - extract filename from path
|
||||
const char *fname = path;
|
||||
int plen = viewer_strlen(path);
|
||||
for (int i = plen - 1; i >= 0; i--) {
|
||||
if (path[i] == '/') {
|
||||
fname = &path[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
viewer_title[0] = 'V'; viewer_title[1] = 'i'; viewer_title[2] = 'e';
|
||||
viewer_title[3] = 'w'; viewer_title[4] = 'e'; viewer_title[5] = 'r';
|
||||
viewer_title[6] = ' '; viewer_title[7] = '-'; viewer_title[8] = ' ';
|
||||
int ti = 9;
|
||||
for (int i = 0; fname[i] && ti < 60; i++) {
|
||||
viewer_title[ti++] = fname[i];
|
||||
}
|
||||
viewer_title[ti] = 0;
|
||||
|
||||
// Resize window to fit image
|
||||
win_viewer.w = fit_w + 16;
|
||||
if (win_viewer.w < 200) win_viewer.w = 200;
|
||||
win_viewer.h = fit_h + 64;
|
||||
if (win_viewer.h < 100) win_viewer.h = 100;
|
||||
|
||||
// Reset position to ensure visibility
|
||||
win_viewer.x = 100;
|
||||
win_viewer.y = 50;
|
||||
|
||||
v_serial_str("[VIEWER] window: x=");
|
||||
v_serial_num(win_viewer.x);
|
||||
v_serial_str(" y=");
|
||||
v_serial_num(win_viewer.y);
|
||||
v_serial_str(" w=");
|
||||
v_serial_num(win_viewer.w);
|
||||
v_serial_str(" h=");
|
||||
v_serial_num(win_viewer.h);
|
||||
v_serial_str(" fit=");
|
||||
v_serial_num(fit_w);
|
||||
v_serial_str("x");
|
||||
v_serial_num(fit_h);
|
||||
v_serial_str("\n");
|
||||
|
||||
// Show and bring to front
|
||||
win_viewer.visible = true;
|
||||
wm_bring_to_front(&win_viewer);
|
||||
|
||||
v_serial_str("[VIEWER] z_index=");
|
||||
v_serial_num(win_viewer.z_index);
|
||||
v_serial_str(" visible=");
|
||||
v_serial_num(win_viewer.visible);
|
||||
v_serial_str(" focused=");
|
||||
v_serial_num(win_viewer.focused);
|
||||
v_serial_str("\n");
|
||||
v_serial_str("[VIEWER] window shown!\n");
|
||||
}
|
||||
|
||||
void viewer_init(void) {
|
||||
win_viewer.title = viewer_title;
|
||||
viewer_title[0] = 'V'; viewer_title[1] = 'i'; viewer_title[2] = 'e';
|
||||
viewer_title[3] = 'w'; viewer_title[4] = 'e'; viewer_title[5] = 'r';
|
||||
viewer_title[6] = 0;
|
||||
win_viewer.x = 100;
|
||||
win_viewer.y = 50;
|
||||
win_viewer.w = 500;
|
||||
win_viewer.h = 400;
|
||||
win_viewer.visible = false;
|
||||
win_viewer.paint = viewer_paint;
|
||||
win_viewer.handle_click = viewer_handle_click;
|
||||
win_viewer.handle_key = viewer_handle_key;
|
||||
win_viewer.handle_right_click = NULL;
|
||||
win_viewer.data = NULL;
|
||||
// Window is registered directly in wm_init's all_windows array
|
||||
v_serial_str("[VIEWER] init done, win_viewer paint=");
|
||||
v_serial_num(win_viewer.paint != NULL);
|
||||
v_serial_str("\n");
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
// viewer.h - Image Viewer app for BoredOS
|
||||
#ifndef VIEWER_H
|
||||
#define VIEWER_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_viewer;
|
||||
|
||||
void viewer_init(void);
|
||||
void viewer_open_file(const char *path); // Safe from interrupt context (deferred)
|
||||
void viewer_process_pending(void); // Call from main loop only
|
||||
|
||||
#endif // VIEWER_H
|
||||
169
src/kernel/wm.c
169
src/kernel/wm.c
|
|
@ -3,22 +3,16 @@
|
|||
#include "io.h"
|
||||
#include "cmd.h"
|
||||
#include "process.h"
|
||||
#include "syscall.h"
|
||||
#include "cli_apps/cli_utils.h"
|
||||
#include "explorer.h"
|
||||
#include "editor.h"
|
||||
#include "markdown.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "viewer.h"
|
||||
#include "wallpaper.h"
|
||||
#include "control_panel.h"
|
||||
#include "about.h"
|
||||
#include "minesweeper.h"
|
||||
#include "fat32.h"
|
||||
#include "nanojpeg.h"
|
||||
#include "memory_manager.h"
|
||||
#include "paint.h"
|
||||
#include "disk.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
|
|
@ -152,7 +146,7 @@ static void refresh_desktop_icons(void) {
|
|||
// Find if this icon still exists in the file list
|
||||
int found_idx = -1;
|
||||
for (int j = 0; j < file_count; j++) {
|
||||
if (!file_processed[j] && str_eq(desktop_icons[i].name, files[j].name) == 0) {
|
||||
if (!file_processed[j] && str_eq(desktop_icons[i].name, files[j].name) != 0) {
|
||||
found_idx = j;
|
||||
break;
|
||||
}
|
||||
|
|
@ -529,7 +523,7 @@ static int thumb_cache_next = 0; // Round-robin eviction
|
|||
|
||||
static uint32_t* thumb_cache_lookup(const char *path) {
|
||||
for (int i = 0; i < THUMB_CACHE_SIZE; i++) {
|
||||
if (thumb_cache[i].valid && str_eq(thumb_cache[i].path, path) == 0) {
|
||||
if (thumb_cache[i].valid && str_eq(thumb_cache[i].path, path) != 0) {
|
||||
return thumb_cache[i].pixels;
|
||||
}
|
||||
}
|
||||
|
|
@ -538,7 +532,7 @@ static uint32_t* thumb_cache_lookup(const char *path) {
|
|||
|
||||
static bool thumb_cache_is_failed(const char *path) {
|
||||
for (int i = 0; i < THUMB_CACHE_SIZE; i++) {
|
||||
if (thumb_cache[i].failed && str_eq(thumb_cache[i].path, path) == 0) {
|
||||
if (thumb_cache[i].failed && str_eq(thumb_cache[i].path, path) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -619,10 +613,10 @@ void draw_image_icon(int x, int y, const char *label) {
|
|||
|
||||
uint32_t *thumb = NULL;
|
||||
// Fast path: check hardcoded wallpaper names
|
||||
if (str_eq(label, "moon.jpg") == 0) thumb = wallpaper_get_thumb(0);
|
||||
else if (str_eq(label, "mountain.jpg") == 0) thumb = wallpaper_get_thumb(1);
|
||||
else if (str_eq(label, "moon") == 0) thumb = wallpaper_get_thumb(0);
|
||||
else if (str_eq(label, "mountain") == 0) thumb = wallpaper_get_thumb(1);
|
||||
if (str_eq(label, "moon.jpg") != 0) thumb = wallpaper_get_thumb(0);
|
||||
else if (str_eq(label, "mountain.jpg") != 0) thumb = wallpaper_get_thumb(1);
|
||||
else if (str_eq(label, "moon") != 0) thumb = wallpaper_get_thumb(0);
|
||||
else if (str_eq(label, "mountain") != 0) thumb = wallpaper_get_thumb(1);
|
||||
|
||||
if (!thumb) {
|
||||
if (str_ends_with(label, "moon.jpg")) thumb = wallpaper_get_thumb(0);
|
||||
|
|
@ -658,7 +652,7 @@ void draw_image_icon(int x, int y, const char *label) {
|
|||
// Draw thumbnail into icon - handle both 100x60 wallpaper thumbs and 48x48 dynamic thumbs
|
||||
bool is_wallpaper_thumb = false;
|
||||
if (str_ends_with(label, "moon.jpg") || str_ends_with(label, "mountain.jpg") ||
|
||||
str_eq(label, "moon") == 0 || str_eq(label, "mountain") == 0) {
|
||||
str_eq(label, "moon") != 0 || str_eq(label, "mountain") != 0) {
|
||||
is_wallpaper_thumb = true;
|
||||
}
|
||||
int dst_w = 44, dst_h = 44;
|
||||
|
|
@ -1567,7 +1561,9 @@ void wm_handle_click(int x, int y) {
|
|||
if (item == 0) { // About
|
||||
wm_bring_to_front(&win_about);
|
||||
} else if (item == 1) { // Settings
|
||||
wm_bring_to_front(&win_control_panel);
|
||||
Window *existing = wm_find_window_by_title("Settings");
|
||||
if (existing) wm_bring_to_front(existing);
|
||||
else process_create_elf("/bin/settings.elf", NULL);
|
||||
} else if (item == 2) { // Shutdown
|
||||
cli_cmd_shutdown(NULL);
|
||||
} else if (item == 3) { // Restart
|
||||
|
|
@ -1608,10 +1604,6 @@ void wm_handle_click(int x, int y) {
|
|||
// Reset window state on close
|
||||
if (topmost == &win_explorer) {
|
||||
explorer_reset();
|
||||
} else if (topmost == &win_control_panel) {
|
||||
control_panel_reset();
|
||||
} else if (topmost == &win_paint) {
|
||||
paint_reset();
|
||||
}
|
||||
} else if (y < topmost->y + 30) {
|
||||
// Dragging the title bar
|
||||
|
|
@ -1729,11 +1721,6 @@ void wm_handle_right_click(int x, int y) {
|
|||
// Mouse Down
|
||||
drag_start_x = mx;
|
||||
drag_start_y = my;
|
||||
|
||||
if (win_paint.focused && win_paint.visible) {
|
||||
paint_reset_last_pos();
|
||||
}
|
||||
|
||||
// Check Dock for app clicks (bottom of screen, floating)
|
||||
int dock_h = 60;
|
||||
int dock_y = sh - dock_h - 6; // Float above bottom
|
||||
|
|
@ -1763,11 +1750,6 @@ void wm_handle_right_click(int x, int y) {
|
|||
}
|
||||
} else if (right && !prev_right) {
|
||||
wm_handle_right_click(mx, my);
|
||||
} else if (left && win_paint.focused && win_paint.visible && !is_dragging) {
|
||||
int rel_x = mx - win_paint.x;
|
||||
int rel_y = my - win_paint.y;
|
||||
paint_handle_mouse(rel_x, rel_y);
|
||||
force_redraw = true;
|
||||
} else if (left && is_dragging && drag_window) {
|
||||
drag_window->x = mx - drag_offset_x;
|
||||
drag_window->y = my - drag_offset_y;
|
||||
|
|
@ -1855,7 +1837,9 @@ void wm_handle_right_click(int x, int y) {
|
|||
process_create_elf("/bin/notepad.elf", NULL);
|
||||
}
|
||||
} else if (str_starts_with(start_menu_pending_app, "Editor")) {
|
||||
wm_bring_to_front(&win_editor);
|
||||
Window *existing = wm_find_window_by_title("Txtedit");
|
||||
if (existing) wm_bring_to_front(existing);
|
||||
else process_create_elf("/bin/txtedit.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Terminal")) {
|
||||
cmd_reset(); wm_bring_to_front(&win_cmd);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Calculator")) {
|
||||
|
|
@ -1866,11 +1850,17 @@ void wm_handle_right_click(int x, int y) {
|
|||
process_create_elf("/bin/calculator.elf", NULL);
|
||||
}
|
||||
} else if (str_starts_with(start_menu_pending_app, "Minesweeper")) {
|
||||
wm_bring_to_front(&win_minesweeper);
|
||||
Window *existing = wm_find_window_by_title("Minesweeper");
|
||||
if (existing) wm_bring_to_front(existing);
|
||||
else process_create_elf("/bin/minesweeper.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Settings")) {
|
||||
wm_bring_to_front(&win_control_panel);
|
||||
Window *existing = wm_find_window_by_title("Settings");
|
||||
if (existing) wm_bring_to_front(existing);
|
||||
else process_create_elf("/bin/settings.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Paint")) {
|
||||
wm_bring_to_front(&win_paint);
|
||||
Window *existing = wm_find_window_by_title("Paint");
|
||||
if (existing) wm_bring_to_front(existing);
|
||||
else process_create_elf("/bin/paint.elf", NULL);
|
||||
} else if (str_starts_with(start_menu_pending_app, "About")) {
|
||||
wm_bring_to_front(&win_about);
|
||||
} else if (str_starts_with(start_menu_pending_app, "Shutdown")) {
|
||||
|
|
@ -1896,9 +1886,9 @@ void wm_handle_right_click(int x, int y) {
|
|||
} else if (str_ends_with(icon->name, "Calculator.shortcut")) {
|
||||
process_create_elf("/bin/calculator.elf", NULL); handled = true;
|
||||
} else if (str_ends_with(icon->name, "Minesweeper.shortcut")) {
|
||||
wm_bring_to_front(&win_minesweeper); handled = true;
|
||||
process_create_elf("/bin/minesweeper.elf", NULL); handled = true;
|
||||
} else if (str_ends_with(icon->name, "Settings.shortcut")) {
|
||||
wm_bring_to_front(&win_control_panel); handled = true;
|
||||
process_create_elf("/bin/settings.elf", NULL); handled = true;
|
||||
} else if (str_ends_with(icon->name, "Terminal.shortcut")) {
|
||||
wm_bring_to_front(&win_cmd); handled = true;
|
||||
} else if (str_ends_with(icon->name, "About.shortcut")) {
|
||||
|
|
@ -1908,7 +1898,7 @@ void wm_handle_right_click(int x, int y) {
|
|||
} else if (str_ends_with(icon->name, "Recycle Bin.shortcut")) {
|
||||
explorer_open_directory("/RecycleBin"); handled = true;
|
||||
} else if (str_ends_with(icon->name, "Paint.shortcut")) {
|
||||
wm_bring_to_front(&win_paint); handled = true;
|
||||
process_create_elf("/bin/paint.elf", NULL); handled = true;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
|
|
@ -1927,8 +1917,7 @@ void wm_handle_right_click(int x, int y) {
|
|||
if (fat32_is_directory(buf)) {
|
||||
explorer_open_directory(buf);
|
||||
} else {
|
||||
editor_open_file(buf);
|
||||
wm_bring_to_front(&win_editor);
|
||||
process_create_elf("/bin/txtedit.elf", buf);
|
||||
}
|
||||
pending_desktop_icon_click = -1;
|
||||
force_redraw = true;
|
||||
|
|
@ -1948,16 +1937,13 @@ void wm_handle_right_click(int x, int y) {
|
|||
if (str_ends_with(icon->name, ".elf")) {
|
||||
process_create_elf(path, NULL);
|
||||
} else if (str_ends_with(icon->name, ".pnt")) {
|
||||
paint_load(path);
|
||||
wm_bring_to_front(&win_paint);
|
||||
process_create_elf("/bin/paint.elf", path);
|
||||
} else if (str_ends_with(icon->name, ".md")) {
|
||||
markdown_open_file(path);
|
||||
wm_bring_to_front(&win_markdown);
|
||||
process_create_elf("/bin/markdown.elf", path);
|
||||
} else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) {
|
||||
viewer_open_file(path);
|
||||
process_create_elf("/bin/viewer.elf", path);
|
||||
} else {
|
||||
editor_open_file(path);
|
||||
wm_bring_to_front(&win_editor);
|
||||
process_create_elf("/bin/txtedit.elf", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2008,7 +1994,7 @@ void wm_handle_right_click(int x, int y) {
|
|||
if (from_desktop) {
|
||||
char path[128] = "/Desktop/";
|
||||
int p=9; int n=0; while(desktop_icons[i].name[n]) path[p++] = desktop_icons[i].name[n++]; path[p]=0;
|
||||
if (str_eq(path, drag_file_path) == 0) continue;
|
||||
if (str_eq(path, drag_file_path) != 0) continue;
|
||||
}
|
||||
|
||||
if (rect_contains(desktop_icons[i].x + 20, desktop_icons[i].y, 40, 40, mx, my)) {
|
||||
|
|
@ -2051,7 +2037,7 @@ void wm_handle_right_click(int x, int y) {
|
|||
if (desktop_auto_align && !msg_box_visible) {
|
||||
int new_idx = -1;
|
||||
for(int i=0; i<desktop_icon_count; i++) {
|
||||
if (str_eq(desktop_icons[i].name, filename) == 0) {
|
||||
if (str_eq(desktop_icons[i].name, filename) != 0) {
|
||||
new_idx = i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2075,7 +2061,7 @@ void wm_handle_right_click(int x, int y) {
|
|||
}
|
||||
} else if (!desktop_auto_align && !msg_box_visible) {
|
||||
for(int i=0; i<desktop_icon_count; i++) {
|
||||
if (str_eq(desktop_icons[i].name, filename) == 0) {
|
||||
if (str_eq(desktop_icons[i].name, filename) != 0) {
|
||||
desktop_icons[i].x = mx - 20;
|
||||
desktop_icons[i].y = my - 20;
|
||||
if (desktop_snap_to_grid) {
|
||||
|
|
@ -2096,7 +2082,7 @@ void wm_handle_right_click(int x, int y) {
|
|||
for(int i=0; i<desktop_icon_count; i++) {
|
||||
char path[128] = "/Desktop/";
|
||||
int p=9; int n=0; while(desktop_icons[i].name[n]) path[p++] = desktop_icons[i].name[n++]; path[p]=0;
|
||||
if (str_eq(path, drag_file_path) == 0) {
|
||||
if (str_eq(path, drag_file_path) != 0) {
|
||||
dragged_idx = i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2173,6 +2159,61 @@ void wm_handle_right_click(int x, int y) {
|
|||
force_redraw = true;
|
||||
}
|
||||
|
||||
// Send mouse events to userland windows
|
||||
if (left && !prev_left) {
|
||||
// Left button pressed - send MOUSE_DOWN event to topmost window
|
||||
Window *topmost = NULL;
|
||||
int topmost_z = -1;
|
||||
for (int w = 0; w < window_count; w++) {
|
||||
Window *win = all_windows[w];
|
||||
if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) {
|
||||
if (win->z_index > topmost_z) {
|
||||
topmost = win;
|
||||
topmost_z = win->z_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (topmost && topmost->data) {
|
||||
syscall_send_mouse_down_event(topmost, mx - topmost->x, my - topmost->y);
|
||||
}
|
||||
}
|
||||
|
||||
if (!left && prev_left) {
|
||||
// Left button released - send MOUSE_UP event to topmost window
|
||||
Window *topmost = NULL;
|
||||
int topmost_z = -1;
|
||||
for (int w = 0; w < window_count; w++) {
|
||||
Window *win = all_windows[w];
|
||||
if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) {
|
||||
if (win->z_index > topmost_z) {
|
||||
topmost = win;
|
||||
topmost_z = win->z_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (topmost && topmost->data) {
|
||||
syscall_send_mouse_up_event(topmost, mx - topmost->x, my - topmost->y);
|
||||
}
|
||||
}
|
||||
|
||||
if (dx != 0 || dy != 0) {
|
||||
// Mouse moved - send MOUSE_MOVE event to topmost window
|
||||
Window *topmost = NULL;
|
||||
int topmost_z = -1;
|
||||
for (int w = 0; w < window_count; w++) {
|
||||
Window *win = all_windows[w];
|
||||
if (win->visible && rect_contains(win->x, win->y, win->w, win->h, mx, my)) {
|
||||
if (win->z_index > topmost_z) {
|
||||
topmost = win;
|
||||
topmost_z = win->z_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (topmost && topmost->data) {
|
||||
syscall_send_mouse_move_event(topmost, mx - topmost->x, my - topmost->y, buttons);
|
||||
}
|
||||
}
|
||||
|
||||
prev_left = left;
|
||||
prev_right = right;
|
||||
|
||||
|
|
@ -2290,13 +2331,7 @@ void wm_init(void) {
|
|||
|
||||
cmd_init();
|
||||
explorer_init();
|
||||
editor_init();
|
||||
markdown_init();
|
||||
control_panel_init();
|
||||
about_init();
|
||||
minesweeper_init();
|
||||
paint_init();
|
||||
viewer_init();
|
||||
wallpaper_init();
|
||||
|
||||
refresh_desktop_icons();
|
||||
|
|
@ -2304,24 +2339,12 @@ void wm_init(void) {
|
|||
// Initialize z-indices
|
||||
win_cmd.z_index = 0;
|
||||
win_explorer.z_index = 1;
|
||||
win_editor.z_index = 2;
|
||||
win_markdown.z_index = 3;
|
||||
win_control_panel.z_index = 4;
|
||||
win_about.z_index = 5;
|
||||
win_minesweeper.z_index = 6;
|
||||
win_paint.z_index = 7;
|
||||
win_viewer.z_index = 8;
|
||||
win_about.z_index = 2;
|
||||
|
||||
all_windows[0] = &win_cmd;
|
||||
all_windows[1] = &win_explorer;
|
||||
all_windows[2] = &win_editor;
|
||||
all_windows[3] = &win_markdown;
|
||||
all_windows[4] = &win_control_panel;
|
||||
all_windows[5] = &win_about;
|
||||
all_windows[6] = &win_minesweeper;
|
||||
all_windows[7] = &win_paint;
|
||||
all_windows[8] = &win_viewer;
|
||||
window_count = 9;
|
||||
all_windows[2] = &win_about;
|
||||
window_count = 3;
|
||||
|
||||
// Only show Explorer on desktop (initially hidden)
|
||||
win_explorer.visible = false;
|
||||
|
|
@ -2330,11 +2353,7 @@ void wm_init(void) {
|
|||
|
||||
// Rest are hidden initially
|
||||
win_cmd.visible = false;
|
||||
win_editor.visible = false;
|
||||
win_markdown.visible = false;
|
||||
win_control_panel.visible = false;
|
||||
win_about.visible = false;
|
||||
win_minesweeper.visible = false;
|
||||
|
||||
force_redraw = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue