summaryrefslogtreecommitdiff
path: root/xine_frontend_cec.c
diff options
context:
space:
mode:
Diffstat (limited to 'xine_frontend_cec.c')
-rw-r--r--xine_frontend_cec.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/xine_frontend_cec.c b/xine_frontend_cec.c
new file mode 100644
index 00000000..d5a10cb4
--- /dev/null
+++ b/xine_frontend_cec.c
@@ -0,0 +1,497 @@
+/*
+ * xine_frontend_cec.c: Forward (local) HDMI-CEC keys to VDR (server)
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_frontend_cec.c,v 1.1 2014-01-14 08:21:33 phintuka Exp $
+ *
+ */
+
+#include "features.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef __FreeBSD__
+#include <string.h>
+#endif
+#include <pthread.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBCEC
+#include <libcec/cecc.h>
+#endif
+
+#define LOG_MODULENAME "[hdmi-cec] "
+#include "logdefs.h"
+
+#include "xine_frontend.h"
+#include "xine_frontend_cec.h"
+
+#ifdef HAVE_LIBCEC
+
+/* static data */
+static volatile int exit_req = 0;
+static pthread_t cec_thread;
+static int cec_hdmi_port = 0;
+static int cec_dev_type = 0; /* 0 - TV, 5 - AVR */
+
+
+static const struct keymap_item {
+ const uint8_t map;
+ const char key[12];
+} keymap[] = {
+ [CEC_USER_CONTROL_CODE_SELECT] = {0, "Ok"},
+ [CEC_USER_CONTROL_CODE_UP] = {0, "Up"},
+ [CEC_USER_CONTROL_CODE_DOWN] = {0, "Down"},
+ [CEC_USER_CONTROL_CODE_LEFT] = {0, "Left"},
+ [CEC_USER_CONTROL_CODE_RIGHT] = {0, "Right"},
+ [CEC_USER_CONTROL_CODE_RIGHT_UP] = {1, "RIGHT_UP"},
+ [CEC_USER_CONTROL_CODE_RIGHT_DOWN] = {1, "RIGHT_DOWN"},
+ [CEC_USER_CONTROL_CODE_LEFT_UP] = {1, "LEFT_UP"},
+ [CEC_USER_CONTROL_CODE_LEFT_DOWN] = {1, "LEFT_DOWN"},
+ [CEC_USER_CONTROL_CODE_ROOT_MENU] = {0, "Menu"},
+ [CEC_USER_CONTROL_CODE_SETUP_MENU] = {0, "Menu"},
+ [CEC_USER_CONTROL_CODE_CONTENTS_MENU] = {0, "Recordings"},
+ [CEC_USER_CONTROL_CODE_FAVORITE_MENU] = {1, "FAVORITE"},
+ [CEC_USER_CONTROL_CODE_EXIT] = {0, "Back"},
+ [CEC_USER_CONTROL_CODE_NUMBER0] = {0, "0"},
+ [CEC_USER_CONTROL_CODE_NUMBER1] = {0, "1"},
+ [CEC_USER_CONTROL_CODE_NUMBER2] = {0, "2"},
+ [CEC_USER_CONTROL_CODE_NUMBER3] = {0, "3"},
+ [CEC_USER_CONTROL_CODE_NUMBER4] = {0, "4"},
+ [CEC_USER_CONTROL_CODE_NUMBER5] = {0, "5"},
+ [CEC_USER_CONTROL_CODE_NUMBER6] = {0, "6"},
+ [CEC_USER_CONTROL_CODE_NUMBER7] = {0, "7"},
+ [CEC_USER_CONTROL_CODE_NUMBER8] = {0, "8"},
+ [CEC_USER_CONTROL_CODE_NUMBER9] = {0, "9"},
+ [CEC_USER_CONTROL_CODE_DOT] = {1, "DOT"},
+ [CEC_USER_CONTROL_CODE_ENTER] = {0, "Ok"},
+ [CEC_USER_CONTROL_CODE_CLEAR] = {1, "CLEAR"},
+ [CEC_USER_CONTROL_CODE_NEXT_FAVORITE] = {1, "FAVORITE+"},
+ [CEC_USER_CONTROL_CODE_CHANNEL_UP] = {0, "Channel+"},
+ [CEC_USER_CONTROL_CODE_CHANNEL_DOWN] = {0, "Channel-"},
+ [CEC_USER_CONTROL_CODE_PREVIOUS_CHANNEL] = {0, "Previous"},
+ [CEC_USER_CONTROL_CODE_SOUND_SELECT] = {0, "Audio"},
+ [CEC_USER_CONTROL_CODE_INPUT_SELECT] = {1, "INP_SELECT"},
+ [CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION] = {0, "Info"},
+ [CEC_USER_CONTROL_CODE_HELP] = {1, "HELP"},
+ [CEC_USER_CONTROL_CODE_PAGE_UP] = {1, "PAGE_UP"},
+ [CEC_USER_CONTROL_CODE_PAGE_DOWN] = {1, "PAGE_DOWN"},
+ [CEC_USER_CONTROL_CODE_POWER] = {0, "Power"},
+ [CEC_USER_CONTROL_CODE_VOLUME_UP] = {0, "Volume+"},
+ [CEC_USER_CONTROL_CODE_VOLUME_DOWN] = {0, "Volume-"},
+ [CEC_USER_CONTROL_CODE_MUTE] = {0, "Mute"},
+ [CEC_USER_CONTROL_CODE_PLAY] = {0, "Play"},
+ [CEC_USER_CONTROL_CODE_STOP] = {0, "Stop"},
+ [CEC_USER_CONTROL_CODE_PAUSE] = {0, "Pause"},
+ [CEC_USER_CONTROL_CODE_RECORD] = {0, "Record"},
+ [CEC_USER_CONTROL_CODE_REWIND] = {0, "FastRew"},
+ [CEC_USER_CONTROL_CODE_FAST_FORWARD] = {0, "FastFwd"},
+ [CEC_USER_CONTROL_CODE_EJECT] = {1, "EJECT"},
+ [CEC_USER_CONTROL_CODE_FORWARD] = {0, "Next"},
+ [CEC_USER_CONTROL_CODE_BACKWARD] = {0, "Previous"},
+ //[CEC_USER_CONTROL_CODE_STOP_RECORD] = {0, ""}, //0x4D,
+ //[CEC_USER_CONTROL_CODE_PAUSE_RECORD] = {0, ""}, //0x4E,
+ [CEC_USER_CONTROL_CODE_ANGLE] = {1, "ANGLE"},
+ [CEC_USER_CONTROL_CODE_SUB_PICTURE] = {0, "Subtitles"},
+ //[CEC_USER_CONTROL_CODE_VIDEO_ON_DEMAND] = {0, ""}, //0x52,
+ [CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE] = {0, "Schedule"},
+ [CEC_USER_CONTROL_CODE_TIMER_PROGRAMMING] = {0, "Timers"},
+ //[CEC_USER_CONTROL_CODE_INITIAL_CONFIGURATION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_PLAY_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_PAUSE_PLAY_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_RECORD_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_PAUSE_RECORD_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_STOP_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_MUTE_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_RESTORE_VOLUME_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_TUNE_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_SELECT_MEDIA_FUNCTION] = {0, ""},
+ //[CEC_USER_CONTROL_CODE_SELECT_AV_INPUT_FUNCTION] = {0, ""},
+ [CEC_USER_CONTROL_CODE_SELECT_AUDIO_INPUT_FUNCTION] = {0, "Audio"},
+ [CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION] = {0, "Power"},
+ [CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION] = {0, "Power"},
+ //[CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION] = {0, ""},
+ [CEC_USER_CONTROL_CODE_F1_BLUE] = {0, "Blue"},
+ [CEC_USER_CONTROL_CODE_F2_RED] = {0, "Red"},
+ [CEC_USER_CONTROL_CODE_F3_GREEN] = {0, "Green"},
+ [CEC_USER_CONTROL_CODE_F4_YELLOW] = {0, "Yellow"},
+ [CEC_USER_CONTROL_CODE_F5] = {0, "User1"},
+ [CEC_USER_CONTROL_CODE_DATA] = {1, "DATA"},
+ //[CEC_USER_CONTROL_CODE_AN_RETURN] = {0, ""},
+ [CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST] = {0, "Channels"},
+ [0xff] = {0, ""},
+};
+
+/*
+ * libcec callbacks
+ */
+
+static int cec_config_changed_cb(void *this_gen, const libcec_configuration config)
+{
+ LOGDBG("cec_config_changed");
+ return 1;
+}
+
+static int cec_menu_state_changed_cb(void *this_gen, const cec_menu_state state)
+{
+ LOGDBG("cec_menu_state_changed");
+ return 1;
+}
+
+static void cec_source_activated_cb(void *this_gen, const cec_logical_address address, const uint8_t param)
+{
+ LOGMSG("cec_source_activated: address %d param %d", address, param);
+}
+
+static int cec_log_cb(void *this_gen, const cec_log_message message)
+{
+ if (message.level <= CEC_LOG_ERROR) {
+ errno = 0;
+ LOGERR("%s", message.message);
+ } else if (message.level <= CEC_LOG_NOTICE) {
+ LOGMSG("%s", message.message);
+ } else if (message.level <= CEC_LOG_DEBUG) {
+ LOGDBG("%s", message.message);
+ } else {
+ LOGVERBOSE("%s", message.message);
+ }
+ return 1;
+}
+
+static int cec_keypress_cb(void *this_gen, const cec_keypress keypress)
+{
+ frontend_t *fe = (frontend_t*)this_gen;
+ static int last_key = -1;
+
+ LOGVERBOSE("keypress 0x%x duration %d", keypress.keycode, keypress.duration);
+
+ if (keypress.keycode == last_key && keypress.duration > 0)
+ return 1;
+ else if (keypress.duration > 0)
+ last_key = -1;
+ else
+ last_key = keypress.keycode;
+
+ if (keypress.keycode >= sizeof(keymap) / sizeof(keymap[0]) ||
+ !keymap[keypress.keycode].key[0]) {
+ LOGMSG("unknown keycode 0x%x", keypress.keycode);
+ return 1;
+ }
+
+ LOGDBG("sending key %s%s", keymap[keypress.keycode].map ? "CEC." : "", keymap[keypress.keycode].key);
+
+ alarm(3);
+ fe->send_input_event(fe, keymap[keypress.keycode].map ? "CEC" : NULL,
+ keymap[keypress.keycode].key, 0, 1);
+ alarm(0);
+
+ return 1;
+}
+
+static int cec_alert_cb(void *this_gen, const libcec_alert type, const libcec_parameter param)
+{
+ switch (type) {
+ case CEC_ALERT_CONNECTION_LOST:
+ LOGMSG("ALERT: CEC connection lost");
+ break;
+ case CEC_ALERT_PERMISSION_ERROR:
+ LOGMSG("ALERT: Permission error");
+ break;
+ case CEC_ALERT_PORT_BUSY:
+ LOGMSG("ALERT: Port busy");
+ break;
+ case CEC_ALERT_PHYSICAL_ADDRESS_ERROR:
+ LOGMSG("ALERT: Physical address error");
+ break;
+ case CEC_ALERT_TV_POLL_FAILED:
+ LOGMSG("ALERT: TV poll failed");
+ break;
+
+ case CEC_ALERT_SERVICE_DEVICE:
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int cec_command_cb(void *this_gen, const cec_command command)
+{
+ LOGMSG("Received command 0x%x from 0x%x", command.opcode, command.initiator);
+
+ switch (command.opcode) {
+ case CEC_OPCODE_STANDBY:
+ case CEC_OPCODE_SET_MENU_LANGUAGE:
+ case CEC_OPCODE_DECK_CONTROL:
+ case CEC_OPCODE_PLAY:
+ default:
+ break;
+ }
+ return 1;
+}
+
+/*
+ * configuration
+ */
+
+static void libcec_config_clear(libcec_configuration *p)
+{
+ memset(p, 0, sizeof(*p));
+
+ p->iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
+ p->baseDevice = CEC_DEFAULT_BASE_DEVICE;
+ p->iHDMIPort = CEC_DEFAULT_HDMI_PORT;
+ p->tvVendor = CEC_VENDOR_UNKNOWN;
+ p->clientVersion = CEC_CLIENT_VERSION_CURRENT;
+ p->serverVersion = CEC_SERVER_VERSION_CURRENT;
+ p->bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
+ p->bGetSettingsFromROM = CEC_DEFAULT_SETTING_GET_SETTINGS_FROM_ROM;
+ p->bUseTVMenuLanguage = CEC_DEFAULT_SETTING_USE_TV_MENU_LANGUAGE;
+ p->bActivateSource = CEC_DEFAULT_SETTING_ACTIVATE_SOURCE;
+ p->bPowerOffScreensaver = CEC_DEFAULT_SETTING_POWER_OFF_SCREENSAVER;
+ p->bPowerOnScreensaver = CEC_DEFAULT_SETTING_POWER_ON_SCREENSAVER;
+ p->bPowerOffOnStandby = CEC_DEFAULT_SETTING_POWER_OFF_ON_STANDBY;
+ p->bShutdownOnStandby = CEC_DEFAULT_SETTING_SHUTDOWN_ON_STANDBY;
+ p->bSendInactiveSource = CEC_DEFAULT_SETTING_SEND_INACTIVE_SOURCE;
+ p->iFirmwareVersion = CEC_FW_VERSION_UNKNOWN;
+ p->bPowerOffDevicesOnStandby = CEC_DEFAULT_SETTING_POWER_OFF_DEVICES_STANDBY;
+ memcpy(p->strDeviceLanguage, CEC_DEFAULT_DEVICE_LANGUAGE, 3);
+ p->iFirmwareBuildDate = CEC_FW_BUILD_UNKNOWN;
+ p->bMonitorOnly = 0;
+ p->cecVersion = CEC_DEFAULT_SETTING_CEC_VERSION;
+ p->adapterType = ADAPTERTYPE_UNKNOWN;
+ p->iDoubleTapTimeoutMs = CEC_DOUBLE_TAP_TIMEOUT_MS;
+ p->comboKey = CEC_USER_CONTROL_CODE_STOP;
+ p->iComboKeyTimeoutMs = CEC_DEFAULT_COMBO_TIMEOUT_MS;
+
+ memset(p->strDeviceName, 0, sizeof(p->strDeviceName));
+
+ //deviceTypes.Clear();
+ int i;
+ for (i = 0; i < sizeof(p->deviceTypes.types) / sizeof(p->deviceTypes.types[0]); i++)
+ p->deviceTypes.types[i] = CEC_DEVICE_TYPE_RESERVED;
+ //logicalAddresses.Clear();
+ p->logicalAddresses.primary = CECDEVICE_UNREGISTERED;
+ memset(p->logicalAddresses.addresses, 0, sizeof(p->logicalAddresses.addresses));
+ //wakeDevices.Clear();
+ p->wakeDevices.primary = CECDEVICE_UNREGISTERED;
+ memset(p->wakeDevices.addresses, 0, sizeof(p->wakeDevices.addresses));
+ //powerOffDevices.Clear();
+ p->powerOffDevices.primary = CECDEVICE_UNREGISTERED;
+ memset(p->powerOffDevices.addresses, 0, sizeof(p->powerOffDevices.addresses));
+
+ #if CEC_DEFAULT_SETTING_POWER_OFF_SHUTDOWN == 1
+ p->powerOffDevices.primary = CECDEVICE_BROADCAST;
+ #endif
+ #if CEC_DEFAULT_SETTING_ACTIVATE_SOURCE == 1
+ p->wakeDevices.primary = CECDEVICE_TV;
+ #endif
+
+ p->callbackParam = NULL;
+ p->callbacks = NULL;
+}
+
+static int cec_parse_edid(uint8_t *edid, int size)
+{
+ /* get cec physical address from edid vendor-specific block */
+ int i;
+ for (i = 0; i < size; i++) {
+ if (edid[i] == 0x03 && edid[i + 1] == 0x0c && edid[i + 2] == 0x00) {
+ /* hdmi marker found */
+ LOGMSG("Got CEC physical address from edid: %d.%d.%d.%d",
+ edid[i + 3] >> 4 & 0xf,
+ edid[i + 3] & 0xf,
+ edid[i + 4] >> 4 & 0xf,
+ edid[i + 4] & 0xf);
+
+ return (edid[i + 3] << 8) | edid[i + 4];
+ }
+ }
+ return -1;
+}
+
+static int detect_hdmi_address(frontend_t *fe_gen)
+{
+ if (cec_hdmi_port <= 0) {
+ frontend_t *fe = (frontend_t*)fe_gen;
+ if (fe->fe_display_edid) {
+ int cec_hdmi_address;
+ int size = 0;
+ uint8_t *edid;
+ edid = fe->fe_display_edid(fe, &size);
+ if (edid) {
+ cec_hdmi_address = cec_parse_edid(edid, size);
+ free(edid);
+
+ if (cec_hdmi_address > 0) {
+ return cec_hdmi_address;
+ }
+ }
+ }
+ LOGMSG("WARNING: CEC HDMI port not given and edid reading/parsing failed");
+ }
+ return 0;
+}
+
+static int libcec_init(void *fe_gen)
+{
+ libcec_configuration config;
+ ICECCallbacks callbacks = {
+ .CBCecKeyPress = cec_keypress_cb,
+ .CBCecCommand = cec_command_cb,
+ .CBCecLogMessage = cec_log_cb,
+ .CBCecAlert = cec_alert_cb,
+ .CBCecConfigurationChanged = cec_config_changed_cb,
+ .CBCecSourceActivated = cec_source_activated_cb,
+ .CBCecMenuStateChanged = cec_menu_state_changed_cb,
+ };
+
+ libcec_config_clear(&config);
+
+ config.clientVersion = CEC_CLIENT_VERSION_CURRENT;
+ strncpy(config.strDeviceName, "VDR", sizeof(config.strDeviceName));
+
+ config.iPhysicalAddress = detect_hdmi_address(fe_gen);
+ config.iHDMIPort = cec_hdmi_port;
+ config.baseDevice = cec_dev_type;
+
+ config.bActivateSource = 0;
+ config.callbackParam = fe_gen;
+ config.callbacks = &callbacks;
+
+ config.deviceTypes.types[0] = CEC_DEVICE_TYPE_PLAYBACK_DEVICE;
+ config.deviceTypes.types[1] = CEC_DEVICE_TYPE_RECORDING_DEVICE;
+ config.deviceTypes.types[2] = CEC_DEVICE_TYPE_TUNER;
+ //config.deviceTypes.types[3] = CEC_DEVICE_TYPE_AUDIO_SYSTEM;
+
+ if (!cec_initialise(&config)) {
+ LOGMSG("cec_initialize() failed");
+ return 0;
+ }
+
+ cec_init_video_standalone();
+
+ return 1;
+}
+
+/*
+ *
+ */
+
+static int libcec_open(void)
+{
+ cec_adapter devices[10];
+ int count = cec_find_adapters(devices, 0, NULL);
+ if (count < 1) {
+ LOGMSG("No HDMI-CEC adapters found");
+ return 0;
+ }
+
+ LOGMSG("%d adapters found. Opening %s", count, devices[0].comm);
+
+ if (!cec_open(devices[0].comm, 3000)) {
+ LOGMSG("error opening CEC adapter");
+ return 0;
+ }
+
+ LOGMSG("opened adapter %s", devices[0].comm);
+
+ return 1;
+}
+
+static int libcec_check_device(void)
+{
+ if (!cec_ping_adapters()) {
+ LOGMSG("cec_ping_adapters() failed");
+ return 0;
+ }
+
+ return 1;
+}
+
+static void cleanup(void *p)
+{
+ cec_close();
+ cec_destroy();
+}
+
+static void *cec_receiver_thread(void *fe_gen)
+{
+
+ LOGDBG("started");
+
+ pthread_cleanup_push(cleanup, NULL);
+
+ enum { INIT, WAIT_DEVICE, RUNNING } state = INIT;
+
+ while (!exit_req) {
+
+ pthread_testcancel();
+
+ switch (state) {
+ case INIT:
+ if (!libcec_init(fe_gen)) {
+ return NULL;
+ }
+ state = WAIT_DEVICE;
+ break;
+ case WAIT_DEVICE:
+ if (libcec_open()) {
+ state = RUNNING;
+ }
+ usleep(5000*1000);
+ break;
+ case RUNNING:
+ if (!libcec_check_device()) {
+ state = WAIT_DEVICE;
+ }
+ usleep(1000*1000);
+ break;
+ }
+ }
+
+ pthread_cleanup_pop(1);
+
+ pthread_exit(NULL);
+ return NULL; /* never reached */
+}
+
+#endif /* HAVE_LIBCEC */
+
+/*
+ * interface
+ */
+
+void cec_start(struct frontend_s *fe, int hdmi_port, int dev_type)
+{
+#ifdef HAVE_LIBCEC
+ if (hdmi_port >= 0) {
+ int err;
+
+ exit_req = 0;
+ cec_hdmi_port = hdmi_port;
+ cec_dev_type = dev_type;
+
+ if ((err = pthread_create (&cec_thread,
+ NULL, cec_receiver_thread,
+ (void*)fe)) != 0) {
+ fprintf(stderr, "can't create new thread for HDMI-CEC (%s)\n",
+ strerror(err));
+ }
+ }
+#endif /* HAVE_LIBCEC */
+}
+
+void cec_stop(void)
+{
+#ifdef HAVE_LIBCEC
+ if (!exit_req) {
+ void *p;
+ exit_req = 1;
+ pthread_cancel (cec_thread);
+ pthread_join (cec_thread, &p);
+ }
+#endif /* HAVE_LIBCEC */
+}