summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--lib/Makefile1
-rw-r--r--lib/libc/src/hyra/socket.c13
-rw-r--r--lib/libgfx/Makefile1
-rw-r--r--lib/libgfx/include/libgfx/draw.h44
-rw-r--r--lib/libgfx/include/libgfx/gfx.h11
-rw-r--r--lib/libgfx/src/draw.c206
-rw-r--r--lib/liboda/Makefile29
-rw-r--r--lib/liboda/include/liboda/input.h74
-rw-r--r--lib/liboda/include/liboda/oda.h128
-rw-r--r--lib/liboda/include/liboda/odavar.h59
-rw-r--r--lib/liboda/include/liboda/types.h41
-rw-r--r--lib/liboda/src/input.c88
-rw-r--r--lib/liboda/src/oda.c77
-rw-r--r--lib/liboda/src/window.c461
-rw-r--r--share/docs/lib/liboda.md229
-rw-r--r--sys/include/sys/sched.h2
-rw-r--r--sys/include/sys/socket.h17
-rw-r--r--sys/include/sys/syscall.h1
-rw-r--r--sys/kern/kern_sched.c86
-rw-r--r--sys/kern/kern_socket.c211
-rw-r--r--sys/kern/kern_syscall.c1
22 files changed, 1750 insertions, 31 deletions
diff --git a/README.md b/README.md
index a8a31fe..b27656e 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ Documentation will be in the form of comments throughout the codebase and can al
- ``share/man/*``: Man pages
- ``share/contrib``: Information on contributing
- ``share/docs/kernel``: Kernel documentation
+- ``share/docs/lib``: Library documentation
Hyra running on bare metal:
--------------
diff --git a/lib/Makefile b/lib/Makefile
index 4ad104b..fc77815 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -7,3 +7,4 @@ ARGS = -I$(ROOT)/builddeps LDSCRIPT=$(LDSCRIPT) USRDIR=$(USRDIR) ROOT=$(ROOT)
all:
make -C libc/ $(ARGS)
make -C libgfx/ $(ARGS)
+ make -C liboda/ $(ARGS)
diff --git a/lib/libc/src/hyra/socket.c b/lib/libc/src/hyra/socket.c
index b3039f6..2a62541 100644
--- a/lib/libc/src/hyra/socket.c
+++ b/lib/libc/src/hyra/socket.c
@@ -71,3 +71,16 @@ connect(int socket, const struct sockaddr *address, socklen_t len)
{
return syscall(SYS_connect, socket, (uintptr_t)address, len);
}
+
+int
+setsockopt(int sockfd, int level, int name, const void *v, socklen_t len)
+{
+ return syscall(
+ SYS_setsockopt,
+ sockfd,
+ level,
+ name,
+ (uintptr_t)v,
+ len
+ );
+}
diff --git a/lib/libgfx/Makefile b/lib/libgfx/Makefile
index 1f866ac..12fdd9a 100644
--- a/lib/libgfx/Makefile
+++ b/lib/libgfx/Makefile
@@ -4,7 +4,6 @@ CFILES = $(shell find src/ -name "*.c")
OBJ = $(CFILES:.c=.o)
all: headers $(OBJ) build/libgfx.a
- echo "----------------------------------------"
echo $(USRDIR)
mv build/libgfx.a $(USRDIR)/lib/
cp -r include/ $(USRDIR)/include/
diff --git a/lib/libgfx/include/libgfx/draw.h b/lib/libgfx/include/libgfx/draw.h
index 60d2b24..4140593 100644
--- a/lib/libgfx/include/libgfx/draw.h
+++ b/lib/libgfx/include/libgfx/draw.h
@@ -34,7 +34,19 @@
#include <libgfx/gfx.h>
/* Shape types */
-#define SHAPE_SQUARE 0x00000000
+#define SHAPE_SQUARE 0x00000000
+#define SHAPE_SQUARE_BORDER 0x00000001
+
+/* Basic color defines */
+#define GFX_BLACK 0x000000
+#define GFX_RED 0xFF0000
+#define GFX_GREEN 0x00FF00
+#define GFX_BLUE 0x0000FF
+#define GFX_WHITE 0xFFFFFF
+#define GFX_PURPLE 0x800080
+#define GFX_YELLOW 0xFFFF00
+#define GFX_DARK 0x1D2021
+#define GFX_AQUA 0x427B58
/*
* Default shape initializer, something that
@@ -78,7 +90,37 @@ struct gfx_shape {
dimm_t height;
};
+/*
+ * A point or single pixel that
+ * may be plotted onto the screen.
+ *
+ * @x,y: Position of the point on the screen
+ * @rgb: Color of the point (RGB)
+ */
+struct gfx_point {
+ scrpos_t x, y;
+ color_t rgb;
+};
+
+/*
+ * Represents a rectangular region on
+ * the screen.
+ *
+ * @x,y: Position of this region on the screen
+ * @width: Region width
+ * @heght: Region height
+ */
+struct gfx_region {
+ scrpos_t x, y;
+ dimm_t width;
+ dimm_t height;
+};
+
int gfx_draw_shape(struct gfx_ctx *ctx, const struct gfx_shape *shape);
+int gfx_plot_point(struct gfx_ctx *ctx, const struct gfx_point *point);
+
+int gfx_copy_region(struct gfx_ctx *ctx, struct gfx_region *r, scrpos_t x, scrpos_t y);
+color_t gfx_get_pix(struct gfx_ctx *ctx, uint32_t x, uint32_t y);
__always_inline static inline size_t
gfx_io_index(struct gfx_ctx *ctx, scrpos_t x, scrpos_t y)
diff --git a/lib/libgfx/include/libgfx/gfx.h b/lib/libgfx/include/libgfx/gfx.h
index 3468571..67a1006 100644
--- a/lib/libgfx/include/libgfx/gfx.h
+++ b/lib/libgfx/include/libgfx/gfx.h
@@ -48,17 +48,6 @@ typedef uint32_t pixel_t;
typedef pixel_t color_t;
/*
- * Basic color defines
- */
-#define GFX_BLACK 0x000000
-#define GFX_RED 0xFF0000
-#define GFX_GREEN 0x00FF00
-#define GFX_BLUE 0x0000FF
-#define GFX_WHITE 0xFFFFFF
-#define GFX_PURPLE 0x800080
-#define GFX_YELLOW 0xFFFF00
-
-/*
* Represents cartesian x/y values
*/
typedef uint32_t cartpos_t;
diff --git a/lib/libgfx/src/draw.c b/lib/libgfx/src/draw.c
index 49f2f53..4df64a8 100644
--- a/lib/libgfx/src/draw.c
+++ b/lib/libgfx/src/draw.c
@@ -33,6 +33,39 @@
#include <libgfx/draw.h>
/*
+ * See if a pixel is within the bounds of
+ * the screen.
+ *
+ * @ctx: Graphics context pointer
+ * @x: X position to check
+ * @y: Y position to check
+ *
+ * Returns 0 if within bounds, otherwise
+ * a negative value.
+ */
+static int
+gfx_pixel_bounds(struct gfx_ctx *ctx, uint32_t x, uint32_t y)
+{
+ scrpos_t scr_width, scr_height;
+ struct fbattr fbdev;
+
+ if (ctx == NULL) {
+ return -1;
+ }
+
+ /* Grab screen dimensions */
+ fbdev = ctx->fbdev;
+ scr_width = fbdev.width;
+ scr_height = fbdev.height;
+
+ if (x >= scr_width || y >= scr_height) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
* Draw a classic square onto the screen.
*
* @ctx: Graphics context
@@ -42,33 +75,142 @@ static int
gfx_draw_square(struct gfx_ctx *ctx, const struct gfx_shape *shape)
{
struct fbattr fbdev;
+ struct gfx_point p;
off_t idx;
- scrpos_t scr_width, scr_height;
scrpos_t x, y;
if (ctx == NULL || shape == NULL) {
return -EINVAL;
}
- /* Grab screen dimensions */
- fbdev = ctx->fbdev;
- scr_width = fbdev.width;
- scr_height = fbdev.height;
-
for (x = shape->x; x < shape->x + shape->width; ++x) {
for (y = shape->y; y < shape->y + shape->height; ++y) {
- if (x >= scr_width || y >= scr_height) {
- break;
+ p.x = x;
+ p.y = y;
+ p.rgb = shape->color;
+ gfx_plot_point(ctx, &p);
+ }
+ }
+ return 0;
+}
+
+/*
+ * Draw a bordered square onto the screen.
+ *
+ * @ctx: Graphics context pointer
+ * @shape: Bordered square to draw
+ */
+static int
+gfx_draw_bsquare(struct gfx_ctx *ctx, const struct gfx_shape *shape)
+{
+ struct gfx_point p;
+ scrpos_t x_i, y_i;
+ scrpos_t x_f, y_f;
+ scrpos_t x, y;
+
+ if (ctx == NULL || shape == NULL) {
+ return -EINVAL;
+ }
+
+ x_i = shape->x;
+ y_i = shape->y;
+ x_f = shape->x + shape->width;
+ y_f = shape->y + shape->height;
+
+ /*
+ * Draw an unfilled square.
+ *
+ * If we are at the `y_i' or `y_f' position, draw
+ * pixels from 'x_i' to 'x_f'. If we are away
+ * from the `y_i' position, draw two pixels,
+ * one at `x_i' and the other at `x_f' for that
+ * current 'y' value.
+ */
+ for (y = y_i; y < y_f; ++y) {
+ for (x = x_i; x < x_f; ++x) {
+ p.x = x;
+ p.y = y;
+ p.rgb = shape->color;
+
+ /* Origin y, draw entire width */
+ if (y == y_i || y == y_f - 1) {
+ gfx_plot_point(ctx, &p);
+ continue;
}
- idx = gfx_io_index(ctx, x, y);
- ctx->io[idx] = shape->color;
+ p.x = x_i;
+ gfx_plot_point(ctx, &p);
+
+ p.x = x_f - 1;
+ gfx_plot_point(ctx, &p);
}
}
+
return 0;
}
/*
+ * Plot a single pixel (aka point) onto
+ * the screen.
+ *
+ * @ctx: The graphics context pointer
+ * @point: Point to plot
+ *
+ * Returns 0 on success, otherwise a less
+ * than zero value.
+ */
+int
+gfx_plot_point(struct gfx_ctx *ctx, const struct gfx_point *point)
+{
+ uint32_t index;
+
+ if (ctx == NULL || point == NULL) {
+ return -EINVAL;
+ }
+
+ /*
+ * Is this even a valid point on the screen for
+ * us to plot on?
+ */
+ if (gfx_pixel_bounds(ctx, point->x, point->y) < 0) {
+ return -1;
+ }
+
+ /* Plot it !! */
+ index = gfx_io_index(ctx, point->x, point->y);
+ ctx->io[index] = point->rgb;
+ return 0;
+}
+
+/*
+ * Grab the RGB value of a single pixel on
+ * the scren.
+ *
+ * @ctx: Graphics context pointer
+ * @x: X position to sample
+ * @y: Y position to sample
+ */
+color_t
+gfx_get_pix(struct gfx_ctx *ctx, uint32_t x, uint32_t y)
+{
+ const color_t ERROR_COLOR = GFX_BLACK;
+ uint32_t index;
+
+ /* The 'ctx' argument is required */
+ if (ctx == NULL) {
+ return ERROR_COLOR;
+ }
+
+ /* Are we within bounds of the screen */
+ if (gfx_pixel_bounds(ctx, x, y) < 0) {
+ return ERROR_COLOR;
+ }
+
+ index = gfx_io_index(ctx, x, y);
+ return ctx->io[index];
+}
+
+/*
* Draw a shape onto the screen
*
* @ctx: libgfx graphics context
@@ -90,7 +232,51 @@ gfx_draw_shape(struct gfx_ctx *ctx, const struct gfx_shape *shape)
switch (shape->type) {
case SHAPE_SQUARE:
return gfx_draw_square(ctx, shape);
+ case SHAPE_SQUARE_BORDER:
+ return gfx_draw_bsquare(ctx, shape);
}
return -1;
}
+
+/*
+ * Copy a region on one part of a screen to
+ * another part of a screen.
+ *
+ * @ctx: Graphics context pointer
+ * @r: Region to copy
+ * @x: X position for copy dest
+ * @y: Y position for copy dest
+ */
+int
+gfx_copy_region(struct gfx_ctx *ctx, struct gfx_region *r, scrpos_t x, scrpos_t y)
+{
+ struct gfx_point point;
+ color_t pixel;
+ scrpos_t src_cx, src_cy;
+ dimm_t w, h;
+
+ if (ctx == NULL || r == NULL) {
+ return -EINVAL;
+ }
+
+ w = r->width;
+ h = r->height;
+
+ for (int xoff = 0; xoff < w; ++xoff) {
+ for (int yoff = 0; yoff < h; ++yoff) {
+ /* Source position */
+ src_cx = r->x + xoff;
+ src_cy = r->y + yoff;
+
+ /* Plot the new pixel */
+ pixel = gfx_get_pix(ctx, src_cx, src_cy);
+ point.x = x + xoff;
+ point.y = y + yoff;
+ point.rgb = pixel;
+ gfx_plot_point(ctx, &point);
+ }
+ }
+
+ return 0;
+}
diff --git a/lib/liboda/Makefile b/lib/liboda/Makefile
new file mode 100644
index 0000000..5b4022c
--- /dev/null
+++ b/lib/liboda/Makefile
@@ -0,0 +1,29 @@
+CFLAGS = -c -fno-stack-protector -nostdlib -static \
+ -Iinclude/ -I$(USRDIR)/include/
+CFILES = $(shell find src/ -name "*.c")
+OBJ = $(CFILES:.c=.o)
+
+all: headers $(OBJ) build/liboda.a
+ echo $(USRDIR)
+ mv build/liboda.a $(USRDIR)/lib/
+ cp -r include/ $(USRDIR)/include/
+
+build/liboda.a:
+ mkdir -p build/
+ ar rcs build/liboda.a $(OBJ)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -Iinclude/ $< -o $@
+
+.PHONY: headers
+headers:
+ cp -rf include/* $(USRDIR)/include/
+
+.PHONY:
+build/:
+ mkdir -p build/
+
+.PHONY: clean
+clean:
+ rm -f $(OBJ)
+ rm -rf build/
diff --git a/lib/liboda/include/liboda/input.h b/lib/liboda/include/liboda/input.h
new file mode 100644
index 0000000..c74a304
--- /dev/null
+++ b/lib/liboda/include/liboda/input.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBODA_INPUT_H
+#define LIBODA_INPUT_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+/*
+ * Macros to help extract scancode and
+ * character
+ */
+#define ODA_SCANCODE(KEY) ((KEY) >> 8)
+#define ODA_KEYCHAR(KEY) ((char )(KEY) & 0xFF)
+
+/*
+ * Key defines
+ */
+#define ODA_KEY_OTHER 0x0000
+#define ODA_KEY_ESCAPE 0x0001
+#define ODA_KEY_TAB 0x0002
+#define ODA_KEY_BACKSPACE 0x0003
+
+/*
+ * Represents a key press
+ *
+ * @type: Key types (see ODA_KEY_*)
+ * @scancode: Scancode
+ * @ch: Character
+ */
+struct oda_key {
+ uint16_t type;
+ uint8_t scancode;
+ char ch;
+};
+
+/*
+ * ODA keyboard object for managing keyboard
+ * input.
+ */
+struct oda_kbd {
+ int(*handle_keyev)(struct oda_kbd *kbd, struct oda_key *key);
+};
+
+int oda_kbd_dispatch(struct oda_kbd *kbd);
+
+#endif /* !LIBODA_INPUT_H */
diff --git a/lib/liboda/include/liboda/oda.h b/lib/liboda/include/liboda/oda.h
new file mode 100644
index 0000000..9d96f2f
--- /dev/null
+++ b/lib/liboda/include/liboda/oda.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBODA_ODA_H
+#define LIBODA_ODA_H 1
+
+#include <sys/queue.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <liboda/types.h>
+#include <libgfx/gfx.h>
+#include <libgfx/draw.h>
+
+/*
+ * ODA representation of a window.
+ *
+ * @wid: Window ID (identifies the window)
+ * @surface: Window surface descriptor
+ * @session: Session this window belongs to
+ */
+struct oda_window {
+ odawid_t wid;
+ struct gfx_shape surface;
+ struct oda_state *session;
+ TAILQ_ENTRY(oda_window) link;
+};
+
+/*
+ * ODA session
+ *
+ * @winq: Window queue
+ * @gctx: Graphics context
+ * @cookie: State cookie (ODA_COOKIE)
+ */
+struct oda_state {
+ TAILQ_HEAD(, oda_window) winq;
+ struct gfx_ctx gctx;
+ uint32_t cookie;
+};
+
+/*
+ * ODA window attributes. Arguments to be
+ * passed to oda_window_new()
+ *
+ * @session: Current ODA session / state
+ * @parent: Window parent (NULL for root)
+ * @pg: Background color (0xRRGGBB)
+ * @x,y: Window position
+ * @w,h: Window width [w] and height [h]
+ */
+struct oda_wattr {
+ struct oda_state *session;
+ struct oda_window *parent;
+ odacolor_t bg;
+ odapos_t x, y;
+ odadimm_t w, h;
+};
+
+/*
+ * Arguments for oda_movewin() are stored
+ * within this structure to minimize the
+ * number of arguments within the function
+ * signature.
+ *
+ * @wp: Window to be moved
+ * @to_x: X position to move window to
+ * @to_y: Y position to move window to
+ */
+struct oda_movewin {
+ struct oda_window *wp;
+ odapos_t to_x;
+ odapos_t to_y;
+};
+
+/*
+ * A pixel point that can be plotted
+ * onto a window.
+ *
+ * @x,y: Point position
+ * @rgb: Color (RGB)
+ * @window: Window this will be plotted to
+ *
+ * Just set x, y, the color (rgb) then point it
+ * to a window!
+ */
+struct oda_point {
+ odapos_t x, y;
+ odacolor_t rgb;
+ struct oda_window *window;
+};
+
+int oda_reqwin(struct oda_wattr *params, struct oda_window **res);
+int oda_termwin(struct oda_state *state, struct oda_window *win);
+
+int oda_plotwin(struct oda_state *state, const struct oda_point *point);
+int oda_movewin(struct oda_state *state, struct oda_movewin *params);
+int oda_start_win(struct oda_state *state, struct oda_window *win);
+
+int oda_init(struct oda_state *res);
+int oda_shutdown(struct oda_state *state);
+
+#endif /* !LIBODA_ODA_H */
diff --git a/lib/liboda/include/liboda/odavar.h b/lib/liboda/include/liboda/odavar.h
new file mode 100644
index 0000000..d2dbe2e
--- /dev/null
+++ b/lib/liboda/include/liboda/odavar.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBODA_ODAVAR_H
+#define LIBODA_ODAVAR_H
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <liboda/oda.h>
+
+/*
+ * Default window attributes
+ */
+#define DEFAULT_WIN_HEIGHT 200
+#define DEFAULT_WIN_WIDTH 150
+
+/*
+ * Verify that ODA structures have been properly
+ * initialized before usage to prevent undefined
+ * behaviour.
+ */
+#define ODA_COOKIE 0xFAFECAD
+
+/*
+ * Verify an ODA cookie - internal usage
+ */
+__always_inline static inline int
+oda_cookie_verify(struct oda_state *state)
+{
+ return (state->cookie == ODA_COOKIE) ? 0 : -EFAULT;
+}
+
+#endif /* !LIBODA_ODAVAR_H */
diff --git a/lib/liboda/include/liboda/types.h b/lib/liboda/include/liboda/types.h
new file mode 100644
index 0000000..ec12330
--- /dev/null
+++ b/lib/liboda/include/liboda/types.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBODA_TYPE_H
+#define LIBODA_TYPE_H
+
+#include <stdint.h>
+
+typedef uint32_t odapos_t; /* X/Y positions */
+typedef uint32_t odapix_t; /* RGB pixel */
+typedef odapix_t odacolor_t; /* RGB color */
+typedef uint32_t odadimm_t; /* Dimensions */
+typedef uint32_t odawid_t; /* Window ID */
+
+#endif /* !LIBODA_TYPE_H */
diff --git a/lib/liboda/src/input.c b/lib/liboda/src/input.c
new file mode 100644
index 0000000..797a9d4
--- /dev/null
+++ b/lib/liboda/src/input.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/ascii.h>
+#include <sys/errno.h>
+#include <sys/param.h>
+#include <liboda/oda.h>
+#include <liboda/input.h>
+#include <stdint.h>
+#include <stdio.h>
+
+/*
+ * Convert key scancode/char values to fixed
+ * ODA key constants
+ */
+static inline uint16_t
+oda_map_key(const struct oda_key *key)
+{
+ uint16_t type = ODA_KEY_OTHER;
+
+ switch (key->ch) {
+ case ASCII_ESC:
+ type = ODA_KEY_ESCAPE;
+ break;
+ case ASCII_HT:
+ type = ODA_KEY_TAB;
+ break;
+ case ASCII_BS:
+ type = ODA_KEY_BACKSPACE;
+ break;
+ }
+
+ return type;
+}
+
+/*
+ * Dispatch keyboard events. This is typically
+ * called in an event loop so that keyboard events
+ * are handled per iteration.
+ *
+ * @kbd: Keyboard to monitor
+ */
+int
+oda_kbd_dispatch(struct oda_kbd *kbd)
+{
+ struct oda_key key;
+ int input;
+
+ if (kbd == NULL) {
+ return -EINVAL;
+ }
+
+ /* Attempt to grab the input */
+ if ((input = getchar()) < 0) {
+ return -EAGAIN;
+ }
+
+ key.scancode = ODA_SCANCODE(input);
+ key.ch = ODA_KEYCHAR(input);
+ key.type = oda_map_key(&key);
+ return kbd->handle_keyev(kbd, &key);
+}
diff --git a/lib/liboda/src/oda.c b/lib/liboda/src/oda.c
new file mode 100644
index 0000000..0eef523
--- /dev/null
+++ b/lib/liboda/src/oda.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/errno.h>
+#include <stdio.h>
+#include <libgfx/gfx.h>
+#include <liboda/oda.h>
+#include <liboda/odavar.h>
+
+#define oda_log(fmt, ...) printf("oda: " fmt, ##__VA_ARGS__)
+
+/*
+ * Initialize the OSMORA Display Architecture
+ * (ODA) library.
+ *
+ * @res: Initialized ODA state result
+ *
+ * Returns 0 on success, otherwise a less than
+ * zero value.
+ */
+int
+oda_init(struct oda_state *res)
+{
+ int error;
+
+ /* Ensure the argument is valid */
+ if (res == NULL) {
+ return -EINVAL;
+ }
+
+ /*
+ * If this state has already been initialized,
+ * assume programmer error / undefined behaviour
+ * and let them know.
+ */
+ if (oda_cookie_verify(res) == 0) {
+ oda_log("oda_init: 'res' already initialized\n");
+ return -EBUSY;
+ }
+
+ /* Initialize the graphics context */
+ error = gfx_init(&res->gctx);
+ if (error != 0) {
+ oda_log("oda_init: could not init graphics context\n");
+ return error;
+ }
+
+ TAILQ_INIT(&res->winq);
+ res->cookie = ODA_COOKIE;
+ return 0;
+}
diff --git a/lib/liboda/src/window.c b/lib/liboda/src/window.c
new file mode 100644
index 0000000..216b106
--- /dev/null
+++ b/lib/liboda/src/window.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <string.h>
+#include <liboda/oda.h>
+#include <liboda/odavar.h>
+#include <liboda/types.h>
+#include <libgfx/gfx.h>
+#include <libgfx/draw.h>
+
+/*
+ * The window cache is used to reduce how many
+ * calls to malloc() and free() are made during
+ * window creation and destruction.
+ */
+static TAILQ_HEAD(, oda_window) wcache;
+static uint32_t wcache_cookie = 0;
+static odawid_t next_wid = 1;
+
+/*
+ * Pop a window from the window cache.
+ * Returns NULL there are no more windows.
+ */
+static struct oda_window *
+oda_window_pop(void)
+{
+ struct oda_window *wdp;
+
+ if (wcache_cookie != ODA_COOKIE) {
+ TAILQ_INIT(&wcache);
+ wcache_cookie = ODA_COOKIE;
+ return NULL;
+ }
+
+ wdp = TAILQ_FIRST(&wcache);
+ TAILQ_REMOVE(&wcache, wdp, link);
+ return wdp;
+}
+
+/*
+ * Place a window into the window cache.
+ */
+static void
+oda_window_cache(struct oda_window *wdp)
+{
+ /* Ensure arg is valid */
+ if (wdp == NULL) {
+ return;
+ }
+
+ if (wcache_cookie != ODA_COOKIE) {
+ TAILQ_INIT(&wcache);
+ wcache_cookie = ODA_COOKIE;
+ }
+
+ TAILQ_INSERT_TAIL(&wcache, wdp, link);
+}
+
+/*
+ * Allocate an ODP window
+ *
+ * Returns NULL on failure
+ */
+static struct oda_window *
+oda_window_alloc(void)
+{
+ struct oda_window *wdp;
+
+ /*
+ * First check if there are any entries
+ * we can grab from the window cache.
+ */
+ wdp = oda_window_pop();
+ if (wdp != NULL) {
+ return wdp;
+ }
+
+ /* Allocate a new window */
+ wdp = malloc(sizeof(*wdp));
+ if (wdp == NULL) {
+ return NULL;
+ }
+
+ memset(wdp, 0, sizeof(*wdp));
+ wdp->wid = next_wid++;
+ return wdp;
+}
+
+/*
+ * Release a given ODA window descriptor
+ *
+ * @wdp: Window to free
+ */
+static void
+oda_window_release(struct oda_state *state, struct oda_window *wdp)
+{
+ if (wdp == NULL) {
+ return;
+ }
+
+ /*
+ * It is probably a good idea to ensure previous
+ * state other than the old window ID is reset
+ * and zeroed.
+ */
+ wdp->session = NULL;
+ memset(&wdp->surface, 0, sizeof(wdp->surface));
+
+ /*
+ * Now we can remove this window from the list
+ * of windows we are tracking and add it to the
+ * cache.
+ */
+ TAILQ_REMOVE(&state->winq, wdp, link);
+ oda_window_cache(wdp);
+}
+
+/*
+ * Check if a point is within the bounds of
+ * a surface.
+ *
+ * @wp: Surface to check with point
+ * @point: Point to check with surface
+ *
+ * Returns 0 if the check has passed,
+ * otherwise a less than zero value.
+ */
+static int
+oda_check_point(struct oda_window *wp, struct oda_point *point)
+{
+ struct gfx_shape *surf;
+ scrpos_t win_startx;
+ scrpos_t win_starty;
+ scrpos_t win_endx;
+ scrpos_t win_endy;
+
+ /* Compute start positions */
+ surf = &wp->surface;
+ win_startx = surf->x;
+ win_starty = surf->y;
+
+ /* Compute end positions */
+ win_endx = surf->x + surf->width;
+ win_endy = surf->y + surf->height;
+
+ /* Check X bounds */
+ if (point->x < win_startx || point->x > win_endx) {
+ return -1;
+ }
+
+ /* Check Y bounds */
+ if (point->y < win_starty || point->y > win_endy) {
+ return -1;
+ }
+
+ /* All good */
+ return 0;
+}
+
+/*
+ * Clean up after ourselves and release
+ * each entry of the wcache.
+ *
+ * Returns 0 on success.
+ */
+static int
+oda_free_wcache(void)
+{
+ struct oda_window *wdp, *next;
+
+ if (wcache_cookie != ODA_COOKIE) {
+ return -1;
+ }
+
+ /*
+ * Go through each entry and call free()
+ * on them.
+ */
+ wdp = TAILQ_FIRST(&wcache);
+ while (wdp != NULL) {
+ next = TAILQ_NEXT(wdp, link);
+ free(wdp);
+ wdp = next;
+ }
+ return 0;
+}
+
+/*
+ * Plot a pixel onto a window
+ *
+ * @state: ODA state pointer
+ * @point: Point to plot
+ *
+ * Returns 0 on success, otherwise a less than
+ * zero value.
+ *
+ * XXX: The x/y params in the 'point' argument must be
+ * relative to the start of the window. In other words,
+ * (0,0) refers to the top left corner of the window.
+ */
+int
+oda_plotwin(struct oda_state *state, const struct oda_point *point)
+{
+ struct gfx_point pixel;
+ struct oda_point point_new;
+ struct oda_window *window;
+ struct gfx_shape *surf;
+ odapos_t plotx, ploty;
+ int error;
+
+ if (state == NULL || point == NULL) {
+ return -EINVAL;
+ }
+
+ /* Validate cookie */
+ if ((error = oda_cookie_verify(state)) != 0) {
+ return error;
+ }
+
+ /* Try to grab the window */
+ if ((window = point->window) == NULL) {
+ return -EINVAL;
+ }
+
+ surf = &window->surface;
+ plotx = surf->x + point->x;
+ ploty = surf->y + point->y;
+
+ /*
+ * We are going to need to transform the coordinates
+ * as they are supposed to be coming in relative to
+ * the window bounds, e.g., (0,0) being the top left
+ * corner of a window.
+ */
+ point_new = *point;
+ point_new.x = plotx;
+ point_new.y = ploty;
+
+ /* Initialize the pixel to plot */
+ pixel.x = plotx;
+ pixel.y = ploty;
+ pixel.rgb = point->rgb;
+
+ /* Is the point within bounds? */
+ error = oda_check_point(window, &point_new);
+ if (error < 0) {
+ return error;
+ }
+
+ gfx_plot_point(&state->gctx, &pixel);
+ return 0;
+}
+
+/*
+ * Request a window from the OSMORA Display
+ * Architecture (ODA).
+ *
+ * @params: Arguments
+ * @res: Resulting pointer for new window
+ *
+ * Returns 0 on success, otherwise a less than
+ * zero value.
+ */
+int
+oda_reqwin(struct oda_wattr *params, struct oda_window **res)
+{
+ struct oda_window *wp;
+ struct gfx_shape *surf;
+ struct oda_state *session;
+ int error;
+
+ if (params == NULL || res == NULL) {
+ return -EINVAL;
+ }
+
+ /* Try to grab the current session */
+ if ((session = params->session) == NULL) {
+ return -EIO;
+ }
+
+ /* Verify that cookie! */
+ if ((error = oda_cookie_verify(session)) != 0) {
+ return error;
+ }
+
+ /* Allocate a new window */
+ wp = oda_window_alloc();
+ if (wp == NULL) {
+ return -ENOMEM;
+ }
+
+ /* Initialize the window */
+ memset(wp, 0, sizeof(*wp));
+ wp->session = session;
+ TAILQ_INSERT_TAIL(&session->winq, wp, link);
+
+ /* Fix up width/height params */
+ if (params->w == 0)
+ params->w = DEFAULT_WIN_WIDTH;
+ if (params->h == 0)
+ params->h = DEFAULT_WIN_HEIGHT;
+
+ /* Initialize the window surface */
+ surf = &wp->surface;
+ surf->color = params->bg;
+ surf->x = params->x;
+ surf->y = params->y;
+ surf->width = params->w;
+ surf->height = params->h;
+ surf->type = SHAPE_SQUARE;
+ *res = wp;
+ return 0;
+}
+
+/*
+ * Move a window to a new position on the
+ * screen.
+ *
+ * @state: ODA state pointer
+ * @params: Arguments to this function
+ */
+int
+oda_movewin(struct oda_state *state, struct oda_movewin *params)
+{
+ struct oda_window *win;
+ struct gfx_shape *wsurf;
+ struct gfx_region r;
+ odadimm_t w, h;
+ odapos_t x, y, x_f, y_f;
+ odapos_t to_x, to_y;
+ uint32_t i = 0;
+ int error;
+
+ /* Make sure arguments are valid */
+ if (state == NULL || params == NULL) {
+ return -EINVAL;
+ }
+
+ /* We need the window */
+ if ((win = params->wp) == NULL) {
+ return -EINVAL;
+ }
+
+ /* Verify state cookie */
+ if ((error = oda_cookie_verify(state)) != 0) {
+ return error;
+ }
+
+ wsurf = &win->surface;
+ to_x = params->to_x;
+ to_y = params->to_y;
+
+ r.x = wsurf->x;
+ r.y = wsurf->y;
+ r.width = wsurf->width;
+ r.height = wsurf->height;
+
+ /*
+ * We will copy the window to the new location and
+ * fill where the old window was with GFX_BLACK.
+ *
+ * TODO: Handle overlapping of windows
+ */
+ gfx_copy_region(&state->gctx, &r, to_x, to_y);
+ wsurf->x = to_x;
+ wsurf->y = to_y;
+ return 0;
+}
+
+/*
+ * Register a window into the current ODA state.
+ * Everytime a compositor requests a window, we
+ * must keep track of it.
+ *
+ * @state: ODA state pointer
+ * @win: Pointer of window to register
+ */
+int
+oda_start_win(struct oda_state *state, struct oda_window *win)
+{
+ int error;
+
+ if (state == NULL || win == NULL) {
+ return -EINVAL;
+ }
+
+ /* Make sure the state is valid */
+ if ((error = oda_cookie_verify(state)) != 0) {
+ return error;
+ }
+
+ gfx_draw_shape(&state->gctx, &win->surface);
+ return 0;
+}
+
+/*
+ * Terminate a running window
+ *
+ * @state: ODA state pointer
+ * @win: Win pointer
+ *
+ * Returns 0 on success, otherwise a less than
+ * zero value.
+ *
+ * TODO: Cleanup screen
+ */
+int
+oda_termwin(struct oda_state *state, struct oda_window *win)
+{
+ int error;
+
+ if (state == NULL || win == NULL) {
+ return -EINVAL;
+ }
+
+ /* Validate the cookie */
+ if ((error = oda_cookie_verify(state)) != 0) {
+ return error;
+ }
+
+ oda_window_release(state, win);
+ return 0;
+}
+
+/*
+ * Shutdown the ODA library
+ */
+int
+oda_shutdown(struct oda_state *state)
+{
+ return oda_free_wcache();
+}
diff --git a/share/docs/lib/liboda.md b/share/docs/lib/liboda.md
new file mode 100644
index 0000000..e5345a4
--- /dev/null
+++ b/share/docs/lib/liboda.md
@@ -0,0 +1,229 @@
+# The OSMORA Display Architecture (ODA)
+
+Written by Ian M. Moffett
+
+## Introduction
+
+The OSMORA Display Architecture (ODA) is a protocol describing how
+a compositor should create and manage graphical windows. A graphical
+session in Hyra consists of a compositor, window management system and
+management of user input.
+
+There are many existing display architectures out there. Take for instace, the X11
+protocol. X11 for example, has the concept of an "X server" in which window managers
+or certain graphical programs would connect to as a means of performing interprocess
+communication (IPC). The idea is that X will service graphics related requests from
+the window manager or compositor.
+
+While this works just fine, the highly centralized nature of X11 or similar protocols
+may complicate the flexibility of the graphics stack. On the other hand with ODA, a
+compositor links with the ODA library and becomes the server for window managers running
+on the system. The idea of ODA is to minimize complexity while preserving flexibility.
+
+Additionally, the compositor should provide its own API for use by window management
+software.
+
+## Scope
+
+This document serves to describe common OSMORA Display Architecture (ODA) concepts
+as well as providing basic example C sources showcasing how compositors and window
+managers may interface with the described APIs for various needs.
+
+## Terminology
+
+### OSMORA Display Architecture (ODA):
+
+OSMORA protocol defining common interfaces for C2W and
+W2C interactions.
+
+### Compositor to window (C2W):
+
+Describes the direction of communication originating from
+a compositor and directed towards a specific window:
+
+``COMPOSITOR -> WINDOW``
+
+### Window to compositor (W2C):
+
+Describes the direction of communication originating from
+a specific window to a compositor running on the system:
+
+``WINDOW -> COMPOSITOR``
+
+## Architecture
+
+```
++-------------+
+| LIBGFX |
++-------------+
+ ^
+ | linked with libgfx
+ V
++-------------+
+| COMPOSITOR |
++-------------+ <---+ signal
+ | | | |
+ | | | | c2w: <winop: e.g., close>
+ | | | | w2c: <winop to accept: e.g., close>
+ WIN WIN WIN <---+
+```
+
+### C2W signal flow:
+
+```
+-- CLOSE SIGNAL EXAMPLE --
+
+WINDOW RECEIVES CLOSE SIGNAL
+ |
+ ECHO BACK TO COMPOSITOR
+ |
+ YES ---+--- NO
+ | |
+ | V
+ | nothing happens
+ |
+ V
+ window is destroyed by compositor
+```
+
+## Libgfx
+
+The Hyra userspace includes ``libgfx`` which is a low-level graphics library aimed
+to facilitate drawing on the screen and performing various graphical operations while
+decoupling from the concept of compositors, windows and the ODA as a whole. In other words,
+libgfx has no knowledge of anything outside of the framebuffer and itself.
+
+The following is an example of how one may draw a yellow square at
+x/y (30,0):
+
+```c
+#include <libgfx/gfx.h> /* Common routines/defs */
+#include <libgfx/draw.h> /* Drawing related routines/defs */
+
+int
+main(void)
+{
+ struct gfx_ctx ctx;
+ struct gfx_shape sh = GFX_SHAPE_DEFAULT;
+ int error;
+
+ /* Set the x/y and color */
+ sh.x = 30;
+ sh.y = 0;
+ sh.color = GFX_YELLOW
+
+ error = gfx_init(&ctx);
+ if (error < 0) {
+ printf("gfx_init returned %d\n", error);
+ return error;
+ }
+
+ /* Draw the square and cleanup */
+ gfx_draw_shape(&ctx, &sh);
+ gfx_cleanup(&ctx);
+ return 0;
+}
+```
+
+## Liboda
+
+The Hyra userspace includes the ``liboda`` library which includes various
+interfaces conforming to the OSMORA Display Architecture (ODA).
+
+### Linking a compositor with liboda
+
+In order for an ODA compliant compositor to reference library
+symbols for ``liboda``, it should use the following linker flags:
+
+``... -loda -logfx``
+
+### ODA Session
+
+For the ODA library to keep track of state, it relies on an ``oda_state``
+structure defined in ``liboda/oda.h``. Additionally, in order for any ODA
+library calls to be made, the compositor must initialize the library with
+``oda_init()`` like in the following example:
+
+```c
+#include <liboda/oda.h>
+
+struct oda_state state;
+int error;
+
+/* Returns 0 on success */
+error = oda_init(&state);
+...
+```
+
+Upon failure of ``oda_init()`` a negative POSIX errno value
+is returned (see ``sys/errno.h``).
+
+### Using liboda to request windows
+
+A compositor may request windows from the ODA by using
+``oda_reqwin()`` like in the following example:
+
+```c
+#include <liboda/oda.h>
+
+...
+struct oda_wattr wattr;
+struct oda_state state;
+
+...
+
+wattr.session = &state;
+wattr.parent = NULL;
+wattr.bg = GFX_YELLOW;
+wattr.x = 200;
+wattr.y = 150;
+wattr.w = 120;
+wattr.h = 300;
+
+/* Returns 0 on success */
+error = oda_reqwin(&wattr, &win);
+```
+
+Arguments passed to ``oda_reqwin()`` are first stored in a ``struct oda_wattr``
+structure to minimize the number of parameters used in the function signature.
+
+Upon failure of ``oda_reqwin()`` a negative POSIX errno value
+is returned (see ``sys/errno.h``).
+
+### Input management
+
+The liboda library additionally provides an input API that facilitates
+management of user input (e.g., keyboard).
+
+(See ``liboda/input.h``)
+
+#### The ``oda_key`` structure
+
+ODA provides a standard description of keys received from a keyboard
+device. Keyboard input is represented by the ``oda_key`` structure shown
+below:
+
+```c
+struct oda_key {
+ uint16_t type;
+ uint8_t scancode;
+ char ch;
+ ...
+};
+```
+
+The ``type`` field describes the key type, valid key types can be
+found within the ``ODA_KEY_*`` definitions in ``liboda/input.h``.
+
+The ``scancode`` field contains the raw keyboard scancode received
+from the device.
+
+The ``ch`` field contains the ASCII representation of the input received
+from the device.
+
+#### The ``oda_kbd`` structure
+
+ODA represents keyboard devices through the ``oda_kbd`` structure found
+in ``liboda/input.h``. This structure contains callbacks that are set up
+by the compositor to be invoked when their respective keyboard related
+event occurs.
diff --git a/sys/include/sys/sched.h b/sys/include/sys/sched.h
index 7a859b2..19ceb7e 100644
--- a/sys/include/sys/sched.h
+++ b/sys/include/sys/sched.h
@@ -33,6 +33,7 @@
#include <sys/proc.h>
#include <sys/cdefs.h>
#include <sys/limits.h>
+#include <sys/time.h>
/*
* Scheduler CPU information
@@ -66,6 +67,7 @@ void sched_stat(struct sched_stat *statp);
void sched_init(void);
void sched_yield(void);
+void sched_suspend(struct proc *td, const struct timeval *tv);
void sched_detach(struct proc *td);
__dead void sched_enter(void);
diff --git a/sys/include/sys/socket.h b/sys/include/sys/socket.h
index c82ae4e..1a33108 100644
--- a/sys/include/sys/socket.h
+++ b/sys/include/sys/socket.h
@@ -69,6 +69,10 @@ typedef uint32_t socklen_t;
/* Socket types */
#define SOCK_STREAM 1
+/* Socket option names */
+#define SO_RCVTIMEO 0 /* Max time recv(2) waits */
+#define _SO_MAX 1 /* Max socket options */
+
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
@@ -149,12 +153,22 @@ struct cmsg_list {
uint8_t is_init : 1;
};
+/*
+ * Socket option that may be applied to
+ * sockets on the system.
+ */
+struct sockopt {
+ socklen_t len;
+ char data[];
+};
+
struct ksocket {
int sockfd;
union {
struct sockaddr sockaddr;
struct sockaddr_un un;
};
+ struct sockopt *opt[_SO_MAX];
struct proc *owner;
struct cmsg_list cmsg_list;
struct sockbuf buf;
@@ -170,10 +184,13 @@ scret_t sys_send(struct syscall_args *scargs);
scret_t sys_recvmsg(struct syscall_args *scargs);
scret_t sys_sendmsg(struct syscall_args *scargs);
+scret_t sys_setsockopt(struct syscall_args *scargs);
#endif /* _KERNEL */
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
+
+int setsockopt(int sockfd, int level, int name, const void *v, socklen_t len);
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
ssize_t send(int sockfd, const void *buf, size_t size, int flags);
diff --git a/sys/include/sys/syscall.h b/sys/include/sys/syscall.h
index 9798d80..f53db8f 100644
--- a/sys/include/sys/syscall.h
+++ b/sys/include/sys/syscall.h
@@ -66,6 +66,7 @@
#define SYS_sendmsg 25
#define SYS_recvmsg 26
#define SYS_connect 27
+#define SYS_setsockopt 28
#if defined(_KERNEL)
/* Syscall return value and arg type */
diff --git a/sys/kern/kern_sched.c b/sys/kern/kern_sched.c
index 23a1ebb..9c5e215 100644
--- a/sys/kern/kern_sched.c
+++ b/sys/kern/kern_sched.c
@@ -315,6 +315,92 @@ proc_unpin(struct proc *td)
td->flags &= ~PROC_PINNED;
}
+/*
+ * Suspend a process for a specified amount
+ * of time. This calling process will yield for
+ * the amount of time specified in 'tv'
+ *
+ * @td: Process to suspend (NULL for current)
+ * @tv: Time value to use
+ *
+ * XXX: 'tv' being NULL is equivalent to calling
+ * sched_detach()
+ */
+void
+sched_suspend(struct proc *td, const struct timeval *tv)
+{
+ struct timer tmr;
+ const time_t USEC_PER_SEC = 1000000;
+ ssize_t usec;
+ time_t usec_cur, usec_tmp;
+ bool have_timer = true;
+ tmrr_status_t tmr_status;
+
+ if (td == NULL)
+ td = this_td();
+ if (__unlikely(td == NULL))
+ return;
+
+ if (tv == NULL) {
+ sched_detach(td);
+ return;
+ }
+
+ /*
+ * Now, we need a generic timer so that we can compute
+ * how much time has elapsed since this process has
+ * requested to be suspended. However, we cannot assume
+ * that it would be present. If the lookup fails, all we
+ * can do is try to estimate how much time went by which
+ * works fine too, just not as accurate.
+ */
+ tmr_status = req_timer(TIMER_GP, &tmr);
+ if (tmr_status != TMRR_SUCCESS) {
+ have_timer = false;
+ }
+
+ /* We need microsecond precision */
+ if (tmr.get_time_sec == NULL) {
+ have_timer = false;
+ }
+
+ /*
+ * Compute the max time in microseconds that
+ * we will wait. We are using both tv->tv_sec
+ * and tv->tv_usec
+ */
+ usec = tv->tv_usec;
+ usec += tv->tv_sec * USEC_PER_SEC;
+ usec_cur = (have_timer) ? tmr.get_time_usec() : 0;
+
+ for (;;) {
+ sched_yield();
+
+ /*
+ * If we have a timer in our paws, compute how much
+ * time went by. Otherwise we estimate by subtracting
+ * the scheduler quantum.
+ *
+ * XXX: The timing here works decently as intended. However,
+ * it would be nice to smoothen out any jitter. Such can
+ * probably be done by subtracting 'usec' by the exponential
+ * moving average of 'usec_tmp' rather than the raw original
+ * value.
+ */
+ if (have_timer) {
+ usec_tmp = (tmr.get_time_usec() - usec_cur);
+ } else {
+ usec_tmp = DEFAULT_TIMESLICE_USEC;
+ }
+
+ /* We are done here! */
+ usec -= usec_tmp;
+ if (usec <= 0) {
+ break;
+ }
+ }
+}
+
void
sched_init(void)
{
diff --git a/sys/kern/kern_socket.c b/sys/kern/kern_socket.c
index 1717f5a..d0fbe19 100644
--- a/sys/kern/kern_socket.c
+++ b/sys/kern/kern_socket.c
@@ -31,6 +31,7 @@
#include <sys/sio.h>
#include <sys/systm.h>
#include <sys/proc.h>
+#include <sys/time.h>
#include <sys/namei.h>
#include <sys/sched.h>
#include <sys/errno.h>
@@ -47,6 +48,17 @@
static struct vops socket_vops;
/*
+ * This table maps socket option names to
+ * lengths of their underlying structure.
+ *
+ * This is used for bounds/length checking within
+ * setsockopt()
+ */
+static size_t sockopt_lentab[_SO_MAX] = {
+ [ SO_RCVTIMEO ] = sizeof(struct timeval)
+};
+
+/*
* Get a kernel socket structure from a
* file descriptor.
*
@@ -101,6 +113,7 @@ static int
socket_reclaim(struct vnode *vp)
{
struct ksocket *ksock;
+ struct sockopt *opt;
/* Is this even a socket? */
if (vp->type != VSOCK) {
@@ -112,6 +125,15 @@ socket_reclaim(struct vnode *vp)
return -EIO;
}
+ /* Free up any used options */
+ for (int i = 0; i < _SO_MAX; ++i) {
+ opt = ksock->opt[i];
+ if (opt != NULL) {
+ dynfree(opt);
+ ksock->opt[i] = NULL;
+ }
+ }
+
fd_close(ksock->sockfd);
mutex_free(ksock->mtx);
dynfree(ksock);
@@ -185,6 +207,46 @@ connect_domain(int sockfd, struct ksocket *ksock, struct sockaddr_un *un)
}
/*
+ * Wait until data is received for the
+ * recv() function.
+ *
+ * @sockfd: Socket we are waiting on
+ *
+ * Returns zero on success, otherwise a less
+ * than zero value is returned.
+ */
+static int
+socket_rx_wait(int sockfd)
+{
+ struct ksocket *ksock;
+ struct sockopt *opt;
+ struct timeval tv;
+ int error;
+
+ if (ksock == NULL) {
+ return -EINVAL;
+ }
+
+ error = get_ksock(sockfd, &ksock);
+ if (error < 0) {
+ return error;
+ }
+
+ /*
+ * If the socket does not have this option set,
+ * we will assume that there is no timeout value.
+ */
+ opt = ksock->opt[SO_RCVTIMEO];
+ if (opt == NULL) {
+ return 0;
+ }
+
+ memcpy(&tv, opt->data, opt->len);
+ sched_suspend(NULL, &tv);
+ return 0;
+}
+
+/*
* Send data to socket - POSIX send(2) core
*
* @sockfd: File descriptor that backs this socket
@@ -254,7 +316,7 @@ send(int sockfd, const void *buf, size_t size, int flags)
* @size: Size of the buffer
* @flags: Optional flags
*
- * Returns zero on success, otherwise a less
+ * Returns length on success, otherwise a less
* than zero errno.
*/
ssize_t
@@ -264,6 +326,7 @@ recv(int sockfd, void *buf, size_t len, int flags)
struct sockbuf *sbuf;
struct netbuf *netbuf;
size_t head;
+ ssize_t retval = len;
int error;
/* Length cannot be zero */
@@ -283,7 +346,8 @@ recv(int sockfd, void *buf, size_t len, int flags)
if (netbuf->len == 0) {
sbuf->head = 0;
sbuf->tail = 0;
- return -EAGAIN;
+ retval = -EAGAIN;
+ goto done;
}
if (len > netbuf->len) {
@@ -292,10 +356,10 @@ recv(int sockfd, void *buf, size_t len, int flags)
head = sbuf->head;
memcpy(buf, &netbuf->data[head], len);
-
sbuf->head = (sbuf->head + len) % NETBUF_LEN;
+done:
mutex_release(ksock->mtx);
- return len;
+ return retval;
}
/*
@@ -322,6 +386,7 @@ socket(int domain, int type, int protocol)
goto fail;
}
+ memset(ksock, 0, sizeof(*ksock));
sbuf = &ksock->buf;
sbuf->head = 0;
sbuf->tail = 0;
@@ -394,6 +459,68 @@ bind(int sockfd, const struct sockaddr *addr, socklen_t len)
}
/*
+ * Set socket options - POSIX setsockopt(3) core
+ *
+ * @sockfd: File descriptor of socket
+ * @level: Protocol level
+ * @v: Options value
+ * @len: Length of data pointed to by 'v'
+ */
+int
+setsockopt(int sockfd, int level, int name, const void *v, socklen_t len)
+{
+ struct ksocket *ksock;
+ struct sockopt *opt;
+ size_t exp_len;
+ int error;
+
+ /* Must have a valid fd */
+ if (sockfd < 0) {
+ return -EBADF;
+ }
+
+ /* Ensure value and length are valid */
+ if (v == NULL || len == 0) {
+ return -EINVAL;
+ }
+
+ /* Verify the name */
+ if (name >= _SO_MAX) {
+ return -EINVAL;
+ }
+
+ /* Grab a new socket */
+ if ((error = get_ksock(sockfd, &ksock)) < 0) {
+ return error;
+ }
+
+ /* Clamp the input length as needed */
+ exp_len = sockopt_lentab[name];
+ if (len > exp_len) {
+ len = exp_len;
+ }
+
+ /*
+ * Here we will grab the socket options. If it is
+ * NULL, we'll need to allocate one.
+ */
+ if ((opt = ksock->opt[name]) == NULL) {
+ opt = dynalloc(sizeof(*opt) + len);
+
+ if (opt == NULL) {
+ return -ENOMEM;
+ }
+
+ opt->len = len;
+ ksock->opt[name] = opt;
+ }
+
+ memcpy(opt->data, v, len);
+ opt->len = len;
+ return 0;
+}
+
+/*
* Connect to a socket
*
* @sockfd: File descriptor to connect
@@ -614,10 +741,29 @@ sys_recv(struct syscall_args *scargs)
return -ENOBUFS;
}
- do {
+ for (;;) {
error = recv(sockfd, buf, len, flags);
- sched_yield();
- } while (error == -EAGAIN);
+ if (error <= 0 && error != -EAGAIN) {
+ break;
+ }
+
+ /*
+ * Wait for data to be ready on the socket.
+ * If a less than zero value is returned, don't
+ * handle timeouts.
+ */
+ error = socket_rx_wait(sockfd);
+ if (error < 0) {
+ continue;
+ }
+
+ /* Try one more time, obey timeout */
+ error = recv(sockfd, buf, len, flags);
+ if (error == -EAGAIN) {
+ return error;
+ }
+ break;
+ }
if (error < 0) {
pr_error("sys_recv: recv() fail (fd=%d)\n", sockfd);
@@ -776,7 +922,7 @@ sys_sendmsg(struct syscall_args *scargs)
}
/*
- * connnect(3) syscall
+ * connect(3) syscall
*
* arg0: sockfd
* arg1: address
@@ -807,6 +953,55 @@ sys_connect(struct syscall_args *scargs)
return connect(sockfd, sockaddr, len);
}
+/*
+ * POSIX setsockopt(3) syscall
+ *
+ * arg0: sockfd
+ * arg1: level
+ * arg2: name
+ * arg3: data
+ * arg4: len
+ */
+scret_t
+sys_setsockopt(struct syscall_args *scargs)
+{
+ int sockfd = scargs->arg0;
+ int level = scargs->arg1;
+ int name = scargs->arg2;
+ void *u_data = (void *)scargs->arg3;
+ socklen_t len = scargs->arg4;
+ void *data;
+ size_t exp_len;
+ int retval;
+
+ /* Verify that the name is correct */
+ if (name >= _SO_MAX) {
+ return -EINVAL;
+ }
+
+ /* Clamp length as needed */
+ exp_len = sockopt_lentab[name];
+ if (len > exp_len) {
+ len = exp_len;
+ }
+
+ data = dynalloc(len);
+ if (data == NULL) {
+ return -ENOMEM;
+ }
+
+ /* Grab data from userland */
+ retval = copyin(u_data, data, len);
+ if (retval < 0) {
+ dynfree(data);
+ return retval;
+ }
+
+ retval = setsockopt(sockfd, level, name, data, len);
+ dynfree(data);
+ return retval;
+}
+
static struct vops socket_vops = {
.read = NULL,
.write = NULL,
diff --git a/sys/kern/kern_syscall.c b/sys/kern/kern_syscall.c
index 576b7aa..ba70b12 100644
--- a/sys/kern/kern_syscall.c
+++ b/sys/kern/kern_syscall.c
@@ -68,6 +68,7 @@ scret_t(*g_sctab[])(struct syscall_args *) = {
sys_sendmsg, /* SYS_sendmsg */
sys_recvmsg, /* SYS_recvmsg */
sys_connect, /* SYS_connect */
+ sys_setsockopt, /* SYS_setsockopt */
};
const size_t MAX_SYSCALLS = NELEM(g_sctab);