Skip to content

Commit 980bd4c

Browse files
authored
Merge pull request #15 from shengwen-tw/main
Support Linux framebuffer and input system
2 parents 0b1625a + 468eb6a commit 980bd4c

File tree

6 files changed

+517
-5
lines changed

6 files changed

+517
-5
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ libtwin.a_cflags-y += $(shell sdl2-config --cflags)
104104
TARGET_LIBS += $(shell sdl2-config --libs)
105105
endif
106106

107+
ifeq ($(CONFIG_BACKEND_FBDEV), y)
108+
BACKEND = fbdev
109+
libtwin.a_files-y += backend/fbdev.c
110+
libtwin.a_files-y += backend/linux_input.c
111+
endif
112+
107113
# Standalone application
108114

109115
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

README.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,54 @@ benefiting the entire application stack.
6060

6161
## Build and Verify
6262

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

70-
Configure via [Kconfiglib](https://pypi.org/project/kconfiglib/)
73+
### Configuration
74+
75+
Configure via [Kconfiglib](https://pypi.org/project/kconfiglib/), you should select either SDL
76+
video output or the Linux framebuffer.
7177
```shell
7278
$ make config
7379
```
7480

75-
Build the library and demo program.
81+
### Build and execution
82+
83+
Build the library and demo program:
84+
7685
```shell
7786
$ make
7887
```
7988

80-
Run sample `Mado` program:
89+
To run demo program with SDL backend:
90+
8191
```shell
8292
$ ./demo-sdl
8393
```
8494

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

97+
To run demo program with the Linux framebuffer backend:
98+
99+
```shell
100+
$ sudo ./demo-fbdev
101+
```
102+
103+
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:
104+
105+
```shell
106+
$ sudo usermod -a -G video $USERNAME
107+
```
108+
109+
In addition, the framebuffer device can be assigned via the environment variable `FRAMEBUFFER`.
110+
87111
## License
88112

89113
`Mado` is available under a MIT-style license, permitting liberal commercial use.

backend/fbdev.c

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/*
2+
* Twin - A Tiny Window System
3+
* Copyright (c) 2024 National Cheng Kung University, Taiwan
4+
* All rights reserved.
5+
*/
6+
7+
#include <fcntl.h>
8+
#include <linux/fb.h>
9+
#include <linux/kd.h>
10+
#include <linux/vt.h>
11+
#include <stdlib.h>
12+
#include <sys/ioctl.h>
13+
#include <sys/mman.h>
14+
#include <twin.h>
15+
#include <unistd.h>
16+
17+
#include "linux_input.h"
18+
#include "twin_backend.h"
19+
#include "twin_private.h"
20+
21+
#define FBDEV_NAME "FRAMEBUFFER"
22+
#define FBDEV_DEFAULT "/dev/fb0"
23+
#define SCREEN(x) ((twin_context_t *) x)->screen
24+
#define PRIV(x) ((twin_fbdev_t *) ((twin_context_t *) x)->priv)
25+
26+
typedef struct {
27+
twin_screen_t *screen;
28+
29+
/* Linux input system */
30+
void *input;
31+
32+
/* Linux virtual terminal (VT) */
33+
int vt_fd;
34+
int vt_num;
35+
bool vt_active;
36+
37+
/* Linux framebuffer */
38+
int fb_fd;
39+
struct fb_var_screeninfo fb_var;
40+
struct fb_fix_screeninfo fb_fix;
41+
uint16_t cmap[3][256];
42+
uint8_t *fb_base;
43+
size_t fb_len;
44+
} twin_fbdev_t;
45+
46+
static void _twin_fbdev_put_span(twin_coord_t left,
47+
twin_coord_t top,
48+
twin_coord_t right,
49+
twin_argb32_t *pixels,
50+
void *closure)
51+
{
52+
twin_screen_t *screen = SCREEN(closure);
53+
twin_fbdev_t *tx = PRIV(closure);
54+
55+
if (tx->fb_base == MAP_FAILED)
56+
return;
57+
58+
twin_coord_t width = right - left;
59+
off_t off = top * screen->width + left;
60+
uint32_t *dest =
61+
(uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(uint32_t)));
62+
memcpy(dest, pixels, width * sizeof(uint32_t));
63+
}
64+
65+
static void twin_fbdev_get_screen_size(twin_fbdev_t *tx,
66+
int *width,
67+
int *height)
68+
{
69+
struct fb_var_screeninfo info;
70+
ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &info);
71+
*width = info.xres;
72+
*height = info.yres;
73+
}
74+
75+
static void twin_fbdev_damage(twin_screen_t *screen, twin_fbdev_t *tx)
76+
{
77+
int width, height;
78+
twin_fbdev_get_screen_size(tx, &width, &height);
79+
twin_screen_damage(tx->screen, 0, 0, width, height);
80+
}
81+
82+
static bool twin_fbdev_work(void *closure)
83+
{
84+
twin_screen_t *screen = SCREEN(closure);
85+
86+
if (twin_screen_damaged(screen))
87+
twin_screen_update(screen);
88+
return true;
89+
}
90+
91+
static bool twin_fbdev_apply_config(twin_fbdev_t *tx)
92+
{
93+
/* Read changable information of the framebuffer */
94+
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) == -1) {
95+
log_error("Failed to get framebuffer information");
96+
return false;
97+
}
98+
99+
/* Set the virtual screen size to be the same as the physical screen */
100+
tx->fb_var.xres_virtual = tx->fb_var.xres;
101+
tx->fb_var.yres_virtual = tx->fb_var.yres;
102+
tx->fb_var.bits_per_pixel = 32;
103+
if (ioctl(tx->fb_fd, FBIOPUT_VSCREENINFO, &tx->fb_var) < 0) {
104+
log_error("Failed to set framebuffer mode");
105+
return false;
106+
}
107+
108+
/* Read changable information of the framebuffer again */
109+
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) < 0) {
110+
log_error("Failed to get framebuffer information");
111+
return false;
112+
}
113+
114+
/* Check bits per pixel */
115+
if (tx->fb_var.bits_per_pixel != 32) {
116+
log_error("Failed to set framebuffer bpp to 32");
117+
return false;
118+
}
119+
120+
/* Read unchangable information of the framebuffer */
121+
ioctl(tx->fb_fd, FBIOGET_FSCREENINFO, &tx->fb_fix);
122+
123+
/* Align the framebuffer memory address with the page size */
124+
off_t pgsize = getpagesize();
125+
off_t start = (off_t) tx->fb_fix.smem_start & (pgsize - 1);
126+
127+
/* Round up the framebuffer memory size to match the page size */
128+
tx->fb_len = start + (size_t) tx->fb_fix.smem_len + (pgsize - 1);
129+
tx->fb_len &= ~(pgsize - 1);
130+
131+
/* Map framebuffer device to the virtual memory */
132+
tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED,
133+
tx->fb_fd, 0);
134+
if (tx->fb_base == MAP_FAILED) {
135+
log_error("Failed to mmap framebuffer");
136+
return false;
137+
}
138+
139+
return true;
140+
}
141+
142+
static int twin_vt_open(int vt_num)
143+
{
144+
int fd;
145+
146+
char vt_dev[30] = {0};
147+
snprintf(vt_dev, 30, "/dev/tty%d", vt_num);
148+
149+
fd = open(vt_dev, O_RDWR);
150+
if (fd < 0) {
151+
log_error("Failed to open %s", vt_dev);
152+
}
153+
154+
return fd;
155+
}
156+
157+
static bool twin_vt_setup(twin_fbdev_t *tx)
158+
{
159+
/* Open VT0 to inquire information */
160+
if ((tx->vt_fd = twin_vt_open(0)) < -1) {
161+
log_error("Failed to open VT0");
162+
return false;
163+
}
164+
165+
/* Inquire for current VT number */
166+
struct vt_stat vt;
167+
if (ioctl(tx->vt_fd, VT_GETSTATE, &vt) == -1) {
168+
log_error("Failed to get VT number");
169+
return false;
170+
}
171+
tx->vt_num = vt.v_active;
172+
173+
/* Open the VT */
174+
if ((tx->vt_fd = twin_vt_open(tx->vt_num)) < -1) {
175+
return false;
176+
}
177+
178+
/* Set VT to graphics mode to inhibit command-line text */
179+
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) {
180+
log_error("Failed to set KD_GRAPHICS mode");
181+
return false;
182+
}
183+
184+
return true;
185+
}
186+
187+
twin_context_t *twin_fbdev_init(int width, int height)
188+
{
189+
char *fbdev_path = getenv(FBDEV_NAME);
190+
if (!fbdev_path) {
191+
log_info("Environment variable $FRAMEBUFFER not set, use %s by default",
192+
FBDEV_DEFAULT);
193+
fbdev_path = FBDEV_DEFAULT;
194+
}
195+
196+
twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
197+
if (!ctx)
198+
return NULL;
199+
ctx->priv = calloc(1, sizeof(twin_fbdev_t));
200+
if (!ctx->priv)
201+
return NULL;
202+
203+
twin_fbdev_t *tx = ctx->priv;
204+
205+
/* Open the framebuffer device */
206+
tx->fb_fd = open(fbdev_path, O_RDWR);
207+
if (tx->fb_fd == -1) {
208+
log_error("Failed to open %s", fbdev_path);
209+
goto bail;
210+
}
211+
212+
/* Set up virtual terminal environment */
213+
if (!twin_vt_setup(tx)) {
214+
goto bail_fb_fd;
215+
}
216+
217+
/* Apply configurations to the framebuffer device */
218+
if (!twin_fbdev_apply_config(tx)) {
219+
log_error("Failed to apply configurations to the framebuffer device");
220+
goto bail_vt_fd;
221+
}
222+
223+
/* Create TWIN screen */
224+
ctx->screen =
225+
twin_screen_create(width, height, NULL, _twin_fbdev_put_span, ctx);
226+
227+
/* Create Linux input system object */
228+
tx->input = twin_linux_input_create(ctx->screen);
229+
if (!tx->input) {
230+
log_error("Failed to create Linux input system object");
231+
goto bail_screen;
232+
}
233+
234+
/* Setup file handler and work functions */
235+
twin_set_work(twin_fbdev_work, TWIN_WORK_REDISPLAY, ctx);
236+
237+
return ctx;
238+
239+
bail_screen:
240+
twin_screen_destroy(ctx->screen);
241+
bail_vt_fd:
242+
close(tx->vt_fd);
243+
bail_fb_fd:
244+
close(tx->fb_fd);
245+
bail:
246+
free(ctx->priv);
247+
free(ctx);
248+
return NULL;
249+
}
250+
251+
static void twin_fbdev_configure(twin_context_t *ctx)
252+
{
253+
int width, height;
254+
twin_fbdev_t *tx = ctx->priv;
255+
twin_fbdev_get_screen_size(tx, &width, &height);
256+
twin_screen_resize(ctx->screen, width, height);
257+
}
258+
259+
static void twin_fbdev_exit(twin_context_t *ctx)
260+
{
261+
if (!ctx)
262+
return;
263+
264+
twin_fbdev_t *tx = PRIV(ctx);
265+
ioctl(tx->vt_fd, KDSETMODE, KD_TEXT);
266+
munmap(tx->fb_base, tx->fb_len);
267+
twin_linux_input_destroy(tx->input);
268+
close(tx->vt_fd);
269+
close(tx->fb_fd);
270+
free(ctx->priv);
271+
free(ctx);
272+
}
273+
274+
/* Register the Linux framebuffer backend */
275+
276+
const twin_backend_t g_twin_backend = {
277+
.init = twin_fbdev_init,
278+
.configure = twin_fbdev_configure,
279+
.exit = twin_fbdev_exit,
280+
};

0 commit comments

Comments
 (0)