From f00d5bb402ff85d608a9c638f1b70fb498973a6a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 4 Jan 2018 20:46:43 +0100 Subject: [PATCH] webui: add possibility to colorify channel names with numbers and/or source (like DVB-T), fixes #4819 --- src/api/api_channel.c | 14 ++++++--- src/channels.c | 50 +++++++++++++++++++++++++++++ src/channels.h | 8 +++++ src/config.c | 18 +++++++++++ src/config.h | 2 ++ src/input/mpegts/mpegts_service.c | 20 ++++++++++++ src/service.c | 10 ++++++ src/service.h | 2 ++ src/webui/comet.c | 2 ++ src/webui/static/app/chconf.js | 52 ++++++++++++++++++++----------- src/webui/static/app/epg.js | 28 +++++++---------- src/webui/static/app/tvheadend.js | 40 +++++++++++++++--------- 12 files changed, 191 insertions(+), 55 deletions(-) diff --git a/src/api/api_channel.c b/src/api/api_channel.c index 4b6a2d94d..347068473 100644 --- a/src/api/api_channel.c +++ b/src/api/api_channel.c @@ -33,15 +33,18 @@ api_channel_is_all(access_t *perm, htsmsg_t *args) !access_verify2(perm, ACCESS_ADMIN); } -// TODO: this will need converting to an idnode system static int api_channel_list ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) { channel_t *ch; htsmsg_t *l; - int cfg = api_channel_is_all(perm, args); - char buf[128], ubuf[UUID_HEX_SIZE]; + const int cfg = api_channel_is_all(perm, args); + const int numbers = htsmsg_get_s32_or_default(args, "numbers", 0); + const int sources = htsmsg_get_s32_or_default(args, "sources", 0); + const int flags = (numbers ? CHANNEL_ENAME_NUMBERS : 0) | + (sources ? CHANNEL_ENAME_SOURCES : 0); + char buf[128], buf1[128], ubuf[UUID_HEX_SIZE]; const char *name, *blank; blank = tvh_gettext_lang(perm->aa_lang_ui, channel_blank_name); @@ -50,10 +53,11 @@ api_channel_list CHANNEL_FOREACH(ch) { if (!cfg && !channel_access(ch, perm, 0)) continue; if (!ch->ch_enabled) { - snprintf(buf, sizeof(buf), "{%s}", channel_get_name(ch, blank)); + snprintf(buf, sizeof(buf), "{%s}", + channel_get_ename(ch, buf1, sizeof(buf1), blank, flags)); name = buf; } else { - name = channel_get_name(ch, blank); + name = channel_get_ename(ch, buf1, sizeof(buf1), blank, flags); } htsmsg_add_msg(l, NULL, htsmsg_create_key_val(idnode_uuid_as_str(&ch->ch_id, ubuf), name)); } diff --git a/src/channels.c b/src/channels.c index a7cf3239a..c0f7b5a0b 100644 --- a/src/channels.c +++ b/src/channels.c @@ -236,6 +236,10 @@ channel_class_get_list(void *o, const char *lang) htsmsg_add_str(m, "uri", "channel/list"); htsmsg_add_str(m, "event", "channel"); htsmsg_add_u32(p, "all", 1); + if (config.chname_num) + htsmsg_add_u32(p, "numbers", 1); + if (config.chname_src) + htsmsg_add_u32(p, "sources", 1); htsmsg_add_msg(m, "params", p); return m; } @@ -773,6 +777,39 @@ channel_get_name ( channel_t *ch, const char *blank ) return blank; } +char * +channel_get_ename + ( channel_t *ch, char *dst, size_t dstlen, const char *blank, uint32_t flags ) +{ + size_t l = 0; + int64_t number; + char buf[128]; + const char *s; + + dst[0] = '\0'; + if (flags & CHANNEL_ENAME_NUMBERS) { + number = channel_get_number(ch); + if (number > 0) { + if (number % CHANNEL_SPLIT) { + tvh_strlcatf(dst, dstlen, l, "%u.%u", + channel_get_major(number), + channel_get_minor(number)); + } else { + tvh_strlcatf(dst, dstlen, l, "%u", channel_get_major(number)); + } + } + } + s = channel_get_name(ch, blank); + if (s) + tvh_strlcatf(dst, dstlen, l, "%s%s", l > 0 ? " " : "", s); + if (flags & CHANNEL_ENAME_SOURCES) { + s = channel_get_source(ch, buf, sizeof(buf)); + if (s) + tvh_strlcatf(dst, dstlen, l, "%s[%s]", l > 0 ? " " : "", s); + } + return dst; +} + int channel_set_name ( channel_t *ch, const char *name ) { @@ -852,6 +889,19 @@ channel_set_number ( channel_t *ch, uint32_t major, uint32_t minor ) return save; } +char * +channel_get_source ( channel_t *ch, char *dst, size_t dstlen ) +{ + const char *s; + idnode_list_mapping_t *ilm; + size_t l = 0; + dst[0] = '\0'; + LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) + if ((s = service_get_source((service_t *)ilm->ilm_in1))) + tvh_strlcatf(dst, dstlen, l, "%s%s", l > 0 ? "," : "", s); + return l > 0 ? dst : NULL; +} + static char * svcnamepicons(const char *svcname) { diff --git a/src/channels.h b/src/channels.h index 769676321..e16804cfb 100644 --- a/src/channels.h +++ b/src/channels.h @@ -181,6 +181,12 @@ int channel_set_name ( channel_t *ch, const char *name ); /// @return number channels that matched "from". int channel_rename_and_save ( const char *from, const char *to ); +#define CHANNEL_ENAME_NUMBERS (1<<0) +#define CHANNEL_ENAME_SOURCES (1<<1) + +char *channel_get_ename ( channel_t *ch, char *dst, size_t dstlen, + const char *blank, uint32_t flags ); + #define CHANNEL_SPLIT ((int64_t)1000000) static inline uint32_t channel_get_major ( int64_t chnum ) { return chnum / CHANNEL_SPLIT; } @@ -189,6 +195,8 @@ static inline uint32_t channel_get_minor ( int64_t chnum ) { return chnum % CHAN int64_t channel_get_number ( channel_t *ch ); int channel_set_number ( channel_t *ch, uint32_t major, uint32_t minor ); +char *channel_get_source ( channel_t *ch, char *dst, size_t dstlen ); + const char *channel_get_icon ( channel_t *ch ); int channel_set_icon ( channel_t *ch, const char *icon ); diff --git a/src/config.c b/src/config.c index 649dec787..2035da220 100644 --- a/src/config.c +++ b/src/config.c @@ -1697,6 +1697,7 @@ config_boot ( const char *path, gid_t gid, uid_t uid ) config.epg_update_window = 24*3600; config_scanfile_ok = 0; config.theme_ui = strdup("blue"); + config.chname_num = 1; idclass_register(&config_class); @@ -2188,6 +2189,23 @@ const idclass_t config_class = { .opts = PO_LORDER | PO_ADVANCED | PO_DOC_NLIST, .group = 2 }, + { + .type = PT_BOOL, + .id = "chname_num", + .name = N_("Channel name with numbers"), + .desc = N_("Add channel numbers to the channel name list"), + .off = offsetof(config_t, chname_num), + .group = 2, + .def.i = 1 + }, + { + .type = PT_BOOL, + .id = "chname_src", + .name = N_("Channel name with sources"), + .desc = N_("Add sources (like DVB-T string) to the channel name list"), + .off = offsetof(config_t, chname_src), + .group = 2 + }, { .type = PT_STR, .islist = 1, diff --git a/src/config.h b/src/config.h index 6524db823..b67f92a64 100644 --- a/src/config.h +++ b/src/config.h @@ -43,6 +43,8 @@ typedef struct config { char *http_server_name; char *language; char *info_area; + int chname_num; + int chname_src; char *language_ui; char *theme_ui; char *muxconf_path; diff --git a/src/input/mpegts/mpegts_service.c b/src/input/mpegts/mpegts_service.c index 8213a33a5..9b682d659 100644 --- a/src/input/mpegts/mpegts_service.c +++ b/src/input/mpegts/mpegts_service.c @@ -545,6 +545,24 @@ mpegts_service_channel_name ( service_t *s ) return ((mpegts_service_t*)s)->s_dvb_svcname; } +static const char * +mpegts_service_source ( service_t *s ) +{ + mpegts_service_t *ms = (mpegts_service_t*)s; + const idclass_t *mux_idc = ms->s_dvb_mux->mm_id.in_class; + if (mux_idc == &dvb_mux_dvbs_class) return "DVB-S"; + if (mux_idc == &dvb_mux_dvbc_class) return "DVB-C"; + if (mux_idc == &dvb_mux_dvbt_class) return "DVB-T"; + if (mux_idc == &dvb_mux_atsc_t_class) return "ATSC-T"; + if (mux_idc == &dvb_mux_atsc_c_class) return "ATSC-C"; + if (mux_idc == &dvb_mux_isdb_t_class) return "ISDB-T"; + if (mux_idc == &dvb_mux_isdb_c_class) return "ISDB-C"; + if (mux_idc == &dvb_mux_isdb_s_class) return "ISDB-S"; + if (mux_idc == &dvb_mux_dtmb_class) return "DTMB"; + if (mux_idc == &dvb_mux_dab_class) return "DAB"; + return NULL; +} + static const char * mpegts_service_provider_name ( service_t *s ) { @@ -817,6 +835,7 @@ mpegts_service_create0 s->s_grace_period = mpegts_service_grace_period; s->s_channel_number = mpegts_service_channel_number; s->s_channel_name = mpegts_service_channel_name; + s->s_source = mpegts_service_source; s->s_provider_name = mpegts_service_provider_name; s->s_channel_icon = mpegts_service_channel_icon; s->s_mapped = mpegts_service_mapped; @@ -1102,6 +1121,7 @@ mpegts_service_create_raw ( mpegts_mux_t *mm ) s->s_grace_period = mpegts_service_grace_period; s->s_channel_number = mpegts_service_channel_number; s->s_channel_name = mpegts_service_channel_name; + s->s_source = mpegts_service_source; s->s_provider_name = mpegts_service_provider_name; s->s_channel_icon = mpegts_service_channel_icon; s->s_mapped = mpegts_service_mapped; diff --git a/src/service.c b/src/service.c index beb33c781..a2e8d0216 100644 --- a/src/service.c +++ b/src/service.c @@ -2018,6 +2018,16 @@ service_get_channel_number ( service_t *s ) return 0; } +/* + * Get source identificator for service + */ +const char * +service_get_source ( service_t *s ) +{ + if (s->s_source) return s->s_source(s); + return 0; +} + /* * Get name for channel from service */ diff --git a/src/service.h b/src/service.h index 993bab0d0..6bceaf46c 100644 --- a/src/service.h +++ b/src/service.h @@ -356,6 +356,7 @@ typedef struct service { */ int64_t (*s_channel_number) (struct service *); const char *(*s_channel_name) (struct service *); + const char *(*s_source) (struct service *); const char *(*s_channel_epgid) (struct service *); htsmsg_t *(*s_channel_tags) (struct service *); const char *(*s_provider_name) (struct service *); @@ -667,6 +668,7 @@ void sort_elementary_streams(service_t *t); const char *service_get_channel_name (service_t *s); const char *service_get_full_channel_name (service_t *s); int64_t service_get_channel_number (service_t *s); +const char *service_get_source (service_t *s); const char *service_get_channel_icon (service_t *s); const char *service_get_channel_epgid (service_t *s); diff --git a/src/webui/comet.c b/src/webui/comet.c index 35ed4a66c..4a41292bc 100644 --- a/src/webui/comet.c +++ b/src/webui/comet.c @@ -189,6 +189,8 @@ comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb) } htsmsg_add_str(m, "theme", access_get_theme(hc->hc_access)); htsmsg_add_u32(m, "quicktips", config.ui_quicktips); + htsmsg_add_u32(m, "chname_num", config.chname_num); + htsmsg_add_u32(m, "chname_src", config.chname_src); if (!access_noacl) htsmsg_add_str(m, "username", username); if (hc->hc_peer_ipstr) diff --git a/src/webui/static/app/chconf.js b/src/webui/static/app/chconf.js index 253f338e3..3f00b7f2f 100644 --- a/src/webui/static/app/chconf.js +++ b/src/webui/static/app/chconf.js @@ -1,24 +1,38 @@ -tvheadend.channelTags = tvheadend.idnode_get_enum({ - url: 'api/channeltag/list', - event: 'channeltag', - listeners: { - 'load': function(scope, records, options) { - var placeholder = Ext.data.Record.create(['key', 'val']); - scope.insert(0,new placeholder({key: '-1', val: _('(Clear filter)')})); +tvheadend.getChannelTags = function() { + if (tvheadend._chtags) + return tvheadend._chtags; + tvheadend._chtags = tvheadend.idnode_get_enum({ + url: 'api/channeltag/list', + event: 'channeltag', + listeners: { + 'load': function(scope, records, options) { + var placeholder = Ext.data.Record.create(['key', 'val']); + scope.insert(0,new placeholder({key: '-1', val: _('(Clear filter)')})); + } } - } -}); - -tvheadend.channels = tvheadend.idnode_get_enum({ - url: 'api/channel/list', - event: 'channel', - listeners: { - 'load': function(scope, records, options) { - var placeholder = Ext.data.Record.create(['key', 'val']); - scope.insert(0,new placeholder({key: '-1', val: _('(Clear filter)')})); + }); + return tvheadend._chtags; +} + +tvheadend.getChannels = function() { + if (tvheadend._channels) + return tvheadend._channels; + tvheadend._channels = tvheadend.idnode_get_enum({ + url: 'api/channel/list', + params: { + 'numbers': tvheadend.chname_num, + 'sources': tvheadend.chname_src, + }, + event: 'channel', + listeners: { + 'load': function(scope, records, options) { + var placeholder = Ext.data.Record.create(['key', 'val']); + scope.insert(0,new placeholder({key: '-1', val: _('(Clear filter)')})); + } } - } -}); + }); + return tvheadend._channels; +} tvheadend.channel_tab = function(panel, index) { diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js index a75cd2491..1cf216853 100644 --- a/src/webui/static/app/epg.js +++ b/src/webui/static/app/epg.js @@ -58,25 +58,21 @@ tvheadend.contentGroupFullLookupName = function(code) { }; tvheadend.channelLookupName = function(key) { - channelString = ""; - - var index = tvheadend.channels.find('key', key); - + var s = ""; + var channels = tvheadend.getChannels(); + var index = channels.find('key', key); if (index !== -1) - var channelString = tvheadend.channels.getAt(index).get('val'); - - return channelString; + s = channels.getAt(index).get('val'); + return s; }; tvheadend.channelTagLookupName = function(key) { - tagString = ""; - - var index = tvheadend.channelTags.find('key', key); - + var s = ""; + var tags = tvheadend.getChannelTags(); + var index = tvheadend.tags.find('key', key); if (index !== -1) - var tagString = tvheadend.channelTags.getAt(index).get('val'); - - return tagString; + s = tags.getAt(index).get('val'); + return s; }; // Store for duration filters - EPG, autorec dialog and autorec rules in the DVR grid @@ -766,7 +762,7 @@ tvheadend.epg = function() { loadingText: _('Loading...'), width: 200, displayField: 'val', - store: tvheadend.channels, + store: tvheadend.getChannels(), mode: 'local', editable: true, forceSelection: true, @@ -789,7 +785,7 @@ tvheadend.epg = function() { loadingText: _('Loading...'), width: 200, displayField: 'val', - store: tvheadend.channelTags, + store: tvheadend.getChannelTags(), mode: 'local', editable: true, forceSelection: true, diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 40c41a4bb..c776f610c 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -6,6 +6,8 @@ tvheadend.dialog = null; tvheadend.uilevel = 'expert'; tvheadend.uilevel_nochange = false; tvheadend.quicktips = true; +tvheadend.chname_num = true; +tvheadend.chname_src = false; tvheadend.wizard = null; tvheadend.docs_toc = null; tvheadend.doc_history = []; @@ -810,7 +812,7 @@ tvheadend.VideoPlayer = function(channelId) { var initialChannelName; if (channelId) { - var record = tvheadend.channels.getById(channelId); + var record = tvheadend.getChannels().getById(channelId); initialChannelName = record.data.val; } @@ -818,7 +820,7 @@ tvheadend.VideoPlayer = function(channelId) { loadingText: _('Loading...'), width: 200, displayField: 'val', - store: tvheadend.channels, + store: tvheadend.getChannels(), mode: 'local', editable: true, triggerAction: 'all', @@ -976,13 +978,15 @@ function diskspaceUpdate(o) { * This function creates top level tabs based on access so users without * access to subsystems won't see them. * - * Obviosuly, access is verified in the server too. + * Obviously, access is verified in the server too. */ function accessUpdate(o) { tvheadend.accessUpdate = o; if (!tvheadend.capabilities) return; + var panel = tvheadend.rootTabPanel; + tvheadend.admin = o.admin == true; if (o.uilevel) @@ -992,18 +996,20 @@ function accessUpdate(o) { tvheadend.theme = o.theme; tvheadend.quicktips = o.quicktips ? true : false; + tvheadend.chname_num = o.chname_num ? 1 : 0; + tvheadend.chname_src = o.chname_src ? 1 : 0; if (o.uilevel_nochange) tvheadend.uilevel_nochange = true; if ('info_area' in o) - tvheadend.rootTabPanel.setInfoArea(o.info_area); + panel.setInfoArea(o.info_area); if ('username' in o) - tvheadend.rootTabPanel.setLogin(o.username); + panel.setLogin(o.username); if ('address' in o) - tvheadend.rootTabPanel.setAddress(o.address); + panel.setAddress(o.address); if ('freediskspace' in o && 'useddiskspace' in o && 'totaldiskspace' in o) - tvheadend.rootTabPanel.setDiskSpace(o.freediskspace, o.useddiskspace, o.totaldiskspace); + panel.setDiskSpace(o.freediskspace, o.useddiskspace, o.totaldiskspace); if ('cookie_expires' in o && o.cookie_expires > 0) tvheadend.cookieProvider.expires = @@ -1012,9 +1018,15 @@ function accessUpdate(o) { if (tvheadend.autorecButton) tvheadend.autorecButton.setDisabled(o.dvr != true); + if (tvheadend.epgpanel == null) { + tvheadend.epgpanel = tvheadend.epg(); + panel.add(tvheadend.epgpanel); + panel.setActiveTab(0); + } + if (o.dvr == true && tvheadend.dvrpanel == null) { tvheadend.dvrpanel = tvheadend.dvr(); - tvheadend.rootTabPanel.add(tvheadend.dvrpanel); + panel.add(tvheadend.dvrpanel); } if (o.admin == true && tvheadend.confpanel == null) { @@ -1148,14 +1160,14 @@ function accessUpdate(o) { } /* Finish */ - tvheadend.rootTabPanel.add(cp); + panel.add(cp); tvheadend.confpanel = cp; cp.doLayout(); } if (o.admin == true && tvheadend.statuspanel == null) { tvheadend.statuspanel = new tvheadend.status; - tvheadend.rootTabPanel.add(tvheadend.statuspanel); + panel.add(tvheadend.statuspanel); } if (tvheadend.aboutPanel == null) { @@ -1167,10 +1179,10 @@ function accessUpdate(o) { iconCls: 'info', autoLoad: 'about.html' }); - tvheadend.rootTabPanel.add(tvheadend.aboutPanel); + panel.add(tvheadend.aboutPanel); } - tvheadend.rootTabPanel.doLayout(); + panel.doLayout(); if (o.wizard) tvheadend.wizard_start(o.wizard); @@ -1362,9 +1374,7 @@ tvheadend.app = function() { }); tvheadend.rootTabPanel = new tvheadend.RootTabPanel({ - region: 'center', - activeTab: 0, - items: [tvheadend.epg()] + region: 'center' }); var viewport = new Ext.Viewport({ -- 2.47.3