├── falsepositive.txt ├── releases ├── @beats-1.0.pbw ├── @beats-1.1.pbw ├── @beats-1.2.pbw ├── @beats-1.3.pbw ├── @beats-1.4.pbw └── @beats-1.5.pbw ├── resources ├── fonts │ ├── ace_futurism.ttf │ ├── akron_sans.ttf │ └── false_positive.ttf └── images │ ├── beatslogo24.png │ └── beatslogo24~color.png ├── beats.c ├── src ├── js │ └── pebble-js-app.js └── main.c ├── README.md ├── appinfo.json ├── CHANGELOG.md ├── wscript └── beats-config.html /falsepositive.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/falsepositive.txt -------------------------------------------------------------------------------- /releases/@beats-1.0.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/releases/@beats-1.0.pbw -------------------------------------------------------------------------------- /releases/@beats-1.1.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/releases/@beats-1.1.pbw -------------------------------------------------------------------------------- /releases/@beats-1.2.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/releases/@beats-1.2.pbw -------------------------------------------------------------------------------- /releases/@beats-1.3.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/releases/@beats-1.3.pbw -------------------------------------------------------------------------------- /releases/@beats-1.4.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/releases/@beats-1.4.pbw -------------------------------------------------------------------------------- /releases/@beats-1.5.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/releases/@beats-1.5.pbw -------------------------------------------------------------------------------- /resources/fonts/ace_futurism.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/resources/fonts/ace_futurism.ttf -------------------------------------------------------------------------------- /resources/fonts/akron_sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/resources/fonts/akron_sans.ttf -------------------------------------------------------------------------------- /resources/images/beatslogo24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/resources/images/beatslogo24.png -------------------------------------------------------------------------------- /resources/fonts/false_positive.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/resources/fonts/false_positive.ttf -------------------------------------------------------------------------------- /resources/images/beatslogo24~color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/pebble-beats/master/resources/images/beatslogo24~color.png -------------------------------------------------------------------------------- /beats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * beats.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * This is used to validate that the modifications done over the time_t 6 | * UTC value, and calculation of the .beats value are valid. To check the 7 | * output for different timezones, the following can be done in a Linux 8 | * box (change "PDT" to the desired timezone): 9 | * TZ=PDT ./beats 10 | * 11 | * Build with: 12 | * gcc -std=c99 -o beats beats.c 13 | * 14 | * Distributed under terms of the MIT license. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | 21 | int 22 | main (int argc, const char *argv[]) 23 | { 24 | time_t local = time (NULL); 25 | struct tm *localtm = localtime (&local); 26 | time_t utctime = mktime (localtm); 27 | 28 | utctime += (60 * 60); /* TZ=UTC+1 */ 29 | struct tm *T = gmtime (&utctime); 30 | 31 | printf ("@%03u\n", 32 | ((T->tm_sec) + (T->tm_min * 60) + (T->tm_hour * 3600)) * 10 / 864); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /src/js/pebble-js-app.js: -------------------------------------------------------------------------------- 1 | Pebble.addEventListener("showConfiguration", 2 | function (e) { 3 | console.log("Configuration requested"); 4 | Pebble.openURL("http://perezdecastro.org/pebble/beats-config.html"); 5 | } 6 | ); 7 | 8 | var TOGGLE_FLAGS = { 9 | batterybar: 1 << 0, 10 | hhmmdisplay: 1 << 1, 11 | }; 12 | 13 | Pebble.addEventListener("webviewclosed", 14 | function (e) { 15 | var json = JSON.parse(decodeURIComponent(e.response)); 16 | console.log("Configuration window returned: " + JSON.stringify(json)); 17 | var toggles = 0; 18 | for (var key in TOGGLE_FLAGS) { 19 | if (typeof json[key] == undefined || !!json[key]) { 20 | toggles = toggles | TOGGLE_FLAGS[key]; 21 | } 22 | } 23 | Pebble.sendAppMessage({ 24 | "PERSIST_KEY_FONT": "" + json.font, 25 | "PERSIST_KEY_TOGGLES": toggles, 26 | }, function (e) { 27 | console.log("Settings sent successfully"); 28 | }, 29 | function (e) { 30 | console.log("Failed to send settings: " + JSON.stringify(e)); 31 | } 32 | ); 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Beats watchface for Pebble 2 | ========================== 3 | 4 | This is a simple watchface for the [Pebble](http://getpebble.com) smartwatches 5 | which shows the [Swatch @beats Internet Time](https://en.wikipedia.org/wiki/Swatch_Internet_Time) 6 | instead of the usual `HH:MM`. 7 | 8 | 9 | License 10 | ------- 11 | 12 | Source is distributed under terms of the [MIT license](http://opensource.org/licenses/mit) 13 | 14 | The custom fonts used for the “beats” display are: 15 | 16 | - Ace Futurism, by [Andrew “NAL” McCluskey](http://nalgames.com/about/me/) 17 | which is licensed under the [Creative Commons CC-BY 18 | 3.0](http://creativecommons.org/licenses/by/3.0/) license. 19 | - False Positive, by [Brian kent](http://www.1001fonts.com/users/kentpw/). The 20 | license terms are in the [falsepositive.txt](falsepositive.txt) file, which 21 | is included vebatim (as required by the license itself). 22 | - Akron NBP Sans, by [Total Fontgeek](https://sites.google.com/site/totalfontgeek/), 23 | which is licensed under the [Creative Commons CC-BY-SA 24 | 3.0](http://creativecommons.org/licenses/by-sa/3.0/). 25 | 26 | -------------------------------------------------------------------------------- /appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "shortName": "@beats", 3 | "longName": "Swatch @beats Internet Time", 4 | "companyName": "Adri\u00e1n P\u00e9rez", 5 | "capabilities": ["configurable"], 6 | "appKeys": { 7 | "PERSIST_KEY_FONT": 0, 8 | "PERSIST_KEY_TOGGLES": 1 9 | }, 10 | "projectType": "native", 11 | "resources": { 12 | "media": [ 13 | { 14 | "file": "images/beatslogo24.png", 15 | "menuIcon": true, 16 | "name": "BEATS_LOGO24", 17 | "type": "png" 18 | }, 19 | { 20 | "characterRegex": "[0-9@]", 21 | "file": "fonts/ace_futurism.ttf", 22 | "name": "FONT_BEATS_60", 23 | "type": "font" 24 | }, 25 | { 26 | "characterRegex": "[0-9@]", 27 | "file": "fonts/false_positive.ttf", 28 | "name": "FONT_FALSE_POSITIVE_70", 29 | "type": "font" 30 | }, 31 | { 32 | "characterRegex": "[0-9@]", 33 | "file": "fonts/akron_sans.ttf", 34 | "name": "FONT_AKRON_SANS_76", 35 | "type": "font" 36 | } 37 | ] 38 | }, 39 | "sdkVersion": "3", 40 | "targetPlatforms": [ 41 | "aplite", 42 | "basalt", 43 | "chalk" 44 | ], 45 | "uuid": "0ea96ac0-06dc-46f4-ac71-b760c9f8d4c4", 46 | "versionLabel": "1.5", 47 | "versionCode": 6, 48 | "watchapp": { 49 | "watchface": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | 7 | ## [1.5] - 2015-01-11 8 | ### Added 9 | - Support for “Chalk” platform (Pebble Time Round). 10 | - Battery bar. 11 | 12 | ## [1.4] - 2015-08-14 13 | ### Added 14 | - Watchface configuration support. 15 | - Customizable beats display font, with two additional fonts to choose from. 16 | 17 | ## [1.3] - 2015-07-21 18 | ### Changed 19 | - Fixed calculations for UTC timezone offset and Internet Time; now the 20 | @beats time value should be correct for all timezones. 21 | 22 | ## [1.2] - 2015-06-19 23 | ### Changed 24 | - Normal time display (HH:MM) uses now a slightly larger font. 25 | - Normal time display respects the 24h/12h setting from the watch settings. 26 | ### Added 27 | - The beats display has now a custom font. 28 | - Bluetooth status is now tracked, and a short vibration produced on 29 | disconnect events. 30 | 31 | ## [1.1] - 2015-06-15 32 | ### Changed 33 | - Fixed calculation of @beats time. 34 | - On Basalt (Pebble Time), the HH:MM text display has light gray color. 35 | - Background color is now black. 36 | 37 | ### Added 38 | - Added small display the time in usual HH:MM format (24h format only 39 | for now, no AM/PM). 40 | 41 | ## 1.0 - 2015-06-12 42 | ### Added 43 | - First release. 44 | 45 | [Unreleased]: https://github.com/aperezdc/pebble-beats/compare/v1.5...HEAD 46 | [1.5]: https://github.com/aperezdc/pebble-beats/compare/v1.4...v1.5 47 | [1.4]: https://github.com/aperezdc/pebble-beats/compare/v1.3...v1.4 48 | [1.3]: https://github.com/aperezdc/pebble-beats/compare/v1.2...v1.3 49 | [1.2]: https://github.com/aperezdc/pebble-beats/compare/v1.1...v1.2 50 | [1.1]: https://github.com/aperezdc/pebble-beats/compare/v1.0...v1.1 51 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | # vim: ft=python 2 | 3 | import os.path 4 | try: 5 | from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2 6 | hint = jshint 7 | except (ImportError, CommandNotFound): 8 | hint = None 9 | 10 | top = '.' 11 | out = 'build' 12 | 13 | def options(ctx): 14 | ctx.load('pebble_sdk') 15 | 16 | def configure(ctx): 17 | ctx.load('pebble_sdk') 18 | 19 | def build(ctx): 20 | if False and hint is not None: 21 | try: 22 | hint([node.abspath() for node in ctx.path.ant_glob("src/**/*.js")], _tty_out=False) # no tty because there are none in the cloudpebble sandbox. 23 | except ErrorReturnCode_2 as e: 24 | ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout) 25 | 26 | # Concatenate all our JS files (but not recursively), and only if any JS exists in the first place. 27 | ctx.path.make_node('src/js/').mkdir() 28 | js_paths = ctx.path.ant_glob(['src/*.js', 'src/**/*.js']) 29 | if js_paths: 30 | ctx(rule='cat ${SRC} > ${TGT}', source=js_paths, target='pebble-js-app.js') 31 | has_js = True 32 | else: 33 | has_js = False 34 | 35 | ctx.load('pebble_sdk') 36 | 37 | build_worker = os.path.exists('worker_src') 38 | binaries = [] 39 | 40 | for p in ctx.env.TARGET_PLATFORMS: 41 | ctx.set_env(ctx.all_envs[p]) 42 | ctx.set_group(ctx.env.PLATFORM_NAME) 43 | app_elf='{}/pebble-app.elf'.format(p) 44 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 45 | target=app_elf) 46 | 47 | if build_worker: 48 | worker_elf='{}/pebble-worker.elf'.format(p) 49 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 50 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 51 | target=worker_elf) 52 | else: 53 | binaries.append({'platform': p, 'app_elf': app_elf}) 54 | 55 | ctx.set_group('bundle') 56 | ctx.pbl_bundle(binaries=binaries, js='pebble-js-app.js' if has_js else []) 57 | -------------------------------------------------------------------------------- /beats-config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @beats Configuration 6 | 7 | 8 | 19 | 20 | 21 |

@beats Configuration

22 | 23 |
24 |
Toggles
25 |
26 | 29 | 32 |
33 |
34 | 35 |
36 |
Styling
37 |
38 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 | 54 | 87 | 88 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define LENGTH_OF(v) ((sizeof (v) / sizeof(v[0]))) 4 | 5 | enum PersistKey { 6 | PERSIST_KEY_FONT = 0, 7 | PERSIST_KEY_TOGGLES = 1, 8 | }; 9 | 10 | enum FontId { 11 | FONT_ACE_FUTURISM = 0, 12 | FONT_FALSE_POSITIVE = 1, 13 | FONT_AKRON_SANS = 2, 14 | }; 15 | 16 | enum ToggleFlag { 17 | TOGGLE_FLAG_BATTERY_BAR = (1 << 0), 18 | TOGGLE_FLAG_HHMM_DISPLAY = (1 << 1), 19 | 20 | TOGGLE_FLAG_ALL = TOGGLE_FLAG_BATTERY_BAR 21 | | TOGGLE_FLAG_HHMM_DISPLAY 22 | , 23 | }; 24 | 25 | static const int8_t font_y_shift[] = { 26 | [FONT_ACE_FUTURISM] = -8, 27 | [FONT_FALSE_POSITIVE] = -6, 28 | [FONT_AKRON_SANS] = -14, 29 | }; 30 | 31 | static Window *s_main_window = NULL; 32 | static TextLayer *s_beats_layer = NULL; 33 | static TextLayer *s_time_layer = NULL; 34 | static Layer *s_battery_layer = NULL; 35 | static char s_beats_buffer[] = "@000"; 36 | static char s_time_buffer[] = "HH:MM NN"; 37 | static uint8_t s_battery_level = 100; 38 | 39 | 40 | static void 41 | update_time (struct tm *localtm, 42 | TimeUnits units_changed) 43 | { 44 | /* The normal HH:MM NN text is straightforward. */ 45 | if (s_time_layer) { 46 | clock_copy_time_string (s_time_buffer, LENGTH_OF (s_time_buffer)); 47 | layer_mark_dirty (text_layer_get_layer (s_time_layer)); 48 | } 49 | 50 | /* We'll do all calculations starting from the UTC time. */ 51 | time_t utctime = mktime (localtm); 52 | 53 | /* 54 | * Convert the "struct tm" back to a time_t (seconds from the Epoch), so 55 | * it is possible to manipulate artifically the timezone, because we cannot 56 | * set TZ in the environment we have to do our own timezone adjustment :-/ 57 | */ 58 | utctime += (60 * 60); /* TZ=UTC+1 */ 59 | struct tm *T = gmtime (&utctime); 60 | 61 | /* 62 | * Internet Time is calculated from UTC+1 as follows: 63 | * (UTC+1seconds + (UTC+1minutes * 60) + (UTC+1hours * 3600)) / 86.4 64 | */ 65 | snprintf (s_beats_buffer, LENGTH_OF (s_beats_buffer), "@%03u", 66 | ((T->tm_sec) + (T->tm_min * 60) + (T->tm_hour * 3600)) * 10 / 864); 67 | layer_mark_dirty (text_layer_get_layer (s_beats_layer)); 68 | } 69 | 70 | 71 | static void 72 | update_bluetooth (bool connected) 73 | { 74 | if (!connected) { 75 | vibes_short_pulse (); 76 | } 77 | } 78 | 79 | 80 | static void 81 | update_battery (BatteryChargeState state) 82 | { 83 | if (s_battery_level != state.charge_percent) { 84 | s_battery_level = state.charge_percent; 85 | layer_mark_dirty (s_battery_layer); 86 | } 87 | } 88 | 89 | 90 | static void 91 | draw_battery_line (Layer *layer, GContext *ctx) 92 | { 93 | GRect bounds = layer_get_bounds (layer); 94 | unsigned width = bounds.size.w * s_battery_level / 100; 95 | 96 | graphics_context_set_fill_color (ctx, GColorBlack); 97 | graphics_fill_rect (ctx, bounds, 0, GCornerNone); 98 | 99 | graphics_context_set_fill_color (ctx, COLOR_FALLBACK ((s_battery_level <= 20 100 | ? GColorRed 101 | : GColorLightGray), 102 | GColorWhite)); 103 | graphics_fill_rect (ctx, 104 | GRect ((bounds.size.w - width) >> 1, 0, width, bounds.size.h), 105 | 1, GCornersAll); 106 | } 107 | 108 | 109 | static GFont 110 | config_get_font (enum FontId *font_id) 111 | { 112 | switch (persist_read_int (PERSIST_KEY_FONT)) { 113 | case FONT_FALSE_POSITIVE: 114 | if (font_id) *font_id = FONT_FALSE_POSITIVE; 115 | return fonts_load_custom_font (resource_get_handle (RESOURCE_ID_FONT_FALSE_POSITIVE_70)); 116 | case FONT_AKRON_SANS: 117 | if (font_id) *font_id = FONT_AKRON_SANS; 118 | return fonts_load_custom_font (resource_get_handle (RESOURCE_ID_FONT_AKRON_SANS_76)); 119 | case FONT_ACE_FUTURISM: 120 | default: 121 | if (font_id) *font_id = FONT_ACE_FUTURISM; 122 | return fonts_load_custom_font (resource_get_handle (RESOURCE_ID_FONT_BEATS_60)); 123 | } 124 | } 125 | 126 | 127 | /* 128 | * Normal HH:MM display 129 | */ 130 | 131 | #define HHMM_DISPLAY_HEIGHT 24 132 | 133 | static void 134 | create_hhmmdisplay (Window *parent, const GRect *bounds) 135 | { 136 | /* Do nothing if layer is already created. */ 137 | if (s_time_layer) return; 138 | 139 | const unsigned hhmm_pos_y = (bounds->size.h * 80 / 100) - (HHMM_DISPLAY_HEIGHT >> 1); 140 | s_time_layer = text_layer_create (GRect (0, hhmm_pos_y, bounds->size.w, HHMM_DISPLAY_HEIGHT)); 141 | text_layer_set_background_color (s_time_layer, GColorClear); 142 | text_layer_set_text_color (s_time_layer, 143 | COLOR_FALLBACK (GColorLightGray, 144 | GColorWhite)); 145 | text_layer_set_text (s_time_layer, s_time_buffer); 146 | text_layer_set_font (s_time_layer, 147 | fonts_get_system_font (FONT_KEY_GOTHIC_24_BOLD)); 148 | text_layer_set_text_alignment (s_time_layer, GTextAlignmentCenter); 149 | layer_add_child (window_get_root_layer (parent), 150 | text_layer_get_layer (s_time_layer)); 151 | } 152 | 153 | static void 154 | destroy_hhmmdisplay (void) 155 | { 156 | /* Do nothing if layer has not been created */ 157 | if (!s_time_layer) return; 158 | 159 | layer_remove_from_parent (text_layer_get_layer (s_time_layer)); 160 | text_layer_destroy (s_time_layer); 161 | s_time_layer = NULL; 162 | } 163 | 164 | 165 | /* 166 | * Battery bar 167 | */ 168 | 169 | static void 170 | create_battery_bar (Window *parent, const GRect *bounds) 171 | { 172 | /* Do nothing if layer is already created. */ 173 | if (s_battery_layer) return; 174 | 175 | const unsigned meter_width = (bounds->size.w * 80 / 100); 176 | const unsigned meter_pos_x = (bounds->size.w - meter_width) >> 1; 177 | s_battery_layer = layer_create (GRect (meter_pos_x, 25, meter_width, 4)); 178 | layer_set_update_proc (s_battery_layer, draw_battery_line); 179 | layer_add_child (window_get_root_layer (parent), s_battery_layer); 180 | 181 | battery_state_service_subscribe (update_battery); 182 | update_battery (battery_state_service_peek ()); 183 | 184 | } 185 | 186 | static void 187 | destroy_battery_bar (void) 188 | { 189 | /* Do nothing if layer has not been created */ 190 | if (!s_battery_layer) return; 191 | 192 | battery_state_service_unsubscribe (); 193 | 194 | layer_remove_from_parent (s_battery_layer); 195 | layer_destroy (s_battery_layer); 196 | s_battery_layer = NULL; 197 | } 198 | 199 | 200 | static void 201 | main_window_load (Window *window) 202 | { 203 | const GRect bounds = layer_get_bounds (window_get_root_layer (s_main_window)); 204 | const enum ToggleFlag toggles = persist_read_int (PERSIST_KEY_TOGGLES); 205 | 206 | window_set_background_color (s_main_window, GColorBlack); 207 | 208 | /* Beats */ 209 | #define BEATS_DISPLAY_HEIGHT 78 210 | enum FontId font_id; 211 | GFont font = config_get_font (&font_id); 212 | const unsigned beats_pos_y = ((bounds.size.h - BEATS_DISPLAY_HEIGHT) >> 1) 213 | + font_y_shift[font_id]; 214 | s_beats_layer = text_layer_create (GRect (0, beats_pos_y, bounds.size.w, BEATS_DISPLAY_HEIGHT)); 215 | text_layer_set_background_color (s_beats_layer, GColorClear); 216 | text_layer_set_text_color (s_beats_layer, GColorWhite); 217 | text_layer_set_text (s_beats_layer, s_beats_buffer); 218 | text_layer_set_font (s_beats_layer, font); 219 | text_layer_set_text_alignment (s_beats_layer, GTextAlignmentCenter); 220 | layer_add_child (window_get_root_layer (s_main_window), 221 | text_layer_get_layer (s_beats_layer)); 222 | 223 | if (toggles & TOGGLE_FLAG_HHMM_DISPLAY) create_hhmmdisplay (window, &bounds); 224 | if (toggles & TOGGLE_FLAG_BATTERY_BAR) create_battery_bar (window, &bounds); 225 | } 226 | 227 | static void 228 | main_window_unload (Window *window) 229 | { 230 | text_layer_destroy (s_beats_layer); 231 | destroy_hhmmdisplay (); 232 | destroy_battery_bar (); 233 | } 234 | 235 | 236 | static void 237 | config_init (void) 238 | { 239 | if (!persist_exists (PERSIST_KEY_FONT)) { 240 | persist_write_int (PERSIST_KEY_FONT, FONT_ACE_FUTURISM); 241 | } 242 | if (!persist_exists (PERSIST_KEY_TOGGLES)) { 243 | persist_write_int (PERSIST_KEY_TOGGLES, TOGGLE_FLAG_ALL); 244 | } 245 | } 246 | 247 | 248 | static void 249 | config_apply (void) 250 | { 251 | enum FontId font_id; 252 | GFont font = config_get_font (&font_id); 253 | 254 | /* Recalculate beats text layer bounds */ 255 | const GRect bounds = layer_get_bounds (window_get_root_layer (s_main_window)); 256 | layer_set_frame (text_layer_get_layer (s_beats_layer), 257 | GRect (bounds.origin.x, 258 | BEATS_DISPLAY_HEIGHT, 259 | bounds.size.w, 260 | ((bounds.size.h - BEATS_DISPLAY_HEIGHT) >> 1) + font_y_shift[font_id])); 261 | 262 | /* ...and set the font */ 263 | text_layer_set_font (s_beats_layer, font); 264 | 265 | /* Handle complications */ 266 | const enum ToggleFlag toggles = persist_read_int (PERSIST_KEY_TOGGLES); 267 | 268 | #define COMPLICATION(flag, create, destroy) \ 269 | do { \ 270 | if (toggles & TOGGLE_FLAG_ ## flag) { \ 271 | create (s_main_window, &bounds); \ 272 | } else { destroy (); } \ 273 | } while (false) 274 | 275 | COMPLICATION (HHMM_DISPLAY, create_hhmmdisplay, destroy_hhmmdisplay); 276 | COMPLICATION (BATTERY_BAR, create_battery_bar, destroy_battery_bar); 277 | 278 | #undef COMPLICATION 279 | } 280 | 281 | 282 | static void 283 | message_received (DictionaryIterator *iter, void *context) 284 | { 285 | for (Tuple *t = dict_read_first (iter); t; t = dict_read_next (iter)) { 286 | switch (t->key) { 287 | case PERSIST_KEY_FONT: 288 | if (!strcmp (t->value->cstring, "ace-futurism")) { 289 | persist_write_int (t->key, FONT_ACE_FUTURISM); 290 | } else if (!strcmp (t->value->cstring, "false-positive")) { 291 | persist_write_int (t->key, FONT_FALSE_POSITIVE); 292 | } else if (!strcmp (t->value->cstring, "akron-sans")) { 293 | persist_write_int (t->key, FONT_AKRON_SANS); 294 | } 295 | break; 296 | case PERSIST_KEY_TOGGLES: 297 | persist_write_int (t->key, t->value->int32 & TOGGLE_FLAG_ALL); 298 | break; 299 | } 300 | } 301 | 302 | config_apply (); 303 | } 304 | 305 | 306 | int main (int argc, char *argv[]) 307 | { 308 | s_main_window = window_create (); 309 | window_set_window_handlers (s_main_window, (WindowHandlers) { 310 | .load = main_window_load, 311 | .unload = main_window_unload, 312 | }); 313 | window_stack_push (s_main_window, true); 314 | 315 | bluetooth_connection_service_subscribe (update_bluetooth); 316 | 317 | tick_timer_service_subscribe (MINUTE_UNIT, update_time); 318 | time_t curtime = time (NULL); 319 | update_time (localtime (&curtime), 0); 320 | 321 | config_init (); 322 | app_message_register_inbox_received (message_received); 323 | app_message_open(app_message_inbox_size_maximum(), 324 | app_message_outbox_size_maximum()); 325 | 326 | app_event_loop (); 327 | 328 | tick_timer_service_unsubscribe (); 329 | bluetooth_connection_service_unsubscribe (); 330 | window_destroy (s_main_window); 331 | return 0; 332 | } 333 | --------------------------------------------------------------------------------