From fab03edcb1039653a1469a9a783046885166fd8b Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sat, 11 Apr 2026 23:05:26 -0400 Subject: [PATCH] Initial code commit --- .clang-format | 7 + .gitignore | 3 + clangd.zsh | 18 +++ package.json | 29 ++++ src/c/main.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++ wscript | 36 +++++ 6 files changed, 503 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100755 clangd.zsh create mode 100644 package.json create mode 100644 src/c/main.c create mode 100644 wscript diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..31aa732 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +--- +BasedOnStyle: LLVM +TabWidth: 4 +IndentWidth: 4 +UseTab: ForIndentation +ColumnLimit: 0 +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b41ca9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.clangd +/build +/.lock* diff --git a/clangd.zsh b/clangd.zsh new file mode 100755 index 0000000..b6fdaa7 --- /dev/null +++ b/clangd.zsh @@ -0,0 +1,18 @@ +#!/usr/bin/env zsh +echo "CompileFlags: + Add: + [ + -DPBL_DISPLAY_WIDTH=200, + -DPBL_DISPLAY_HEIGHT=228, + -xc, + -nostdinc, + -DPBL_COLOR, + -I${HOME}/.pebble-sdk/SDKs/current/sdk-core/pebble/emery/include, + -include$(pwd)/build/include/message_keys.auto.h, + -I$(pwd)/build/emery, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/stdint.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/stdlib.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/time.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/include/stddef.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/include/stdbool.h, + ]" > ./.clangd diff --git a/package.json b/package.json new file mode 100644 index 0000000..8c78c10 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "author": "RandyTheSilly", + "keywords": [ + "networking", + "subnet", + "converter" + ], + "name": "subnet-helper", + "pebble": { + "displayName": "Subnet Helper", + "enableMultiJS": false, + "projectType": "native", + "sdkVersion": "3", + "targetPlatforms": [ + "aplite", + "basalt", + "chalk", + "diorite", + "flint", + "emery", + "gabbro" + ], + "uuid": "09853807-57bd-47e5-8684-e8f2bb3eecad", + "watchapp": { + "watchface": false + } + }, + "version": "1.0.0" +} diff --git a/src/c/main.c b/src/c/main.c new file mode 100644 index 0000000..ef81a34 --- /dev/null +++ b/src/c/main.c @@ -0,0 +1,410 @@ +#include + +#define ITEM_COUNT 33 + +static Window *s_main_window; +static Window *s_cidr_window; +static Window *s_mask_window; +static Window *s_result_window; + +static MenuLayer *s_main_menu_layer; +static MenuLayer *s_cidr_menu_layer; +static MenuLayer *s_mask_menu_layer; +static TextLayer *s_result_text_layer; +static TextLayer *s_bitwidth_text_layer; + +static char s_result_text[192]; +static char s_bitwidth_text[36]; +static uint8_t s_current_prefix = 24; +static bool s_up_boundary_haptic_sent = false; +static bool s_down_boundary_haptic_sent = false; + +static void trigger_soft_boundary_haptic(void) { + static const uint32_t segments[] = {45}; + VibePattern pattern = { + .durations = segments, + .num_segments = ARRAY_LENGTH(segments), + }; + vibes_enqueue_custom_pattern(pattern); +} + +static uint32_t cidr_to_mask_uint8(int prefix) { + if (prefix <= 0) { + return 0; + } + if (prefix >= 32) { + return 0xFFFFFFFFu; + } + return 0xFFFFFFFFu << (32 - prefix); +} + +static void mask_to_dotted(uint32_t mask, char *out, size_t out_len) { + uint8_t o1 = (uint8_t)((mask >> 24) & 0xFF); + uint8_t o2 = (uint8_t)((mask >> 16) & 0xFF); + uint8_t o3 = (uint8_t)((mask >> 8) & 0xFF); + uint8_t o4 = (uint8_t)(mask & 0xFF); + snprintf(out, out_len, "%u.%u.%u.%u", o1, o2, o3, o4); +} + +static int index_to_prefix(int index) { + return 32 - index; +} + +static uint64_t prefix_to_usable_hosts(int prefix) { + if (prefix == 31) { + return 2; + } + if (prefix == 32) { + return 0; + } + + uint64_t total = 1ULL << (32 - prefix); + return total - 2ULL; +} + +static void layout_result_text_layer(void) { + if (!s_result_text_layer || !s_bitwidth_text_layer) { + return; + } + + Layer *root = window_get_root_layer(s_result_window); + GRect bounds = layer_get_bounds(root); + const int16_t side_margin = 6; + const int16_t width = bounds.size.w - (2 * side_margin); + const int16_t gap = 2; + + GSize main_size = text_layer_get_content_size(s_result_text_layer); + GSize bit_size = text_layer_get_content_size(s_bitwidth_text_layer); + + int16_t main_h = main_size.h; + int16_t bit_h = bit_size.h; + + if (main_h < 1) { + main_h = 1; + } + if (bit_h < 1) { + bit_h = 1; + } + + int16_t total_h = main_h + gap + bit_h; + if (total_h > bounds.size.h - 10) { + total_h = bounds.size.h - 10; + } + + int16_t start_y = (bounds.size.h - total_h) / 2; + layer_set_frame(text_layer_get_layer(s_result_text_layer), GRect(side_margin, start_y, width, main_h)); + layer_set_frame(text_layer_get_layer(s_bitwidth_text_layer), GRect(side_margin, start_y + main_h + gap, width, bit_h)); +} + +static void update_result_text(void) { + uint32_t mask = cidr_to_mask_uint8(s_current_prefix); + char mask_text[20]; + uint64_t usable_hosts = prefix_to_usable_hosts(s_current_prefix); + int selected_row = 32 - s_current_prefix; + + mask_to_dotted(mask, mask_text, sizeof(mask_text)); + + snprintf( + s_result_text, + sizeof(s_result_text), + "CIDR: /%d\nMask: %s\nUsable hosts: %llu", + s_current_prefix, + mask_text, + usable_hosts); + + int pos = 0; + for (int i = 0; i < 32; i++) { + s_bitwidth_text[pos++] = (i < s_current_prefix) ? '1' : '0'; + if (i == 7 || i == 15 || i == 23) { + s_bitwidth_text[pos++] = '.'; + } + } + s_bitwidth_text[pos] = '\0'; + + if (s_result_text_layer && s_bitwidth_text_layer) { + text_layer_set_text(s_result_text_layer, s_result_text); + text_layer_set_text(s_bitwidth_text_layer, s_bitwidth_text); + layout_result_text_layer(); + } + + if (s_cidr_menu_layer) { + menu_layer_set_selected_index(s_cidr_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)selected_row}, MenuRowAlignCenter, false); + } + if (s_mask_menu_layer) { + menu_layer_set_selected_index(s_mask_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)selected_row}, MenuRowAlignCenter, false); + } +} + +static void show_result_for_prefix(int prefix) { + if (prefix < 0) { + prefix = 0; + } + if (prefix > 32) { + prefix = 32; + } + + s_current_prefix = prefix; + s_up_boundary_haptic_sent = false; + s_down_boundary_haptic_sent = false; + + // Push first so the result window/text layer is guaranteed to be loaded. + window_stack_push(s_result_window, true); + + update_result_text(); +} + +static void result_up_click_handler(ClickRecognizerRef recognizer, void *context) { + (void)recognizer; + (void)context; + + if (s_current_prefix < 32) { + s_current_prefix++; + update_result_text(); + + s_down_boundary_haptic_sent = false; + if (s_current_prefix == 32 && !s_up_boundary_haptic_sent) { + trigger_soft_boundary_haptic(); + s_up_boundary_haptic_sent = true; + } else if (s_current_prefix < 32) { + s_up_boundary_haptic_sent = false; + } + } else if (!s_up_boundary_haptic_sent) { + trigger_soft_boundary_haptic(); + s_up_boundary_haptic_sent = true; + } +} + +static void result_down_click_handler(ClickRecognizerRef recognizer, void *context) { + (void)recognizer; + (void)context; + + if (s_current_prefix > 0) { + s_current_prefix--; + update_result_text(); + + s_up_boundary_haptic_sent = false; + if (s_current_prefix == 0 && !s_down_boundary_haptic_sent) { + trigger_soft_boundary_haptic(); + s_down_boundary_haptic_sent = true; + } else if (s_current_prefix > 0) { + s_down_boundary_haptic_sent = false; + } + } else if (!s_down_boundary_haptic_sent) { + trigger_soft_boundary_haptic(); + s_down_boundary_haptic_sent = true; + } +} + +static void result_click_config_provider(void *context) { + (void)context; + window_single_repeating_click_subscribe(BUTTON_ID_UP, 120, result_up_click_handler); + window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 120, result_down_click_handler); +} + +static uint16_t main_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { + (void)menu_layer; + (void)section_index; + (void)context; + return 2; +} + +static void main_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { + (void)context; + switch (cell_index->row) { + case 0: + menu_cell_basic_draw(ctx, cell_layer, "CIDR", NULL, NULL); + break; + case 1: + menu_cell_basic_draw(ctx, cell_layer, "Mask", NULL, NULL); + break; + } +} + +static void main_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { + (void)menu_layer; + (void)context; + if (cell_index->row == 0) { + window_stack_push(s_cidr_window, true); + } else { + window_stack_push(s_mask_window, true); + } +} + +static uint16_t value_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { + (void)menu_layer; + (void)section_index; + (void)context; + return ITEM_COUNT; +} + +static void cidr_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { + (void)context; + int prefix = index_to_prefix(cell_index->row); + static char cidr_label[8]; + snprintf(cidr_label, sizeof(cidr_label), "/%d", prefix); + menu_cell_basic_draw(ctx, cell_layer, cidr_label, NULL, NULL); +} + +static void mask_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { + (void)context; + int prefix = index_to_prefix(cell_index->row); + uint32_t mask = cidr_to_mask_uint8(prefix); + static char mask_label[20]; + mask_to_dotted(mask, mask_label, sizeof(mask_label)); + menu_cell_basic_draw(ctx, cell_layer, mask_label, NULL, NULL); +} + +static void cidr_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { + (void)menu_layer; + (void)context; + int prefix = index_to_prefix(cell_index->row); + show_result_for_prefix(prefix); +} + +static void mask_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { + (void)menu_layer; + (void)context; + int prefix = index_to_prefix(cell_index->row); + show_result_for_prefix(prefix); +} + +static void main_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + s_main_menu_layer = menu_layer_create(bounds); + menu_layer_set_callbacks(s_main_menu_layer, NULL, (MenuLayerCallbacks){ + .get_num_rows = main_get_num_rows, + .draw_row = main_draw_row, + .select_click = main_select, + }); + menu_layer_set_click_config_onto_window(s_main_menu_layer, window); + layer_add_child(window_layer, menu_layer_get_layer(s_main_menu_layer)); +} + +static void main_window_unload(Window *window) { + (void)window; + menu_layer_destroy(s_main_menu_layer); + s_main_menu_layer = NULL; +} + +static void cidr_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + s_cidr_menu_layer = menu_layer_create(bounds); + menu_layer_set_callbacks(s_cidr_menu_layer, NULL, (MenuLayerCallbacks){ + .get_num_rows = value_get_num_rows, + .draw_row = cidr_draw_row, + .select_click = cidr_select, + }); + menu_layer_set_click_config_onto_window(s_cidr_menu_layer, window); + menu_layer_set_selected_index(s_cidr_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)(32 - 24)}, MenuRowAlignCenter, false); + layer_add_child(window_layer, menu_layer_get_layer(s_cidr_menu_layer)); +} + +static void cidr_window_unload(Window *window) { + (void)window; + menu_layer_destroy(s_cidr_menu_layer); + s_cidr_menu_layer = NULL; +} + +static void mask_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + s_mask_menu_layer = menu_layer_create(bounds); + menu_layer_set_callbacks(s_mask_menu_layer, NULL, (MenuLayerCallbacks){ + .get_num_rows = value_get_num_rows, + .draw_row = mask_draw_row, + .select_click = mask_select, + }); + menu_layer_set_click_config_onto_window(s_mask_menu_layer, window); + menu_layer_set_selected_index(s_mask_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)(32 - 24)}, MenuRowAlignCenter, false); + layer_add_child(window_layer, menu_layer_get_layer(s_mask_menu_layer)); +} + +static void mask_window_unload(Window *window) { + (void)window; + menu_layer_destroy(s_mask_menu_layer); + s_mask_menu_layer = NULL; +} + +static void result_window_load(Window *window) { + s_result_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 24)); + text_layer_set_background_color(s_result_text_layer, GColorClear); + text_layer_set_text_color(s_result_text_layer, GColorWhite); + text_layer_set_font(s_result_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); + text_layer_set_overflow_mode(s_result_text_layer, GTextOverflowModeWordWrap); + text_layer_set_text(s_result_text_layer, s_result_text); + + s_bitwidth_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 14)); + text_layer_set_background_color(s_bitwidth_text_layer, GColorClear); + text_layer_set_text_color(s_bitwidth_text_layer, GColorLightGray); + text_layer_set_font(s_bitwidth_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_09)); + text_layer_set_text(s_bitwidth_text_layer, s_bitwidth_text); + +#if PBL_ROUND + text_layer_set_text_alignment(s_result_text_layer, GTextAlignmentCenter); + text_layer_set_text_alignment(s_bitwidth_text_layer, GTextAlignmentCenter); +#endif + + Layer *window_layer = window_get_root_layer(window); + layer_add_child(window_layer, text_layer_get_layer(s_result_text_layer)); + layer_add_child(window_layer, text_layer_get_layer(s_bitwidth_text_layer)); + update_result_text(); +} + +static void result_window_unload(Window *window) { + (void)window; + text_layer_destroy(s_result_text_layer); + text_layer_destroy(s_bitwidth_text_layer); + s_result_text_layer = NULL; + s_bitwidth_text_layer = NULL; +} + +static void init(void) { + s_main_window = window_create(); + window_set_window_handlers(s_main_window, (WindowHandlers){ + .load = main_window_load, + .unload = main_window_unload, + }); + window_set_background_color(s_main_window, GColorBlack); + + s_cidr_window = window_create(); + window_set_window_handlers(s_cidr_window, (WindowHandlers){ + .load = cidr_window_load, + .unload = cidr_window_unload, + }); + window_set_background_color(s_cidr_window, GColorBlack); + + s_mask_window = window_create(); + window_set_window_handlers(s_mask_window, (WindowHandlers){ + .load = mask_window_load, + .unload = mask_window_unload, + }); + window_set_background_color(s_mask_window, GColorBlack); + + s_result_window = window_create(); + window_set_window_handlers(s_result_window, (WindowHandlers){ + .load = result_window_load, + .unload = result_window_unload, + }); + window_set_background_color(s_result_window, GColorBlack); + window_set_click_config_provider(s_result_window, result_click_config_provider); + + window_stack_push(s_main_window, true); +} + +static void deinit(void) { + window_destroy(s_result_window); + window_destroy(s_mask_window); + window_destroy(s_cidr_window); + window_destroy(s_main_window); +} + +int main(void) { + init(); + app_event_loop(); + deinit(); +} diff --git a/wscript b/wscript new file mode 100644 index 0000000..ae9615a --- /dev/null +++ b/wscript @@ -0,0 +1,36 @@ +import os.path + +top = '.' +out = 'build' + +def options(ctx): + ctx.load('pebble_sdk') + +def configure(ctx): + ctx.load('pebble_sdk') + +def build(ctx): + ctx.load('pebble_sdk') + build_worker = os.path.exists('worker_src') + binaries = [] + cached_env = ctx.env + for platform in ctx.env.TARGET_PLATFORMS: + ctx.env = ctx.all_envs[platform] + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), + target=app_elf, bin_type='app') + if build_worker: + worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': platform, 'app_elf': app_elf, + 'worker_elf': worker_elf}) + ctx.pbl_build(source=ctx.path.ant_glob('worker_src/c/**/*.c'), + target=worker_elf, bin_type='worker') + else: + binaries.append({'platform': platform, 'app_elf': app_elf}) + ctx.env = cached_env + ctx.set_group('bundle') + ctx.pbl_bundle(binaries=binaries, + js=ctx.path.ant_glob(['src/pkjs/**/*.js', + 'src/pkjs/**/*.json']), + js_entry_file='src/pkjs/index.js')