From 0c80d390b5d7830beece254e77499e9d8083d9be Mon Sep 17 00:00:00 2001 From: Leask Wong Date: Sat, 6 Jun 2026 12:37:29 -0400 Subject: [PATCH 1/5] Add CJK notification font fallback --- Apps/System/musicapp.c | 45 ++- Apps/System/settings.c | 4 + Apps/System/systemapp.c | 4 +- Apps/System/testapp.c | 2 +- Apps/System/tests/text_alignment_test.c | 6 +- Utilities/cjk/notification_zh_seed.txt | 11 + Utilities/mkcjkfont.py | 382 ++++++++++++++++++++ Utilities/mkpack.py | 5 +- Utilities/requirements.txt | 3 +- docs/cjk_fonts.md | 95 +++++ hw/platform/asterix/platform.h | 4 + hw/platform/tintin/platform.h | 5 + hw/platform/tintin/tintin_asm.s | 21 +- lib/musl/time/localtime_r.c | 1 + lib/musl/time/mktime.c | 1 + rcore/api_func_symbols.h | 2 +- rcore/bluetooth.c | 2 + rcore/overlay_manager.h | 1 + rcore/power.c | 1 + rcore/protocol/protocol_blob.c | 3 +- rcore/protocol/protocol_transfer.c | 1 + rcore/service.c | 1 + rcore/service/protocol_service.c | 11 +- rcore/service/protocol_service.h | 8 +- res | 2 +- rwatch/graphics/font_file.c | 164 +++++++-- rwatch/graphics/font_loader.c | 51 ++- rwatch/graphics/font_loader.h | 4 +- rwatch/graphics/graphics.c | 8 +- rwatch/graphics/graphics_wrapper.h | 3 + rwatch/graphics/libros_graphics.h | 7 +- rwatch/graphics/system_font.h | 1 + rwatch/pebble.h | 9 +- rwatch/storage/storage_persist.c | 4 +- rwatch/storage/storage_persist.h | 2 +- rwatch/ui/layer/action_bar_layer.c | 2 +- rwatch/ui/layer/action_bar_layer.h | 2 +- rwatch/ui/layer/single_notification_layer.c | 12 + 38 files changed, 790 insertions(+), 100 deletions(-) create mode 100644 Utilities/cjk/notification_zh_seed.txt create mode 100644 Utilities/mkcjkfont.py create mode 100644 docs/cjk_fonts.md diff --git a/Apps/System/musicapp.c b/Apps/System/musicapp.c index fc5d17e6..62312946 100644 --- a/Apps/System/musicapp.c +++ b/Apps/System/musicapp.c @@ -146,30 +146,33 @@ uint32_t one_at_a_time_hash(const uint8_t* key, size_t length) { return hash; } -n_GColor8 songColor(unsigned char *str) +n_GColor8 songColor(const char *str) { - uint32_t hash = one_at_a_time_hash(str,strlen(str)); - APP_LOG("music", APP_LOG_LEVEL_DEBUG, "song hash:%lu",hash); + uint32_t hash = one_at_a_time_hash((const uint8_t *)str, strlen(str)); + APP_LOG("music", APP_LOG_LEVEL_DEBUG, "song hash:%lu", hash); - return GColorFromRGB(hash, hash >> 8, hash >> 16); + return GColorFromRGB(hash, hash >> 8, hash >> 16); } -void _music_info(EventServiceCommand command, void *data) +void _music_info(EventServiceCommand command, void *data, void *context) { - if (_music_track) - app_free(_music_track); + (void)context; - MusicTrackInfo *amusic = protocol_music_decode(data); - _music_track = amusic; + if (_music_track) + app_free(_music_track); - APP_LOG("music", APP_LOG_LEVEL_DEBUG,"Title: %s", amusic->title); - APP_LOG("music", APP_LOG_LEVEL_DEBUG,"Artist: %s", amusic->artist); - APP_LOG("music", APP_LOG_LEVEL_DEBUG,"Album: %s", amusic->album); - APP_LOG("music", APP_LOG_LEVEL_DEBUG,"Length: %s", amusic->track_length); + MusicTrackInfo *amusic = protocol_music_decode(data); + _music_track = amusic; - s_artist = (char *)amusic->artist; - s_track = (char *)amusic->title; - s_length = (char *)amusic->track_length; + APP_LOG("music", APP_LOG_LEVEL_DEBUG, "Title: %s", amusic->title); + APP_LOG("music", APP_LOG_LEVEL_DEBUG, "Artist: %s", amusic->artist); + APP_LOG("music", APP_LOG_LEVEL_DEBUG, "Album: %s", amusic->album); + APP_LOG("music", APP_LOG_LEVEL_DEBUG, "Length: %lu", + amusic->track_length); + + s_artist = (char *)amusic->artist; + s_track = (char *)amusic->title; + s_length = amusic->track_length; } @@ -432,12 +435,14 @@ static void _main_layer_update_proc(Layer *layer, GContext *ctx) { char time_string[8] = ""; strftime(time_string, 8, "%R", &s_last_time); - char progress_string[6] = ""; + char progress_string[16] = ""; // TODO display tracks over 59:59s long differently - snprintf(progress_string, 6, "%ld:%02ld", s_progress / 60, s_progress % 60); + snprintf(progress_string, sizeof(progress_string), "%ld:%02ld", + s_progress / 60, s_progress % 60); - char length_string[6] = ""; - snprintf(length_string, 6, "%ld:%02ld", s_length / 60, s_length % 60); + char length_string[16] = ""; + snprintf(length_string, sizeof(length_string), "%ld:%02ld", + s_length / 60, s_length % 60); graphics_context_set_text_color(ctx, GColorBlack); graphics_draw_text(ctx, s_artist, diff --git a/Apps/System/settings.c b/Apps/System/settings.c index 3630f65d..920c7fb2 100644 --- a/Apps/System/settings.c +++ b/Apps/System/settings.c @@ -19,6 +19,10 @@ static GBitmap *_bt_pair_accept, *_bt_pair_reject; static StatusBarLayer *_bt_pair_status; static char *_bt_pair_name; +void settings_tz_invoke(void); +void settings_tz_init(void); +void settings_tz_deinit(void); + static void _bluetooth_pair_request(EventServiceCommand svc, void *data, void *ctx) { const char *name = (const char *)data; APP_LOG("settings", APP_LOG_LEVEL_INFO, "BT pair request: %s", name); diff --git a/Apps/System/systemapp.c b/Apps/System/systemapp.c index 082a4210..4db7095f 100644 --- a/Apps/System/systemapp.c +++ b/Apps/System/systemapp.c @@ -147,8 +147,10 @@ static void exit_to_watchface(struct Menu *menu, void *context) static MusicTrackInfo *_music_track; static MenuItems *items; -static void _music_info(EventServiceCommand command, void *data) +static void _music_info(EventServiceCommand command, void *data, void *context) { + (void)context; + if (_music_track) app_free(_music_track); diff --git a/Apps/System/testapp.c b/Apps/System/testapp.c index 1ddb3c5b..cda8c72b 100644 --- a/Apps/System/testapp.c +++ b/Apps/System/testapp.c @@ -108,7 +108,7 @@ app_test _tests[] = { #define TEST_COUNT sizeof(_tests) / sizeof(app_test) static Window *_test_window; -static AppTimer *_test_exec_timer; +static AppTimerHandle _test_exec_timer; static app_test *_running_test = NULL; static bool _window_initialised = false; diff --git a/Apps/System/tests/text_alignment_test.c b/Apps/System/tests/text_alignment_test.c index 7fc8a8ff..02a1d798 100644 --- a/Apps/System/tests/text_alignment_test.c +++ b/Apps/System/tests/text_alignment_test.c @@ -28,7 +28,7 @@ static void select_click_handler(ClickRecognizerRef recognizer, void *context) text_layer_set_text_alignment(s_multiline_label_layer, s_text_alignment); - layer_mark_dirty(window_get_root_layer(s_test_layer)); + layer_mark_dirty(s_test_layer); } static void up_click_handler(ClickRecognizerRef recognizer, void *context) @@ -39,14 +39,14 @@ static void up_click_handler(ClickRecognizerRef recognizer, void *context) s_string_index = (int)(sizeof(s_test_strings) / sizeof(s_test_strings[0])) - 1; text_layer_set_text(s_multiline_label_layer, s_test_strings[s_string_index]); - layer_mark_dirty(window_get_root_layer(s_test_layer)); + layer_mark_dirty(s_test_layer); } static void down_click_handler(ClickRecognizerRef recognizer, void *context) { s_string_index = (s_string_index + 1) % (int)(sizeof(s_test_strings) / sizeof(s_test_strings[0])); text_layer_set_text(s_multiline_label_layer, s_test_strings[s_string_index]); - layer_mark_dirty(window_get_root_layer(s_test_layer)); + layer_mark_dirty(s_test_layer); } static void click_config_provider(void *context) diff --git a/Utilities/cjk/notification_zh_seed.txt b/Utilities/cjk/notification_zh_seed.txt new file mode 100644 index 00000000..02f2451b --- /dev/null +++ b/Utilities/cjk/notification_zh_seed.txt @@ -0,0 +1,11 @@ +# Seed characters for small CJK notification font experiments. +# +# This file is intentionally not a complete Simplified/Traditional set. +# Use it as a first notification fallback subset, then expand it from a +# real notification corpus and platform resource budget. + +的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组别计指权支识完 +我你他她它我們你們他們她們它們收到通知消息短信電話电话微信郵件邮件群聊回覆回复提醒日程會議会议快遞快递外賣外卖支付銀行银行驗證碼验证码登入登錄登录訂單订单更新中文簡體简体繁體繁体漢字汉字台灣臺灣台湾香港澳門澳门 +驗證碼验证码密碼密码註冊注册重置修改綁定绑定解綁解绑設備设备安全警告緊急紧急異常异常成功失敗失败取消確認确认批准拒絕拒绝邀請邀请加入退出已讀已读未讀未读查看查收請请回覆回复轉發转发分享評論评论點讚点赞收藏附件照片圖片图片文件位置地址導航导航門禁门禁鬧鐘闹钟航班列車列车票券優惠券优惠券賬單账单充值付款收款退款到賬到账餘額余额交易收入支出 +張张王李趙赵劉刘陳陈楊杨黃黄吳吴周徐孫孙馬马朱胡林郭何高羅罗鄭郑梁謝谢宋唐許许鄧邓韓韩馮冯曹曾彭蕭萧蔡潘田董袁余葉叶蔣蒋杜蘇苏魏程呂吕丁沈任姚盧卢姜崔鐘钟譚谭陸陆汪范金石廖賈贾夏韋韦傅白鄒邹孟熊秦邱江尹薛閆闫段雷侯龍龙史陶黎賀贺顧顾毛郝龔龚邵萬万錢钱嚴严賴赖洪武莫孔湯汤向常溫温康施牛樊葛邢安齊齐易喬乔伍龐庞顏颜倪莊庄聶聂章魯鲁岳翟殷詹歐欧耿關关蘭兰焦俞左柳甘祝包寧宁尚符舒阮 +。,、;:?!“”‘’()《》〈〉【】「」『』—…¥ diff --git a/Utilities/mkcjkfont.py b/Utilities/mkcjkfont.py new file mode 100644 index 00000000..ea58a9c9 --- /dev/null +++ b/Utilities/mkcjkfont.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python3 +"""Build a Pebble bitmap font from a CJK outline font subset.""" + +import argparse +import math +import struct +import sys +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + + +HASH_TABLE_SIZE = 255 +FALLBACK_CODEPOINT = 0x25AF + + +class Glyph: + def __init__(self, width, height, left, top, advance, bits): + self.width = width + self.height = height + self.left = left + self.top = top + self.advance = advance + self.bits = bits + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Build a RebbleOS/Pebble .pbf bitmap font from a ' + 'subset of an outline CJK font.' + ) + parser.add_argument( + '--font', + required=True, + help='Path to an OFL-compatible .otf/.ttf/.ttc source font.', + ) + parser.add_argument( + '--font-index', + type=int, + default=0, + help='Font index for .ttc/.otc collections. Default: 0.', + ) + parser.add_argument( + '--charset-file', + action='append', + default=[], + help='UTF-8 text file whose characters should be included. ' + 'May be passed more than once. Lines starting with # are ' + 'ignored.', + ) + parser.add_argument( + '--text', + action='append', + default=[], + help='Literal UTF-8 characters to include. May be passed more ' + 'than once.', + ) + parser.add_argument( + '--size', + type=int, + default=18, + help='Source font pixel size. Default: 18.', + ) + parser.add_argument( + '--line-height', + type=int, + default=None, + help='PBF line height. Default: max(size, font metrics height).', + ) + parser.add_argument( + '--threshold', + type=int, + default=96, + help='Antialias threshold from 0-255. Default: 96.', + ) + parser.add_argument( + '--no-ascii', + action='store_true', + help='Do not include ASCII 0x20-0x7E.', + ) + parser.add_argument( + '--output', + required=True, + help='Output .pbf path.', + ) + parser.add_argument( + '--preview', + help='Optional text preview output path.', + ) + return parser.parse_args() + + +def load_charset(paths, text_items, include_ascii): + chars = set() + + if include_ascii: + chars.update(chr(cp) for cp in range(0x20, 0x7F)) + + chars.update(chr(cp) for cp in ( + 0x3001, 0x3002, 0x300A, 0x300B, 0x300C, 0x300D, + 0x300E, 0x300F, 0x2014, 0x2026, 0x25AF, 0xFF01, + 0xFF08, 0xFF09, 0xFF0C, 0xFF1A, 0xFF1B, 0xFF1F, + )) + + for item in text_items: + chars.update(item) + + for path in paths: + with open(path, 'r', encoding='utf-8') as fh: + for line in fh: + if line.startswith('#'): + continue + chars.update(line.rstrip('\n\r')) + + chars.discard('\n') + chars.discard('\r') + chars.discard('\t') + chars.add(chr(FALLBACK_CODEPOINT)) + + return sorted(ord(ch) for ch in chars) + + +def clamp_int8(value, name, codepoint): + if value < -128 or value > 127: + raise ValueError( + f'{name} for U+{codepoint:04X} is outside int8: {value}' + ) + return value + + +def clamp_u8(value, name, codepoint): + if value < 0 or value > 255: + raise ValueError( + f'{name} for U+{codepoint:04X} is outside uint8: {value}' + ) + return value + + +def render_fallback(line_height): + size = max(5, min(24, line_height - 2)) + bits = [] + for y in range(size): + for x in range(size): + bits.append(y == 0 or y == size - 1 or x == 0 or x == size - 1) + + return Glyph( + width=size, + height=size, + left=1, + top=max(0, (line_height - size) // 2), + advance=size + 2, + bits=bits, + ) + + +def render_glyph(font, codepoint, line_height, threshold): + if codepoint == FALLBACK_CODEPOINT: + return render_fallback(line_height) + + char = chr(codepoint) + bbox = font.getbbox(char) + advance = int(math.ceil(font.getlength(char))) + + if bbox is None: + return None + + left, top, right, bottom = [int(math.floor(v)) for v in bbox] + width = max(0, right - left) + height = max(0, bottom - top) + + if width == 0 or height == 0: + return Glyph(0, 0, 0, 0, max(advance, 0), []) + + image = Image.new('L', (width, height), 0) + draw = ImageDraw.Draw(image) + draw.text((-left, -top), char, font=font, fill=255) + crop = image.getbbox() + + if crop is None: + return Glyph(0, 0, 0, 0, max(advance, 0), []) + + crop_left, crop_top, crop_right, crop_bottom = crop + image = image.crop(crop) + left += crop_left + top += crop_top + width = crop_right - crop_left + height = crop_bottom - crop_top + + pixels = image.tobytes() + bits = [pixel >= threshold for pixel in pixels] + + return Glyph( + width=clamp_u8(width, 'width', codepoint), + height=clamp_u8(height, 'height', codepoint), + left=clamp_int8(left, 'left', codepoint), + top=clamp_int8(top, 'top', codepoint), + advance=clamp_int8(max(advance, width + left), 'advance', codepoint), + bits=bits, + ) + + +def build_glyphs(font, codepoints, line_height, threshold): + glyphs = {} + missing = [] + + for codepoint in codepoints: + glyph = render_glyph(font, codepoint, line_height, threshold) + if glyph is None: + missing.append(codepoint) + continue + glyphs[codepoint] = glyph + + glyphs[FALLBACK_CODEPOINT] = render_fallback(line_height) + return glyphs, missing + + +def pack_bits(bits): + padded = list(bits) + while len(padded) % 32: + padded.append(False) + + out = bytearray() + for offset in range(0, len(padded), 8): + byte = 0 + for bit in range(8): + if padded[offset + bit]: + byte |= 1 << bit + out.append(byte) + + return bytes(out) + + +def write_pbf(path, glyphs, line_height): + Path(path).parent.mkdir(parents=True, exist_ok=True) + + if len(glyphs) > 0xFFFF: + raise ValueError( + f'PBF stores glyph count in uint16: {len(glyphs)} glyphs' + ) + + max_codepoint = max(glyphs.keys()) + codepoint_bytes = 2 if max_codepoint <= 0xFFFF else 4 + offset_entry_len = codepoint_bytes + 4 + + glyph_blob = bytearray(struct.pack(' 255: + raise ValueError( + f'hash bucket {hash_value} has {len(group)} glyphs; ' + 'PBF stores bucket sizes in uint8' + ) + offset_meta[hash_value] = (len(offset_blob), len(group)) + for codepoint in group: + if codepoint_bytes == 2: + offset_blob.extend(struct.pack(' 0xFFFF: + raise ValueError( + 'offset table is too large for the current PBF hash table ' + f'format: {len(offset_blob)} bytes' + ) + + hash_blob = bytearray() + for hash_value in range(HASH_TABLE_SIZE): + offset, size = offset_meta.get(hash_value, (0, 0)) + hash_blob.extend(struct.pack('=0.20220715.0,<1 +Pillow>=9,<13 diff --git a/docs/cjk_fonts.md b/docs/cjk_fonts.md new file mode 100644 index 00000000..ce7c0080 --- /dev/null +++ b/docs/cjk_fonts.md @@ -0,0 +1,95 @@ +# CJK Font Support + +RebbleOS currently renders Chinese notification text as tofu boxes because +the system Gothic fonts do not contain CJK glyphs. The notification parser +preserves UTF-8 text; the missing piece is glyph coverage. + +## Constraints + +Pebble `.pbf` fonts are bitmap fonts. Adding all Simplified and Traditional +Chinese glyphs to every Gothic size is not practical: + +- Every glyph stores bitmap data plus per-glyph metadata and lookup table + entries. +- The current PBF hash table stores per-bucket counts in `uint8_t` and offset + table offsets in `uint16_t`, so very large all-CJK fonts exceed the format + before flash size is the only problem. +- Existing resource packs for classic platforms leave limited room; Asterix + has more resource flash, but full CJK at multiple sizes is still wasteful. + +The best practice is therefore: + +1. Use an OFL-compatible CJK source font, such as + [Noto Sans CJK](https://github.com/notofonts/noto-cjk) or + [Source Han Sans](https://github.com/adobe-fonts/source-han-sans). Do not + generate distributable assets from proprietary system fonts. +2. Generate a CJK subset from a charset or notification corpus. +3. Prefer one notification fallback font size first, then add more sizes only + after measuring resource cost. +4. Keep Latin text on the existing Renaissance/Gothic fonts and use the CJK + font only as a fallback for codepoints missing from the primary font. + +Noto Sans CJK and Source Han Sans both publish region-specific Simplified and +Traditional downloads. Their upstream documentation recommends region-specific +subset fonts when only one region's glyphs are needed. For very large source +fonts, use [fontTools subset](https://fonttools.readthedocs.io/en/latest/subset/) +first, then convert the reduced outline font to `.pbf`. + +## Generating A PBF + +Install the Python dependencies, then generate a test font: + +```sh +python3 -m pip install -r Utilities/requirements.txt +python3 Utilities/mkcjkfont.py \ + --font /path/to/NotoSansCJKsc-Regular.otf \ + --charset-file Utilities/cjk/notification_zh_seed.txt \ + --size 18 \ + --line-height 20 \ + --output build/cjk-notification-18.pbf \ + --preview build/cjk-notification-18.txt +``` + +For Traditional Chinese, use a TC/TW or HK source font and a Traditional +charset. For mixed Simplified/Traditional notifications, merge the charset +files and generate a single fallback font, then check the resulting `.pbf` +size. + +The seed charset in `Utilities/cjk/notification_zh_seed.txt` is deliberately +small. It is useful for smoke tests, not for production coverage. A production +charset should come from a real notification corpus or from a documented +common-character standard, then be measured against platform resource limits. + +## Runtime Integration + +The renderer asks one font for every glyph. Missing glyphs are replaced with +the font's fallback box, so RebbleOS adds an optional CJK fallback hook in the +font loader. If no fallback font has been configured, behavior is unchanged. + +The glyph lookup path is: + +1. Try the requested Gothic/Renaissance font. +2. If the glyph is absent and the codepoint is CJK or full-width punctuation, + try the configured CJK fallback font. +3. If that also fails, draw tofu. + +This keeps existing UI metrics for English text and avoids bloating every +system font with the same CJK bitmaps. The fallback font should be generated +with a line height close to the notification font it supplements; line layout +still uses the primary font's line height. + +After adding a generated `.pbf` to a resource pack, load it once for the current +app or overlay thread and register it: + +```c +GFont cjk_font = fonts_get_system_font(FONT_KEY_CJK_NOTIFICATION_18); +fonts_set_cjk_fallback_font(cjk_font); +``` + +The notification layer does this automatically when the platform resource +header defines `RESOURCE_ID_CJK_NOTIFICATION_18`. Without that resource, the +fallback hook is empty and the firmware keeps its previous behavior. + +Do not call `fonts_set_cjk_fallback_font()` with a temporary custom font unless +the same code also unloads it through `fonts_unload_custom_font()`. Unloading a +registered custom fallback clears the hook automatically. diff --git a/hw/platform/asterix/platform.h b/hw/platform/asterix/platform.h index 900fe298..723ad801 100644 --- a/hw/platform/asterix/platform.h +++ b/hw/platform/asterix/platform.h @@ -95,4 +95,8 @@ static inline uint8_t is_interrupt_set(void) return ((volatile int)(SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)) != 0 ; } +void hw_power_init(void); +uint16_t hw_power_get_bat_mv(void); +uint8_t hw_power_get_chg_status(void); + #endif diff --git a/hw/platform/tintin/platform.h b/hw/platform/tintin/platform.h index 3c04b4e1..99e10437 100644 --- a/hw/platform/tintin/platform.h +++ b/hw/platform/tintin/platform.h @@ -19,6 +19,7 @@ #define REGION_FS_START 0x2c0000 #define REGION_FS_PAGE_SIZE 0x1000 +#define REGION_FS_ERASE_SIZE REGION_FS_PAGE_SIZE #define REGION_FS_N_PAGES ((0x3E0000 - REGION_FS_START) / REGION_FS_PAGE_SIZE) #define REGION_APP_RES_START 0xB3A000 @@ -70,4 +71,8 @@ static inline uint8_t is_interrupt_set(void) return ((volatile int)(SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)) != 0 ; } +void hw_power_init(void); +uint16_t hw_power_get_bat_mv(void); +uint8_t hw_power_get_chg_status(void); + #endif diff --git a/hw/platform/tintin/tintin_asm.s b/hw/platform/tintin/tintin_asm.s index 107f69a9..e0f86dee 100644 --- a/hw/platform/tintin/tintin_asm.s +++ b/hw/platform/tintin/tintin_asm.s @@ -1,12 +1,15 @@ - .cpu cortex-m3 - .syntax unified - .code 16 + .cpu cortex-m3 + .syntax unified + .thumb - .globl delay_us + .globl delay_us + .type delay_us, %function + .thumb_func delay_us: - mov r3, #6 - muls r0, r3 + mov r3, #6 + muls r0, r3 1: - subs r0, #1 - bne 1b - bx lr + subs r0, #1 + bne 1b + bx lr + .size delay_us, . - delay_us diff --git a/lib/musl/time/localtime_r.c b/lib/musl/time/localtime_r.c index a5949d16..57698f34 100644 --- a/lib/musl/time/localtime_r.c +++ b/lib/musl/time/localtime_r.c @@ -1,6 +1,7 @@ #include "time_impl.h" #include #include +#include "rebble_time.h" //#include "libc.h" struct tm *localtime_r(const time_t *restrict t, struct tm *restrict tm) diff --git a/lib/musl/time/mktime.c b/lib/musl/time/mktime.c index 808ff924..95f431f0 100644 --- a/lib/musl/time/mktime.c +++ b/lib/musl/time/mktime.c @@ -1,5 +1,6 @@ #include "time_impl.h" #include +#include "rebble_time.h" time_t mktime(struct tm *tm) { diff --git a/rcore/api_func_symbols.h b/rcore/api_func_symbols.h index 2ae8eab9..1e238808 100644 --- a/rcore/api_func_symbols.h +++ b/rcore/api_func_symbols.h @@ -281,7 +281,7 @@ const VoidFunc sym[] = { [101] = (VoidFunc)gbitmap_create_with_data, // gbitmap_create_with_data@00000194 [102] = (VoidFunc)gbitmap_create_with_resource_proxy, // gbitmap_create_with_resource@00000198 [103] = (VoidFunc)gbitmap_destroy, // gbitmap_destroy@0000019c - [104] = gmtime, // gmtime@000001a0 + [104] = (VoidFunc)gmtime, // gmtime@000001a0 [105] = (VoidFunc)n_gpath_create, // gpath_create@000001a4 [106] = (VoidFunc)n_gpath_destroy, // gpath_destroy@000001a8 [107] = (VoidFunc)gpath_fill_app_legacy, // gpath_draw_filled_legacy@000001ac diff --git a/rcore/bluetooth.c b/rcore/bluetooth.c index 89ce968f..a4f74732 100644 --- a/rcore/bluetooth.c +++ b/rcore/bluetooth.c @@ -41,6 +41,8 @@ #include "protocol_service.h" #include "service.h" #include "rdb.h" +#include "btstack_config.h" +#include "btstack_rebble.h" /* Stack sizes of the threads */ #define STACK_SZ_CMD configMINIMAL_STACK_SIZE + 600 diff --git a/rcore/overlay_manager.h b/rcore/overlay_manager.h index 077a2434..4090e4e6 100644 --- a/rcore/overlay_manager.h +++ b/rcore/overlay_manager.h @@ -138,6 +138,7 @@ Window *overlay_window_stack_get_top_window(void); * @return \ref OverlayWindow that is very topmost */ OverlayWindow *overlay_stack_get_top_overlay_window(void); +list_head *overlay_window_get_list_head(void); /** * @brief Given a \ref Window, check all \ref OverlayWindow objects for any match diff --git a/rcore/power.c b/rcore/power.c index 9b5abbbb..c15767d6 100644 --- a/rcore/power.c +++ b/rcore/power.c @@ -5,6 +5,7 @@ * Author: Barry Carter */ #include +#include "platform.h" #include "power.h" #include "rebbleos.h" #include "notification_manager.h" diff --git a/rcore/protocol/protocol_blob.c b/rcore/protocol/protocol_blob.c index 0d35b39a..bc22c00d 100644 --- a/rcore/protocol/protocol_blob.c +++ b/rcore/protocol/protocol_blob.c @@ -68,9 +68,10 @@ uint8_t blob_insert(pcol_blob_db_key *blob) { pcol_blob_db_insert *iblob = (pcol_blob_db_insert *)blob; void *val_sz_start = (void *)blob + sizeof(pcol_blob_db_key) + blob->key_size; - uint8_t val_sz = *((uint8_t *)val_sz_start); + uint16_t val_sz; int key_size = blob->key_size; + memcpy(&val_sz, val_sz_start, sizeof(val_sz)); printf(" ValueSize: %d, %d\n", val_sz, key_size); void *val_start = val_sz_start + sizeof(iblob->value_size); diff --git a/rcore/protocol/protocol_transfer.c b/rcore/protocol/protocol_transfer.c index ba4c9269..701c5c02 100644 --- a/rcore/protocol/protocol_transfer.c +++ b/rcore/protocol/protocol_transfer.c @@ -24,6 +24,7 @@ #include "pebble_protocol.h" #include "protocol_service.h" #include "notification_manager.h" +#include "fs_internal.h" /* Configure Logging */ #define MODULE_NAME "pcolxfr" diff --git a/rcore/service.c b/rcore/service.c index 24e59183..20e2b4c9 100644 --- a/rcore/service.c +++ b/rcore/service.c @@ -9,6 +9,7 @@ */ #include "FreeRTOS.h" +#include #include "task.h" #include "queue.h" #include "service.h" diff --git a/rcore/service/protocol_service.c b/rcore/service/protocol_service.c index e10328be..4c063759 100644 --- a/rcore/service/protocol_service.c +++ b/rcore/service/protocol_service.c @@ -271,13 +271,22 @@ void packet_send_to_transport(RebblePacket packet, uint16_t endpoint, uint8_t *d /* Timer */ +static void _protocol_service_timer_callback(CoreTimer *timer) +{ + ProtocolTimer *ptimer = (ProtocolTimer *)timer; + + if (ptimer->callback) + ptimer->callback(ptimer); +} + ProtocolTimer *protocol_service_timer_create(ProtocolTimerCallback pcallback, TickType_t timeout) { ProtocolTimer *ct = mem_heap_alloc(&mem_heaps[HEAP_LOWPRIO], sizeof(ProtocolTimer)); assert(ct); memset(ct, 0, sizeof(ProtocolTimer)); - ct->timer.callback = pcallback; + ct->timer.callback = _protocol_service_timer_callback; + ct->callback = pcallback; ct->timeout_ms = timeout; return ct; } diff --git a/rcore/service/protocol_service.h b/rcore/service/protocol_service.h index b4e74ac3..ceca0f31 100644 --- a/rcore/service/protocol_service.h +++ b/rcore/service/protocol_service.h @@ -19,16 +19,18 @@ ProtocolTransportSender packet_get_transport(RebblePacket packet); void packet_set_transport(RebblePacket packet, ProtocolTransportSender transport); void packet_send_to_transport(RebblePacket packet, uint16_t endpoint, uint8_t *data, uint16_t len); +struct ProtocolTimer; +typedef void (*ProtocolTimerCallback)(struct ProtocolTimer *); + typedef struct ProtocolTimer { CoreTimer timer; + ProtocolTimerCallback callback; TickType_t timeout_ms; uint8_t on_queue; } ProtocolTimer; -typedef void (*ProtocolTimerCallback)(struct ProtocolTimer *) ; - void protocol_service_timer_restart(ProtocolTimer *timer); void protocol_service_timer_start(ProtocolTimer *timer, TickType_t timeout); void protocol_service_timer_cancel(ProtocolTimer *timer); void protocol_service_timer_destroy(ProtocolTimer *timer); -ProtocolTimer *protocol_service_timer_create(ProtocolTimerCallback callback, TickType_t timeout); \ No newline at end of file +ProtocolTimer *protocol_service_timer_create(ProtocolTimerCallback callback, TickType_t timeout); diff --git a/res b/res index 4746cac8..b48c0d38 160000 --- a/res +++ b/res @@ -1 +1 @@ -Subproject commit 4746cac86d91ae8a98e2788c80bf27613a33b212 +Subproject commit b48c0d382e9cde3a61840afeb46058c3ca9fbbd9 diff --git a/rwatch/graphics/font_file.c b/rwatch/graphics/font_file.c index ecb53b21..2541b462 100644 --- a/rwatch/graphics/font_file.c +++ b/rwatch/graphics/font_file.c @@ -2,31 +2,74 @@ #include "font_loader.h" #include "fs.h" -uint8_t n_graphics_font_get_line_height(struct file *font) { - struct fd fd; - n_GFontInfo info; - - /* XXX: cache this */ - fs_open(&fd, font); - fs_read(&fd, &info, sizeof(info)); - return info.line_height; +#define FONT_TOFU_CACHE_BIT 0x80000000UL + +static bool _font_codepoint_is_cjk_fallback_candidate(uint32_t codepoint) +{ + return (codepoint >= 0x2E80 && codepoint <= 0x2EFF) || + (codepoint >= 0x3000 && codepoint <= 0x303F) || + (codepoint >= 0x31C0 && codepoint <= 0x31EF) || + (codepoint >= 0x3400 && codepoint <= 0x4DBF) || + (codepoint >= 0x4E00 && codepoint <= 0x9FFF) || + (codepoint >= 0xF900 && codepoint <= 0xFAFF) || + (codepoint >= 0xFF00 && codepoint <= 0xFFEF); } -n_GGlyphInfo * n_graphics_font_get_glyph_info(struct file *font, uint32_t codepoint) { +static uint32_t _font_tofu_cache_codepoint(uint32_t codepoint) +{ + return codepoint | FONT_TOFU_CACHE_BIT; +} + +static uint32_t _font_read_codepoint(uint8_t *offset_entry, + uint8_t codepoint_bytes) +{ + uint32_t codepoint = 0; + + memcpy(&codepoint, offset_entry, codepoint_bytes); + return codepoint; +} + +static uint32_t _font_read_glyph_offset(uint8_t *offset_entry, + uint8_t codepoint_bytes, uint8_t features) +{ + uint32_t offset = 0; + + if (features & n_GFontFeature2ByteGlyphOffset) + memcpy(&offset, offset_entry + codepoint_bytes, sizeof(uint16_t)); + else + memcpy(&offset, offset_entry + codepoint_bytes, sizeof(uint32_t)); + + return offset; +} + +static n_GGlyphInfo *_font_load_glyph_info(struct file *font, + uint32_t codepoint, bool *found, bool use_tofu) +{ struct fd fd; n_GFontInfo info; n_GGlyphInfo pglyph; - + bool matched = false; + + if (found) + *found = false; + n_GGlyphInfo *cglyph = fonts_glyphcache_get(font, codepoint); + if (cglyph) { + if (found) + *found = true; return cglyph; } - + + cglyph = fonts_glyphcache_get(font, _font_tofu_cache_codepoint(codepoint)); + + if (cglyph) + return use_tofu ? cglyph : NULL; + /* XXX: cache this */ fs_open(&fd, font); fs_read(&fd, &info, sizeof(info)); - uint8_t * data; uint8_t hash_table_size = 255, codepoint_bytes = 4, features = 0; switch (info.version) { case 1: @@ -55,46 +98,55 @@ n_GGlyphInfo * n_graphics_font_get_glyph_info(struct file *font, uint32_t codepo (features & n_GFontFeature2ByteGlyphOffset ? 2 : 4); /* Read the codepoint from the hash table ... */ - fs_seek(&fd, (codepoint % hash_table_size) * sizeof(n_GFontHashTableEntry), FS_SEEK_CUR); - + fs_seek(&fd, + (codepoint % hash_table_size) * sizeof(n_GFontHashTableEntry), + FS_SEEK_CUR); + n_GFontHashTableEntry hash_data; fs_read(&fd, &hash_data, sizeof(hash_data)); /* ... and seek past the rest of the hash table. */ - loc = fs_seek(&fd, loc + hash_table_size * sizeof(n_GFontHashTableEntry), FS_SEEK_SET); + loc = fs_seek(&fd, loc + hash_table_size * sizeof(n_GFontHashTableEntry), + FS_SEEK_SET); if (hash_data.hash_value != (codepoint % hash_table_size)) { - // There was no hash table entry with the correct hash. Fall back to tofu. - fs_seek(&fd, offset_table_item_length * info.glyph_amount + 4, FS_SEEK_CUR); + if (!use_tofu) + return NULL; + + // There was no hash table entry with the correct hash. Use tofu. + fs_seek(&fd, offset_table_item_length * info.glyph_amount + 4, + FS_SEEK_CUR); goto readglyph; } - + /* It exists, so we find it in the offset table. */ fs_seek(&fd, loc + hash_data.offset_table_offset, FS_SEEK_SET); - uint8_t offset_entry[8]; /* 4 bytes max for codepoint, 4 bytes max for glyph offset */ + uint8_t offset_entry[8]; /* 4 bytes codepoint, 4 bytes glyph offset */ - uint16_t iters = 0; // theoretical possibility of 255 entries in an offset - // table mean that we can't use a uint8 for safety - do { + for (uint16_t i = 0; i < hash_data.offset_table_size; i++) { fs_read(&fd, offset_entry, offset_table_item_length); - iters++; - } while ((codepoint_bytes == 2 - ? *((uint16_t *) offset_entry) - : *((uint32_t *) offset_entry)) != codepoint && - iters <= hash_data.offset_table_size); - - if ((codepoint_bytes == 2 - ? *((uint16_t *) offset_entry) - : *((uint32_t *) offset_entry)) != codepoint) { - // We couldn't find the correct entry. Fall back to tofu. - fs_seek(&fd, loc + offset_table_item_length * info.glyph_amount + 4, FS_SEEK_SET); + + if (_font_read_codepoint(offset_entry, codepoint_bytes) == codepoint) { + matched = true; + break; + } + } + + if (!matched) { + if (!use_tofu) + return NULL; + + // We couldn't find the correct entry. Use tofu. + fs_seek(&fd, loc + offset_table_item_length * info.glyph_amount + 4, + FS_SEEK_SET); goto readglyph; } + if (found) + *found = true; + fs_seek(&fd, loc + offset_table_item_length * info.glyph_amount + - (features & n_GFontFeature2ByteGlyphOffset - ? *((uint16_t *) (offset_entry + codepoint_bytes)) - : *((uint32_t *) (offset_entry + codepoint_bytes))), + _font_read_glyph_offset(offset_entry, codepoint_bytes, features), FS_SEEK_SET); readglyph: @@ -102,12 +154,48 @@ n_GGlyphInfo * n_graphics_font_get_glyph_info(struct file *font, uint32_t codepo fs_read(&fd, &pglyph, sizeof(pglyph)); int gbits = pglyph.height * pglyph.width; int gbytes = (gbits + 7) / 8; - + n_GGlyphInfo *glyph = malloc(sizeof(pglyph) + gbytes); memcpy(glyph, &pglyph, sizeof(pglyph)); fs_read(&fd, glyph + 1, gbytes); + + fonts_glyphcache_put(font, + matched ? codepoint : _font_tofu_cache_codepoint(codepoint), glyph); + + return glyph; +} + +uint8_t n_graphics_font_get_line_height(struct file *font) { + struct fd fd; + n_GFontInfo info; - fonts_glyphcache_put(font, codepoint, glyph); + /* XXX: cache this */ + fs_open(&fd, font); + fs_read(&fd, &info, sizeof(info)); + return info.line_height; +} + +n_GGlyphInfo * n_graphics_font_get_glyph_info(struct file *font, uint32_t codepoint) { + bool found = false; + n_GGlyphInfo *glyph = _font_load_glyph_info(font, codepoint, &found, + false); + + if (found) + return glyph; + + if (_font_codepoint_is_cjk_fallback_candidate(codepoint)) { + GFont fallback = fonts_get_cjk_fallback_font(); + + if (fallback && fallback != font) { + glyph = _font_load_glyph_info(fallback, codepoint, &found, false); + if (found) { + _font_load_glyph_info(font, codepoint, NULL, true); + return glyph; + } + } + } + + glyph = _font_load_glyph_info(font, codepoint, NULL, true); return glyph; } diff --git a/rwatch/graphics/font_loader.c b/rwatch/graphics/font_loader.c index 45f47fa2..d1268abb 100644 --- a/rwatch/graphics/font_loader.c +++ b/rwatch/graphics/font_loader.c @@ -30,16 +30,36 @@ GFont fonts_get_system_font_by_resource_id(uint32_t resource_id); static GFontCache *_app_font_cache = NULL; static GFontCache *_ovl_font_cache = NULL; +static GFont _app_cjk_fallback_font = NULL; +static GFont _ovl_cjk_fallback_font = NULL; + +static GFont *_thread_cjk_fallback_font(void) +{ + switch (appmanager_get_thread_type()) + { + case AppThreadMainApp: + return &_app_cjk_fallback_font; + case AppThreadOverlay: + return &_ovl_cjk_fallback_font; + default: + KERN_LOG("font", APP_LOG_LEVEL_ERROR, "Why you need fonts?"); + return NULL; + } +} void fonts_resetcache() { - struct GFontCache **cachep; - KERN_LOG("font", APP_LOG_LEVEL_DEBUG, "Purging fonts"); switch (appmanager_get_thread_type()) { - case AppThreadMainApp: _app_font_cache = NULL; break; - case AppThreadOverlay: _ovl_font_cache = NULL; break; + case AppThreadMainApp: + _app_font_cache = NULL; + _app_cjk_fallback_font = NULL; + break; + case AppThreadOverlay: + _ovl_font_cache = NULL; + _ovl_cjk_fallback_font = NULL; + break; default: KERN_LOG("font", APP_LOG_LEVEL_ERROR, "Why you need fonts?"); return; @@ -123,10 +143,30 @@ GFont fonts_load_custom_font_proxy(ResHandle handle) */ void fonts_unload_custom_font(GFont font) { + GFont *fallback = _thread_cjk_fallback_font(); + + if (fallback && *fallback == font) + *fallback = NULL; + _fonts_glyphcache_purge(font); app_free(font); } +GFont fonts_get_cjk_fallback_font(void) +{ + GFont *fallback = _thread_cjk_fallback_font(); + + return fallback ? *fallback : NULL; +} + +void fonts_set_cjk_fallback_font(GFont font) +{ + GFont *fallback = _thread_cjk_fallback_font(); + + if (fallback) + *fallback = font; +} + #define EQ_FONT(font) (strncmp(key, "RESOURCE_ID_" #font, strlen(key)) == 0) return RESOURCE_ID_ ## font; /* @@ -173,6 +213,9 @@ uint16_t _fonts_get_resource_id_for_key(const char *key) else if EQ_FONT(LECO_28_LIGHT_NUMBERS) else if EQ_FONT(LECO_42_NUMBERS) else if EQ_FONT(FONT_FALLBACK) +#ifdef RESOURCE_ID_CJK_NOTIFICATION_18 + else if EQ_FONT(CJK_NOTIFICATION_18) +#endif return RESOURCE_ID_FONT_FALLBACK; } diff --git a/rwatch/graphics/font_loader.h b/rwatch/graphics/font_loader.h index 8e686980..c72535cc 100644 --- a/rwatch/graphics/font_loader.h +++ b/rwatch/graphics/font_loader.h @@ -13,6 +13,8 @@ GFont fonts_get_system_font(const char *key); GFont fonts_load_custom_font(ResHandle handle, const struct file* file); void fonts_unload_custom_font(GFont font); GFont fonts_load_custom_font_proxy(ResHandle handle); +GFont fonts_get_cjk_fallback_font(void); +void fonts_set_cjk_fallback_font(GFont font); n_GGlyphInfo *fonts_glyphcache_get(GFont font, uint32_t codepoint); -void fonts_glyphcache_put(GFont font, uint32_t codepoint, n_GGlyphInfo *glyph); \ No newline at end of file +void fonts_glyphcache_put(GFont font, uint32_t codepoint, n_GGlyphInfo *glyph); diff --git a/rwatch/graphics/graphics.c b/rwatch/graphics/graphics.c index 70c0dab8..3a371c02 100644 --- a/rwatch/graphics/graphics.c +++ b/rwatch/graphics/graphics.c @@ -120,7 +120,8 @@ GBitmap *graphics_capture_frame_buffer(n_GContext *context) return &_fb_gbitmap; } -GBitmap *graphics_capture_frame_buffer_format(n_GContext *context, int format) +GBitmap *graphics_capture_frame_buffer_format(n_GContext *context, + int format) { // rbl_lock_frame_buffer LOG_DEBUG("fb lock"); @@ -133,10 +134,11 @@ GBitmap *graphics_capture_frame_buffer_format(n_GContext *context, int format) #endif } -void graphics_release_frame_buffer(n_GContext *context, GBitmap *bitmap) +bool graphics_release_frame_buffer(n_GContext *context, GBitmap *bitmap) { // rbl_unlock_frame_buffer LOG_DEBUG("fb unlock"); + return true; } @@ -323,4 +325,4 @@ void graphics_context_set_stroke_color_2bit(GContext * ctx, int color) void gpath_fill_app_legacy(n_GContext * ctx, n_GPath * path) { gpath_fill_app(ctx, path); -} \ No newline at end of file +} diff --git a/rwatch/graphics/graphics_wrapper.h b/rwatch/graphics/graphics_wrapper.h index fecc8add..1e134d42 100644 --- a/rwatch/graphics/graphics_wrapper.h +++ b/rwatch/graphics/graphics_wrapper.h @@ -19,3 +19,6 @@ void graphics_draw_bitmap_in_rect(GContext *ctx, const GBitmap *bitmap, GRect re void graphics_draw_pixel(n_GContext * ctx, n_GPoint p); void graphics_draw_rect(n_GContext * ctx, n_GRect rect, uint16_t radius, n_GCornerMask mask); GBitmap *graphics_capture_frame_buffer(n_GContext *context); +GBitmap *graphics_capture_frame_buffer_format(n_GContext *context, + int format); +bool graphics_release_frame_buffer(n_GContext *context, GBitmap *bitmap); diff --git a/rwatch/graphics/libros_graphics.h b/rwatch/graphics/libros_graphics.h index 5b81d1ef..dbbb2860 100644 --- a/rwatch/graphics/libros_graphics.h +++ b/rwatch/graphics/libros_graphics.h @@ -27,8 +27,9 @@ void gpath_move_to_app(n_GPath * path, n_GPoint offset); // n_GPath * gpath_create_app(n_GPathInfo * path_info); GBitmap *graphics_capture_frame_buffer(n_GContext *context); -GBitmap *graphics_capture_frame_buffer_format(n_GContext *context, int format); -void graphics_release_frame_buffer(n_GContext *context, GBitmap *bitmap); +GBitmap *graphics_capture_frame_buffer_format(n_GContext *context, + int format); +bool graphics_release_frame_buffer(n_GContext *context, GBitmap *bitmap); bool graphics_frame_buffer_is_captured(GContext * ctx); void grect_align(GRect *rect, const GRect *inside_rect, const GAlign alignment, const bool clip); @@ -37,4 +38,4 @@ GColor graphics_gcolor_from_2bit(int color_2bit); void graphics_context_set_fill_color_2bit(GContext * ctx, int color); void graphics_context_set_stroke_color_2bit(GContext * ctx, int color); void gpath_fill_app_legacy(n_GContext * ctx, n_GPath * path); -void graphics_context_set_text_color_2bit(GContext * ctx, int color); \ No newline at end of file +void graphics_context_set_text_color_2bit(GContext * ctx, int color); diff --git a/rwatch/graphics/system_font.h b/rwatch/graphics/system_font.h index 0cb569a3..f46b23d5 100644 --- a/rwatch/graphics/system_font.h +++ b/rwatch/graphics/system_font.h @@ -57,6 +57,7 @@ #define FONT_KEY_GOTHIC_28 "RESOURCE_ID_GOTHIC_28" #define FONT_KEY_GOTHIC_28_BOLD "RESOURCE_ID_GOTHIC_28_BOLD" #define FONT_KEY_GOTHIC_36 "RESOURCE_ID_GOTHIC_36" +#define FONT_KEY_CJK_NOTIFICATION_18 "RESOURCE_ID_CJK_NOTIFICATION_18" #define FONT_KEY_BITHAM_30_BLACK "RESOURCE_ID_BITHAM_30_BLACK" #define FONT_KEY_BITHAM_42_BOLD "RESOURCE_ID_BITHAM_42_BOLD" diff --git a/rwatch/pebble.h b/rwatch/pebble.h index e655e65d..dc93b891 100644 --- a/rwatch/pebble.h +++ b/rwatch/pebble.h @@ -35,6 +35,14 @@ struct n_GRect; #include "gbitmap.h" +struct n_GContext; + +GBitmap *graphics_capture_frame_buffer(struct n_GContext *context); +GBitmap *graphics_capture_frame_buffer_format(struct n_GContext *context, + int format); +bool graphics_release_frame_buffer(struct n_GContext *context, + GBitmap *bitmap); + int32_t sin_lookup(int32_t angle); int32_t cos_lookup(int32_t angle); @@ -87,4 +95,3 @@ typedef struct { GEdgeInsetsN(__VA_ARGS__, GEdgeInsets4, GEdgeInsets3, GEdgeInsets2, GEdgeInsets1)(__VA_ARGS__) GRect grect_inset(GRect rect, GEdgeInsets insets); - diff --git a/rwatch/storage/storage_persist.c b/rwatch/storage/storage_persist.c index bdf1028b..ee4734e2 100644 --- a/rwatch/storage/storage_persist.c +++ b/rwatch/storage/storage_persist.c @@ -191,7 +191,7 @@ status_t persist_write(const uint32_t key, const void *data, const size_t size) return E_ERROR; } } else { - if (rdb_update(db, &c_key, sizeof(c_key), data, size) != Blob_Success) { + if (rdb_update(db, (uint8_t *)&c_key, sizeof(c_key), data, size) != Blob_Success) { rdb_close(db); return E_ERROR; } @@ -201,7 +201,7 @@ status_t persist_write(const uint32_t key, const void *data, const size_t size) return size; } -status_t persist_read(const uint32_t key, const void *buffer, const size_t size) +status_t persist_read(const uint32_t key, void *buffer, const size_t size) { struct rdb_iter it; rdb_select_result_list head; diff --git a/rwatch/storage/storage_persist.h b/rwatch/storage/storage_persist.h index 9c8749ef..313da696 100644 --- a/rwatch/storage/storage_persist.h +++ b/rwatch/storage/storage_persist.h @@ -169,4 +169,4 @@ status_t persist_delete(const uint32_t key); status_t persist_write(const uint32_t key, const void *data, size_t size); -status_t persist_read(const uint32_t key, const void *buffer, const size_t size); +status_t persist_read(const uint32_t key, void *buffer, const size_t size); diff --git a/rwatch/ui/layer/action_bar_layer.c b/rwatch/ui/layer/action_bar_layer.c index 54a392c7..05e816a3 100644 --- a/rwatch/ui/layer/action_bar_layer.c +++ b/rwatch/ui/layer/action_bar_layer.c @@ -77,7 +77,7 @@ void action_bar_layer_add_to_window(ActionBarLayer *action_bar, struct Window *w { Layer *window_layer = window_get_root_layer(window); layer_add_child(window_layer, &action_bar->layer); - window_set_click_config_provider_with_context(window, (ClickConfigProvider) action_bar->click_config_provider, + window_set_click_config_provider_with_context(window, action_bar->click_config_provider, action_bar); GRect bounds = layer_get_unobstructed_bounds(window_layer); diff --git a/rwatch/ui/layer/action_bar_layer.h b/rwatch/ui/layer/action_bar_layer.h index 28fab2a0..ee68e5c3 100644 --- a/rwatch/ui/layer/action_bar_layer.h +++ b/rwatch/ui/layer/action_bar_layer.h @@ -22,7 +22,7 @@ typedef struct ActionBarLayer GBitmap *icons[NUM_ACTION_BAR_ITEMS + 1]; GColor background_color; void *context; - ClickConfigProvider *click_config_provider; + ClickConfigProvider click_config_provider; } ActionBarLayer; void action_bar_layer_ctor(ActionBarLayer *mlayer, GRect frame); diff --git a/rwatch/ui/layer/single_notification_layer.c b/rwatch/ui/layer/single_notification_layer.c index 5efd4b9b..9a3800be 100644 --- a/rwatch/ui/layer/single_notification_layer.c +++ b/rwatch/ui/layer/single_notification_layer.c @@ -19,6 +19,14 @@ static void single_notification_layer_update_proc(Layer *layer, GContext *ctx); +static void _single_notification_layer_configure_cjk_fallback(void) +{ +#ifdef RESOURCE_ID_CJK_NOTIFICATION_18 + fonts_set_cjk_fallback_font( + fonts_get_system_font(FONT_KEY_CJK_NOTIFICATION_18)); +#endif +} + #define X_PADDING 4 #define APPNAME_HEIGHT 28 @@ -135,6 +143,8 @@ Layer *single_notification_layer_get_layer(SingleNotificationLayer *l) { uint16_t single_notification_layer_height(SingleNotificationLayer *l) { uint16_t height = 0; + + _single_notification_layer_configure_cjk_fallback(); GRect szrect = layer_get_frame(&l->layer); szrect.size.h = 1000; @@ -169,6 +179,8 @@ static void single_notification_layer_update_proc(Layer *layer, GContext *ctx) { SingleNotificationLayer *l = container_of(layer, SingleNotificationLayer, layer); GRect szrect = layer_get_frame(layer); GSize outsz; + + _single_notification_layer_configure_cjk_fallback(); graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, szrect, 0, GCornerNone); From 913d5d375f49feb0212ae00db386d7030c3c5564 Mon Sep 17 00:00:00 2001 From: Leask Wong Date: Sat, 6 Jun 2026 12:45:33 -0400 Subject: [PATCH 2/5] Trim tintin app memory budget --- hw/platform/tintin/platform_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/platform/tintin/platform_config.h b/hw/platform/tintin/platform_config.h index 10854159..5fc838da 100644 --- a/hw/platform/tintin/platform_config.h +++ b/hw/platform/tintin/platform_config.h @@ -10,12 +10,12 @@ */ #define MEMORY_SIZE_SYSTEM 8192 #define MEMORY_SIZE_LOWPRIO 2048 -#define MEMORY_SIZE_APP 40000 +#define MEMORY_SIZE_APP 39600 #define MEMORY_SIZE_WORKER 10000 #define MEMORY_SIZE_OVERLAY 16000 /* Size of the stack in WORDS */ -#define MEMORY_SIZE_APP_STACK 4000 +#define MEMORY_SIZE_APP_STACK 3900 #define MEMORY_SIZE_WORKER_STACK 100 #define MEMORY_SIZE_OVERLAY_STACK 450 From 026affc3b8b0933d70b5b4c887461a03de7683ac Mon Sep 17 00:00:00 2001 From: Leask Wong Date: Sat, 6 Jun 2026 12:52:20 -0400 Subject: [PATCH 3/5] Increase tintin RAM headroom --- Utilities/space.sh | 10 ++++++++++ hw/platform/tintin/platform_config.h | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Utilities/space.sh b/Utilities/space.sh index c276ab5d..7b4bfad2 100755 --- a/Utilities/space.sh +++ b/Utilities/space.sh @@ -7,11 +7,21 @@ getsym() { FLASH_REMAIN=$(($(getsym _flash_top) - ($(getsym _edata) - $(getsym _sdata) + $(getsym _erodata)))) RAM_REMAIN=$(($(getsym _ram_top) - $(getsym _end))) +STATUS=0 if [[ $(getsym _ccm_top) ]]; then CCRAM_REMAIN=$(($(getsym _ccm_top) - $(getsym _eccmidata))) echo "$CCRAM_REMAIN bytes of CCRAM available." + if (( CCRAM_REMAIN < 0 )); then + STATUS=1 + fi fi echo "$RAM_REMAIN bytes of RAM available for heap." echo "$FLASH_REMAIN bytes of flash unused." + +if (( RAM_REMAIN < 0 || FLASH_REMAIN < 0 )); then + STATUS=1 +fi + +exit $STATUS diff --git a/hw/platform/tintin/platform_config.h b/hw/platform/tintin/platform_config.h index 5fc838da..e406d8db 100644 --- a/hw/platform/tintin/platform_config.h +++ b/hw/platform/tintin/platform_config.h @@ -10,12 +10,12 @@ */ #define MEMORY_SIZE_SYSTEM 8192 #define MEMORY_SIZE_LOWPRIO 2048 -#define MEMORY_SIZE_APP 39600 +#define MEMORY_SIZE_APP 38000 #define MEMORY_SIZE_WORKER 10000 #define MEMORY_SIZE_OVERLAY 16000 /* Size of the stack in WORDS */ -#define MEMORY_SIZE_APP_STACK 3900 +#define MEMORY_SIZE_APP_STACK 3500 #define MEMORY_SIZE_WORKER_STACK 100 #define MEMORY_SIZE_OVERLAY_STACK 450 From 49f04d57c9903d7fe6c7233c99bd8d3139ed95e5 Mon Sep 17 00:00:00 2001 From: Leask Wong Date: Sat, 6 Jun 2026 12:59:53 -0400 Subject: [PATCH 4/5] Make CJK font fallback global --- Utilities/cjk/notification_zh_seed.txt | 1 + docs/cjk_fonts.md | 15 ++++++++------ res | 2 +- rwatch/graphics/font_loader.c | 22 ++++++++++++++++++--- rwatch/ui/layer/single_notification_layer.c | 12 ----------- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Utilities/cjk/notification_zh_seed.txt b/Utilities/cjk/notification_zh_seed.txt index 02f2451b..d7a00472 100644 --- a/Utilities/cjk/notification_zh_seed.txt +++ b/Utilities/cjk/notification_zh_seed.txt @@ -7,5 +7,6 @@ 的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组别计指权支识完 我你他她它我們你們他們她們它們收到通知消息短信電話电话微信郵件邮件群聊回覆回复提醒日程會議会议快遞快递外賣外卖支付銀行银行驗證碼验证码登入登錄登录訂單订单更新中文簡體简体繁體繁体漢字汉字台灣臺灣台湾香港澳門澳门 驗證碼验证码密碼密码註冊注册重置修改綁定绑定解綁解绑設備设备安全警告緊急紧急異常异常成功失敗失败取消確認确认批准拒絕拒绝邀請邀请加入退出已讀已读未讀未读查看查收請请回覆回复轉發转发分享評論评论點讚点赞收藏附件照片圖片图片文件位置地址導航导航門禁门禁鬧鐘闹钟航班列車列车票券優惠券优惠券賬單账单充值付款收款退款到賬到账餘額余额交易收入支出 +日程行程議程议程會議会议約會约会活動活动安排提醒待辦待办任務任务事項事项邀約邀约邀請邀请出席缺席參加参加參與参与參會参会主持主辦主办協辦协办嘉賓嘉宾客戶客户同事團隊团队部門部门產品产品項目项目專案专案討論讨论評審评审評估评估面試面试訪問访问拜訪拜访會客会客報告报告週會周会例會例会晨會晨会午會午会晚會晚会電話會电话会視訊视讯視頻视频語音语音直播培訓培训課程课程講座讲座研討研讨演示演講演讲簡報简报同步排期改期延期取消確認确认開始开始結束结束提前延後延后準時准时全天今天明天後天后天昨天本週本周下週下周上週上周上午中午下午晚上凌晨時間时间時區时区提醒稍後稍后小時小时分鐘分钟日期地點地点位置地址會議室会议室辦公室办公室大廳大厅前台樓層楼层座位線上线上線下线下遠端远端現場现场鏈接链接連結連結二维码二維碼備註备注說明说明詳情详情上海北京深圳廣州广州杭州南京成都武漢武汉西安台北臺北香港澳門澳门新加坡東京东京首爾首尔 張张王李趙赵劉刘陳陈楊杨黃黄吳吴周徐孫孙馬马朱胡林郭何高羅罗鄭郑梁謝谢宋唐許许鄧邓韓韩馮冯曹曾彭蕭萧蔡潘田董袁余葉叶蔣蒋杜蘇苏魏程呂吕丁沈任姚盧卢姜崔鐘钟譚谭陸陆汪范金石廖賈贾夏韋韦傅白鄒邹孟熊秦邱江尹薛閆闫段雷侯龍龙史陶黎賀贺顧顾毛郝龔龚邵萬万錢钱嚴严賴赖洪武莫孔湯汤向常溫温康施牛樊葛邢安齊齐易喬乔伍龐庞顏颜倪莊庄聶聂章魯鲁岳翟殷詹歐欧耿關关蘭兰焦俞左柳甘祝包寧宁尚符舒阮 。,、;:?!“”‘’()《》〈〉【】「」『』—…¥ diff --git a/docs/cjk_fonts.md b/docs/cjk_fonts.md index ce7c0080..c177a5ab 100644 --- a/docs/cjk_fonts.md +++ b/docs/cjk_fonts.md @@ -64,7 +64,9 @@ common-character standard, then be measured against platform resource limits. The renderer asks one font for every glyph. Missing glyphs are replaced with the font's fallback box, so RebbleOS adds an optional CJK fallback hook in the -font loader. If no fallback font has been configured, behavior is unchanged. +font loader. If the platform resource header defines +`RESOURCE_ID_CJK_NOTIFICATION_18`, the fallback font is loaded lazily the first +time a CJK glyph is needed. If that resource is absent, behavior is unchanged. The glyph lookup path is: @@ -78,17 +80,18 @@ system font with the same CJK bitmaps. The fallback font should be generated with a line height close to the notification font it supplements; line layout still uses the primary font's line height. -After adding a generated `.pbf` to a resource pack, load it once for the current -app or overlay thread and register it: +After adding a generated `.pbf` to a resource pack, expose it as +`RESOURCE_ID_CJK_NOTIFICATION_18`. The system renderer will then load it +automatically per app or overlay thread when Chinese text is first rendered. +Code that needs to override the fallback can still register a custom font: ```c GFont cjk_font = fonts_get_system_font(FONT_KEY_CJK_NOTIFICATION_18); fonts_set_cjk_fallback_font(cjk_font); ``` -The notification layer does this automatically when the platform resource -header defines `RESOURCE_ID_CJK_NOTIFICATION_18`. Without that resource, the -fallback hook is empty and the firmware keeps its previous behavior. +Without `RESOURCE_ID_CJK_NOTIFICATION_18`, the fallback hook is empty and the +firmware keeps its previous behavior. Do not call `fonts_set_cjk_fallback_font()` with a temporary custom font unless the same code also unloads it through `fonts_unload_custom_font()`. Unloading a diff --git a/res b/res index b48c0d38..5a7e84c8 160000 --- a/res +++ b/res @@ -1 +1 @@ -Subproject commit b48c0d382e9cde3a61840afeb46058c3ca9fbbd9 +Subproject commit 5a7e84c8182ca9c03b8d77159d8898986f5cf1aa diff --git a/rwatch/graphics/font_loader.c b/rwatch/graphics/font_loader.c index d1268abb..d7123171 100644 --- a/rwatch/graphics/font_loader.c +++ b/rwatch/graphics/font_loader.c @@ -47,6 +47,24 @@ static GFont *_thread_cjk_fallback_font(void) } } +static GFont _fonts_load_cjk_fallback_font(void) +{ +#ifdef RESOURCE_ID_CJK_NOTIFICATION_18 + GFont *fallback = _thread_cjk_fallback_font(); + + if (!fallback) + return NULL; + + if (!*fallback) + *fallback = fonts_get_system_font_by_resource_id( + RESOURCE_ID_CJK_NOTIFICATION_18); + + return *fallback; +#else + return NULL; +#endif +} + void fonts_resetcache() { KERN_LOG("font", APP_LOG_LEVEL_DEBUG, "Purging fonts"); @@ -154,9 +172,7 @@ void fonts_unload_custom_font(GFont font) GFont fonts_get_cjk_fallback_font(void) { - GFont *fallback = _thread_cjk_fallback_font(); - - return fallback ? *fallback : NULL; + return _fonts_load_cjk_fallback_font(); } void fonts_set_cjk_fallback_font(GFont font) diff --git a/rwatch/ui/layer/single_notification_layer.c b/rwatch/ui/layer/single_notification_layer.c index 9a3800be..5efd4b9b 100644 --- a/rwatch/ui/layer/single_notification_layer.c +++ b/rwatch/ui/layer/single_notification_layer.c @@ -19,14 +19,6 @@ static void single_notification_layer_update_proc(Layer *layer, GContext *ctx); -static void _single_notification_layer_configure_cjk_fallback(void) -{ -#ifdef RESOURCE_ID_CJK_NOTIFICATION_18 - fonts_set_cjk_fallback_font( - fonts_get_system_font(FONT_KEY_CJK_NOTIFICATION_18)); -#endif -} - #define X_PADDING 4 #define APPNAME_HEIGHT 28 @@ -143,8 +135,6 @@ Layer *single_notification_layer_get_layer(SingleNotificationLayer *l) { uint16_t single_notification_layer_height(SingleNotificationLayer *l) { uint16_t height = 0; - - _single_notification_layer_configure_cjk_fallback(); GRect szrect = layer_get_frame(&l->layer); szrect.size.h = 1000; @@ -179,8 +169,6 @@ static void single_notification_layer_update_proc(Layer *layer, GContext *ctx) { SingleNotificationLayer *l = container_of(layer, SingleNotificationLayer, layer); GRect szrect = layer_get_frame(layer); GSize outsz; - - _single_notification_layer_configure_cjk_fallback(); graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, szrect, 0, GCornerNone); From 74d6dce84be8c19d14d5b7389f158dda12337885 Mon Sep 17 00:00:00 2001 From: Leask Wong Date: Tue, 16 Jun 2026 15:54:39 -0400 Subject: [PATCH 5/5] Expand CJK fallback coverage --- Utilities/cjk/notification_zh_seed.txt | 8 +++++++- Utilities/mkpbz.py | 2 +- res | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Utilities/cjk/notification_zh_seed.txt b/Utilities/cjk/notification_zh_seed.txt index d7a00472..182611f7 100644 --- a/Utilities/cjk/notification_zh_seed.txt +++ b/Utilities/cjk/notification_zh_seed.txt @@ -7,6 +7,12 @@ 的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组别计指权支识完 我你他她它我們你們他們她們它們收到通知消息短信電話电话微信郵件邮件群聊回覆回复提醒日程會議会议快遞快递外賣外卖支付銀行银行驗證碼验证码登入登錄登录訂單订单更新中文簡體简体繁體繁体漢字汉字台灣臺灣台湾香港澳門澳门 驗證碼验证码密碼密码註冊注册重置修改綁定绑定解綁解绑設備设备安全警告緊急紧急異常异常成功失敗失败取消確認确认批准拒絕拒绝邀請邀请加入退出已讀已读未讀未读查看查收請请回覆回复轉發转发分享評論评论點讚点赞收藏附件照片圖片图片文件位置地址導航导航門禁门禁鬧鐘闹钟航班列車列车票券優惠券优惠券賬單账单充值付款收款退款到賬到账餘額余额交易收入支出 -日程行程議程议程會議会议約會约会活動活动安排提醒待辦待办任務任务事項事项邀約邀约邀請邀请出席缺席參加参加參與参与參會参会主持主辦主办協辦协办嘉賓嘉宾客戶客户同事團隊团队部門部门產品产品項目项目專案专案討論讨论評審评审評估评估面試面试訪問访问拜訪拜访會客会客報告报告週會周会例會例会晨會晨会午會午会晚會晚会電話會电话会視訊视讯視頻视频語音语音直播培訓培训課程课程講座讲座研討研讨演示演講演讲簡報简报同步排期改期延期取消確認确认開始开始結束结束提前延後延后準時准时全天今天明天後天后天昨天本週本周下週下周上週上周上午中午下午晚上凌晨時間时间時區时区提醒稍後稍后小時小时分鐘分钟日期地點地点位置地址會議室会议室辦公室办公室大廳大厅前台樓層楼层座位線上线上線下线下遠端远端現場现场鏈接链接連結連結二维码二維碼備註备注說明说明詳情详情上海北京深圳廣州广州杭州南京成都武漢武汉西安台北臺北香港澳門澳门新加坡東京东京首爾首尔 +日程行程議程议程會議会议約會约会預約预约活動活动安排提醒待辦待办任務任务事項事项邀約邀约邀請邀请出席缺席參加参加參與参与參會参会主持主辦主办協辦协办嘉賓嘉宾客戶客户同事團隊团队部門部门產品产品項目项目專案专案討論讨论評審评审評估评估面試面试訪問访问拜訪拜访會客会客報告报告週會周会例會例会晨會晨会午會午会晚會晚会電話會电话会視訊视讯視頻视频語音语音直播培訓培训課程课程講座讲座研討研讨演示演講演讲簡報简报同步排期改期延期取消確認确认開始开始結束结束提前延後延后準時准时全天今天明天後天后天昨天本週本周下週下周上週上周上午中午下午晚上凌晨時間时间時區时区提醒稍後稍后小時小时分鐘分钟日期地點地点位置地址會議室会议室辦公室办公室大廳大厅前台樓層楼层座位線上线上線下线下遠端远端現場现场鏈接链接連結連結二维码二維碼備註备注說明说明詳情详情上海北京深圳廣州广州杭州南京成都武漢武汉西安台北臺北香港澳門澳门新加坡東京东京首爾首尔 張张王李趙赵劉刘陳陈楊杨黃黄吳吴周徐孫孙馬马朱胡林郭何高羅罗鄭郑梁謝谢宋唐許许鄧邓韓韩馮冯曹曾彭蕭萧蔡潘田董袁余葉叶蔣蒋杜蘇苏魏程呂吕丁沈任姚盧卢姜崔鐘钟譚谭陸陆汪范金石廖賈贾夏韋韦傅白鄒邹孟熊秦邱江尹薛閆闫段雷侯龍龙史陶黎賀贺顧顾毛郝龔龚邵萬万錢钱嚴严賴赖洪武莫孔湯汤向常溫温康施牛樊葛邢安齊齐易喬乔伍龐庞顏颜倪莊庄聶聂章魯鲁岳翟殷詹歐欧耿關关蘭兰焦俞左柳甘祝包寧宁尚符舒阮 +偉伟傑杰倫伦娜敏芳磊強强軍军洋勇艷艳靜静麗丽超濤涛明華华平媽媽妈妈爸爸 +音樂音乐歌曲歌手專輯专辑標題标题播放暫停暂停上一首下一首正在播放周杰倫周杰伦七里香 +來電来电通話通话未接拒接掛斷挂断聯絡人联系人號碼号码來自来自 +藍牙蓝牙配對配对手環手环耳機耳机小米華為华为蘋果苹果掃描扫描連線连接斷線断线 +菜單菜单設定设置返回保存刪除删除新增編輯编辑搜尋搜索選擇选择完成更多關閉关闭開啟开启 +訂閱订阅退訂退订簽收签收物流不足已簽收已签收餘額不足余额不足 。,、;:?!“”‘’()《》〈〉【】「」『』—…¥ diff --git a/Utilities/mkpbz.py b/Utilities/mkpbz.py index 172a23f4..c579aedb 100755 --- a/Utilities/mkpbz.py +++ b/Utilities/mkpbz.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Builds a firmware image zipball. diff --git a/res b/res index 5a7e84c8..bc173cc9 160000 --- a/res +++ b/res @@ -1 +1 @@ -Subproject commit 5a7e84c8182ca9c03b8d77159d8898986f5cf1aa +Subproject commit bc173cc94634bc16c4c85679eff6fedc9241091c