A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 by Daniel Stenberg
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/*
23 * Rockbox button functions
24 */
25
26#include <stdlib.h>
27#include "config.h"
28#include "system.h"
29#include "button.h"
30#include "kernel.h"
31#include "thread.h"
32#include "backlight.h"
33#include "serial.h"
34#include "power.h"
35#include "powermgmt.h"
36#if defined(HAVE_SDL)
37#include <SDL.h>
38#if (SDL_MAJOR_VERSION > 1)
39#include "button-sdl.h"
40#else
41#include "button-target.h"
42#endif
43#endif /* HAVE_SDL */
44#ifdef HAVE_REMOTE_LCD
45#include "lcd-remote.h"
46#endif
47#if defined(HAVE_TRANSFLECTIVE_LCD) && defined(HAVE_LCD_SLEEP)
48#include "lcd.h" /* lcd_active() prototype */
49#endif
50
51#if defined(IPOD_ACCESSORY_PROTOCOL) && (defined(IPOD_COLOR) || defined(IPOD_4G) || defined(IPOD_MINI) || defined(IPOD_MINI2G))
52#include "iap.h"
53#endif
54
55static long lastbtn; /* Last valid button status */
56static long last_read; /* Last button status, for debouncing/filtering */
57static bool flipped; /* buttons can be flipped to match the LCD flip */
58
59#ifdef HAVE_BACKLIGHT /* Filter first keypress function pointer */
60static bool (*keypress_filter_fn)(int, int);
61#ifdef HAVE_REMOTE_LCD
62static bool (*remote_keypress_filter_fn)(int, int);
63#endif
64#endif /* HAVE_BACKLIGHT */
65
66#ifdef HAVE_SW_POWEROFF
67static bool enable_sw_poweroff = true;
68#endif
69
70/* how long until repeat kicks in, in centiseconds */
71#define REPEAT_START (30*HZ/100)
72
73/* The next two make repeat "accelerate", which is nice for lists
74 * which begin to scroll a bit faster when holding until the
75 * real list acceleration kicks in (this smooths acceleration).
76 *
77 * Note that touchscreen pointing events are not subject to this
78 * acceleration and always use REPEAT_INTERVAL_TOUCH. (Do repeat
79 * events even do anything sane for touchscreens??)
80 */
81
82/* the speed repeat starts at, in centiseconds */
83#define REPEAT_INTERVAL_START (16*HZ/100)
84/* speed repeat finishes at, in centiseconds */
85#define REPEAT_INTERVAL_FINISH (5*HZ/100)
86/* repeat interval for touch events */
87#define REPEAT_INTERVAL_TOUCH (5*HZ/100)
88
89static int lastdata = 0;
90static int button_read(int *data);
91
92#ifdef HAVE_TOUCHSCREEN
93static long last_touchscreen_touch;
94#endif
95
96static void button_remote_post(void)
97{
98#if defined(HAS_SERIAL_REMOTE) && !defined(SIMULATOR)
99 /* Post events for the remote control */
100 int btn = remote_control_rx();
101 if(btn)
102 button_queue_try_post(btn, 0);
103#endif
104}
105
106#if defined(HAVE_HEADPHONE_DETECTION)
107static int hp_detect_callback(struct timeout *tmo)
108{
109 /* Try to post only transistions */
110 const long id = tmo->data ? SYS_PHONE_PLUGGED : SYS_PHONE_UNPLUGGED;
111 button_queue_post_remove_head(id, 0);
112
113#if defined(IPOD_ACCESSORY_PROTOCOL) && (defined(IPOD_COLOR) || defined(IPOD_4G) || defined(IPOD_MINI) || defined(IPOD_MINI2G))
114 if (id == SYS_PHONE_UNPLUGGED)
115 iap_reset_state(IF_IAP_MP(1));
116#endif
117
118 return 0;
119 /*misc.c:hp_unplug_change*/
120}
121#endif
122
123#if defined(HAVE_LINEOUT_DETECTION)
124static int lo_detect_callback(struct timeout *tmo)
125{
126 /* Try to post only transistions */
127 const long id = tmo->data ? SYS_LINEOUT_PLUGGED : SYS_LINEOUT_UNPLUGGED;
128 button_queue_post_remove_head(id, 0);
129
130 return 0;
131 /*misc.c:lo_unplug_change*/
132}
133#endif
134
135static void check_audio_peripheral_state(void)
136{
137#if defined(HAVE_HEADPHONE_DETECTION)
138 static struct timeout hp_detect_timeout; /* Debouncer for headphone plug/unplug */
139 static bool phones_present = false;
140
141 if (headphones_inserted() != phones_present)
142 {
143 /* Use the autoresetting oneshot to debounce the detection signal */
144 phones_present = !phones_present;
145 timeout_register(&hp_detect_timeout, hp_detect_callback,
146 HZ/2, phones_present);
147 }
148#endif
149#if defined(HAVE_LINEOUT_DETECTION)
150 static struct timeout lo_detect_timeout; /* Debouncer for lineout plug/unplug */
151 static bool lineout_present = false;
152
153 if (lineout_inserted() != lineout_present)
154 {
155 /* Use the autoresetting oneshot to debounce the detection signal */
156 lineout_present = !lineout_present;
157 timeout_register(&lo_detect_timeout, lo_detect_callback,
158 HZ/2, lineout_present);
159 }
160#endif
161}
162
163#ifdef HAVE_BACKLIGHT
164/* disabled function is shared between Main & Remote LCDs */
165static bool filter_first_keypress_disabled(int button, int data)
166{
167 button_queue_try_post(button, data);
168 return false;
169}
170
171static bool filter_first_keypress_enabled(int button, int data)
172{
173#if defined(HAVE_TRANSFLECTIVE_LCD) && defined(HAVE_LCD_SLEEP)
174 if (is_backlight_on(false) && lcd_active())
175#else
176 if (is_backlight_on(false))
177#endif
178 {
179 return filter_first_keypress_disabled(button, data);
180 }
181 return true;
182}
183
184#ifdef HAVE_REMOTE_LCD
185static bool filter_first_remote_keypress_enabled(int button, int data)
186{
187 if (is_remote_backlight_on(false)
188#if defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES)
189 || (remote_type()==REMOTETYPE_H300_NONLCD)
190#endif
191 )
192 {
193 return filter_first_keypress_disabled(button, data);
194 }
195 return true;
196}
197#endif /* def HAVE_REMOTE_LCD */
198#endif /* def HAVE_BACKLIGHT */
199
200static void button_tick(void)
201{
202 static int count = 0;
203 static int repeat_speed = REPEAT_INTERVAL_START;
204 static int repeat_count = 0;
205 static bool repeat = false;
206 static bool post = false;
207#ifdef HAVE_BACKLIGHT
208 static bool skip_release = false;
209#ifdef HAVE_REMOTE_LCD
210 static bool skip_remote_release = false;
211#endif
212#endif
213 int diff;
214 int btn;
215 int data = 0;
216
217 button_remote_post();
218
219 btn = button_read(&data);
220
221 check_audio_peripheral_state();
222
223 /* Find out if a key has been released */
224 diff = btn ^ lastbtn;
225 if(diff && (btn & diff) == 0)
226 {
227#ifdef HAVE_BACKLIGHT
228#ifdef HAVE_REMOTE_LCD
229 if(diff & BUTTON_REMOTE)
230 if(!skip_remote_release)
231 button_queue_try_post(BUTTON_REL | diff, data);
232 else
233 skip_remote_release = false;
234 else
235#endif
236 if(!skip_release)
237 button_queue_try_post(BUTTON_REL | diff, data);
238 else
239 skip_release = false;
240#else
241 button_queue_try_post(BUTTON_REL | diff, data);
242#endif
243 }
244 else
245 {
246 if ( btn )
247 {
248 /* normal keypress */
249 if ( btn != lastbtn )
250 {
251 post = true;
252 repeat = false;
253 repeat_speed = REPEAT_INTERVAL_START;
254 }
255 else /* repeat? */
256 {
257
258#if defined(DX50) || defined(DX90)
259 /*
260 Power button on these devices reports two distinct key codes, which are
261 triggerd by a short or medium duration press. Additionlly a long duration press
262 will trigger a hard reset, which is hardwired.
263
264 The time delta between medium and long duration press is not large enough to
265 register here as power off repeat. A hard reset is triggered before Rockbox
266 can power off.
267
268 To cirumvent the hard reset, Rockbox will shutdown on the first POWEROFF_BUTTON
269 repeat. POWEROFF_BUTTON is associated with the a medium duration press of the
270 power button.
271 */
272 if(btn & POWEROFF_BUTTON)
273 {
274 sys_poweroff();
275 }
276#endif
277
278 if ( repeat )
279 {
280 if (!post)
281 count--;
282 if (count == 0) {
283 post = true;
284 /* yes we have repeat */
285 if (repeat_speed > REPEAT_INTERVAL_FINISH)
286 repeat_speed--;
287#ifdef HAVE_TOUCHSCREEN
288 if(btn & BUTTON_TOUCHSCREEN)
289 repeat_speed = REPEAT_INTERVAL_TOUCH;
290#endif
291
292 count = repeat_speed;
293
294 repeat_count++;
295
296 /* Send a SYS_POWEROFF event if we have a device
297 which doesn't shut down easily with the OFF
298 key */
299#ifdef HAVE_SW_POWEROFF
300 if (enable_sw_poweroff &&
301 (btn & POWEROFF_BUTTON
302#ifdef RC_POWEROFF_BUTTON
303 || btn == RC_POWEROFF_BUTTON
304#endif
305 ) &&
306#if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
307 !charger_inserted() &&
308#endif
309 repeat_count > POWEROFF_COUNT)
310 {
311 /* Tell the main thread that it's time to
312 power off */
313 sys_poweroff();
314
315 /* Safety net for players without hardware
316 poweroff */
317#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
318 if(repeat_count > POWEROFF_COUNT * 10)
319 power_off();
320#endif
321 }
322#endif
323 }
324 }
325 else
326 {
327 if (count++ > REPEAT_START)
328 {
329 post = true;
330 repeat = true;
331 repeat_count = 0;
332 /* initial repeat */
333 count = REPEAT_INTERVAL_START;
334 }
335#ifdef HAVE_TOUCHSCREEN
336 else if (lastdata != data && btn == lastbtn)
337 { /* only coordinates changed, post anyway */
338 if (touchscreen_get_mode() == TOUCHSCREEN_POINT)
339 post = true;
340 }
341#endif
342 }
343 }
344 if ( post )
345 {
346 if (repeat)
347 {
348 /* Only post repeat events if the queue is empty,
349 * to avoid afterscroll effects. */
350 if (button_queue_try_post(BUTTON_REPEAT | btn, data))
351 {
352#ifdef HAVE_BACKLIGHT
353#ifdef HAVE_REMOTE_LCD
354 skip_remote_release = false;
355#endif
356 skip_release = false;
357#endif
358 post = false;
359 /* Need to post back/buttonlight_on() on repeat buttons */
360#ifdef HAVE_BACKLIGHT
361#ifdef HAVE_REMOTE_LCD
362 if (btn & BUTTON_REMOTE) {
363 remote_backlight_on();
364 }
365 else
366#endif
367 {
368 backlight_on();
369 buttonlight_on();
370 }
371#endif
372 }
373 }
374 else
375 {
376#ifdef HAVE_BACKLIGHT
377#ifdef HAVE_REMOTE_LCD
378 if (btn & BUTTON_REMOTE) {
379 skip_remote_release = remote_keypress_filter_fn(btn, data);
380 remote_backlight_on();
381 }
382 else
383#endif
384 {
385 skip_release = keypress_filter_fn(btn, data);
386 backlight_on();
387 buttonlight_on();
388 }
389#else /* no backlight, nothing to skip */
390 button_queue_try_post(btn, data);
391#endif
392 post = false;
393 }
394 reset_poweroff_timer();
395 }
396 }
397 else
398 {
399 repeat = false;
400 count = 0;
401 }
402 }
403 lastbtn = btn & ~(BUTTON_REL | BUTTON_REPEAT);
404
405 lastdata = data;
406}
407
408void button_init(void)
409{
410 int temp;
411 /* Init used objects first */
412 button_queue_init();
413
414 /* hardware inits */
415 button_init_device();
416
417 button_read(&temp);
418 lastbtn = button_read(&temp);
419
420 reset_poweroff_timer();
421
422 flipped = false;
423#ifdef HAVE_BACKLIGHT
424 set_backlight_filter_keypress(false);
425#ifdef HAVE_REMOTE_LCD
426 set_remote_backlight_filter_keypress(false);
427#endif
428#endif
429#ifdef HAVE_TOUCHSCREEN
430 last_touchscreen_touch = -1;
431#endif
432 /* Start polling last */
433 tick_add_task(button_tick);
434}
435
436#ifdef BUTTON_DRIVER_CLOSE
437void button_close(void)
438{
439 tick_remove_task(button_tick);
440}
441#endif /* BUTTON_DRIVER_CLOSE */
442
443#ifdef HAVE_LCD_FLIP
444/*
445 * helper function to swap LEFT/RIGHT, UP/DOWN (if present)
446 */
447static int button_flip(int button)
448{
449 int newbutton = button;
450
451#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
452 newbutton &= ~(
453#if defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
454 BUTTON_LEFT | BUTTON_RIGHT
455#else
456#warning "LEFT/RIGHT not defined!"
457 0
458#endif
459#if defined(BUTTON_UP) && defined(BUTTON_DOWN)
460 | BUTTON_UP | BUTTON_DOWN
461#endif
462#if defined(BUTTON_SCROLL_BACK) && defined(BUTTON_SCROLL_FWD)
463 | BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD
464#endif
465#if (CONFIG_KEYPAD == SANSA_C200_PAD) || (CONFIG_KEYPAD == SANSA_CLIP_PAD) ||\
466 (CONFIG_KEYPAD == GIGABEAT_PAD) || (CONFIG_KEYPAD == GIGABEAT_S_PAD)
467 | BUTTON_VOL_UP | BUTTON_VOL_DOWN
468#endif
469#if CONFIG_KEYPAD == PHILIPS_SA9200_PAD
470 | BUTTON_VOL_UP | BUTTON_VOL_DOWN
471 | BUTTON_NEXT | BUTTON_PREV
472#endif
473 );
474
475#if defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
476 if (button & BUTTON_LEFT)
477 newbutton |= BUTTON_RIGHT;
478 if (button & BUTTON_RIGHT)
479 newbutton |= BUTTON_LEFT;
480#else
481#warning "LEFT/RIGHT not defined!"
482#endif
483
484#if defined(BUTTON_UP) && defined(BUTTON_DOWN)
485 if (button & BUTTON_UP)
486 newbutton |= BUTTON_DOWN;
487 if (button & BUTTON_DOWN)
488 newbutton |= BUTTON_UP;
489#endif
490#if defined(BUTTON_SCROLL_BACK) && defined(BUTTON_SCROLL_FWD)
491 if (button & BUTTON_SCROLL_BACK)
492 newbutton |= BUTTON_SCROLL_FWD;
493 if (button & BUTTON_SCROLL_FWD)
494 newbutton |= BUTTON_SCROLL_BACK;
495#endif
496#if (CONFIG_KEYPAD == SANSA_C200_PAD) || (CONFIG_KEYPAD == SANSA_CLIP_PAD) ||\
497 (CONFIG_KEYPAD == GIGABEAT_PAD) || (CONFIG_KEYPAD == GIGABEAT_S_PAD)
498 if (button & BUTTON_VOL_UP)
499 newbutton |= BUTTON_VOL_DOWN;
500 if (button & BUTTON_VOL_DOWN)
501 newbutton |= BUTTON_VOL_UP;
502#endif
503#if CONFIG_KEYPAD == PHILIPS_SA9200_PAD
504 if (button & BUTTON_VOL_UP)
505 newbutton |= BUTTON_VOL_DOWN;
506 if (button & BUTTON_VOL_DOWN)
507 newbutton |= BUTTON_VOL_UP;
508 if (button & BUTTON_NEXT)
509 newbutton |= BUTTON_PREV;
510 if (button & BUTTON_PREV)
511 newbutton |= BUTTON_NEXT;
512#endif
513#endif /* !SIMULATOR */
514 return newbutton;
515}
516
517/*
518 * set the flip attribute
519 * better only call this when the queue is empty
520 */
521void button_set_flip(bool flip)
522{
523 if (flip != flipped) /* not the current setting */
524 {
525 /* avoid race condition with the button_tick() */
526 int oldlevel = disable_irq_save();
527 lastbtn = button_flip(lastbtn);
528 flipped = flip;
529 restore_irq(oldlevel);
530 }
531}
532#endif /* HAVE_LCD_FLIP */
533
534#ifdef HAVE_BACKLIGHT
535void set_backlight_filter_keypress(bool value)
536{
537 if (!value)
538 keypress_filter_fn = filter_first_keypress_disabled;
539 else
540 keypress_filter_fn = filter_first_keypress_enabled;
541}
542#ifdef HAVE_REMOTE_LCD
543void set_remote_backlight_filter_keypress(bool value)
544{
545 if (!value)
546 remote_keypress_filter_fn = filter_first_keypress_disabled;
547 else
548 remote_keypress_filter_fn = filter_first_remote_keypress_enabled;
549}
550#endif
551#endif
552
553/*
554 * Get button pressed from hardware
555 */
556
557static int button_read(int *data)
558{
559#ifdef HAVE_BUTTON_DATA
560 int btn = button_read_device(data);
561#else
562 (void) data;
563 int btn = button_read_device();
564#endif
565 int retval;
566
567#ifdef HAVE_LCD_FLIP
568 if (btn && flipped)
569 btn = button_flip(btn); /* swap upside down */
570#endif /* HAVE_LCD_FLIP */
571
572#ifdef HAVE_TOUCHSCREEN
573 if (btn & BUTTON_TOUCHSCREEN)
574 last_touchscreen_touch = current_tick;
575#endif
576 /* Filter the button status. It is only accepted if we get the same
577 status twice in a row. */
578#ifndef HAVE_TOUCHSCREEN
579 if (btn != last_read)
580 retval = lastbtn;
581 else
582#endif
583 retval = btn;
584 last_read = btn;
585
586 return retval;
587}
588
589int button_status(void)
590{
591 return lastbtn;
592}
593
594#ifdef HAVE_BUTTON_DATA
595int button_status_wdata(int *pdata)
596{
597 *pdata = lastdata;
598 return lastbtn;
599}
600#endif
601
602#ifdef HAVE_TOUCHSCREEN
603long touchscreen_last_touch(void)
604{
605 return last_touchscreen_touch;
606}
607#endif
608
609#ifdef HAVE_WHEEL_ACCELERATION
610/* WHEEL_ACCEL_FACTOR = 2^16 / WHEEL_ACCEL_START */
611#define WHEEL_ACCEL_FACTOR (1<<16)/WHEEL_ACCEL_START
612/**
613 * data:
614 * [31] Use acceleration
615 * [30:24] Message post count (skipped + 1) (1-127)
616 * [23:0] Velocity - degree/sec
617 *
618 * WHEEL_ACCEL_FACTOR:
619 * Value in degree/sec -- configurable via settings -- above which
620 * the accelerated scrolling starts. Factor is internally scaled by
621 * 1<<16 in respect to the following 32bit integer operations.
622 */
623int button_apply_acceleration(const unsigned int data)
624{
625 int delta = (data >> 24) & 0x7f;
626
627 if ((data & (1 << 31)) != 0)
628 {
629 /* read driver's velocity from data */
630 unsigned int v = data & 0xffffff;
631
632 /* v = 28.4 fixed point */
633 v = (WHEEL_ACCEL_FACTOR * v)>>(16-4);
634
635 /* Calculate real numbers item to scroll based upon acceleration
636 * setting, use correct roundoff */
637#if (WHEEL_ACCELERATION == 1)
638 v = (v*v + (1<< 7))>> 8;
639#elif (WHEEL_ACCELERATION == 2)
640 v = (v*v*v + (1<<11))>>12;
641#elif (WHEEL_ACCELERATION == 3)
642 v = (v*v*v*v + (1<<15))>>16;
643#endif
644
645 if (v > 1)
646 delta *= v;
647 }
648
649 return delta;
650}
651#endif /* HAVE_WHEEL_ACCELERATION */
652
653#if (defined(HAVE_TOUCHPAD) || defined(HAVE_TOUCHSCREEN)) && !defined(HAS_BUTTON_HOLD)
654void button_enable_touch(bool en)
655{
656#ifdef HAVE_TOUCHPAD
657 touchpad_enable(en);
658#endif
659#ifdef HAVE_TOUCHSCREEN
660 touchscreen_enable(en);
661#endif
662}
663#endif
664
665#ifdef HAVE_SW_POWEROFF
666void button_set_sw_poweroff_state(bool en) {
667 enable_sw_poweroff = en;
668}
669
670bool button_get_sw_poweroff_state() {
671 return enable_sw_poweroff;
672}
673#endif