A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 477 lines 15 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2008 by Jonathan Gordon 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 <stdio.h> 23#include "config.h" 24#include "system.h" 25#include "icons.h" 26#include "font.h" 27#include "kernel.h" 28#include "misc.h" 29#include "sound.h" 30#include "action.h" 31#include "settings_list.h" 32#include "lang.h" 33#include "playlist.h" 34#include "viewport.h" 35#include "audio.h" 36#include "quickscreen.h" 37#include "talk.h" 38#include "list.h" 39#include "option_select.h" 40#include "debug.h" 41#include "shortcuts.h" 42#include "appevents.h" 43 44 /* 1 top, 1 bottom, 2 on either side, 1 for the icons 45 * if enough space, top and bottom have 2 lines */ 46#define MIN_LINES 5 47#define MAX_NEEDED_LINES 10 48 /* pixels between the 2 center items minimum or between text and icons, 49 * and between text and parent boundaries */ 50#define MARGIN 10 51#define CENTER_ICONAREA_SIZE (MARGIN+8*2) 52 53static bool redraw; 54 55static void quickscreen_update_callback(unsigned short id, 56 void *data, void *userdata) 57{ 58 (void)id; 59 (void)data; 60 (void)userdata; 61 62 redraw = true; 63} 64 65static void quickscreen_fix_viewports(struct gui_quickscreen *qs, 66 struct screen *display, 67 struct viewport *parent, 68 struct viewport 69 vps[QUICKSCREEN_ITEM_COUNT], 70 struct viewport *vp_icons) 71{ 72 int char_height, width, pad = 0; 73 int left_width = 0, right_width = 0, vert_lines; 74 unsigned char *s; 75 int nb_lines = viewport_get_nb_lines(parent); 76 77 /* nb_lines only returns the number of fully visible lines, small screens 78 or really large fonts could cause problems with the calculation below. 79 */ 80 if (nb_lines == 0) 81 nb_lines++; 82 83 char_height = parent->height/nb_lines; 84 85 /* center the icons VP first */ 86 *vp_icons = *parent; 87 vp_icons->width = CENTER_ICONAREA_SIZE; /* abosulte smallest allowed */ 88 vp_icons->x = parent->x; 89 vp_icons->x += (parent->width-CENTER_ICONAREA_SIZE)/2; 90 91 vps[QUICKSCREEN_BOTTOM] = *parent; 92 vps[QUICKSCREEN_TOP] = *parent; 93 /* depending on the space the top/buttom items use 1 or 2 lines */ 94 if (nb_lines < MIN_LINES) 95 vert_lines = 1; 96 else 97 vert_lines = 2; 98 vps[QUICKSCREEN_TOP].y = parent->y; 99 vps[QUICKSCREEN_TOP].height = vps[QUICKSCREEN_BOTTOM].height 100 = vert_lines*char_height; 101 vps[QUICKSCREEN_BOTTOM].y 102 = parent->y + parent->height - vps[QUICKSCREEN_BOTTOM].height; 103 104 /* enough space vertically, so put a nice margin */ 105 if (nb_lines >= MAX_NEEDED_LINES) 106 { 107 vps[QUICKSCREEN_TOP].y += MARGIN; 108 vps[QUICKSCREEN_BOTTOM].y -= MARGIN; 109 } 110 111 vp_icons->y = vps[QUICKSCREEN_TOP].y 112 + vps[QUICKSCREEN_TOP].height; 113 vp_icons->height = vps[QUICKSCREEN_BOTTOM].y - vp_icons->y; 114 115 /* adjust the left/right items widths to fit the screen nicely */ 116 if (qs->items[QUICKSCREEN_LEFT]) 117 { 118 s = P2STR(ID2P(qs->items[QUICKSCREEN_LEFT]->lang_id)); 119 left_width = display->getstringsize(s, NULL, NULL); 120 } 121 if (qs->items[QUICKSCREEN_RIGHT]) 122 { 123 s = P2STR(ID2P(qs->items[QUICKSCREEN_RIGHT]->lang_id)); 124 right_width = display->getstringsize(s, NULL, NULL); 125 } 126 127 width = MAX(left_width, right_width); 128 if (width*2 + vp_icons->width > parent->width) 129 { /* crop text viewports */ 130 width = (parent->width - vp_icons->width)/2; 131 } 132 else 133 { /* add more gap in icons vp */ 134 int excess = parent->width - vp_icons->width - width*2; 135 if (excess > MARGIN*4) 136 { 137 pad = MARGIN; 138 excess -= MARGIN*2; 139 } 140 vp_icons->x -= excess/2; 141 vp_icons->width += excess; 142 } 143 144 vps[QUICKSCREEN_LEFT] = *parent; 145 vps[QUICKSCREEN_LEFT].x = parent->x + pad; 146 vps[QUICKSCREEN_LEFT].width = width; 147 148 vps[QUICKSCREEN_RIGHT] = *parent; 149 vps[QUICKSCREEN_RIGHT].x = parent->x + parent->width - width - pad; 150 vps[QUICKSCREEN_RIGHT].width = width; 151 152 vps[QUICKSCREEN_LEFT].height = vps[QUICKSCREEN_RIGHT].height 153 = 2*char_height; 154 155 vps[QUICKSCREEN_LEFT].y = vps[QUICKSCREEN_RIGHT].y 156 = parent->y + (parent->height/2) - char_height; 157 158 /* shrink the icons vp by a few pixels if there is room so the arrows 159 aren't drawn right next to the text */ 160 if (vp_icons->width > CENTER_ICONAREA_SIZE*2) 161 { 162 vp_icons->width -= CENTER_ICONAREA_SIZE*2/3; 163 vp_icons->x += CENTER_ICONAREA_SIZE*2/6; 164 } 165 if (vp_icons->height > CENTER_ICONAREA_SIZE*2) 166 { 167 vp_icons->height -= CENTER_ICONAREA_SIZE*2/3; 168 vp_icons->y += CENTER_ICONAREA_SIZE*2/6; 169 } 170 171 /* text alignment */ 172 vps[QUICKSCREEN_LEFT].flags &= ~VP_FLAG_ALIGNMENT_MASK; /* left-aligned */ 173 vps[QUICKSCREEN_TOP].flags |= VP_FLAG_ALIGN_CENTER; /* centered */ 174 vps[QUICKSCREEN_BOTTOM].flags |= VP_FLAG_ALIGN_CENTER; /* centered */ 175 vps[QUICKSCREEN_RIGHT].flags &= ~VP_FLAG_ALIGNMENT_MASK;/* right aligned*/ 176 vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT; 177} 178 179static void gui_quickscreen_draw(const struct gui_quickscreen *qs, 180 struct screen *display, 181 struct viewport *parent, 182 struct viewport vps[QUICKSCREEN_ITEM_COUNT], 183 struct viewport *vp_icons) 184{ 185 int i; 186 char buf[MAX_PATH]; 187 unsigned const char *title, *value; 188 int temp; 189 struct viewport *last_vp = display->set_viewport(parent); 190 display->clear_viewport(); 191 192 for (i = 0; i < QUICKSCREEN_ITEM_COUNT; i++) 193 { 194 struct viewport *vp = &vps[i]; 195 if (!qs->items[i]) 196 continue; 197 display->set_viewport(vp); 198 199 title = P2STR(ID2P(qs->items[i]->lang_id)); 200 temp = option_value_as_int(qs->items[i]); 201 value = option_get_valuestring(qs->items[i], 202 buf, MAX_PATH, temp); 203 204 if (viewport_get_nb_lines(vp) < 2) 205 { 206 char text[MAX_PATH]; 207 snprintf(text, MAX_PATH, "%s: %s", title, value); 208 display->puts_scroll(0, 0, text); 209 } 210 else 211 { 212 display->puts_scroll(0, 0, title); 213 display->puts_scroll(0, 1, value); 214 } 215 } 216 /* draw the icons */ 217 display->set_viewport(vp_icons); 218 219 if (qs->items[QUICKSCREEN_TOP] != NULL) 220 { 221 display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow], 222 (vp_icons->width/2) - 4, 0, 7, 8); 223 } 224 if (qs->items[QUICKSCREEN_RIGHT] != NULL) 225 { 226 display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward], 227 vp_icons->width - 8, (vp_icons->height/2) - 4, 7, 8); 228 } 229 if (qs->items[QUICKSCREEN_LEFT] != NULL) 230 { 231 display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward], 232 0, (vp_icons->height/2) - 4, 7, 8); 233 } 234 if (qs->items[QUICKSCREEN_BOTTOM] != NULL) 235 { 236 display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow], 237 (vp_icons->width/2) - 4, vp_icons->height - 8, 7, 8); 238 } 239 240 display->set_viewport(parent); 241 display->update_viewport(); 242 display->set_viewport(last_vp); 243} 244 245static void talk_qs_option(const struct settings_list *opt, bool enqueue) 246{ 247 if (!global_settings.talk_menu || !opt) 248 return; 249 250 if (enqueue) 251 talk_id(opt->lang_id, enqueue); 252 option_talk_value(opt, option_value_as_int(opt), enqueue); 253} 254 255/* 256 * Does the actions associated to the given button if any 257 * - qs : the quickscreen 258 * - button : the key we are going to analyse 259 * returns : true if the button corresponded to an action, false otherwise 260 */ 261static bool gui_quickscreen_do_button(struct gui_quickscreen * qs, int button) 262{ 263 int item; 264 bool previous = false; 265 switch(button) 266 { 267 case ACTION_QS_TOP: 268 item = QUICKSCREEN_TOP; 269 break; 270 271 case ACTION_QS_LEFT: 272 item = QUICKSCREEN_LEFT; 273 previous = true; 274 break; 275 276 case ACTION_QS_DOWN: 277 item = QUICKSCREEN_BOTTOM; 278 previous = true; 279 break; 280 281 case ACTION_QS_RIGHT: 282 item = QUICKSCREEN_RIGHT; 283 break; 284 285 default: 286 return false; 287 } 288 289 if (qs->items[item] == NULL) 290 return false; 291 292 option_select_next_val(qs->items[item], previous, true); 293 talk_qs_option(qs->items[item], false); 294 return true; 295} 296 297#ifdef HAVE_TOUCHSCREEN 298static int quickscreen_touchscreen_button(void) 299{ 300 short x,y; 301 if (action_get_touchscreen_press(&x, &y) != BUTTON_REL) 302 return ACTION_NONE; 303 304 enum { left=1, right=2, top=4, bottom=8 }; 305 306 int bits = 0; 307 308 if(x < LCD_WIDTH/3) 309 bits |= left; 310 else if(x > 2*LCD_WIDTH/3) 311 bits |= right; 312 313 if(y < LCD_HEIGHT/3) 314 bits |= top; 315 else if(y > 2*LCD_HEIGHT/3) 316 bits |= bottom; 317 318 switch(bits) { 319 case top: 320 return ACTION_QS_TOP; 321 case bottom: 322 return ACTION_QS_DOWN; 323 case left: 324 return ACTION_QS_LEFT; 325 case right: 326 return ACTION_QS_RIGHT; 327 default: 328 return ACTION_STD_CANCEL; 329 } 330} 331#endif 332 333static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter, bool *usb) 334{ 335 int button; 336 struct viewport parent[NB_SCREENS]; 337 struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT]; 338 struct viewport vp_icons[NB_SCREENS]; 339 int ret = QUICKSCREEN_OK; 340 /* To quit we need either : 341 * - a second press on the button that made us enter 342 * - an action taken while pressing the enter button, 343 * then release the enter button*/ 344 bool can_quit = false; 345 346 push_current_activity(ACTIVITY_QUICKSCREEN); 347 348 add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_update_callback, NULL); 349 350 FOR_NB_SCREENS(i) 351 { 352 screens[i].set_viewport(NULL); 353 screens[i].scroll_stop(); 354 viewportmanager_theme_enable(i, true, &parent[i]); 355 quickscreen_fix_viewports(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]); 356 gui_quickscreen_draw(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]); 357 } 358 *usb = false; 359 /* Announce current selection on entering this screen. This is all 360 queued up, but can be interrupted as soon as a setting is 361 changed. */ 362 cond_talk_ids(VOICE_QUICKSCREEN); 363 talk_qs_option(qs->items[QUICKSCREEN_TOP], true); 364 if (qs->items[QUICKSCREEN_TOP] != qs->items[QUICKSCREEN_BOTTOM]) 365 talk_qs_option(qs->items[QUICKSCREEN_BOTTOM], true); 366 talk_qs_option(qs->items[QUICKSCREEN_LEFT], true); 367 if (qs->items[QUICKSCREEN_LEFT] != qs->items[QUICKSCREEN_RIGHT]) 368 talk_qs_option(qs->items[QUICKSCREEN_RIGHT], true); 369 while (true) { 370 if (redraw) 371 { 372 redraw = false; 373 FOR_NB_SCREENS(i) 374 gui_quickscreen_draw(qs, &screens[i], &parent[i], 375 vps[i], &vp_icons[i]); 376 } 377 button = get_action(CONTEXT_QUICKSCREEN, HZ/5); 378#ifdef HAVE_TOUCHSCREEN 379 if (button == ACTION_TOUCHSCREEN) 380 button = quickscreen_touchscreen_button(); 381#endif 382 if (default_event_handler(button) == SYS_USB_CONNECTED) 383 { 384 *usb = true; 385 break; 386 } 387 if (gui_quickscreen_do_button(qs, button)) 388 { 389 ret |= QUICKSCREEN_CHANGED; 390 can_quit = true; 391 redraw = true; 392 } 393 else if (button == button_enter) 394 can_quit = true; 395 else if (button == ACTION_QS_VOLUP) { 396 adjust_volume(1); 397 FOR_NB_SCREENS(i) 398 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC); 399 } 400 else if (button == ACTION_QS_VOLDOWN) { 401 adjust_volume(-1); 402 FOR_NB_SCREENS(i) 403 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC); 404 } 405 else if (button == ACTION_STD_CONTEXT) 406 { 407 ret |= QUICKSCREEN_GOTO_SHORTCUTS_MENU; 408 break; 409 } 410 if ((button == button_enter) && can_quit) 411 break; 412 413 if (button == ACTION_STD_CANCEL) 414 break; 415 } 416 /* Notify that we're exiting this screen */ 417 cond_talk_ids_fq(VOICE_OK); 418 FOR_NB_SCREENS(i) 419 { /* stop scrolling before exiting */ 420 for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++) 421 screens[i].scroll_stop_viewport(&vps[i][j]); 422 viewportmanager_theme_undo(i, !(ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU)); 423 } 424 425 if (ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU) /* Eliminate flashing of parent during */ 426 pop_current_activity_without_refresh(); /* transition to Shortcuts */ 427 else 428 pop_current_activity(); 429 430 remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_update_callback, NULL); 431 432 return ret; 433} 434 435int quick_screen_quick(int button_enter) 436{ 437 struct gui_quickscreen qs; 438 bool usb = false; 439 440 for (int i = 0; i < 4; ++i) 441 { 442 qs.items[i] = global_settings.qs_items[i]; 443 444 if (!is_setting_quickscreenable(qs.items[i])) 445 qs.items[i] = NULL; 446 } 447 448 int ret = gui_syncquickscreen_run(&qs, button_enter, &usb); 449 if (ret & QUICKSCREEN_CHANGED) 450 settings_save(); 451 if (usb) 452 return QUICKSCREEN_IN_USB; 453 return ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU ? QUICKSCREEN_GOTO_SHORTCUTS_MENU : 454 QUICKSCREEN_OK; 455} 456 457/* stuff to make the quickscreen configurable */ 458bool is_setting_quickscreenable(const struct settings_list *setting) 459{ 460 if (!setting) 461 return true; 462 463 /* to keep things simple, only settings which have a lang_id set are ok */ 464 if (setting->lang_id < 0 || (setting->flags & F_BANFROMQS)) 465 return false; 466 467 switch (setting->flags & F_T_MASK) 468 { 469 case F_T_BOOL: 470 return true; 471 case F_T_INT: 472 case F_T_UINT: 473 return (setting->RESERVED != NULL); 474 default: 475 return false; 476 } 477}