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