summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNemu Manaka <beatrice@dangofactory.local>2026-08-17 23:18:35 +0900
committerNemu Manaka <beatrice@dangofactory.local>2026-08-17 23:18:35 +0900
commit64cacba18f7e1eb2059fe6d8318931c105e927ae (patch)
tree230b80e28efb827283e31287b7a4cefe07a13dec
downloadwm_pasori-64cacba18f7e1eb2059fe6d8318931c105e927ae.tar.gz
wm_pasori-64cacba18f7e1eb2059fe6d8318931c105e927ae.tar.bz2
wm_pasori-64cacba18f7e1eb2059fe6d8318931c105e927ae.zip
initial
-rw-r--r--Makefile16
-rw-r--r--wm_pasori.c657
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;
+}