diff options
| author | Nemu Manaka <beatrice@dangofactory.local> | 2026-08-17 23:18:35 +0900 |
|---|---|---|
| committer | Nemu Manaka <beatrice@dangofactory.local> | 2026-08-17 23:18:35 +0900 |
| commit | 64cacba18f7e1eb2059fe6d8318931c105e927ae (patch) | |
| tree | 230b80e28efb827283e31287b7a4cefe07a13dec | |
| download | wm_pasori-64cacba18f7e1eb2059fe6d8318931c105e927ae.tar.gz wm_pasori-64cacba18f7e1eb2059fe6d8318931c105e927ae.tar.bz2 wm_pasori-64cacba18f7e1eb2059fe6d8318931c105e927ae.zip | |
initial
| -rw-r--r-- | Makefile | 16 | ||||
| -rw-r--r-- | wm_pasori.c | 657 |
2 files changed, 673 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bbbeae4 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Wno-deprecated-declarations -O2 $(shell pkg-config --cflags gtk+-3.0) -I/usr/local/include +LDFLAGS = -L/usr/local/lib -lpafe -ldockapp -lX11 $(shell pkg-config --libs gtk+-3.0) + +TARGET = wm_pasori +SRCS = wm_pasori.c + +all: $(TARGET) + +$(TARGET): $(SRCS) + $(CC) $(CFLAGS) $(SRCS) -o $(TARGET) $(LDFLAGS) + +clean: + rm -f $(TARGET) + +.PHONY: all clean diff --git a/wm_pasori.c b/wm_pasori.c new file mode 100644 index 0000000..51b8bee --- /dev/null +++ b/wm_pasori.c @@ -0,0 +1,657 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <libdockapp/dockapp.h> +#include <libpafe/libpafe.h> + +#define APP_NAME "wm_pasori" +#define APP_WIDTH 58 +#define APP_HEIGHT 58 +#define SCAN_INTERVAL_MS 700 +#define PASORI_TIMEOUT_MS 200 +#define SUICA_SERVICE_CODE 0x090f +#define SUICA_BLOCK_NUMBER 0 + +typedef enum { + DISPLAY_START, + DISPLAY_WAIT, + DISPLAY_BALANCE, + DISPLAY_READ_ERROR, + DISPLAY_NO_DEVICE +} DisplayState; + +static pasori *reader = NULL; +static DisplayState display_state = DISPLAY_START; +static unsigned int current_balance = 0; +static int current_error = 0; +static XFontStruct *font_small = NULL; +static XFontStruct *font_large = NULL; + + +static void close_pasori(void) { + if (reader != NULL) { + pasori_close(reader); + reader = NULL; + } +} + + +static int open_pasori(void) { + int result; + + if (reader != NULL) { + return 0; + } + + reader = pasori_open(); + + if (reader == NULL) { + return -1; + } + + result = pasori_init(reader); + + if (result != 0) { + fprintf(stderr, "pasori_init: error %d\n", result); + + pasori_close(reader); + reader = NULL; + + return -1; + } + + pasori_set_timeout(reader, PASORI_TIMEOUT_MS); + + fprintf(stderr, "PaSoRi initialized\n"); + + return 0; +} + + +static void draw_centered_text(Drawable drawable, GC gc, XFontStruct *font, int y, const char *text) { + int width; + int x; + + if (font == NULL || text == NULL) { + return; + } + + width = XTextWidth( + font, + text, + (int)strlen(text) + ); + + x = (APP_WIDTH - width) / 2; + + if (x < 1) { + x = 1; + } + + XSetFont( + DADisplay, + gc, + font->fid + ); + + XDrawString( + DADisplay, + drawable, + gc, + x, + y, + text, + (int)strlen(text) + ); +} + + +static void redraw_display(void) { + Pixmap back; + XGCValues values; + GC background_gc; + GC border_dark_gc; + GC border_light_gc; + GC normal_gc; + GC accent_gc; + GC warning_gc; + GC error_gc; + GC dim_gc; + char balance_text[32]; + char error_text[32]; + + back = DAMakePixmap(); + + values.foreground = DAGetColor("#101418"); + values.graphics_exposures = False; + + background_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground | GCGraphicsExposures, + &values + ); + + XFillRectangle( + DADisplay, + back, + background_gc, + 0, + 0, + APP_WIDTH, + APP_HEIGHT + ); + + values.foreground = DAGetColor("#313943"); + + border_dark_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + /* + * 内枠 + */ + values.foreground = DAGetColor("#56616d"); + + border_light_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + XDrawRectangle( + DADisplay, + back, + border_dark_gc, + 0, + 0, + APP_WIDTH - 1, + APP_HEIGHT - 1 + ); + + XDrawRectangle( + DADisplay, + back, + border_light_gc, + 1, + 1, + APP_WIDTH - 3, + APP_HEIGHT - 3 + ); + + /* + * ステータス + */ + values.foreground = DAGetColor("#d0d7de"); + + normal_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + /* + * 残高表示 + */ + values.foreground = DAGetColor("#74ff89"); + + accent_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + /* + * 読取り警告 + */ + values.foreground = DAGetColor("#ffd75f"); + + warning_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + /* + * デバイスエラー + */ + values.foreground = DAGetColor("#ff6464"); + + error_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + values.foreground = DAGetColor("#69737d"); + + dim_gc = XCreateGC( + DADisplay, + DAWindow, + GCForeground, + &values + ); + + switch (display_state) { + case DISPLAY_BALANCE: + snprintf( + balance_text, + sizeof(balance_text), + "%u", + current_balance + ); + + draw_centered_text( + back, + accent_gc, + font_small, + 13, + "Suica" + ); + + draw_centered_text( + back, + accent_gc, + font_large, + 36, + balance_text + ); + + draw_centered_text( + back, + dim_gc, + font_small, + 51, + "YEN" + ); + break; + + case DISPLAY_WAIT: + draw_centered_text( + back, + normal_gc, + font_small, + 15, + "Suica" + ); + + draw_centered_text( + back, + normal_gc, + font_small, + 33, + "TOUCH" + ); + + draw_centered_text( + back, + dim_gc, + font_small, + 49, + "----" + ); + break; + + case DISPLAY_READ_ERROR: + snprintf( + error_text, + sizeof(error_text), + "ERR %d", + current_error + ); + + draw_centered_text( + back, + warning_gc, + font_small, + 15, + "Suica" + ); + + draw_centered_text( + back, + warning_gc, + font_small, + 33, + "READ" + ); + + draw_centered_text( + back, + warning_gc, + font_small, + 49, + error_text + ); + break; + + case DISPLAY_NO_DEVICE: + draw_centered_text( + back, + error_gc, + font_small, + 15, + "PaSoRi" + ); + + draw_centered_text( + back, + error_gc, + font_small, + 33, + "NO DEV" + ); + + draw_centered_text( + back, + dim_gc, + font_small, + 49, + "USB?" + ); + break; + + case DISPLAY_START: + default: + draw_centered_text( + back, + normal_gc, + font_small, + 18, + "PaSoRi" + ); + + draw_centered_text( + back, + dim_gc, + font_small, + 39, + "START" + ); + break; + } + + /* + * DockAppのicon windowへ反映する + * + * DASetPixmap()は渡した内容を表示側へ設定する + */ + DASetPixmap(back); + + XFreePixmap(DADisplay, back); + + XFreeGC(DADisplay, background_gc); + XFreeGC(DADisplay, border_dark_gc); + XFreeGC(DADisplay, border_light_gc); + XFreeGC(DADisplay, normal_gc); + XFreeGC(DADisplay, accent_gc); + XFreeGC(DADisplay, warning_gc); + XFreeGC(DADisplay, error_gc); + XFreeGC(DADisplay, dim_gc); + + XFlush(DADisplay); +} + + +/* + * Suica残高を読み取る + * + * 戻り値: + * 0 成功 + * 1 Suicaなし + * -1 読取り失敗 + * -2 PaSoRiなし・初期化失敗 + */ +static int read_suica_balance(unsigned int *balance, int *read_error) { + felica *card; + uint8_t block_data[16]; + int result; + size_t i; + + if (balance == NULL) { + return -2; + } + + *balance = 0; + + if (read_error != NULL) { + *read_error = 0; + } + + if (open_pasori() != 0) { + return -2; + } + + card = felica_polling( + reader, + FELICA_POLLING_SUICA, + 0, + 0 + ); + + if (card == NULL) { + return 1; + } + + memset( + block_data, + 0, + sizeof(block_data) + ); + + result = felica_read_single( + card, + SUICA_SERVICE_CODE, + 0, + SUICA_BLOCK_NUMBER, + block_data + ); + + free(card); + card = NULL; + + if (result != 0) { + if (read_error != NULL) { + *read_error = result; + } + + fprintf( + stderr, + "felica_read_single: error %d\n", + result + ); + + return -1; + } + + fprintf(stderr, "Suica block:"); + + for (i = 0; i < sizeof(block_data); i++) { + fprintf(stderr, " %02x", block_data[i]); + } + + fprintf(stderr, "\n"); + + /* + * Suica利用履歴の残高。 + * + * オフセット10が下位バイト、 + * オフセット11が上位バイト。 + */ + *balance = + (unsigned int)block_data[10] | + ((unsigned int)block_data[11] << 8); + + return 0; +} + + +/* + * libdockappのタイマーコールバック。 + */ +static void timeout_callback(void) { + unsigned int balance; + int read_error; + int result; + DisplayState new_state; + + result = read_suica_balance( + &balance, + &read_error + ); + + switch (result) { + case 0: + new_state = DISPLAY_BALANCE; + current_balance = balance; + current_error = 0; + break; + + case 1: + new_state = DISPLAY_WAIT; + current_error = 0; + break; + + case -1: + new_state = DISPLAY_READ_ERROR; + current_error = read_error; + break; + + default: + new_state = DISPLAY_NO_DEVICE; + current_error = 0; + close_pasori(); + break; + } + + display_state = new_state; + redraw_display(); +} + + +/* + * 終了処理 + */ +static void destroy_callback(void) { + close_pasori(); + + if (font_small != NULL) { + XFreeFont(DADisplay, font_small); + font_small = NULL; + } + + if (font_large != NULL) { + XFreeFont(DADisplay, font_large); + font_large = NULL; + } + + fprintf(stderr, "wm_pasori terminated\n"); +} + + +/* + * メイン + */ +int main(int argc, char **argv) { + DACallbacks callbacks = { + destroy_callback, /* destroy */ + NULL, /* buttonPress */ + NULL, /* buttonRelease */ + NULL, /* motion */ + NULL, /* mouse enters window */ + NULL, /* mouse leaves window */ + timeout_callback /* timeout */ + }; + + DAParseArguments( + argc, + argv, + NULL, + 0, + "Window Maker DockApp for displaying Suica balance.\n", + "wm_pasori 1.0" + ); + + /* + * FreeBSD libdockapp 0.7.2サンプル + */ + DASetExpectedVersion(20020126); + + DAOpenDisplay( + NULL, + argc, + argv + ); + + DACreateIcon( + APP_NAME, + APP_WIDTH, + APP_HEIGHT, + argc, + argv + ); + + font_small = XLoadQueryFont( + DADisplay, + "-misc-fixed-medium-r-normal--10-*-*-*-*-*-iso8859-1" + ); + + if (font_small == NULL) { + font_small = XLoadQueryFont( + DADisplay, + "fixed" + ); + } + + font_large = XLoadQueryFont( + DADisplay, + "-misc-fixed-bold-r-normal--18-*-*-*-*-*-iso8859-1" + ); + + if (font_large == NULL) { + font_large = XLoadQueryFont( + DADisplay, + "9x15bold" + ); + } + + if (font_large == NULL) { + font_large = XLoadQueryFont( + DADisplay, + "fixed" + ); + } + + if (font_small == NULL || font_large == NULL) { + fprintf(stderr, "Could not load X11 fonts\n"); + close_pasori(); + return EXIT_FAILURE; + } + + DASetCallbacks(&callbacks); + + DASetTimeout(SCAN_INTERVAL_MS); + + display_state = DISPLAY_START; + redraw_display(); + + DAShow(); + + timeout_callback(); + + /* + * X11イベントとタイマー + */ + DAEventLoop(); + + return EXIT_SUCCESS; +} |
