Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Linux framebuffer and input system #15

Merged
merged 2 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ libtwin.a_cflags-y += $(shell sdl2-config --cflags)
TARGET_LIBS += $(shell sdl2-config --libs)
endif

ifeq ($(CONFIG_BACKEND_FBDEV), y)
jserv marked this conversation as resolved.
Show resolved Hide resolved
BACKEND = fbdev
libtwin.a_files-y += backend/fbdev.c
libtwin.a_files-y += backend/linux_input.c
endif

# Standalone application

ifeq ($(CONFIG_DEMO_APPLICATIONS), y)
Expand Down
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,54 @@ benefiting the entire application stack.

## Build and Verify

### Prerequisites

`Mado` is built with a minimalist design in mind. However, its verification
relies on certain third-party packages for full functionality and access to all
its features. To ensure proper operation, the development environment should
have the [SDL2 library](https://www.libsdl.org/), [libjpeg](https://www.ijg.org/), and [libpng](https://github.com/pnggroup/libpng) installed.
its features. We encourage the development environment to be installed with all optional
packages, including [libjpeg](https://www.ijg.org/), [libpng](https://github.com/pnggroup/libpng),
and the [SDL2 library](https://www.libsdl.org/).
* macOS: `brew install sdl2 jpeg libpng`
* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev libjpeg-dev libpng-dev`

Configure via [Kconfiglib](https://pypi.org/project/kconfiglib/)
### Configuration

Configure via [Kconfiglib](https://pypi.org/project/kconfiglib/), you should select either SDL
video output or the Linux framebuffer.
```shell
$ make config
```

Build the library and demo program.
### Build and execution

Build the library and demo program:

```shell
$ make
```

Run sample `Mado` program:
To run demo program with SDL backend:

```shell
$ ./demo-sdl
```

Once the window appears, you should be able to move the windows and interact with the widgets.

To run demo program with the Linux framebuffer backend:

```shell
$ sudo ./demo-fbdev
```

Normal users don't have access to `/dev/fb0` so require `sudo`. Alternatively, you can add the user to the video group to avoid typing `sudo` every time:

```shell
$ sudo usermod -a -G video $USERNAME
```

In addition, the framebuffer device can be assigned via the environment variable `FRAMEBUFFER`.

## License

`Mado` is available under a MIT-style license, permitting liberal commercial use.
Expand Down
280 changes: 280 additions & 0 deletions backend/fbdev.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/*
* Twin - A Tiny Window System
* Copyright (c) 2024 National Cheng Kung University, Taiwan
* All rights reserved.
*/

#include <fcntl.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <linux/vt.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <twin.h>
#include <unistd.h>

#include "linux_input.h"
#include "twin_backend.h"
#include "twin_private.h"

#define FBDEV_NAME "FRAMEBUFFER"
#define FBDEV_DEFAULT "/dev/fb0"
#define SCREEN(x) ((twin_context_t *) x)->screen
#define PRIV(x) ((twin_fbdev_t *) ((twin_context_t *) x)->priv)

typedef struct {
twin_screen_t *screen;

/* Linux input system */
void *input;

/* Linux virtual terminal (VT) */
int vt_fd;
int vt_num;
bool vt_active;

/* Linux framebuffer */
int fb_fd;
struct fb_var_screeninfo fb_var;
struct fb_fix_screeninfo fb_fix;
uint16_t cmap[3][256];
uint8_t *fb_base;
size_t fb_len;
} twin_fbdev_t;

static void _twin_fbdev_put_span(twin_coord_t left,
jserv marked this conversation as resolved.
Show resolved Hide resolved
twin_coord_t top,
twin_coord_t right,
twin_argb32_t *pixels,
void *closure)
{
twin_screen_t *screen = SCREEN(closure);
twin_fbdev_t *tx = PRIV(closure);

if (tx->fb_base == MAP_FAILED)
return;

twin_coord_t width = right - left;
off_t off = top * screen->width + left;
uint32_t *dest =
(uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(uint32_t)));
memcpy(dest, pixels, width * sizeof(uint32_t));
}

static void twin_fbdev_get_screen_size(twin_fbdev_t *tx,
int *width,
int *height)
{
struct fb_var_screeninfo info;
ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &info);
*width = info.xres;
*height = info.yres;
}

static void twin_fbdev_damage(twin_screen_t *screen, twin_fbdev_t *tx)
{
int width, height;
twin_fbdev_get_screen_size(tx, &width, &height);
twin_screen_damage(tx->screen, 0, 0, width, height);
}

static bool twin_fbdev_work(void *closure)
{
twin_screen_t *screen = SCREEN(closure);

if (twin_screen_damaged(screen))
twin_screen_update(screen);
return true;
}

static bool twin_fbdev_apply_config(twin_fbdev_t *tx)
{
/* Read changable information of the framebuffer */
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) == -1) {
log_error("Failed to get framebuffer information");
return false;
}

/* Set the virtual screen size to be the same as the physical screen */
tx->fb_var.xres_virtual = tx->fb_var.xres;
tx->fb_var.yres_virtual = tx->fb_var.yres;
tx->fb_var.bits_per_pixel = 32;
if (ioctl(tx->fb_fd, FBIOPUT_VSCREENINFO, &tx->fb_var) < 0) {
log_error("Failed to set framebuffer mode");
return false;
}

/* Read changable information of the framebuffer again */
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) < 0) {
log_error("Failed to get framebuffer information");
return false;
}

/* Check bits per pixel */
if (tx->fb_var.bits_per_pixel != 32) {
log_error("Failed to set framebuffer bpp to 32");
return false;
}

/* Read unchangable information of the framebuffer */
ioctl(tx->fb_fd, FBIOGET_FSCREENINFO, &tx->fb_fix);

/* Align the framebuffer memory address with the page size */
off_t pgsize = getpagesize();
off_t start = (off_t) tx->fb_fix.smem_start & (pgsize - 1);

/* Round up the framebuffer memory size to match the page size */
tx->fb_len = start + (size_t) tx->fb_fix.smem_len + (pgsize - 1);
tx->fb_len &= ~(pgsize - 1);

/* Map framebuffer device to the virtual memory */
tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED,
tx->fb_fd, 0);
if (tx->fb_base == MAP_FAILED) {
log_error("Failed to mmap framebuffer");
return false;
}

return true;
}

static int twin_vt_open(int vt_num)
{
int fd;

char vt_dev[30] = {0};
snprintf(vt_dev, 30, "/dev/tty%d", vt_num);

fd = open(vt_dev, O_RDWR);
if (fd < 0) {
log_error("Failed to open %s", vt_dev);
}

return fd;
}

static bool twin_vt_setup(twin_fbdev_t *tx)
{
/* Open VT0 to inquire information */
if ((tx->vt_fd = twin_vt_open(0)) < -1) {
log_error("Failed to open VT0");
return false;
jserv marked this conversation as resolved.
Show resolved Hide resolved
}

/* Inquire for current VT number */
struct vt_stat vt;
if (ioctl(tx->vt_fd, VT_GETSTATE, &vt) == -1) {
log_error("Failed to get VT number");
return false;
}
tx->vt_num = vt.v_active;

/* Open the VT */
if ((tx->vt_fd = twin_vt_open(tx->vt_num)) < -1) {
return false;
}

/* Set VT to graphics mode to inhibit command-line text */
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) {
log_error("Failed to set KD_GRAPHICS mode");
return false;
}

return true;
}

twin_context_t *twin_fbdev_init(int width, int height)
{
char *fbdev_path = getenv(FBDEV_NAME);
if (!fbdev_path) {
log_info("Environment variable $FRAMEBUFFER not set, use %s by default",
FBDEV_DEFAULT);
fbdev_path = FBDEV_DEFAULT;
}

twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
if (!ctx)
return NULL;
ctx->priv = calloc(1, sizeof(twin_fbdev_t));
if (!ctx->priv)
return NULL;

twin_fbdev_t *tx = ctx->priv;

/* Open the framebuffer device */
tx->fb_fd = open(fbdev_path, O_RDWR);
if (tx->fb_fd == -1) {
log_error("Failed to open %s", fbdev_path);
goto bail;
}

/* Set up virtual terminal environment */
if (!twin_vt_setup(tx)) {
goto bail_fb_fd;
}

/* Apply configurations to the framebuffer device */
if (!twin_fbdev_apply_config(tx)) {
log_error("Failed to apply configurations to the framebuffer device");
goto bail_vt_fd;
}

/* Create TWIN screen */
ctx->screen =
twin_screen_create(width, height, NULL, _twin_fbdev_put_span, ctx);

/* Create Linux input system object */
tx->input = twin_linux_input_create(ctx->screen);
if (!tx->input) {
log_error("Failed to create Linux input system object");
goto bail_screen;
}

/* Setup file handler and work functions */
twin_set_work(twin_fbdev_work, TWIN_WORK_REDISPLAY, ctx);

return ctx;

bail_screen:
twin_screen_destroy(ctx->screen);
bail_vt_fd:
close(tx->vt_fd);
bail_fb_fd:
close(tx->fb_fd);
bail:
free(ctx->priv);
free(ctx);
return NULL;
}

static void twin_fbdev_configure(twin_context_t *ctx)
{
int width, height;
twin_fbdev_t *tx = ctx->priv;
twin_fbdev_get_screen_size(tx, &width, &height);
twin_screen_resize(ctx->screen, width, height);
}

static void twin_fbdev_exit(twin_context_t *ctx)
{
if (!ctx)
return;

twin_fbdev_t *tx = PRIV(ctx);
ioctl(tx->vt_fd, KDSETMODE, KD_TEXT);
munmap(tx->fb_base, tx->fb_len);
twin_linux_input_destroy(tx->input);
close(tx->vt_fd);
close(tx->fb_fd);
free(ctx->priv);
free(ctx);
}

/* Register the Linux framebuffer backend */

const twin_backend_t g_twin_backend = {
.init = twin_fbdev_init,
.configure = twin_fbdev_configure,
.exit = twin_fbdev_exit,
};
Loading