Upload files to "/"
This commit is contained in:
parent
2ab956aa33
commit
5d35c2d8a2
824
penpad_fb2.c
Normal file
824
penpad_fb2.c
Normal file
@ -0,0 +1,824 @@
|
||||
/*
|
||||
* penpad_fb2.c
|
||||
*
|
||||
* Simple native notepad for Kindle Scribe:
|
||||
* - Reads stylus events from /dev/input/stylus
|
||||
* - Draws into an 8bpp /dev/fb0 framebuffer
|
||||
* - Maintains a 1-bit offscreen bitmap (g_bitmap) for PBM export
|
||||
* - Also exports PNG via LodePNG from the same bitmap
|
||||
* - Double-tap gestures:
|
||||
* top-left = save (PBM + PNG) to /mnt/us/simple_notes
|
||||
* top-right = exit
|
||||
* bottom-right = reset page (clear) without saving
|
||||
* - Optional: load a P1 PBM passed as argv[1] into framebuffer + g_bitmap
|
||||
*
|
||||
* Coordinate mapping:
|
||||
* - Stylus X: RAW_X_MIN..RAW_X_MAX mapped to 0..(g_fb_width-1), reversed axis
|
||||
* - Stylus Y: RAW_Y_MIN..RAW_Y_MAX mapped to 0..(g_fb_height-1), inverted axis
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <linux/fb.h>
|
||||
|
||||
// ---- LodePNG for PNG export ----
|
||||
#include "lodepng.h"
|
||||
|
||||
// DEV devices
|
||||
#define DEV_PATH_STYLUS "/dev/input/stylus"
|
||||
#define DEV_PATH_TOUCH "/dev/input/touch"
|
||||
#define DEV_PATH_FB "/dev/fb0"
|
||||
|
||||
// Path definitions
|
||||
#define US_HOME_PATH "/mnt/us/extensions/simplenotes"
|
||||
#define US_SAVE_PATH "/mnt/us/simplenotes"
|
||||
|
||||
// Raw stylus ranges (from full calibration)
|
||||
#define RAW_X_MIN 0
|
||||
#define RAW_X_MAX 15624 // left ~15624, right ~0
|
||||
#define RAW_Y_MIN 0
|
||||
#define RAW_Y_MAX 20832 // top ~20832, bottom ~0
|
||||
|
||||
|
||||
// Stroke thickness in pixels
|
||||
#define STROKE_PX 3
|
||||
|
||||
// Height of the top/bottom gesture bar in pixels
|
||||
#define TOPBAR_H 100
|
||||
|
||||
// Refresh interval in ms
|
||||
#define REFRESH_INTERVAL_MS 45
|
||||
|
||||
// Double-tap window and feedback delay
|
||||
#define DOUBLE_TAP_WINDOW_SEC 2
|
||||
#define SAVE_FEEDBACK_SLEEP_SEC 1
|
||||
|
||||
// ============================================================================
|
||||
// Framebuffer context
|
||||
// ============================================================================
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
struct fb_fix_screeninfo finfo;
|
||||
struct fb_var_screeninfo vinfo;
|
||||
uint8_t *fbp;
|
||||
size_t screensize;
|
||||
} fb_ctx_t;
|
||||
|
||||
// ============================================================================
|
||||
// Global drawing / state
|
||||
// ============================================================================
|
||||
|
||||
static uint8_t *g_bitmap = NULL; // 1-bit (stored as bytes) full-screen: 1=black, 0=white
|
||||
static int g_fb_width = 0;
|
||||
static int g_fb_height = 0;
|
||||
|
||||
typedef enum {
|
||||
TAP_NONE = 0,
|
||||
TAP_LEFT,
|
||||
TAP_RIGHT,
|
||||
TAP_BOTTOM_RIGHT
|
||||
} tap_zone_t;
|
||||
|
||||
static tap_zone_t g_last_tap_zone = TAP_NONE;
|
||||
static int g_tap_count = 0;
|
||||
static time_t g_last_tap_timestamp = 0;
|
||||
|
||||
// ============================================================================
|
||||
// Time helpers
|
||||
// ============================================================================
|
||||
|
||||
static uint64_t now_ms(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (uint64_t)tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Framebuffer helpers
|
||||
// ============================================================================
|
||||
|
||||
static void framebuffer_clear_white(uint8_t *fbp, long screensize) {
|
||||
memset(fbp, 0xFF, screensize); // white = 0xFF
|
||||
}
|
||||
|
||||
// Initialize framebuffer context for /dev/fb0:
|
||||
// - opens device
|
||||
// - mmaps framebuffer to ctx->fbp
|
||||
// - clears screen to white
|
||||
// - allocates 1-bit g_bitmap backing store
|
||||
static int fb_init(fb_ctx_t *ctx, const char *path) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->fd = open(path, O_RDWR);
|
||||
if (ctx->fd < 0) {
|
||||
perror("open fb");
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(ctx->fd, FBIOGET_FSCREENINFO, &ctx->finfo) < 0) {
|
||||
perror("FBIOGET_FSCREENINFO");
|
||||
close(ctx->fd);
|
||||
ctx->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(ctx->fd, FBIOGET_VSCREENINFO, &ctx->vinfo) < 0) {
|
||||
perror("FBIOGET_VSCREENINFO");
|
||||
close(ctx->fd);
|
||||
ctx->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
ctx->screensize = ctx->finfo.line_length * ctx->vinfo.yres;
|
||||
ctx->fbp = mmap(NULL, ctx->screensize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, ctx->fd, 0);
|
||||
if (ctx->fbp == MAP_FAILED) {
|
||||
perror("mmap fb");
|
||||
ctx->fbp = NULL;
|
||||
close(ctx->fd);
|
||||
ctx->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initial blank overlay: clear whole framebuffer to white once
|
||||
framebuffer_clear_white(ctx->fbp, ctx->screensize);
|
||||
msync(ctx->fbp, ctx->screensize, MS_SYNC);
|
||||
|
||||
fprintf(stderr, "fb: %ux%u, bpp=%u, line_length=%u\n",
|
||||
ctx->vinfo.xres, ctx->vinfo.yres,
|
||||
ctx->vinfo.bits_per_pixel, ctx->finfo.line_length);
|
||||
|
||||
if (ctx->vinfo.bits_per_pixel != 8) {
|
||||
fprintf(stderr, "WARNUNG: bits_per_pixel != 8, Code erwartet 8bpp!\n");
|
||||
}
|
||||
|
||||
g_fb_width = ctx->vinfo.xres;
|
||||
g_fb_height = ctx->vinfo.yres;
|
||||
|
||||
g_bitmap = calloc(g_fb_width * g_fb_height, 1);
|
||||
if (!g_bitmap) {
|
||||
perror("calloc bitmap");
|
||||
munmap(ctx->fbp, ctx->screensize);
|
||||
ctx->fbp = NULL;
|
||||
close(ctx->fd);
|
||||
ctx->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clear framebuffer and logical bitmap
|
||||
static void fb_clear(fb_ctx_t *ctx, uint8_t value) {
|
||||
if (!ctx->fbp) return;
|
||||
memset(ctx->fbp, value, ctx->screensize);
|
||||
if (g_bitmap) {
|
||||
memset(g_bitmap, 0, g_fb_width * g_fb_height);
|
||||
}
|
||||
}
|
||||
|
||||
static void fb_close(fb_ctx_t *ctx) {
|
||||
if (g_bitmap) {
|
||||
free(g_bitmap);
|
||||
g_bitmap = NULL;
|
||||
}
|
||||
|
||||
if (ctx->fbp && ctx->fbp != MAP_FAILED) {
|
||||
munmap(ctx->fbp, ctx->screensize);
|
||||
ctx->fbp = NULL;
|
||||
}
|
||||
if (ctx->fd >= 0) {
|
||||
close(ctx->fd);
|
||||
ctx->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Global eInk refresh
|
||||
static void fb_flush(void) {
|
||||
system("eips -r 2>/dev/null");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Drawing primitives
|
||||
// ============================================================================
|
||||
|
||||
// Set a single pixel in framebuffer + g_bitmap
|
||||
static void fb_set_pixel(fb_ctx_t *ctx, int x, int y, uint8_t value) {
|
||||
if (!ctx->fbp) return;
|
||||
if (x < 0 || x >= g_fb_width || y < 0 || y >= g_fb_height) return;
|
||||
|
||||
size_t offset = (size_t)y * ctx->finfo.line_length + (size_t)x;
|
||||
if (offset >= ctx->screensize) return;
|
||||
ctx->fbp[offset] = value;
|
||||
|
||||
if (g_bitmap) {
|
||||
size_t idx = (size_t)y * g_fb_width + (size_t)x;
|
||||
if (idx < (size_t)g_fb_width * g_fb_height) {
|
||||
g_bitmap[idx] = (value == 0x00) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a thick block around (cx, cy) more efficiently
|
||||
static void fb_draw_thick_point(fb_ctx_t *ctx, int cx, int cy, uint8_t value) {
|
||||
if (!ctx || !ctx->fbp) return;
|
||||
|
||||
// Fast path: 1-pixel stroke → behave exactly like before
|
||||
if (STROKE_PX <= 1) {
|
||||
fb_set_pixel(ctx, cx, cy, value);
|
||||
return;
|
||||
}
|
||||
|
||||
int half = STROKE_PX / 2;
|
||||
|
||||
int x0 = cx - half;
|
||||
int x1 = cx + half;
|
||||
int y0 = cy - half;
|
||||
int y1 = cy + half;
|
||||
|
||||
// Completely outside the screen?
|
||||
if (x1 < 0 || x0 >= g_fb_width || y1 < 0 || y0 >= g_fb_height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp to framebuffer bounds
|
||||
if (x0 < 0) x0 = 0;
|
||||
if (y0 < 0) y0 = 0;
|
||||
if (x1 >= g_fb_width) x1 = g_fb_width - 1;
|
||||
if (y1 >= g_fb_height) y1 = g_fb_height - 1;
|
||||
|
||||
const size_t total_bits = (size_t)g_fb_width * g_fb_height;
|
||||
|
||||
for (int y = y0; y <= y1; ++y) {
|
||||
// Framebuffer row start
|
||||
size_t fb_off = (size_t)y * ctx->finfo.line_length + (size_t)x0;
|
||||
if (fb_off >= ctx->screensize) {
|
||||
// Safety; should not happen after clamping
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t *fb_row = ctx->fbp + fb_off;
|
||||
|
||||
// Bitmap row start
|
||||
size_t bmp_idx = (size_t)y * g_fb_width + (size_t)x0;
|
||||
|
||||
for (int x = x0; x <= x1; ++x) {
|
||||
*fb_row++ = value;
|
||||
|
||||
if (g_bitmap && bmp_idx < total_bits) {
|
||||
// Keep same semantics as fb_set_pixel: 0x00 = black = 1
|
||||
g_bitmap[bmp_idx] = (value == 0x00) ? 1 : 0;
|
||||
}
|
||||
++bmp_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Bresenham in full-res pixel space
|
||||
static void fb_draw_line(fb_ctx_t *ctx, int x0, int y0, int x1, int y1, uint8_t value) {
|
||||
int dx = abs(x1 - x0);
|
||||
int sx = (x0 < x1) ? 1 : -1;
|
||||
int dy = -abs(y1 - y0);
|
||||
int sy = (y0 < y1) ? 1 : -1;
|
||||
int err = dx + dy;
|
||||
|
||||
int x = x0;
|
||||
int y = y0;
|
||||
|
||||
while (1) {
|
||||
fb_draw_thick_point(ctx, x, y, value);
|
||||
if (x == x1 && y == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 >= dy) { err += dy; x += sx; }
|
||||
if (e2 <= dx) { err += dx; y += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PBM load
|
||||
// ============================================================================
|
||||
|
||||
// Load a full-screen P1 PBM into:
|
||||
// - g_bitmap (1=black, 0=white)
|
||||
// - framebuffer (0x00=black, 0xFF=white)
|
||||
static int load_pbm_into_fb(fb_ctx_t *ctx, const char *path) {
|
||||
if (!ctx || !ctx->fbp) {
|
||||
fprintf(stderr, "load_pbm_into_fb: framebuffer not initialized\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
perror("fopen load_pbm_into_fb");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read magic "P1"
|
||||
char magic[3] = {0};
|
||||
if (!fgets(magic, sizeof(magic), f)) {
|
||||
fprintf(stderr, "load_pbm_into_fb: failed to read magic\n");
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (magic[0] != 'P' || magic[1] != '1') {
|
||||
fprintf(stderr, "load_pbm_into_fb: not a P1 PBM file\n");
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read width/height (no comments in our own PBMs)
|
||||
int w = 0, h = 0;
|
||||
if (fscanf(f, "%d %d", &w, &h) != 2) {
|
||||
fprintf(stderr, "load_pbm_into_fb: failed to read width/height\n");
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (w != g_fb_width || h != g_fb_height) {
|
||||
fprintf(stderr,
|
||||
"load_pbm_into_fb: size mismatch (pbm=%dx%d, fb=%dx%d)\n",
|
||||
w, h, g_fb_width, g_fb_height);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clear current content (framebuffer + g_bitmap)
|
||||
fb_clear(ctx, 0xFF); // white background
|
||||
|
||||
// Read each pixel (0/1) and map: 1 -> black (0x00), 0 -> white (0xFF)
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
int bit = 0;
|
||||
if (fscanf(f, "%d", &bit) != 1) {
|
||||
fprintf(stderr,
|
||||
"load_pbm_into_fb: unexpected EOF or parse error\n");
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
uint8_t v = bit ? 0x00 : 0xFF;
|
||||
fb_set_pixel(ctx, x, y, v);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
// Ensure data is pushed out to the device
|
||||
msync(ctx->fbp, ctx->screensize, MS_SYNC);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PNG export using LodePNG
|
||||
// ============================================================================
|
||||
|
||||
// Convert g_bitmap (1-bit as bytes) to RGBA and write PNG.
|
||||
static int save_page_png(const char *png_path) {
|
||||
if (!g_bitmap || g_fb_width <= 0 || g_fb_height <= 0) return -1;
|
||||
|
||||
size_t w = (size_t)g_fb_width;
|
||||
size_t h = (size_t)g_fb_height;
|
||||
size_t num_pixels = w * h;
|
||||
size_t buf_size = num_pixels * 4;
|
||||
|
||||
unsigned char *image = (unsigned char *)malloc(buf_size);
|
||||
if (!image) {
|
||||
perror("malloc PNG buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_pixels; i++) {
|
||||
uint8_t bit = g_bitmap[i]; // 1=black, 0=white
|
||||
uint8_t c = bit ? 0x00 : 0xFF; // black or white
|
||||
size_t idx = i * 4;
|
||||
image[idx + 0] = c; // R
|
||||
image[idx + 1] = c; // G
|
||||
image[idx + 2] = c; // B
|
||||
image[idx + 3] = 0xFF; // A
|
||||
}
|
||||
|
||||
unsigned error = lodepng_encode32_file(png_path, image, (unsigned)w, (unsigned)h);
|
||||
free(image);
|
||||
|
||||
if (error) {
|
||||
fprintf(stderr, "LodePNG error %u: %s\n", error, lodepng_error_text(error));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PBM save
|
||||
// ============================================================================
|
||||
|
||||
|
||||
// Save current g_bitmap as a P1 PBM in /mnt/us/simple_notes with timestamped name.
|
||||
// Additionally write a PNG with the same basename via LodePNG.
|
||||
static void save_page(void) {
|
||||
if (!g_bitmap || g_fb_width <= 0 || g_fb_height <= 0) return;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *tm = localtime(&now);
|
||||
|
||||
mkdir(US_SAVE_PATH, 0777);
|
||||
|
||||
char file_ts[50];
|
||||
char pbm_path[160];
|
||||
char png_path[160];
|
||||
|
||||
// Base name with .pbm
|
||||
snprintf(file_ts, sizeof(file_ts),
|
||||
"%04d%02d%02d-%02d%02d%02d",
|
||||
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
//build pbm file path -> only one pbm ist stored because of its size atm so filename latest.fb
|
||||
snprintf(pbm_path, sizeof(pbm_path), "%s/bin/latest.fb", US_SAVE_PATH);
|
||||
|
||||
//build png fale using timestamp and global save path
|
||||
snprintf(png_path, sizeof(png_path), "%s/note-%s.png", US_SAVE_PATH, file_ts);
|
||||
|
||||
|
||||
// --- PBM write as before ---
|
||||
FILE *f = fopen(pbm_path, "w");
|
||||
if (!f) {
|
||||
perror("fopen save_page (PBM)");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f, "P1\n");
|
||||
fprintf(f, "%d %d\n", g_fb_width, g_fb_height);
|
||||
for (int y = 0; y < g_fb_height; y++) {
|
||||
for (int x = 0; x < g_fb_width; x++) {
|
||||
size_t idx = (size_t)y * g_fb_width + (size_t)x;
|
||||
int v = (idx < (size_t)g_fb_width * g_fb_height && g_bitmap[idx]) ? 1 : 0;
|
||||
fputc(v ? '1' : '0', f);
|
||||
if (x < g_fb_width - 1) fputc(' ', f);
|
||||
}
|
||||
fputc('\n', f);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
// --- PNG write via LodePNG ---
|
||||
int png_ok = save_page_png(png_path);
|
||||
|
||||
char msg[256];
|
||||
if (png_ok == 0) {
|
||||
snprintf(msg, sizeof(msg),
|
||||
"eips 7 1 \"Saved: note-%s.png\"",
|
||||
file_ts);
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg),
|
||||
"eips 7 1 \"Failed saving PNG - %s\"",
|
||||
file_ts);
|
||||
}
|
||||
system(msg);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UI / title
|
||||
// ============================================================================
|
||||
|
||||
// Draw a small "X" icon in the topbar (currently unused, kept for reference)
|
||||
static void draw_topbar_icons(fb_ctx_t *ctx) {
|
||||
int margin = 10;
|
||||
int size = TOPBAR_H - 5 * margin;
|
||||
if (size <= 0) return;
|
||||
|
||||
int x1 = g_fb_width - margin - size;
|
||||
int y1 = margin;
|
||||
int x2 = g_fb_width - margin;
|
||||
int y2 = margin + size;
|
||||
|
||||
fb_draw_line(ctx, x1, y1, x2, y2, 0x00);
|
||||
fb_draw_line(ctx, x1, y2, x2, y1, 0x00);
|
||||
}
|
||||
|
||||
// Draw a small PNG icon using eips at absolute pixel coordinates.
|
||||
static void draw_icon_png(const char *file, int x, int y)
|
||||
{
|
||||
char path[160];
|
||||
|
||||
// Build: /mnt/us/penpad/assets/<file>
|
||||
snprintf(path, sizeof(path), "%s/assets/%s", US_HOME_PATH, file);
|
||||
|
||||
char cmd[512];
|
||||
// -g : PNG
|
||||
// -x, -y : position
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"eips -g %s -x %d -y %d",
|
||||
path, x, y);
|
||||
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
static void printTitle(void) {
|
||||
system("eips -a 40");
|
||||
system("eips 0 0 \" \"");
|
||||
system("eips 12 1 \"simplenotes by maru21\"");
|
||||
|
||||
draw_icon_png("exit.png", 1800, 1);
|
||||
draw_icon_png("reset.png", 1795, 2410);
|
||||
draw_icon_png("save.png", 100, 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mapping raw stylus → screen pixels
|
||||
// ============================================================================
|
||||
|
||||
// X: raw left ~15624, right ~0 → reversed axis.
|
||||
// raw range RAW_X_MIN..RAW_X_MAX mapped to pixel 0..(xres-1).
|
||||
static int map_raw_to_px(int rawx, const fb_ctx_t *ctx) {
|
||||
if (rawx < RAW_X_MIN) rawx = RAW_X_MIN;
|
||||
if (rawx > RAW_X_MAX) rawx = RAW_X_MAX;
|
||||
int range = RAW_X_MAX - RAW_X_MIN;
|
||||
if (range <= 0) return 0;
|
||||
|
||||
// reverse axis: RAW_X_MAX → x=0, RAW_X_MIN → x=max
|
||||
int reversed = RAW_X_MAX - rawx;
|
||||
int px = reversed * (int)(ctx->vinfo.xres - 1) / (range + 1);
|
||||
if (px < 0) px = 0;
|
||||
if (px >= (int)ctx->vinfo.xres) px = ctx->vinfo.xres - 1;
|
||||
return px;
|
||||
}
|
||||
|
||||
// Y: raw top ~20832, bottom ~0 → inverted.
|
||||
// raw range RAW_Y_MIN..RAW_Y_MAX mapped to pixel 0..(yres-1).
|
||||
static int map_raw_to_py(int rawy, const fb_ctx_t *ctx) {
|
||||
if (rawy < RAW_Y_MIN) rawy = RAW_Y_MIN;
|
||||
if (rawy > RAW_Y_MAX) rawy = RAW_Y_MAX;
|
||||
int range = RAW_Y_MAX - RAW_Y_MIN;
|
||||
if (range <= 0) return 0;
|
||||
|
||||
int inverted = RAW_Y_MAX - rawy;
|
||||
int py = inverted * (int)(ctx->vinfo.yres - 1) / (range + 1);
|
||||
if (py < 0) py = 0;
|
||||
if (py >= (int)ctx->vinfo.yres) py = ctx->vinfo.yres - 1;
|
||||
return py;
|
||||
}
|
||||
|
||||
static int u_map_raw_to_px(int rawx, const fb_ctx_t *ctx) {
|
||||
if (rawx < RAW_X_MIN) rawx = RAW_X_MIN;
|
||||
if (rawx > RAW_X_MAX) rawx = RAW_X_MAX;
|
||||
int range = RAW_X_MAX - RAW_X_MIN;
|
||||
if (range <= 0) return 0;
|
||||
|
||||
// direct mapping, no reversed axis
|
||||
int px = (rawx - RAW_X_MIN) * (int)(ctx->vinfo.xres - 1) / range;
|
||||
|
||||
if (px < 0) px = 0;
|
||||
if (px >= (int)ctx->vinfo.xres) px = ctx->vinfo.xres - 1;
|
||||
return px;
|
||||
}
|
||||
|
||||
static int u_map_raw_to_py(int rawy, const fb_ctx_t *ctx) {
|
||||
if (rawy < RAW_Y_MIN) rawy = RAW_Y_MIN;
|
||||
if (rawy > RAW_Y_MAX) rawy = RAW_Y_MAX;
|
||||
int range = RAW_Y_MAX - RAW_Y_MIN;
|
||||
if (range <= 0) return 0;
|
||||
|
||||
// direct mapping, no inversion
|
||||
int py = (rawy - RAW_Y_MIN) * (int)(ctx->vinfo.yres - 1) / range;
|
||||
|
||||
if (py < 0) py = 0;
|
||||
if (py >= (int)ctx->vinfo.yres) py = ctx->vinfo.yres - 1;
|
||||
return py;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Double-tap handling
|
||||
// ============================================================================
|
||||
//
|
||||
// Zones:
|
||||
// top-left (py < TOPBAR_H, px < g_fb_width/2) → save
|
||||
// top-right (py < TOPBAR_H, px >= g_fb_width/2) → exit
|
||||
// bottom-right (py > g_fb_height - TOPBAR_H,
|
||||
// px >= g_fb_width/2) → reset (no save)
|
||||
//
|
||||
// Returns:
|
||||
// 0 = no action
|
||||
// 1 = double-tap top-left (save)
|
||||
// 2 = double-tap top-right (exit)
|
||||
// 3 = double-tap bottom-right (reset page, no save)
|
||||
|
||||
static int handle_tap(int px, int py) {
|
||||
tap_zone_t zone = TAP_NONE;
|
||||
|
||||
if (py < TOPBAR_H) {
|
||||
zone = (px < g_fb_width / 2) ? TAP_LEFT : TAP_RIGHT;
|
||||
} else if (py > g_fb_height - TOPBAR_H && px >= g_fb_width / 2) {
|
||||
zone = TAP_BOTTOM_RIGHT;
|
||||
} else {
|
||||
// outside gesture regions: reset tap tracking
|
||||
g_tap_count = 0;
|
||||
g_last_tap_zone = TAP_NONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t now_t = time(NULL);
|
||||
|
||||
if (zone == g_last_tap_zone &&
|
||||
(now_t - g_last_tap_timestamp) <= DOUBLE_TAP_WINDOW_SEC) {
|
||||
g_tap_count++;
|
||||
} else {
|
||||
g_tap_count = 1;
|
||||
g_last_tap_zone = zone;
|
||||
}
|
||||
g_last_tap_timestamp = now_t;
|
||||
|
||||
if (g_tap_count >= 2) {
|
||||
g_tap_count = 0;
|
||||
g_last_tap_zone = TAP_NONE;
|
||||
if (zone == TAP_LEFT) return 1;
|
||||
else if (zone == TAP_RIGHT) return 2;
|
||||
else if (zone == TAP_BOTTOM_RIGHT) return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// main
|
||||
// ============================================================================
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
fb_ctx_t fb;
|
||||
if (fb_init(&fb, DEV_PATH_FB) != 0) {
|
||||
fprintf(stderr, "fb_init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int stylus_fd = open(DEV_PATH_STYLUS, O_RDONLY);
|
||||
if (stylus_fd < 0) {
|
||||
perror("open stylus");
|
||||
fb_close(&fb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Exclusive grab so the framework cannot see stylus events
|
||||
if (ioctl(stylus_fd, EVIOCGRAB, (void *)1) < 0) {
|
||||
perror("EVIOCGRAB stylus");
|
||||
close(stylus_fd);
|
||||
fb_close(&fb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int touch_fd = open(DEV_PATH_TOUCH, O_RDONLY);
|
||||
if (touch_fd < 0) {
|
||||
perror("open touch");
|
||||
touch_fd = -1; // weiterlaufen ohne Touch-Grab
|
||||
} else {
|
||||
// Exclusive grab so the framework cannot see touch events
|
||||
if (ioctl(touch_fd, EVIOCGRAB, (void *)1) < 0) {
|
||||
perror("EVIOCGRAB touch");
|
||||
// trotzdem weiterlaufen, touch_fd bleibt offen
|
||||
}
|
||||
}
|
||||
|
||||
// Give the framework time to draw its last stuff, then wipe it away.
|
||||
sleep(1);
|
||||
fb_clear(&fb, 0xFF); // all white (also clears g_bitmap)
|
||||
|
||||
system("eips -c");
|
||||
|
||||
// ---- Load PBM if provided as first argument ----
|
||||
if (argc > 1) {
|
||||
const char *pbm_path = argv[1];
|
||||
fprintf(stderr, "Loading PBM: %s\n", pbm_path);
|
||||
|
||||
if (load_pbm_into_fb(&fb, pbm_path) != 0) {
|
||||
fprintf(stderr, "Failed to load PBM from %s\n", pbm_path);
|
||||
}
|
||||
}
|
||||
|
||||
printTitle();
|
||||
//draw_topbar_icons(&fb);
|
||||
|
||||
fb_flush();
|
||||
|
||||
struct input_event ev;
|
||||
int32_t stylus_raw_x = 0;
|
||||
int32_t stylus_raw_y = 0;
|
||||
int pen_is_down = 0;
|
||||
int last_pen_px = -1;
|
||||
int last_pen_py = -1;
|
||||
|
||||
uint64_t last_flush = now_ms();
|
||||
int dirty_since_flush = 0;
|
||||
|
||||
while (1) {
|
||||
ssize_t r = read(stylus_fd, &ev, sizeof(ev));
|
||||
if (r < (ssize_t)sizeof(ev)) {
|
||||
if (r < 0 && errno == EINTR) continue;
|
||||
perror("read stylus");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ev.type == EV_ABS) {
|
||||
if (ev.code == ABS_X) stylus_raw_x = ev.value;
|
||||
else if (ev.code == ABS_Y) stylus_raw_y = ev.value;
|
||||
} else if (ev.type == EV_KEY && ev.code == 0x14A) {
|
||||
// Pen down / up
|
||||
pen_is_down = (ev.value == 1);
|
||||
if (!pen_is_down) {
|
||||
last_pen_px = last_pen_py = -1;
|
||||
}
|
||||
|
||||
// Double-tap detection ONLY on pen-down events
|
||||
if (ev.value == 1) {
|
||||
int px = map_raw_to_px(stylus_raw_x, &fb);
|
||||
int py = map_raw_to_py(stylus_raw_y, &fb);
|
||||
|
||||
int tap_result = handle_tap(px, py);
|
||||
if (tap_result == 1) {
|
||||
// top-left: save (PBM + PNG)
|
||||
fb_flush();
|
||||
save_page();
|
||||
sleep(SAVE_FEEDBACK_SLEEP_SEC);
|
||||
fb_clear(&fb, 0xFF);
|
||||
system("eips -c");
|
||||
printTitle();
|
||||
//draw_topbar_icons(&fb);
|
||||
fb_flush();
|
||||
|
||||
// reset stroke state and skip drawing for this event
|
||||
pen_is_down = 0;
|
||||
last_pen_px = last_pen_py = -1;
|
||||
continue;
|
||||
} else if (tap_result == 2) {
|
||||
// top-right: EXIT
|
||||
fb_flush();
|
||||
//save_page();
|
||||
fb_clear(&fb, 0xFF);
|
||||
system("eips 1 1 \"bye bye\"");
|
||||
break; // aus while(1)
|
||||
} else if (tap_result == 3) {
|
||||
// bottom-right: RESET (no save)
|
||||
fb_flush();
|
||||
fb_clear(&fb, 0xFF);
|
||||
system("eips -c");
|
||||
printTitle();
|
||||
//draw_topbar_icons(&fb);
|
||||
fb_flush();
|
||||
|
||||
// reset stroke state and skip drawing for this event
|
||||
pen_is_down = 0;
|
||||
last_pen_px = last_pen_py = -1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drawing while pen is down
|
||||
if (pen_is_down) {
|
||||
int px = map_raw_to_px(stylus_raw_x, &fb);
|
||||
int py = map_raw_to_py(stylus_raw_y, &fb);
|
||||
|
||||
// Do not draw in top bar
|
||||
if (py >= TOPBAR_H) {
|
||||
if (last_pen_px >= 0 && last_pen_py >= 0) {
|
||||
fb_draw_line(&fb, last_pen_px, last_pen_py, px, py, 0x00);
|
||||
} else {
|
||||
fb_draw_thick_point(&fb, px, py, 0x00);
|
||||
}
|
||||
last_pen_px = px;
|
||||
last_pen_py = py;
|
||||
dirty_since_flush = 1;
|
||||
} else {
|
||||
// If pen moves into top bar, break the stroke
|
||||
last_pen_px = last_pen_py = -1;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t now_m = now_ms();
|
||||
if (dirty_since_flush && (now_m - last_flush >= REFRESH_INTERVAL_MS)) {
|
||||
fb_flush();
|
||||
last_flush = now_m;
|
||||
dirty_since_flush = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// On normal loop break: release stylus/touch grab and clean up
|
||||
ioctl(stylus_fd, EVIOCGRAB, (void *)0);
|
||||
close(stylus_fd);
|
||||
|
||||
if (touch_fd >= 0) {
|
||||
ioctl(touch_fd, EVIOCGRAB, (void *)0);
|
||||
close(touch_fd);
|
||||
}
|
||||
|
||||
fb_close(&fb);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user