Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

plugins: add timer/countdown plugin

-New countdown timer plugin with pause, overtime support
-Add full name to credits and manual entry
-Make status strings translatable

Change-Id: I1437b2e5ac5ede292bdab8d36e58b81326ea2ba3

authored by

seroteunine and committed by
Solomon Peachy
a1040cda cc7418dd

+438
+70
apps/lang/english-us.lang
··· 16984 16984 *: "U S B" 16985 16985 </voice> 16986 16986 </phrase> 16987 + <phrase> 16988 + id: LANG_COUNTDOWN_TIMER_SET 16989 + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration 16990 + user: core 16991 + <source> 16992 + *: "SET TIMER" 16993 + </source> 16994 + <dest> 16995 + *: "SET TIMER" 16996 + </dest> 16997 + <voice> 16998 + *: "Set timer" 16999 + </voice> 17000 + </phrase> 17001 + <phrase> 17002 + id: LANG_COUNTDOWN_TIMER_RUNNING 17003 + desc: countdown_timer plugin - status label shown while the countdown is active 17004 + user: core 17005 + <source> 17006 + *: "RUNNING" 17007 + </source> 17008 + <dest> 17009 + *: "RUNNING" 17010 + </dest> 17011 + <voice> 17012 + *: "Running" 17013 + </voice> 17014 + </phrase> 17015 + <phrase> 17016 + id: LANG_COUNTDOWN_TIMER_PAUSED 17017 + desc: countdown_timer plugin - status label shown while the countdown is paused 17018 + user: core 17019 + <source> 17020 + *: "PAUSED" 17021 + </source> 17022 + <dest> 17023 + *: "PAUSED" 17024 + </dest> 17025 + <voice> 17026 + *: "Paused" 17027 + </voice> 17028 + </phrase> 17029 + <phrase> 17030 + id: LANG_COUNTDOWN_TIMER_OVERTIME 17031 + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up 17032 + user: core 17033 + <source> 17034 + *: "OVERTIME" 17035 + </source> 17036 + <dest> 17037 + *: "OVERTIME" 17038 + </dest> 17039 + <voice> 17040 + *: "Overtime" 17041 + </voice> 17042 + </phrase> 17043 + <phrase> 17044 + id: LANG_COUNTDOWN_TIMER_FINISHED 17045 + desc: countdown_timer plugin - status label shown at the moment the countdown expires 17046 + user: core 17047 + <source> 17048 + *: "FINISHED" 17049 + </source> 17050 + <dest> 17051 + *: "FINISHED" 17052 + </dest> 17053 + <voice> 17054 + *: "Finished" 17055 + </voice> 17056 + </phrase>
+70
apps/lang/english.lang
··· 17099 17099 *: "Swap Left & Right" 17100 17100 </voice> 17101 17101 </phrase> 17102 + <phrase> 17103 + id: LANG_COUNTDOWN_TIMER_SET 17104 + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration 17105 + user: core 17106 + <source> 17107 + *: "SET TIMER" 17108 + </source> 17109 + <dest> 17110 + *: "SET TIMER" 17111 + </dest> 17112 + <voice> 17113 + *: "Set timer" 17114 + </voice> 17115 + </phrase> 17116 + <phrase> 17117 + id: LANG_COUNTDOWN_TIMER_RUNNING 17118 + desc: countdown_timer plugin - status label shown while the countdown is active 17119 + user: core 17120 + <source> 17121 + *: "RUNNING" 17122 + </source> 17123 + <dest> 17124 + *: "RUNNING" 17125 + </dest> 17126 + <voice> 17127 + *: "Running" 17128 + </voice> 17129 + </phrase> 17130 + <phrase> 17131 + id: LANG_COUNTDOWN_TIMER_PAUSED 17132 + desc: countdown_timer plugin - status label shown while the countdown is paused 17133 + user: core 17134 + <source> 17135 + *: "PAUSED" 17136 + </source> 17137 + <dest> 17138 + *: "PAUSED" 17139 + </dest> 17140 + <voice> 17141 + *: "Paused" 17142 + </voice> 17143 + </phrase> 17144 + <phrase> 17145 + id: LANG_COUNTDOWN_TIMER_OVERTIME 17146 + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up 17147 + user: core 17148 + <source> 17149 + *: "OVERTIME" 17150 + </source> 17151 + <dest> 17152 + *: "OVERTIME" 17153 + </dest> 17154 + <voice> 17155 + *: "Overtime" 17156 + </voice> 17157 + </phrase> 17158 + <phrase> 17159 + id: LANG_COUNTDOWN_TIMER_FINISHED 17160 + desc: countdown_timer plugin - status label shown at the moment the countdown expires 17161 + user: core 17162 + <source> 17163 + *: "FINISHED" 17164 + </source> 17165 + <dest> 17166 + *: "FINISHED" 17167 + </dest> 17168 + <voice> 17169 + *: "Finished" 17170 + </voice> 17171 + </phrase>
+1
apps/plugins/CATEGORIES
··· 21 21 clix,games 22 22 clock,apps 23 23 codebuster,games 24 + countdown_timer,apps 24 25 credits,viewers 25 26 cube,demos 26 27 cue_playlist,viewers
+1
apps/plugins/SOURCES
··· 7 7 tagcache/tagcache.c 8 8 #endif 9 9 chessclock.c 10 + countdown_timer.c 10 11 credits.c 11 12 cube.c 12 13 cue_playlist.c
+242
apps/plugins/countdown_timer.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2026 Teun van Dalen 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include "plugin.h" 23 + #include "lib/pluginlib_actions.h" 24 + #include "lib/pluginlib_exit.h" 25 + 26 + const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; 27 + 28 + #define MAX_MINUTES 99 29 + #define MAX_SECONDS 59 30 + 31 + typedef enum { 32 + STATE_SETUP, 33 + STATE_RUNNING, 34 + STATE_PAUSED, 35 + STATE_FINISHED, 36 + } timer_state_t; 37 + 38 + typedef struct { 39 + int set_minutes; 40 + int set_seconds; 41 + int current_field; 42 + long remaining_ticks; 43 + long last_tick; 44 + timer_state_t state; 45 + } timer_ctx_t; 46 + 47 + static inline int get_button(void) 48 + { 49 + return pluginlib_getaction(HZ / 10, plugin_contexts, 50 + ARRAYLEN(plugin_contexts)); 51 + } 52 + 53 + static void draw(const timer_ctx_t *ctx) 54 + { 55 + char time_str[12]; 56 + const char *status_str; 57 + int disp_min, disp_sec; 58 + int w, h, x, y; 59 + 60 + rb->lcd_clear_display(); 61 + 62 + if (ctx->state == STATE_SETUP) { 63 + disp_min = ctx->set_minutes; 64 + disp_sec = ctx->set_seconds; 65 + if (ctx->current_field == 0) 66 + rb->snprintf(time_str, sizeof(time_str), "[%02d]:%02d", 67 + disp_min, disp_sec); 68 + else 69 + rb->snprintf(time_str, sizeof(time_str), "%02d:[%02d]", 70 + disp_min, disp_sec); 71 + status_str = rb->str(LANG_COUNTDOWN_TIMER_SET); 72 + } else if (ctx->state == STATE_FINISHED) { 73 + rb->snprintf(time_str, sizeof(time_str), "00:00"); 74 + status_str = rb->str(LANG_COUNTDOWN_TIMER_FINISHED); 75 + } else { 76 + if (ctx->remaining_ticks >= 0) { 77 + int remaining_secs = (ctx->remaining_ticks + HZ - 1) / HZ; 78 + disp_min = remaining_secs / 60; 79 + disp_sec = remaining_secs % 60; 80 + rb->snprintf(time_str, sizeof(time_str), "%02d:%02d", 81 + disp_min, disp_sec); 82 + } else { 83 + int over_secs = ((-ctx->remaining_ticks) + HZ - 1) / HZ; 84 + disp_min = over_secs / 60; 85 + disp_sec = over_secs % 60; 86 + rb->snprintf(time_str, sizeof(time_str), "-%02d:%02d", 87 + disp_min, disp_sec); 88 + } 89 + 90 + if (ctx->state == STATE_RUNNING && ctx->remaining_ticks < 0) 91 + status_str = rb->str(LANG_COUNTDOWN_TIMER_OVERTIME); 92 + else if (ctx->state == STATE_RUNNING) 93 + status_str = rb->str(LANG_COUNTDOWN_TIMER_RUNNING); 94 + else 95 + status_str = rb->str(LANG_COUNTDOWN_TIMER_PAUSED); 96 + } 97 + 98 + /* Draw status string above center */ 99 + rb->lcd_getstringsize(status_str, &w, &h); 100 + x = (LCD_WIDTH - w) / 2; 101 + y = (LCD_HEIGHT / 2) - h - 2; 102 + rb->lcd_putsxy(x, y, status_str); 103 + 104 + /* Draw time string at center */ 105 + rb->lcd_getstringsize(time_str, &w, &h); 106 + x = (LCD_WIDTH - w) / 2; 107 + y = LCD_HEIGHT / 2; 108 + rb->lcd_putsxy(x, y, time_str); 109 + 110 + rb->lcd_update(); 111 + } 112 + 113 + enum plugin_status plugin_start(const void *parameter) 114 + { 115 + timer_ctx_t ctx = { 116 + .set_minutes = 10, 117 + .set_seconds = 0, 118 + .current_field = 0, 119 + .remaining_ticks = 60 * HZ, 120 + .last_tick = 0, 121 + .state = STATE_SETUP, 122 + }; 123 + bool overtime_alerted = false; 124 + int button; 125 + 126 + (void)parameter; 127 + 128 + while (true) { 129 + if (ctx.state == STATE_RUNNING) { 130 + long now = *rb->current_tick; 131 + ctx.remaining_ticks -= now - ctx.last_tick; 132 + ctx.last_tick = now; 133 + 134 + if (ctx.remaining_ticks < 0 && !overtime_alerted) { 135 + overtime_alerted = true; 136 + ctx.state = STATE_FINISHED; 137 + draw(&ctx); 138 + rb->audio_pause(); 139 + #ifdef HAVE_BACKLIGHT 140 + rb->backlight_on(); 141 + #endif 142 + rb->beep_play(1000, 200, 1000); 143 + rb->sleep(HZ / 4); 144 + rb->beep_play(1000, 200, 1000); 145 + rb->sleep(HZ / 4); 146 + rb->beep_play(1000, 400, 1000); 147 + rb->sleep(HZ + HZ / 2); 148 + rb->audio_resume(); 149 + ctx.state = STATE_RUNNING; 150 + /* Reset last_tick so the beep/sleep time is not counted 151 + * against remaining_ticks on the next iteration. */ 152 + ctx.last_tick = *rb->current_tick; 153 + } 154 + } 155 + 156 + draw(&ctx); 157 + button = get_button(); 158 + 159 + switch (ctx.state) { 160 + case STATE_SETUP: { 161 + bool time_changed = false; 162 + switch (button) { 163 + #ifdef HAVE_SCROLLWHEEL 164 + case PLA_SCROLL_FWD: 165 + case PLA_SCROLL_FWD_REPEAT: 166 + #endif 167 + case PLA_UP: 168 + case PLA_UP_REPEAT: 169 + if (ctx.current_field == 0) 170 + ctx.set_minutes = (ctx.set_minutes + 1) % (MAX_MINUTES + 1); 171 + else 172 + ctx.set_seconds = (ctx.set_seconds + 1) % (MAX_SECONDS + 1); 173 + time_changed = true; 174 + break; 175 + #ifdef HAVE_SCROLLWHEEL 176 + case PLA_SCROLL_BACK: 177 + case PLA_SCROLL_BACK_REPEAT: 178 + #endif 179 + case PLA_DOWN: 180 + case PLA_DOWN_REPEAT: 181 + if (ctx.current_field == 0) 182 + ctx.set_minutes = (ctx.set_minutes + MAX_MINUTES) % (MAX_MINUTES + 1); 183 + else 184 + ctx.set_seconds = (ctx.set_seconds + MAX_SECONDS) % (MAX_SECONDS + 1); 185 + time_changed = true; 186 + break; 187 + case PLA_LEFT: 188 + case PLA_LEFT_REPEAT: 189 + case PLA_RIGHT: 190 + case PLA_RIGHT_REPEAT: 191 + ctx.current_field = (ctx.current_field + 1) % 2; 192 + break; 193 + case PLA_SELECT: 194 + if (ctx.set_minutes > 0 || ctx.set_seconds > 0) { 195 + ctx.remaining_ticks = (ctx.set_minutes * 60 + ctx.set_seconds) * HZ; 196 + ctx.last_tick = *rb->current_tick; 197 + ctx.state = STATE_RUNNING; 198 + } 199 + break; 200 + default: 201 + exit_on_usb(button); 202 + break; 203 + } 204 + if (time_changed) 205 + ctx.remaining_ticks = (ctx.set_minutes * 60 + ctx.set_seconds) * HZ; 206 + break; 207 + } 208 + 209 + case STATE_RUNNING: 210 + switch (button) { 211 + case PLA_SELECT: 212 + ctx.state = STATE_PAUSED; 213 + break; 214 + default: 215 + exit_on_usb(button); 216 + break; 217 + } 218 + break; 219 + 220 + case STATE_FINISHED: 221 + exit_on_usb(button); 222 + break; 223 + 224 + case STATE_PAUSED: 225 + switch (button) { 226 + case PLA_SELECT: 227 + ctx.last_tick = *rb->current_tick; 228 + ctx.state = STATE_RUNNING; 229 + break; 230 + case PLA_UP: 231 + case PLA_UP_REPEAT: 232 + case PLA_CANCEL: 233 + case PLA_EXIT: 234 + return PLUGIN_OK; 235 + default: 236 + exit_on_usb(button); 237 + break; 238 + } 239 + break; 240 + } 241 + } 242 + }
+1
docs/CREDITS
··· 760 760 Javier Gutiérrez Gertrúdix 761 761 Sergey Puskov 762 762 Eivind Ødegård 763 + Teun van Dalen 763 764 764 765 The libmad team 765 766 The wavpack team
+51
manual/plugins/countdown_timer.tex
··· 1 + % $Id$ % 2 + \subsection{Countdown_timer} 3 + 4 + A countdown timer. Set the desired duration, start the countdown, and the 5 + \dap{} will alert you with a beep sequence when time is up. The timer 6 + defaults to 10 minutes on launch. 7 + 8 + \subsubsection{Setting the timer} 9 + 10 + When the plugin starts, the display shows the time to count down from with 11 + the active field highlighted in brackets (e.g.\ \texttt{[10]:00}). 12 + 13 + \begin{btnmap} 14 + \opt{scrollwheel}{\PluginScrollFwd{} / \PluginScrollBack{} or } 15 + \PluginUp{} / \PluginDown 16 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCUp{} / \PluginRCDown} 17 + & Increase / decrease the selected field \\ 18 + 19 + \PluginLeft{} / \PluginRight 20 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCLeft{} / \PluginRCRight} 21 + & Switch between the minutes and seconds fields \\ 22 + 23 + \PluginSelect 24 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCSelect} 25 + & Start the countdown \\ 26 + \end{btnmap} 27 + 28 + Minutes can be set from 0 to 99 and seconds from 0 to 59. The timer cannot 29 + be started if both fields are zero. Values wrap around when incremented or 30 + decremented past their limits. 31 + 32 + \subsubsection{Running the timer} 33 + 34 + Once started, the remaining time counts down in \texttt{MM:SS} format. 35 + 36 + \begin{btnmap} 37 + \PluginSelect 38 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCSelect} 39 + & Pause / resume the timer \\ 40 + 41 + \nopt{IPOD_4G_PAD,IPOD_3G_PAD}{\PluginCancel} 42 + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu} 43 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCCancel} 44 + & Quit (only available while paused) \\ 45 + \end{btnmap} 46 + 47 + When the countdown reaches zero the \dap{} pauses playback, turns on the 48 + backlight, and plays three beeps. After the alert the timer continues 49 + running into overtime, displaying the elapsed overtime as 50 + \texttt{-MM:SS}. Playback resumes automatically after the beep sequence. 51 + The timer can be quit by pausing it and pressing the quit button.
+2
manual/plugins/main.tex
··· 297 297 \input{plugins/stopwatch.tex} 298 298 299 299 \input{plugins/text_editor.tex} 300 + 301 + \input{plugins/countdown_timer.tex}