b170c73f92
Originally written and uploaded by Lalufu (Ralf Ertzinger) in Feb 2012. They have been condensed into a single patch and some further additions by Andy Potter. Currently includes Authentication V2 support from iPod to Accessory, RF/BlueTooth transmitter support, selecting a playlist and selecting a track from the current playlist. Does not support uploading Album Art or podcasts. Has been tested on the following iPods, 4th Gen Grayscale, 4th Gen Color/Photo, Mini 2nd Gen, Nano 1st Gen and Video 5.5Gen. Change-Id: Ie8fc098361844132f0228ecbe3c48da948726f5e Co-Authored by: Andy Potter <liveboxandy@gmail.com> Reviewed-on: http://gerrit.rockbox.org/533 Reviewed-by: Frank Gevaerts <frank@gevaerts.be>
1508 lines
42 KiB
C
1508 lines
42 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 by Alan Korr & Nick Robinson
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/* Lingo 0x03: Display Remote Lingo
|
|
*
|
|
* A bit of a hodgepogde of odds and ends.
|
|
*
|
|
* Used to control the equalizer in version 1.00 of the Lingo, but later
|
|
* grew functions to control album art transfer and check the player
|
|
* status.
|
|
*
|
|
* TODO:
|
|
* - Actually support multiple equalizer profiles, currently only the
|
|
* profile 0 (equalizer disabled) is supported
|
|
*/
|
|
|
|
#include "iap-core.h"
|
|
#include "iap-lingo.h"
|
|
#include "system.h"
|
|
#include "audio.h"
|
|
#include "powermgmt.h"
|
|
#include "settings.h"
|
|
#include "metadata.h"
|
|
#include "playback.h"
|
|
|
|
/*
|
|
* This macro is meant to be used inside an IAP mode message handler.
|
|
* It is passed the expected minimum length of the message buffer.
|
|
* If the buffer does not have the required lenght an ACK
|
|
* packet with a Bad Parameter error is generated.
|
|
*/
|
|
#define CHECKLEN(x) do { \
|
|
if (len < (x)) { \
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
|
|
return; \
|
|
}} while(0)
|
|
|
|
/* Check for authenticated state, and return an ACK Not
|
|
* Authenticated on failure.
|
|
*/
|
|
#define CHECKAUTH do { \
|
|
if (!DEVICE_AUTHENTICATED) { \
|
|
cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \
|
|
return; \
|
|
}} while(0)
|
|
|
|
static void cmd_ack(const unsigned char cmd, const unsigned char status)
|
|
{
|
|
IAP_TX_INIT(0x03, 0x00);
|
|
IAP_TX_PUT(status);
|
|
IAP_TX_PUT(cmd);
|
|
|
|
iap_send_tx();
|
|
}
|
|
|
|
#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
|
|
|
|
void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf)
|
|
{
|
|
unsigned int cmd = buf[1];
|
|
|
|
/* We expect at least two bytes in the buffer, one for the
|
|
* state bits.
|
|
*/
|
|
CHECKLEN(2);
|
|
|
|
/* Lingo 0x03 must have been negotiated */
|
|
if (!DEVICE_LINGO_SUPPORTED(0x03)) {
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
return;
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
/* ACK (0x00)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetCurrentEQProfileIndex (0x01)
|
|
*
|
|
* Return the index of the current equalizer profile.
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x01
|
|
*
|
|
* Returns:
|
|
* RetCurrentEQProfileIndex
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x02
|
|
* 0x02-0x05: Index as an unsigned 32bit integer
|
|
*/
|
|
case 0x01:
|
|
{
|
|
IAP_TX_INIT(0x03, 0x02);
|
|
IAP_TX_PUT_U32(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetCurrentEQProfileIndex (0x02)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* SetCurrentEQProfileIndex (0x03)
|
|
*
|
|
* Set the active equalizer profile
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x03
|
|
* 0x02-0x05: Profile index to activate
|
|
* 0x06: Whether to restore the previous profile on detach
|
|
*
|
|
* Returns on success:
|
|
* IAP_ACK_OK
|
|
*
|
|
* Returns on failure:
|
|
* IAP_ACK_CMD_FAILED
|
|
*
|
|
* TODO: Figure out return code for invalid index
|
|
*/
|
|
case 0x03:
|
|
{
|
|
uint32_t index;
|
|
|
|
CHECKLEN(7);
|
|
|
|
index = get_u32(&buf[2]);
|
|
|
|
if (index > 0) {
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
|
|
/* Currently, we just ignore the command and acknowledge it */
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
/* GetNumEQProfiles (0x04)
|
|
*
|
|
* Get the number of available equalizer profiles
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x04
|
|
*
|
|
* Returns:
|
|
* RetNumEQProfiles
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x05
|
|
* 0x02-0x05: Number as an unsigned 32bit integer
|
|
*/
|
|
case 0x04:
|
|
{
|
|
IAP_TX_INIT(0x03, 0x05);
|
|
/* Return one profile (0, the disabled profile) */
|
|
IAP_TX_PUT_U32(0x01);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetNumEQProfiles (0x05)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetIndexedEQProfileName (0x06)
|
|
*
|
|
* Return the name of the indexed equalizer profile
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x06
|
|
* 0x02-0x05: Profile index to get the name of
|
|
*
|
|
* Returns on success:
|
|
* RetIndexedEQProfileName
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x06
|
|
* 0x02-0xNN: Name as an UTF-8 null terminated string
|
|
*
|
|
* Returns on failure:
|
|
* IAP_ACK_BAD_PARAM
|
|
*
|
|
* TODO: Figure out return code for out of range index
|
|
*/
|
|
case 0x06:
|
|
{
|
|
uint32_t index;
|
|
|
|
CHECKLEN(6);
|
|
|
|
index = get_u32(&buf[2]);
|
|
|
|
if (index > 0) {
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
IAP_TX_INIT(0x03, 0x07);
|
|
IAP_TX_PUT_STRING("Default");
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetIndexedQUProfileName (0x07)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* SetRemoteEventNotification (0x08)
|
|
*
|
|
* Set events the device would like to be notified about
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x08
|
|
* 0x02-0x05: Event bitmask
|
|
*
|
|
* Returns:
|
|
* IAP_ACK_OK
|
|
*/
|
|
case 0x08:
|
|
{
|
|
struct tm* tm;
|
|
|
|
CHECKLEN(6);
|
|
CHECKAUTH;
|
|
|
|
/* Save the current state of the various attributes we track */
|
|
device.trackpos_ms = iap_get_trackpos();
|
|
device.track_index = iap_get_trackindex();
|
|
device.chapter_index = 0;
|
|
device.play_status = audio_status();
|
|
/* TODO: Fix this */
|
|
device.mute = false;
|
|
device.volume = 0x80;
|
|
device.power_state = charger_input_state;
|
|
device.battery_level = battery_level();
|
|
/* TODO: Fix this */
|
|
device.equalizer_index = 0;
|
|
device.shuffle = global_settings.playlist_shuffle;
|
|
device.repeat = global_settings.repeat_mode;
|
|
tm = get_time();
|
|
memcpy(&(device.datetime), tm, sizeof(struct tm));
|
|
device.alarm_state = 0;
|
|
device.alarm_hour = 0;
|
|
device.alarm_minute = 0;
|
|
/* TODO: Fix this */
|
|
device.backlight = 0;
|
|
device.hold = button_hold();
|
|
device.soundcheck = 0;
|
|
device.audiobook = 0;
|
|
device.trackpos_s = (device.trackpos_ms/1000) & 0xFFFF;
|
|
|
|
/* Get the notification bits */
|
|
device.do_notify = false;
|
|
device.changed_notifications = 0;
|
|
device.notifications = get_u32(&buf[0x02]);
|
|
if (device.notifications)
|
|
device.do_notify = true;
|
|
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
/* RemoteEventNotification (0x09)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetRemoteEventStatus (0x0A)
|
|
*
|
|
* Request the events changed since the last call to GetREmoteEventStatus
|
|
* or SetRemoteEventNotification
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x0A
|
|
*
|
|
* This command requires authentication
|
|
*
|
|
* Returns:
|
|
* RetRemoteEventNotification
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x0B
|
|
* 0x02-0x05: Event status bits
|
|
*/
|
|
case 0x0A:
|
|
{
|
|
CHECKAUTH;
|
|
IAP_TX_INIT(0x03, 0x0B);
|
|
IAP_TX_PUT_U32(device.changed_notifications);
|
|
|
|
iap_send_tx();
|
|
|
|
device.changed_notifications = 0;
|
|
break;
|
|
}
|
|
|
|
/* RetRemoteEventStatus (0x0B)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetiPodStateInfo (0x0C)
|
|
*
|
|
* Request state information from the iPod
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x0C
|
|
* 0x02: Type information
|
|
*
|
|
* This command requires authentication
|
|
*
|
|
* Returns:
|
|
* RetiPodStateInfo
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x0D
|
|
* 0x02: Type information
|
|
* 0x03-0xNN: State information
|
|
*/
|
|
case 0x0C:
|
|
{
|
|
struct mp3entry* id3;
|
|
struct playlist_info* playlist;
|
|
int play_status;
|
|
struct tm* tm;
|
|
|
|
CHECKLEN(3);
|
|
CHECKAUTH;
|
|
|
|
IAP_TX_INIT(0x03, 0x0D);
|
|
IAP_TX_PUT(buf[0x02]);
|
|
|
|
switch (buf[0x02])
|
|
{
|
|
/* 0x00: Track position
|
|
* Data length: 4
|
|
*/
|
|
case 0x00:
|
|
{
|
|
id3 = audio_current_track();
|
|
IAP_TX_PUT_U32(id3->elapsed);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x01: Track index
|
|
* Data length: 4
|
|
*/
|
|
case 0x01:
|
|
{
|
|
playlist = playlist_get_current();
|
|
IAP_TX_PUT_U32(playlist->index - playlist->first_index);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x02: Chapter information
|
|
* Data length: 8
|
|
*/
|
|
case 0x02:
|
|
{
|
|
playlist = playlist_get_current();
|
|
IAP_TX_PUT_U32(playlist->index - playlist->first_index);
|
|
/* Indicate that track does not have chapters */
|
|
IAP_TX_PUT_U16(0x0000);
|
|
IAP_TX_PUT_U16(0xFFFF);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x03: Play status
|
|
* Data length: 1
|
|
*/
|
|
case 0x03:
|
|
{
|
|
/* TODO: Handle FF/REW
|
|
*/
|
|
play_status = audio_status();
|
|
if (play_status & AUDIO_STATUS_PLAY) {
|
|
if (play_status & AUDIO_STATUS_PAUSE) {
|
|
IAP_TX_PUT(0x02);
|
|
} else {
|
|
IAP_TX_PUT(0x01);
|
|
}
|
|
} else {
|
|
IAP_TX_PUT(0x00);
|
|
}
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x04: Mute/UI/Volume
|
|
* Data length: 2
|
|
*/
|
|
case 0x04:
|
|
{
|
|
/* Figuring out what the current volume is
|
|
* seems to be tricky.
|
|
* TODO: Fix.
|
|
*/
|
|
|
|
/* Mute status */
|
|
IAP_TX_PUT(0x00);
|
|
/* Volume */
|
|
IAP_TX_PUT(0x80);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x05: Power/Battery
|
|
* Data length: 2
|
|
*/
|
|
case 0x05:
|
|
{
|
|
iap_fill_power_state();
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x06: Equalizer state
|
|
* Data length: 4
|
|
*/
|
|
case 0x06:
|
|
{
|
|
/* Currently only one equalizer setting supported, 0 */
|
|
IAP_TX_PUT_U32(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x07: Shuffle
|
|
* Data length: 1
|
|
*/
|
|
case 0x07:
|
|
{
|
|
IAP_TX_PUT(global_settings.playlist_shuffle?0x01:0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x08: Repeat
|
|
* Data length: 1
|
|
*/
|
|
case 0x08:
|
|
{
|
|
switch (global_settings.repeat_mode)
|
|
{
|
|
case REPEAT_OFF:
|
|
{
|
|
IAP_TX_PUT(0x00);
|
|
break;
|
|
}
|
|
|
|
case REPEAT_ONE:
|
|
{
|
|
IAP_TX_PUT(0x01);
|
|
break;
|
|
}
|
|
|
|
case REPEAT_ALL:
|
|
{
|
|
IAP_TX_PUT(0x02);
|
|
break;
|
|
}
|
|
}
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x09: Data/Time
|
|
* Data length: 6
|
|
*/
|
|
case 0x09:
|
|
{
|
|
tm = get_time();
|
|
|
|
/* Year */
|
|
IAP_TX_PUT_U16(tm->tm_year);
|
|
|
|
/* Month */
|
|
IAP_TX_PUT(tm->tm_mon+1);
|
|
|
|
/* Day */
|
|
IAP_TX_PUT(tm->tm_mday);
|
|
|
|
/* Hour */
|
|
IAP_TX_PUT(tm->tm_hour);
|
|
|
|
/* Minute */
|
|
IAP_TX_PUT(tm->tm_min);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x0A: Alarm
|
|
* Data length: 3
|
|
*/
|
|
case 0x0A:
|
|
{
|
|
/* Alarm not supported, always off */
|
|
IAP_TX_PUT(0x00);
|
|
IAP_TX_PUT(0x00);
|
|
IAP_TX_PUT(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x0B: Backlight
|
|
* Data length: 1
|
|
*/
|
|
case 0x0B:
|
|
{
|
|
/* TOOD: Find out how to do this */
|
|
IAP_TX_PUT(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x0C: Hold switch
|
|
* Data length: 1
|
|
*/
|
|
case 0x0C:
|
|
{
|
|
IAP_TX_PUT(button_hold()?0x01:0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x0D: Sound check
|
|
* Data length: 1
|
|
*/
|
|
case 0x0D:
|
|
{
|
|
/* TODO: Find out what the hell this is. Default to off */
|
|
IAP_TX_PUT(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x0E: Audiobook
|
|
* Data length: 1
|
|
*/
|
|
case 0x0E:
|
|
{
|
|
/* Default to normal */
|
|
IAP_TX_PUT(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x0F: Track position in seconds
|
|
* Data length: 2
|
|
*/
|
|
case 0x0F:
|
|
{
|
|
unsigned int pos;
|
|
|
|
id3 = audio_current_track();
|
|
pos = id3->elapsed/1000;
|
|
|
|
IAP_TX_PUT_U16(pos);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x10: Mute/UI/Absolute volume
|
|
* Data length: 3
|
|
*/
|
|
case 0x10:
|
|
{
|
|
/* TODO: See volume above */
|
|
IAP_TX_PUT(0x00);
|
|
IAP_TX_PUT(0x80);
|
|
IAP_TX_PUT(0x80);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* RetiPodStateInfo (0x0D)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* SetiPodStateInfo (0x0E)
|
|
*
|
|
* Set status information to new values
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x0E
|
|
* 0x02: Type of information to change
|
|
* 0x03-0xNN: New information
|
|
*
|
|
* This command requires authentication
|
|
*
|
|
* Returns on success:
|
|
* IAP_ACK_OK
|
|
*
|
|
* Returns on failure:
|
|
* IAP_ACK_CMD_FAILED
|
|
* IAP_ACK_BAD_PARAM
|
|
*/
|
|
case 0x0E:
|
|
{
|
|
CHECKLEN(3);
|
|
CHECKAUTH;
|
|
switch (buf[0x02])
|
|
{
|
|
/* Track position (ms)
|
|
* Data length: 4
|
|
*/
|
|
case 0x00:
|
|
{
|
|
uint32_t pos;
|
|
|
|
CHECKLEN(7);
|
|
pos = get_u32(&buf[0x03]);
|
|
audio_ff_rewind(pos);
|
|
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
/* Track index
|
|
* Data length: 4
|
|
*/
|
|
case 0x01:
|
|
{
|
|
uint32_t index;
|
|
|
|
CHECKLEN(7);
|
|
index = get_u32(&buf[0x03]);
|
|
audio_skip(index-iap_get_trackindex());
|
|
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
/* Chapter index
|
|
* Data length: 2
|
|
*/
|
|
case 0x02:
|
|
{
|
|
/* This is not supported */
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Play status
|
|
* Data length: 1
|
|
*/
|
|
case 0x03:
|
|
{
|
|
CHECKLEN(4);
|
|
switch(buf[0x03])
|
|
{
|
|
case 0x00:
|
|
{
|
|
audio_stop();
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
case 0x01:
|
|
{
|
|
audio_resume();
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
case 0x02:
|
|
{
|
|
audio_pause();
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Volume/Mute
|
|
* Data length: 2
|
|
* TODO: Fix this
|
|
*/
|
|
case 0x04:
|
|
{
|
|
CHECKLEN(5);
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Equalizer
|
|
* Data length: 5
|
|
*/
|
|
case 0x06:
|
|
{
|
|
uint32_t index;
|
|
|
|
CHECKLEN(8);
|
|
index = get_u32(&buf[0x03]);
|
|
if (index == 0) {
|
|
cmd_ok(cmd);
|
|
} else {
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Shuffle
|
|
* Data length: 2
|
|
*/
|
|
case 0x07:
|
|
{
|
|
CHECKLEN(5);
|
|
|
|
switch(buf[0x03])
|
|
{
|
|
case 0x00:
|
|
{
|
|
iap_shuffle_state(false);
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
case 0x01:
|
|
case 0x02:
|
|
{
|
|
iap_shuffle_state(true);
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Repeat
|
|
* Data length: 2
|
|
*/
|
|
case 0x08:
|
|
{
|
|
CHECKLEN(5);
|
|
|
|
switch(buf[0x03])
|
|
{
|
|
case 0x00:
|
|
{
|
|
iap_repeat_state(REPEAT_OFF);
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
case 0x01:
|
|
{
|
|
iap_repeat_state(REPEAT_ONE);
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
case 0x02:
|
|
{
|
|
iap_repeat_state(REPEAT_ALL);
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Date/Time
|
|
* Data length: 6
|
|
*/
|
|
case 0x09:
|
|
{
|
|
CHECKLEN(9);
|
|
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Alarm
|
|
* Data length: 4
|
|
*/
|
|
case 0x0A:
|
|
{
|
|
CHECKLEN(7);
|
|
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Backlight
|
|
* Data length: 2
|
|
*/
|
|
case 0x0B:
|
|
{
|
|
CHECKLEN(5);
|
|
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Sound check
|
|
* Data length: 2
|
|
*/
|
|
case 0x0D:
|
|
{
|
|
CHECKLEN(5);
|
|
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Audio book speed
|
|
* Data length: 1
|
|
*/
|
|
case 0x0E:
|
|
{
|
|
CHECKLEN(4);
|
|
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* Track position (s)
|
|
* Data length: 2
|
|
*/
|
|
case 0x0F:
|
|
{
|
|
uint16_t pos;
|
|
|
|
CHECKLEN(5);
|
|
pos = get_u16(&buf[0x03]);
|
|
audio_ff_rewind(1000L * pos);
|
|
|
|
cmd_ok(cmd);
|
|
break;
|
|
}
|
|
|
|
/* Volume/Mute/Absolute
|
|
* Data length: 4
|
|
* TODO: Fix this
|
|
*/
|
|
case 0x10:
|
|
{
|
|
CHECKLEN(7);
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* GetPlayStatus (0x0F)
|
|
*
|
|
* Request the current play status information
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x0F
|
|
*
|
|
* This command requires authentication
|
|
*
|
|
* Returns:
|
|
* RetPlayStatus
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x10
|
|
* 0x02: Play state
|
|
* 0x03-0x06: Current track index
|
|
* 0x07-0x0A: Current track length (ms)
|
|
* 0x0B-0x0E: Current track position (ms)
|
|
*/
|
|
case 0x0F:
|
|
{
|
|
int play_status;
|
|
struct mp3entry* id3;
|
|
struct playlist_info* playlist;
|
|
|
|
CHECKAUTH;
|
|
|
|
IAP_TX_INIT(0x03, 0x10);
|
|
|
|
play_status = audio_status();
|
|
|
|
if (play_status & AUDIO_STATUS_PLAY) {
|
|
/* Playing or paused */
|
|
if (play_status & AUDIO_STATUS_PAUSE) {
|
|
/* Paused */
|
|
IAP_TX_PUT(0x02);
|
|
} else {
|
|
/* Playing */
|
|
IAP_TX_PUT(0x01);
|
|
}
|
|
playlist = playlist_get_current();
|
|
IAP_TX_PUT_U32(playlist->index - playlist->first_index);
|
|
id3 = audio_current_track();
|
|
IAP_TX_PUT_U32(id3->length);
|
|
IAP_TX_PUT_U32(id3->elapsed);
|
|
} else {
|
|
/* Stopped, all values are 0x00 */
|
|
IAP_TX_PUT(0x00);
|
|
IAP_TX_PUT_U32(0x00);
|
|
IAP_TX_PUT_U32(0x00);
|
|
IAP_TX_PUT_U32(0x00);
|
|
}
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetPlayStatus (0x10)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* SetCurrentPlayingTrack (0x11)
|
|
*
|
|
* Set the current playing track
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x11
|
|
* 0x02-0x05: Index of track to play
|
|
*
|
|
* This command requires authentication
|
|
*
|
|
* Returns on success:
|
|
* IAP_ACK_OK
|
|
*
|
|
* Returns on failure:
|
|
* IAP_ACK_BAD_PARAM
|
|
*/
|
|
case 0x11:
|
|
{
|
|
uint32_t index;
|
|
uint32_t trackcount;
|
|
|
|
CHECKAUTH;
|
|
CHECKLEN(6);
|
|
|
|
index = get_u32(&buf[0x02]);
|
|
trackcount = playlist_amount();
|
|
|
|
if (index >= trackcount)
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
audio_skip(index-iap_get_trackindex());
|
|
cmd_ok(cmd);
|
|
|
|
break;
|
|
}
|
|
|
|
/* GetIndexedPlayingTrackInfo (0x12)
|
|
*
|
|
* Request information about a given track
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x12
|
|
* 0x02: Type of information to retrieve
|
|
* 0x03-0x06: Track index
|
|
* 0x07-0x08: Chapter index
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetIndexedPlayingTrackInfo
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x13
|
|
* 0x02: Type of information returned
|
|
* 0x03-0xNN: Information
|
|
*/
|
|
case 0x12:
|
|
{
|
|
/* NOTE:
|
|
*
|
|
* Retrieving the track information from a track which is not
|
|
* the currently playing track can take a seriously long time,
|
|
* in the order of several seconds.
|
|
*
|
|
* This most certainly violates the IAP spec, but there's no way
|
|
* around this for now.
|
|
*/
|
|
uint32_t track_index;
|
|
struct playlist_track_info track;
|
|
struct mp3entry id3;
|
|
|
|
CHECKLEN(0x09);
|
|
CHECKAUTH;
|
|
|
|
track_index = get_u32(&buf[0x03]);
|
|
if (-1 == playlist_get_track_info(NULL, track_index, &track)) {
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
|
|
IAP_TX_INIT(0x03, 0x13);
|
|
IAP_TX_PUT(buf[2]);
|
|
switch (buf[2])
|
|
{
|
|
/* 0x00: Track caps/info
|
|
* Information length: 10 bytes
|
|
*/
|
|
case 0x00:
|
|
{
|
|
iap_get_trackinfo(track_index, &id3);
|
|
/* Track capabilities. None of these are supported, yet */
|
|
IAP_TX_PUT_U32(0x00);
|
|
|
|
/* Track length in ms */
|
|
IAP_TX_PUT_U32(id3.length);
|
|
|
|
/* Chapter count, stays at 0 */
|
|
IAP_TX_PUT_U16(0x00);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x01: Chapter time/name
|
|
* Information length: 4+variable
|
|
*/
|
|
case 0x01:
|
|
{
|
|
/* Chapter length, set at 0 (no chapters) */
|
|
IAP_TX_PUT_U32(0x00);
|
|
|
|
/* Chapter name, empty */
|
|
IAP_TX_PUT_STRING("");
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x02, Artist name
|
|
* Information length: variable
|
|
*/
|
|
case 0x02:
|
|
{
|
|
/* Artist name */
|
|
iap_get_trackinfo(track_index, &id3);
|
|
IAP_TX_PUT_STRLCPY(id3.artist);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x03, Album name
|
|
* Information length: variable
|
|
*/
|
|
case 0x03:
|
|
{
|
|
/* Album name */
|
|
iap_get_trackinfo(track_index, &id3);
|
|
IAP_TX_PUT_STRLCPY(id3.album);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x04, Genre name
|
|
* Information length: variable
|
|
*/
|
|
case 0x04:
|
|
{
|
|
/* Genre name */
|
|
iap_get_trackinfo(track_index, &id3);
|
|
IAP_TX_PUT_STRLCPY(id3.genre_string);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x05, Track title
|
|
* Information length: variable
|
|
*/
|
|
case 0x05:
|
|
{
|
|
/* Track title */
|
|
iap_get_trackinfo(track_index, &id3);
|
|
IAP_TX_PUT_STRLCPY(id3.title);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x06, Composer name
|
|
* Information length: variable
|
|
*/
|
|
case 0x06:
|
|
{
|
|
/* Track Composer */
|
|
iap_get_trackinfo(track_index, &id3);
|
|
IAP_TX_PUT_STRLCPY(id3.composer);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x07, Lyrics
|
|
* Information length: variable
|
|
*/
|
|
case 0x07:
|
|
{
|
|
/* Packet information bits. All 0 (single packet) */
|
|
IAP_TX_PUT(0x00);
|
|
|
|
/* Packet index */
|
|
IAP_TX_PUT_U16(0x00);
|
|
|
|
/* Lyrics */
|
|
IAP_TX_PUT_STRING("");
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* 0x08, Artwork count
|
|
* Information length: variable
|
|
*/
|
|
case 0x08:
|
|
{
|
|
/* No artwork, return packet containing just the type byte */
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* RetIndexedPlayingTrackInfo (0x13)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetNumPlayingTracks (0x14)
|
|
*
|
|
* Request the number of tracks in the current playlist
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x14
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetNumPlayingTracks
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x15
|
|
* 0x02-0xNN: Number of tracks
|
|
*/
|
|
case 0x14:
|
|
{
|
|
CHECKAUTH;
|
|
|
|
IAP_TX_INIT(0x03, 0x15);
|
|
IAP_TX_PUT_U32(playlist_amount());
|
|
|
|
iap_send_tx();
|
|
}
|
|
|
|
/* RetNumPlayingTracks (0x15)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetArtworkFormats (0x16)
|
|
*
|
|
* Request a list of supported artwork formats
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x16
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetArtworkFormats
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x17
|
|
* 0x02-0xNN: list of 7 byte format descriptors
|
|
*/
|
|
case 0x16:
|
|
{
|
|
CHECKAUTH;
|
|
|
|
/* We return the empty list, meaning no artwork
|
|
* TODO: Fix to return actual artwork formats
|
|
*/
|
|
IAP_TX_INIT(0x03, 0x17);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetArtworkFormats (0x17)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetTrackArtworkData (0x18)
|
|
*
|
|
* Request artwork for the given track
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x18
|
|
* 0x02-0x05: Track index
|
|
* 0x06-0x07: Format ID
|
|
* 0x08-0x0B: Track offset in ms
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetTrackArtworkData
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x19
|
|
* 0x02-0x03: Descriptor index
|
|
* 0x04: Pixel format code
|
|
* 0x05-0x06: Image width in pixels
|
|
* 0x07-0x08: Image height in pixels
|
|
* 0x09-0x0A: Inset rectangle, top left x
|
|
* 0x0B-0x0C: Inset rectangle, top left y
|
|
* 0x0D-0x0E: Inset rectangle, bottom right x
|
|
* 0x0F-0x10: Inset rectangle, bottom right y
|
|
* 0x11-0x14: Row size in bytes
|
|
* 0x15-0xNN: Image data
|
|
*
|
|
* If the image data does not fit in a single packet, subsequent
|
|
* packets omit bytes 0x04-0x14.
|
|
*/
|
|
case 0x18:
|
|
{
|
|
CHECKAUTH;
|
|
CHECKLEN(0x0C);
|
|
|
|
/* No artwork support currently */
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
|
|
/* RetTrackArtworkFormat (0x19)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetPowerBatteryState (0x1A)
|
|
*
|
|
* Request the current power state
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x1A
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetPowerBatteryState
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x1B
|
|
* 0x02: Power state
|
|
* 0x03: Battery state
|
|
*/
|
|
case 0x1A:
|
|
{
|
|
IAP_TX_INIT(0x03, 0x1B);
|
|
|
|
iap_fill_power_state();
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetPowerBatteryState (0x1B)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* GetSoundCheckState (0x1C)
|
|
*
|
|
* Request the current sound check state
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x1C
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetSoundCheckState
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x1D
|
|
* 0x02: Sound check state
|
|
*/
|
|
case 0x1C:
|
|
{
|
|
CHECKAUTH;
|
|
|
|
IAP_TX_INIT(0x03, 0x1D);
|
|
IAP_TX_PUT(0x00); /* Always off */
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* RetSoundCheckState (0x1D)
|
|
*
|
|
* Sent from the iPod to the device
|
|
*/
|
|
|
|
/* SetSoundCheckState (0x1E)
|
|
*
|
|
* Set the sound check state
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x1E
|
|
* 0x02: Sound check state
|
|
* 0x03: Restore on exit
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns on success
|
|
* IAP_ACK_OK
|
|
*
|
|
* Returns on failure
|
|
* IAP_ACK_CMD_FAILED
|
|
*/
|
|
case 0x1E:
|
|
{
|
|
CHECKAUTH;
|
|
CHECKLEN(4);
|
|
|
|
/* Sound check is not supported right now
|
|
* TODO: Fix
|
|
*/
|
|
|
|
cmd_ack(cmd, IAP_ACK_CMD_FAILED);
|
|
break;
|
|
}
|
|
|
|
/* GetTrackArtworkTimes (0x1F)
|
|
*
|
|
* Request a list of timestamps at which artwork exists in a track
|
|
*
|
|
* Packet format (offset in buf[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x1F
|
|
* 0x02-0x05: Track index
|
|
* 0x06-0x07: Format ID
|
|
* 0x08-0x09: Artwork Index
|
|
* 0x0A-0x0B: Artwork count
|
|
*
|
|
* This command requires authentication.
|
|
*
|
|
* Returns:
|
|
* RetTrackArtworkTimes
|
|
*
|
|
* Packet format (offset in data[]: Description)
|
|
* 0x00: Lingo ID: Display Remote Lingo, always 0x03
|
|
* 0x01: Command, always 0x20
|
|
* 0x02-0x05: Offset in ms
|
|
*
|
|
* Bytes 0x02-0x05 can be repeated multiple times
|
|
*/
|
|
case 0x1F:
|
|
{
|
|
uint32_t index;
|
|
uint32_t trackcount;
|
|
|
|
CHECKAUTH;
|
|
CHECKLEN(0x0C);
|
|
|
|
/* Artwork is currently unsuported, just check for a valid
|
|
* track index
|
|
*/
|
|
index = get_u32(&buf[0x02]);
|
|
trackcount = playlist_amount();
|
|
|
|
if (index >= trackcount)
|
|
{
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
break;
|
|
}
|
|
|
|
/* Send an empty list */
|
|
IAP_TX_INIT(0x03, 0x20);
|
|
|
|
iap_send_tx();
|
|
break;
|
|
}
|
|
|
|
/* The default response is IAP_ACK_BAD_PARAM */
|
|
default:
|
|
{
|
|
#ifdef LOGF_ENABLE
|
|
logf("iap: Unsupported Mode03 Command");
|
|
#else
|
|
cmd_ack(cmd, IAP_ACK_BAD_PARAM);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|