Add UI to configure V4L adapters.
Not 100% done yet. Just moving code around. #102
This commit is contained in:
parent
2c0ef0f264
commit
4dc2891a2c
11 changed files with 1047 additions and 200 deletions
|
@ -499,6 +499,8 @@ dvb_adapter_build_msg(th_dvb_adapter_t *tda)
|
|||
}
|
||||
}
|
||||
|
||||
htsmsg_add_str(m, "type", "dvb");
|
||||
|
||||
htsmsg_add_u32(m, "services", numsvc);
|
||||
htsmsg_add_u32(m, "muxes", nummux);
|
||||
htsmsg_add_u32(m, "initialMuxes", tda->tda_initial_num_mux);
|
||||
|
@ -538,7 +540,7 @@ dvb_adapter_build_msg(th_dvb_adapter_t *tda)
|
|||
void
|
||||
dvb_adapter_notify(th_dvb_adapter_t *tda)
|
||||
{
|
||||
notify_by_msg("dvbAdapter", dvb_adapter_build_msg(tda));
|
||||
notify_by_msg("tvAdapter", dvb_adapter_build_msg(tda));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
#include "avahi.h"
|
||||
#include "iptv_input.h"
|
||||
#include "transports.h"
|
||||
|
||||
#include "v4l.h"
|
||||
#include "parachute.h"
|
||||
#include "settings.h"
|
||||
|
||||
|
@ -341,6 +341,8 @@ main(int argc, char **argv)
|
|||
|
||||
iptv_input_init();
|
||||
|
||||
v4l_init();
|
||||
|
||||
http_server_init();
|
||||
|
||||
webui_init(contentpath);
|
||||
|
|
|
@ -553,7 +553,7 @@ typedef struct th_transport {
|
|||
*/
|
||||
|
||||
struct v4l_adapter *tht_v4l_adapter;
|
||||
|
||||
int tht_v4l_frequency; // In Hz
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
|
329
src/v4l.c
329
src/v4l.c
|
@ -31,42 +31,17 @@
|
|||
#include <poll.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define __user
|
||||
#include <linux/videodev2.h>
|
||||
#include "settings.h"
|
||||
|
||||
#include "tvhead.h"
|
||||
#include "transports.h"
|
||||
#include "v4l.h"
|
||||
#include "parsers.h"
|
||||
|
||||
LIST_HEAD(v4l_adapter_list, v4l_adapter);
|
||||
|
||||
struct v4l_adapter_list v4l_adapters;
|
||||
|
||||
typedef struct v4l_adapter {
|
||||
|
||||
LIST_ENTRY(v4l_adapter) va_global_link;
|
||||
|
||||
char *va_path;
|
||||
|
||||
char *va_identifier;
|
||||
|
||||
struct v4l2_capability va_caps;
|
||||
|
||||
struct th_transport *va_current_transport;
|
||||
#include "notify.h"
|
||||
#include "psi.h"
|
||||
|
||||
|
||||
int va_fd;
|
||||
|
||||
pthread_t va_thread;
|
||||
|
||||
int va_pipe[2];
|
||||
|
||||
/** Mpeg stream parsing */
|
||||
uint32_t va_startcode;
|
||||
int va_lenlock;
|
||||
|
||||
} v4l_adapter_t;
|
||||
struct v4l_adapter_queue v4l_adapters;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -290,7 +265,25 @@ v4l_transport_stop(th_transport_t *t)
|
|||
static void
|
||||
v4l_transport_save(th_transport_t *t)
|
||||
{
|
||||
v4l_adapter_t *va = t->tht_v4l_adapter;
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
htsmsg_add_u32(m, "frequency", t->tht_v4l_frequency);
|
||||
|
||||
if(t->tht_ch != NULL) {
|
||||
htsmsg_add_str(m, "channelname", t->tht_ch->ch_name);
|
||||
htsmsg_add_u32(m, "mapped", 1);
|
||||
}
|
||||
|
||||
|
||||
pthread_mutex_lock(&t->tht_stream_mutex);
|
||||
psi_save_transport_settings(m, t);
|
||||
pthread_mutex_unlock(&t->tht_stream_mutex);
|
||||
|
||||
hts_settings_save(m, "v4lservices/%s/%s",
|
||||
va->va_identifier, t->tht_identifier);
|
||||
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
|
||||
|
@ -323,18 +316,36 @@ v4l_transport_sourceinfo(th_transport_t *t)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
static th_transport_t *
|
||||
v4l_add_transport(v4l_adapter_t *va)
|
||||
th_transport_t *
|
||||
v4l_transport_find(v4l_adapter_t *va, const char *id, int create)
|
||||
{
|
||||
th_transport_t *t;
|
||||
char buf[200];
|
||||
|
||||
char id[256];
|
||||
int vaidlen = strlen(va->va_identifier);
|
||||
|
||||
snprintf(id, sizeof(id), "%s_%s", va->va_identifier, "foo");
|
||||
printf("Adding transport %s\n", id);
|
||||
if(id != NULL) {
|
||||
|
||||
if(strncmp(id, va->va_identifier, vaidlen))
|
||||
return NULL;
|
||||
|
||||
LIST_FOREACH(t, &va->va_transports, tht_group_link)
|
||||
if(!strcmp(t->tht_identifier, id))
|
||||
return t;
|
||||
}
|
||||
|
||||
if(create == 0)
|
||||
return NULL;
|
||||
|
||||
if(id == NULL) {
|
||||
va->va_tally++;
|
||||
snprintf(buf, sizeof(buf), "%s_%d", va->va_identifier, va->va_tally);
|
||||
id = buf;
|
||||
} else {
|
||||
va->va_tally = MAX(atoi(id + vaidlen), va->va_tally);
|
||||
}
|
||||
|
||||
t = transport_create(id, TRANSPORT_V4L, 0);
|
||||
t->tht_flags |= THT_DEBUG;
|
||||
|
||||
t->tht_start_feed = v4l_transport_start;
|
||||
t->tht_refresh_feed = v4l_transport_refresh;
|
||||
|
@ -342,25 +353,50 @@ v4l_add_transport(v4l_adapter_t *va)
|
|||
t->tht_config_save = v4l_transport_save;
|
||||
t->tht_sourceinfo = v4l_transport_sourceinfo;
|
||||
t->tht_quality_index = v4l_transport_quality;
|
||||
t->tht_iptv_fd = -1;
|
||||
|
||||
t->tht_v4l_adapter = va;
|
||||
|
||||
pthread_mutex_lock(&t->tht_stream_mutex);
|
||||
LIST_INSERT_HEAD(&va->va_transports, t, tht_group_link);
|
||||
|
||||
t->tht_video = transport_stream_create(t, -1, SCT_MPEG2VIDEO);
|
||||
t->tht_audio = transport_stream_create(t, -1, SCT_MPEG2AUDIO);
|
||||
|
||||
pthread_mutex_unlock(&t->tht_stream_mutex);
|
||||
|
||||
transport_map_channel(t, channel_find_by_name("alpha", 1), 0);
|
||||
|
||||
//XXX LIST_INSERT_HEAD(&v4l_all_transports, t, tht_group_link);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
v4l_adapter_add(const char *path, const char *displayname,
|
||||
const char *devicename )
|
||||
{
|
||||
v4l_adapter_t *va;
|
||||
int i, r;
|
||||
|
||||
va = calloc(1, sizeof(v4l_adapter_t));
|
||||
|
||||
va->va_identifier = strdup(path);
|
||||
|
||||
r = strlen(va->va_identifier);
|
||||
for(i = 0; i < r; i++)
|
||||
if(!isalnum((int)va->va_identifier[i]))
|
||||
va->va_identifier[i] = '_';
|
||||
|
||||
va->va_displayname = strdup(displayname);
|
||||
va->va_path = path ? strdup(path) : NULL;
|
||||
va->va_devicename = devicename ? strdup(devicename) : NULL;
|
||||
|
||||
// va->va_caps = caps;
|
||||
|
||||
TAILQ_INSERT_TAIL(&v4l_adapters, va, va_global_link);
|
||||
|
||||
#if 0
|
||||
tvhlog(LOG_INFO, "v4l", "Adding adapter %s: %s (%s) @ %s",
|
||||
path, caps.card, caps.driver, caps.bus_info, caps.bus_info);
|
||||
#endif
|
||||
|
||||
// v4l_add_transport(va);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -371,7 +407,6 @@ v4l_adapter_check(const char *path, int fd)
|
|||
{
|
||||
int r, i;
|
||||
|
||||
v4l_adapter_t *va;
|
||||
struct v4l2_capability caps;
|
||||
|
||||
r = ioctl(fd, VIDIOC_QUERYCAP, &caps);
|
||||
|
@ -432,26 +467,7 @@ v4l_adapter_check(const char *path, int fd)
|
|||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
va = calloc(1, sizeof(v4l_adapter_t));
|
||||
|
||||
va->va_identifier = strdup(path);
|
||||
|
||||
r = strlen(va->va_identifier);
|
||||
for(i = 0; i < r; i++)
|
||||
if(!isalnum((int)va->va_identifier[i]))
|
||||
va->va_identifier[i] = '_';
|
||||
|
||||
va->va_path = strdup(path);
|
||||
va->va_caps = caps;
|
||||
|
||||
LIST_INSERT_HEAD(&v4l_adapters, va, va_global_link);
|
||||
|
||||
tvhlog(LOG_INFO, "v4l", "Adding adapter %s: %s (%s) @ %s",
|
||||
path, caps.card, caps.driver, caps.bus_info, caps.bus_info);
|
||||
|
||||
v4l_add_transport(va);
|
||||
v4l_adapter_add(path, "fiktiv adapter", "andomas hw");
|
||||
}
|
||||
|
||||
|
||||
|
@ -481,15 +497,196 @@ v4l_adapter_probe(const char *path)
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Save config for the given adapter
|
||||
*/
|
||||
static void
|
||||
v4l_adapter_save(v4l_adapter_t *va)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
htsmsg_add_str(m, "displayname", va->va_displayname);
|
||||
htsmsg_add_u32(m, "logging", va->va_logging);
|
||||
hts_settings_save(m, "v4ladapters/%s", va->va_identifier);
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
htsmsg_t *
|
||||
v4l_adapter_build_msg(v4l_adapter_t *va)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
htsmsg_add_str(m, "identifier", va->va_identifier);
|
||||
htsmsg_add_str(m, "name", va->va_displayname);
|
||||
htsmsg_add_str(m, "type", "v4l");
|
||||
|
||||
if(va->va_path)
|
||||
htsmsg_add_str(m, "path", va->va_path);
|
||||
|
||||
if(va->va_devicename)
|
||||
htsmsg_add_str(m, "devicename", va->va_devicename);
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
v4l_adapter_notify(v4l_adapter_t *va)
|
||||
{
|
||||
notify_by_msg("tvAdapter", v4l_adapter_build_msg(va));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
v4l_adapter_t *
|
||||
v4l_adapter_find_by_identifier(const char *identifier)
|
||||
{
|
||||
v4l_adapter_t *va;
|
||||
|
||||
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
|
||||
if(!strcmp(identifier, va->va_identifier))
|
||||
return va;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
v4l_adapter_set_displayname(v4l_adapter_t *va, const char *name)
|
||||
{
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if(!strcmp(name, va->va_displayname))
|
||||
return;
|
||||
|
||||
tvhlog(LOG_NOTICE, "v4l", "Adapter \"%s\" renamed to \"%s\"",
|
||||
va->va_displayname, name);
|
||||
|
||||
tvh_str_set(&va->va_displayname, name);
|
||||
v4l_adapter_save(va);
|
||||
v4l_adapter_notify(va);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
v4l_adapter_set_logging(v4l_adapter_t *va, int on)
|
||||
{
|
||||
if(va->va_logging == on)
|
||||
return;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
tvhlog(LOG_NOTICE, "v4l", "Adapter \"%s\" detailed logging set to: %s",
|
||||
va->va_displayname, on ? "On" : "Off");
|
||||
|
||||
va->va_logging = on;
|
||||
v4l_adapter_save(va);
|
||||
v4l_adapter_notify(va);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
v4l_service_create_by_msg(v4l_adapter_t *va, htsmsg_t *c, const char *name)
|
||||
{
|
||||
const char *s;
|
||||
unsigned int u32;
|
||||
|
||||
th_transport_t *t = v4l_transport_find(va, name, 1);
|
||||
|
||||
if(t == NULL)
|
||||
return;
|
||||
|
||||
s = htsmsg_get_str(c, "channelname");
|
||||
if(htsmsg_get_u32(c, "mapped", &u32))
|
||||
u32 = 0;
|
||||
|
||||
if(!htsmsg_get_u32(c, "frequency", &u32))
|
||||
t->tht_v4l_frequency = u32;
|
||||
|
||||
if(s && u32)
|
||||
transport_map_channel(t, channel_find_by_name(s, 1), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
v4l_service_load(v4l_adapter_t *va)
|
||||
{
|
||||
htsmsg_t *l, *c;
|
||||
htsmsg_field_t *f;
|
||||
|
||||
if((l = hts_settings_load("v4lservices/%s", va->va_identifier)) == NULL)
|
||||
return;
|
||||
|
||||
HTSMSG_FOREACH(f, l) {
|
||||
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
||||
continue;
|
||||
|
||||
v4l_service_create_by_msg(va, c, f->hmf_name);
|
||||
}
|
||||
htsmsg_destroy(l);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
v4l_init(void)
|
||||
{
|
||||
htsmsg_t *l, *c;
|
||||
htsmsg_field_t *f;
|
||||
char buf[256];
|
||||
int i;
|
||||
v4l_adapter_t *va;
|
||||
|
||||
TAILQ_INIT(&v4l_adapters);
|
||||
|
||||
for(i = 0; i < 1; i++) {
|
||||
snprintf(buf, sizeof(buf), "/dev/video%d", i);
|
||||
v4l_adapter_probe(buf);
|
||||
}
|
||||
|
||||
l = hts_settings_load("v4ladapters");
|
||||
if(l != NULL) {
|
||||
HTSMSG_FOREACH(f, l) {
|
||||
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
||||
continue;
|
||||
|
||||
if((va = v4l_adapter_find_by_identifier(f->hmf_name)) == NULL) {
|
||||
/* Not discovered by hardware, create it */
|
||||
|
||||
va = calloc(1, sizeof(v4l_adapter_t));
|
||||
va->va_identifier = strdup(f->hmf_name);
|
||||
va->va_path = NULL;
|
||||
va->va_devicename = NULL;
|
||||
}
|
||||
|
||||
tvh_str_update(&va->va_displayname, htsmsg_get_str(c, "displayname"));
|
||||
htsmsg_get_u32(c, "logging", &va->va_logging);
|
||||
}
|
||||
htsmsg_destroy(l);
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
|
||||
v4l_service_load(va);
|
||||
}
|
||||
|
|
56
src/v4l.h
56
src/v4l.h
|
@ -19,6 +19,62 @@
|
|||
#ifndef V4L_H_
|
||||
#define V4L_H_
|
||||
|
||||
#define __user
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
LIST_HEAD(v4l_adapter_list, v4l_adapter);
|
||||
TAILQ_HEAD(v4l_adapter_queue, v4l_adapter);
|
||||
|
||||
|
||||
extern struct v4l_adapter_queue v4l_adapters;
|
||||
|
||||
typedef struct v4l_adapter {
|
||||
|
||||
TAILQ_ENTRY(v4l_adapter) va_global_link;
|
||||
|
||||
char *va_path;
|
||||
|
||||
char *va_identifier;
|
||||
|
||||
char *va_displayname;
|
||||
|
||||
char *va_devicename;
|
||||
|
||||
uint32_t va_logging;
|
||||
|
||||
// struct v4l2_capability va_caps;
|
||||
|
||||
struct th_transport *va_current_transport;
|
||||
|
||||
struct th_transport_list va_transports;
|
||||
int va_tally;
|
||||
|
||||
/** Receiver thread stuff */
|
||||
|
||||
int va_fd;
|
||||
|
||||
pthread_t va_thread;
|
||||
|
||||
int va_pipe[2];
|
||||
|
||||
/** Mpeg stream parsing */
|
||||
uint32_t va_startcode;
|
||||
int va_lenlock;
|
||||
|
||||
} v4l_adapter_t;
|
||||
|
||||
|
||||
v4l_adapter_t *v4l_adapter_find_by_identifier(const char *identifier);
|
||||
|
||||
void v4l_adapter_set_displayname(v4l_adapter_t *va, const char *name);
|
||||
|
||||
void v4l_adapter_set_logging(v4l_adapter_t *va, int on);
|
||||
|
||||
htsmsg_t *v4l_adapter_build_msg(v4l_adapter_t *va);
|
||||
|
||||
th_transport_t *v4l_transport_find(v4l_adapter_t *va, const char *id,
|
||||
int create);
|
||||
|
||||
void v4l_init(void);
|
||||
|
||||
#endif /* V4L_H */
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "dvb/dvb_support.h"
|
||||
#include "dvb/dvb_preconf.h"
|
||||
#include "dvr/dvr.h"
|
||||
#include "v4l.h"
|
||||
#include "transports.h"
|
||||
#include "serviceprobe.h"
|
||||
#include "xmltv.h"
|
||||
|
@ -126,8 +127,10 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
|
|||
extjs_load(hq, "static/app/cteditor.js");
|
||||
extjs_load(hq, "static/app/acleditor.js");
|
||||
extjs_load(hq, "static/app/cwceditor.js");
|
||||
extjs_load(hq, "static/app/tvadapters.js");
|
||||
extjs_load(hq, "static/app/dvb.js");
|
||||
extjs_load(hq, "static/app/iptv.js");
|
||||
extjs_load(hq, "static/app/v4l.js");
|
||||
extjs_load(hq, "static/app/chconf.js");
|
||||
extjs_load(hq, "static/app/epg.js");
|
||||
extjs_load(hq, "static/app/dvr.js");
|
||||
|
@ -1765,6 +1768,245 @@ extjs_iptvservices(http_connection_t *hc, const char *remain, void *opaque)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_v4ladapter(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
v4l_adapter_t *va;
|
||||
htsmsg_t *out, *array, *r;
|
||||
const char *op = http_arg_get(&hc->hc_req_args, "op");
|
||||
const char *s;
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
if(remain == NULL) {
|
||||
/* Just list all adapters */
|
||||
|
||||
array = htsmsg_create_list();
|
||||
|
||||
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
|
||||
htsmsg_add_msg(array, NULL, v4l_adapter_build_msg(va));
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if((va = v4l_adapter_find_by_identifier(remain)) == NULL) {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
return 404;
|
||||
}
|
||||
|
||||
if(!strcmp(op, "load")) {
|
||||
r = htsmsg_create_map();
|
||||
htsmsg_add_str(r, "id", va->va_identifier);
|
||||
htsmsg_add_str(r, "device", va->va_path ?: "No hardware attached");
|
||||
htsmsg_add_str(r, "name", va->va_displayname);
|
||||
htsmsg_add_u32(r, "logging", va->va_logging);
|
||||
|
||||
out = json_single_record(r, "v4ladapters");
|
||||
} else if(!strcmp(op, "save")) {
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL)
|
||||
v4l_adapter_set_displayname(va, s);
|
||||
|
||||
s = http_arg_get(&hc->hc_req_args, "logging");
|
||||
v4l_adapter_set_logging(va, !!s);
|
||||
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_u32(out, "success", 1);
|
||||
|
||||
} else {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
transport_update_v4l(htsmsg_t *in)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
htsmsg_t *c;
|
||||
th_transport_t *t;
|
||||
uint32_t u32;
|
||||
const char *id;
|
||||
int save;
|
||||
|
||||
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
|
||||
if((c = htsmsg_get_map_by_field(f)) == NULL ||
|
||||
(id = htsmsg_get_str(c, "id")) == NULL)
|
||||
continue;
|
||||
|
||||
if((t = transport_find_by_identifier(id)) == NULL)
|
||||
continue;
|
||||
|
||||
save = 0;
|
||||
|
||||
if(!htsmsg_get_u32(c, "frequency", &u32)) {
|
||||
t->tht_v4l_frequency = u32;
|
||||
save = 1;
|
||||
}
|
||||
if(save)
|
||||
t->tht_config_save(t); // Save config
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static htsmsg_t *
|
||||
build_record_v4l(th_transport_t *t)
|
||||
{
|
||||
htsmsg_t *r = htsmsg_create_map();
|
||||
|
||||
htsmsg_add_str(r, "id", t->tht_identifier);
|
||||
|
||||
htsmsg_add_str(r, "channelname", t->tht_ch ? t->tht_ch->ch_name : "");
|
||||
htsmsg_add_u32(r, "frequency", t->tht_v4l_frequency);
|
||||
htsmsg_add_u32(r, "enabled", t->tht_enabled);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
v4l_transportcmp(const void *A, const void *B)
|
||||
{
|
||||
th_transport_t *a = *(th_transport_t **)A;
|
||||
th_transport_t *b = *(th_transport_t **)B;
|
||||
|
||||
return (int)a->tht_v4l_frequency - (int)b->tht_v4l_frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_v4lservices(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
v4l_adapter_t *va;
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
htsmsg_t *out, *in, *array;
|
||||
const char *op = http_arg_get(&hc->hc_req_args, "op");
|
||||
const char *entries = http_arg_get(&hc->hc_req_args, "entries");
|
||||
th_transport_t *t, **tvec;
|
||||
int count = 0, i = 0;
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
if((va = v4l_adapter_find_by_identifier(remain)) == NULL) {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
return 404;
|
||||
}
|
||||
|
||||
in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
|
||||
|
||||
if(!strcmp(op, "get")) {
|
||||
|
||||
LIST_FOREACH(t, &va->va_transports, tht_group_link)
|
||||
count++;
|
||||
tvec = alloca(sizeof(th_transport_t *) * count);
|
||||
LIST_FOREACH(t, &va->va_transports, tht_group_link)
|
||||
tvec[i++] = t;
|
||||
|
||||
out = htsmsg_create_map();
|
||||
array = htsmsg_create_list();
|
||||
|
||||
qsort(tvec, count, sizeof(th_transport_t *), v4l_transportcmp);
|
||||
|
||||
for(i = 0; i < count; i++)
|
||||
htsmsg_add_msg(array, NULL, build_record_v4l(tvec[i]));
|
||||
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
} else if(!strcmp(op, "update")) {
|
||||
if(in != NULL) {
|
||||
transport_update(in); // Generic transport parameters
|
||||
transport_update_v4l(in); // V4L speicifc
|
||||
}
|
||||
|
||||
out = htsmsg_create_map();
|
||||
|
||||
} else if(!strcmp(op, "create")) {
|
||||
|
||||
out = build_record_v4l(v4l_transport_find(va, NULL, 1));
|
||||
|
||||
} else if(!strcmp(op, "delete")) {
|
||||
if(in != NULL)
|
||||
transport_delete(in);
|
||||
|
||||
out = htsmsg_create_map();
|
||||
|
||||
} else {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
htsmsg_destroy(in);
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
htsmsg_destroy(in);
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_tvadapter(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
htsmsg_t *out, *array;
|
||||
th_dvb_adapter_t *tda;
|
||||
v4l_adapter_t *va;
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
/* Just list all adapters */
|
||||
array = htsmsg_create_list();
|
||||
|
||||
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
|
||||
htsmsg_add_msg(array, NULL, dvb_adapter_build_msg(tda));
|
||||
|
||||
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
|
||||
htsmsg_add_msg(array, NULL, v4l_adapter_build_msg(va));
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WEB user interface
|
||||
|
@ -1817,4 +2059,14 @@ extjs_start(void)
|
|||
http_path_add("/servicedetails",
|
||||
NULL, extjs_servicedetails, ACCESS_ADMIN);
|
||||
|
||||
|
||||
http_path_add("/v4l/adapter",
|
||||
NULL, extjs_v4ladapter, ACCESS_ADMIN);
|
||||
|
||||
http_path_add("/v4l/services",
|
||||
NULL, extjs_v4lservices, ACCESS_ADMIN);
|
||||
|
||||
http_path_add("/tv/adapter",
|
||||
NULL, extjs_tvadapter, ACCESS_ADMIN);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Ext.extend(tvheadend.Comet = function() {
|
||||
this.addEvents({
|
||||
accessUpdate: true,
|
||||
dvbAdapter: true,
|
||||
tvAdapter: true,
|
||||
dvbMux: true,
|
||||
dvbStore: true,
|
||||
dvbSatConf: true,
|
||||
|
|
|
@ -1,40 +1,3 @@
|
|||
/**
|
||||
* Datastore for adapters
|
||||
*/
|
||||
tvheadend.dvbAdapterStore = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
id: 'identifier',
|
||||
fields: ['identifier',
|
||||
'name',
|
||||
'path',
|
||||
'devicename',
|
||||
'currentMux',
|
||||
'services',
|
||||
'muxes',
|
||||
'initialMuxes',
|
||||
'satConf',
|
||||
'deliverySystem',
|
||||
'freqMin',
|
||||
'freqMax',
|
||||
'freqStep',
|
||||
'symrateMin',
|
||||
'symrateMax'
|
||||
],
|
||||
url:'dvb/adapter'
|
||||
});
|
||||
|
||||
tvheadend.comet.on('dvbAdapter', function(m) {
|
||||
idx = tvheadend.dvbAdapterStore.find('identifier', m.identifier);
|
||||
if(idx == -1)
|
||||
return;
|
||||
r = tvheadend.dvbAdapterStore.getAt(idx);
|
||||
|
||||
r.beginEdit();
|
||||
for (key in m)
|
||||
r.set(key, m[key]);
|
||||
r.endEdit();
|
||||
tvheadend.dvbAdapterStore.commitChanges();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1116,7 +1079,7 @@ tvheadend.dvb_adapter_general = function(adapterData, satConfStore) {
|
|||
/**
|
||||
* Subscribe and react on updates for this adapter
|
||||
*/
|
||||
tvheadend.dvbAdapterStore.on('update', function(s, r, o) {
|
||||
tvheadend.tvAdapterStore.on('update', function(s, r, o) {
|
||||
if(r.data.identifier != adapterId)
|
||||
return;
|
||||
infoTemplate.overwrite(infoPanel.body, r.data);
|
||||
|
@ -1240,94 +1203,3 @@ tvheadend.dvb_adapter = function(data)
|
|||
return panel;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tvheadend.dvb = function()
|
||||
{
|
||||
|
||||
var adapterSelection = new Ext.form.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
width: 300,
|
||||
displayField:'name',
|
||||
store: tvheadend.dvbAdapterStore,
|
||||
mode: 'remote',
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'Select DVB adapter...'
|
||||
});
|
||||
|
||||
var dummyadapter = new Ext.Panel({
|
||||
region:'center', layout:'fit',
|
||||
items:[{border: false}]
|
||||
});
|
||||
|
||||
|
||||
var panel = new Ext.Panel({
|
||||
title: 'DVB Adapters',
|
||||
iconCls: 'hardware',
|
||||
layout:'fit',
|
||||
tbar: [
|
||||
adapterSelection, '->', {
|
||||
text: 'Help',
|
||||
handler: function() {
|
||||
new tvheadend.help('DVB', 'config_dvb.html');
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
items: [
|
||||
dummyadapter
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
adapterSelection.on('select', function(c, r) {
|
||||
|
||||
panel.remove(panel.getComponent(0));
|
||||
panel.doLayout();
|
||||
|
||||
var newPanel = new tvheadend.dvb_adapter(r.data)
|
||||
panel.add(newPanel);
|
||||
panel.doLayout();
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tvheadend.showTransportDetails = function(data)
|
||||
{
|
||||
html = '';
|
||||
|
||||
html += '<div style="display:block;font-weight:bold;margin-bottom:4px">';
|
||||
html += '<span style="float:left;width:100px">PID </span>';
|
||||
html += '<span style="float:left;width:100px">Type</span>';
|
||||
html += '<span>Details</span>';
|
||||
html += '</div>';
|
||||
|
||||
for(i = 0; i < data.streams.length; i++) {
|
||||
s = data.streams[i];
|
||||
|
||||
html += '<div style="display:block">';
|
||||
html += '<span style="float:left;width:100px">' + s.pid + '</span>';
|
||||
html += '<span style="float:left;width:100px">' + s.type + '</span>';
|
||||
html += '<span>' + (s.details.length > 0 ? s.details : ' ') + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
win = new Ext.Window({
|
||||
title: 'Service details for ' + data.title,
|
||||
layout: 'fit',
|
||||
width: 400,
|
||||
height: 400,
|
||||
plain: true,
|
||||
bodyStyle: 'padding: 5px',
|
||||
html: html
|
||||
});
|
||||
win.show();
|
||||
}
|
||||
|
|
133
src/webui/static/app/tvadapters.js
Normal file
133
src/webui/static/app/tvadapters.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Datastore for adapters
|
||||
*/
|
||||
tvheadend.tvAdapterStore = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
id: 'identifier',
|
||||
fields: ['identifier',
|
||||
'type',
|
||||
'name',
|
||||
'path',
|
||||
'devicename',
|
||||
'currentMux',
|
||||
'services',
|
||||
'muxes',
|
||||
'initialMuxes',
|
||||
'satConf',
|
||||
'deliverySystem',
|
||||
'freqMin',
|
||||
'freqMax',
|
||||
'freqStep',
|
||||
'symrateMin',
|
||||
'symrateMax'
|
||||
],
|
||||
url:'tv/adapter'
|
||||
});
|
||||
|
||||
tvheadend.comet.on('tvAdapter', function(m) {
|
||||
idx = tvheadend.tvAdapterStore.find('identifier', m.identifier);
|
||||
if(idx == -1)
|
||||
return;
|
||||
r = tvheadend.tvAdapterStore.getAt(idx);
|
||||
|
||||
r.beginEdit();
|
||||
for (key in m)
|
||||
r.set(key, m[key]);
|
||||
r.endEdit();
|
||||
tvheadend.tvAdapterStore.commitChanges();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tvheadend.tvadapters = function()
|
||||
{
|
||||
var adapterSelection = new Ext.form.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
width: 300,
|
||||
displayField:'name',
|
||||
store: tvheadend.tvAdapterStore,
|
||||
mode: 'remote',
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'Select TV adapter...'
|
||||
});
|
||||
|
||||
var dummyadapter = new Ext.Panel({
|
||||
region:'center', layout:'fit',
|
||||
items:[{border: false}]
|
||||
});
|
||||
|
||||
|
||||
var panel = new Ext.Panel({
|
||||
title: 'TV Adapters',
|
||||
iconCls: 'hardware',
|
||||
layout:'fit',
|
||||
tbar: [
|
||||
adapterSelection, '->', {
|
||||
text: 'Help',
|
||||
handler: function() {
|
||||
new tvheadend.help('DVB', 'config_dvb.html');
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
items: [
|
||||
dummyadapter
|
||||
]
|
||||
});
|
||||
|
||||
adapterSelection.on('select', function(c, r) {
|
||||
panel.remove(panel.getComponent(0));
|
||||
panel.doLayout();
|
||||
|
||||
if(r.data.type == 'dvb')
|
||||
var newPanel = new tvheadend.dvb_adapter(r.data)
|
||||
else
|
||||
var newPanel = new tvheadend.v4l_adapter(r.data)
|
||||
|
||||
panel.add(newPanel);
|
||||
panel.doLayout();
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tvheadend.showTransportDetails = function(data)
|
||||
{
|
||||
html = '';
|
||||
|
||||
html += '<div style="display:block;font-weight:bold;margin-bottom:4px">';
|
||||
html += '<span style="float:left;width:100px">PID </span>';
|
||||
html += '<span style="float:left;width:100px">Type</span>';
|
||||
html += '<span>Details</span>';
|
||||
html += '</div>';
|
||||
|
||||
for(i = 0; i < data.streams.length; i++) {
|
||||
s = data.streams[i];
|
||||
|
||||
html += '<div style="display:block">';
|
||||
html += '<span style="float:left;width:100px">' + s.pid + '</span>';
|
||||
html += '<span style="float:left;width:100px">' + s.type + '</span>';
|
||||
html += '<span>' + (s.details.length > 0 ? s.details : ' ') + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
win = new Ext.Window({
|
||||
title: 'Service details for ' + data.title,
|
||||
layout: 'fit',
|
||||
width: 400,
|
||||
height: 400,
|
||||
plain: true,
|
||||
bodyStyle: 'padding: 5px',
|
||||
html: html
|
||||
});
|
||||
win.show();
|
||||
}
|
|
@ -51,7 +51,7 @@ function accessUpdate(o) {
|
|||
new tvheadend.xmltv,
|
||||
new tvheadend.cteditor,
|
||||
new tvheadend.dvrsettings,
|
||||
new tvheadend.dvb,
|
||||
new tvheadend.tvadapters,
|
||||
new tvheadend.iptv,
|
||||
new tvheadend.acleditor,
|
||||
new tvheadend.cwceditor]
|
||||
|
|
333
src/webui/static/app/v4l.js
Normal file
333
src/webui/static/app/v4l.js
Normal file
|
@ -0,0 +1,333 @@
|
|||
|
||||
/**
|
||||
* V4L adapter details
|
||||
*/
|
||||
tvheadend.v4l_adapter_general = function(adapterData) {
|
||||
|
||||
adapterId = adapterData.identifier;
|
||||
|
||||
/* Conf panel */
|
||||
|
||||
var confreader = new Ext.data.JsonReader({
|
||||
root: 'v4ladapters'
|
||||
}, ['name', 'logging']);
|
||||
|
||||
|
||||
function saveConfForm () {
|
||||
confform.getForm().submit({
|
||||
url:'v4l/adapter/' + adapterId,
|
||||
params:{'op':'save'},
|
||||
waitMsg:'Saving Data...'
|
||||
});
|
||||
}
|
||||
|
||||
var items = [
|
||||
{
|
||||
fieldLabel: 'Adapter name',
|
||||
name: 'name',
|
||||
width: 250
|
||||
},
|
||||
new Ext.form.Checkbox({
|
||||
fieldLabel: 'Detailed logging',
|
||||
name: 'logging'
|
||||
})
|
||||
];
|
||||
|
||||
var confform = new Ext.FormPanel({
|
||||
title:'Adapter configuration',
|
||||
columnWidth: .40,
|
||||
frame:true,
|
||||
border:true,
|
||||
disabled:true,
|
||||
style:'margin:10px',
|
||||
bodyStyle:'padding:5px',
|
||||
labelAlign: 'right',
|
||||
labelWidth: 110,
|
||||
waitMsgTarget: true,
|
||||
reader: confreader,
|
||||
defaultType: 'textfield',
|
||||
items: items,
|
||||
buttons: [{
|
||||
text: 'Save',
|
||||
handler: saveConfForm
|
||||
}]
|
||||
});
|
||||
|
||||
confform.getForm().load({
|
||||
url:'v4l/adapter/' + adapterId,
|
||||
params:{'op':'load'},
|
||||
success:function(form, action) {
|
||||
confform.enable();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Information / capabilities panel
|
||||
*/
|
||||
|
||||
var infoTemplate = new Ext.XTemplate(
|
||||
'<h2 style="font-size: 150%">Hardware</h2>' +
|
||||
'<h3>Device path:</h3>{path}' +
|
||||
'<h3>Device name:</h3>{devicename}' +
|
||||
'<h2 style="font-size: 150%">Status</h2>' +
|
||||
'<h3>Currently tuned to:</h3>{currentFrequency} '
|
||||
);
|
||||
|
||||
|
||||
var infoPanel = new Ext.Panel({
|
||||
title:'Information and capabilities',
|
||||
columnWidth: .35,
|
||||
frame:true,
|
||||
border:true,
|
||||
style:'margin:10px',
|
||||
bodyStyle:'padding:5px',
|
||||
html: infoTemplate.applyTemplate(adapterData)
|
||||
});
|
||||
|
||||
/**
|
||||
* Main adapter panel
|
||||
*/
|
||||
var panel = new Ext.Panel({
|
||||
title: 'General',
|
||||
layout:'column',
|
||||
items: [confform, infoPanel]
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Subscribe and react on updates for this adapter
|
||||
*/
|
||||
tvheadend.tvAdapterStore.on('update', function(s, r, o) {
|
||||
if(r.data.identifier != adapterId)
|
||||
return;
|
||||
infoTemplate.overwrite(infoPanel.body, r.data);
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* V4L service grid
|
||||
*/
|
||||
tvheadend.v4l_services = function(adapterId) {
|
||||
|
||||
var fm = Ext.form;
|
||||
|
||||
var enabledColumn = new Ext.grid.CheckColumn({
|
||||
header: "Enabled",
|
||||
dataIndex: 'enabled',
|
||||
width: 45
|
||||
});
|
||||
|
||||
var cm = new Ext.grid.ColumnModel([
|
||||
enabledColumn,
|
||||
{
|
||||
header: "Channel name",
|
||||
dataIndex: 'channelname',
|
||||
width: 150,
|
||||
renderer: function(value, metadata, record, row, col, store) {
|
||||
return value ? value :
|
||||
'<span class="tvh-grid-unset">Unmapped</span>';
|
||||
},
|
||||
editor: new fm.ComboBox({
|
||||
store: tvheadend.channels,
|
||||
allowBlank: true,
|
||||
typeAhead: true,
|
||||
minChars: 2,
|
||||
lazyRender: true,
|
||||
triggerAction: 'all',
|
||||
mode: 'local',
|
||||
displayField:'name'
|
||||
})
|
||||
},
|
||||
{
|
||||
header: "Frequency",
|
||||
dataIndex: 'frequency',
|
||||
width: 60,
|
||||
editor: new fm.NumberField({
|
||||
minValue: 10000,
|
||||
maxValue: 1000000000
|
||||
})
|
||||
}
|
||||
]);
|
||||
|
||||
cm.defaultSortable = true;
|
||||
|
||||
var rec = Ext.data.Record.create([
|
||||
'id', 'enabled', 'channelname', 'frequency'
|
||||
]);
|
||||
|
||||
var store = new Ext.data.JsonStore({
|
||||
root: 'entries',
|
||||
fields: rec,
|
||||
url: "v4l/services/" + adapterId,
|
||||
autoLoad: true,
|
||||
id: 'id',
|
||||
baseParams: {op: "get"},
|
||||
listeners: {
|
||||
'update': function(s, r, o) {
|
||||
d = s.getModifiedRecords().length == 0
|
||||
saveBtn.setDisabled(d);
|
||||
rejectBtn.setDisabled(d);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function addRecord() {
|
||||
Ext.Ajax.request({
|
||||
url: "v4l/services/" + adapterId,
|
||||
params: {
|
||||
op:"create"
|
||||
},
|
||||
failure:function(response,options){
|
||||
Ext.MessageBox.alert('Server Error',
|
||||
'Unable to generate new record');
|
||||
},
|
||||
success:function(response,options){
|
||||
var responseData = Ext.util.JSON.decode(response.responseText);
|
||||
var p = new rec(responseData, responseData.id);
|
||||
grid.stopEditing();
|
||||
store.insert(0, p);
|
||||
grid.startEditing(0, 0);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
function delSelected() {
|
||||
var selectedKeys = grid.selModel.selections.keys;
|
||||
if(selectedKeys.length > 0) {
|
||||
Ext.MessageBox.confirm('Message',
|
||||
'Do you really want to delete selection?',
|
||||
deleteRecord);
|
||||
} else {
|
||||
Ext.MessageBox.alert('Message',
|
||||
'Please select at least one item to delete');
|
||||
}
|
||||
};
|
||||
|
||||
function deleteRecord(btn) {
|
||||
if(btn=='yes') {
|
||||
var selectedKeys = grid.selModel.selections.keys;
|
||||
|
||||
Ext.Ajax.request({
|
||||
url: "v4l/services/" + adapterId,
|
||||
params: {
|
||||
op:"delete",
|
||||
entries:Ext.encode(selectedKeys)
|
||||
},
|
||||
failure:function(response,options) {
|
||||
Ext.MessageBox.alert('Server Error','Unable to delete');
|
||||
},
|
||||
success:function(response,options) {
|
||||
store.reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function saveChanges() {
|
||||
var mr = store.getModifiedRecords();
|
||||
var out = new Array();
|
||||
for (var x = 0; x < mr.length; x++) {
|
||||
v = mr[x].getChanges();
|
||||
out[x] = v;
|
||||
out[x].id = mr[x].id;
|
||||
}
|
||||
|
||||
Ext.Ajax.request({
|
||||
url: "v4l/services/" + adapterId,
|
||||
params: {
|
||||
op:"update",
|
||||
entries:Ext.encode(out)
|
||||
},
|
||||
success:function(response,options) {
|
||||
store.commitChanges();
|
||||
},
|
||||
failure:function(response,options) {
|
||||
Ext.MessageBox.alert('Message',response.statusText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var delButton = new Ext.Toolbar.Button({
|
||||
tooltip: 'Delete one or more selected rows',
|
||||
iconCls:'remove',
|
||||
text: 'Delete selected services',
|
||||
handler: delSelected,
|
||||
disabled: true
|
||||
});
|
||||
|
||||
var saveBtn = new Ext.Toolbar.Button({
|
||||
tooltip: 'Save any changes made (Changed cells have red borders).',
|
||||
iconCls:'save',
|
||||
text: "Save changes",
|
||||
handler: saveChanges,
|
||||
disabled: true
|
||||
});
|
||||
|
||||
var rejectBtn = new Ext.Toolbar.Button({
|
||||
tooltip: 'Revert any changes made (Changed cells have red borders).',
|
||||
iconCls:'undo',
|
||||
text: "Revert changes",
|
||||
handler: function() {
|
||||
store.rejectChanges();
|
||||
},
|
||||
disabled: true
|
||||
});
|
||||
|
||||
var selModel = new Ext.grid.RowSelectionModel({
|
||||
singleSelect:false
|
||||
});
|
||||
|
||||
var grid = new Ext.grid.EditorGridPanel({
|
||||
stripeRows: true,
|
||||
title: 'Services',
|
||||
plugins: [enabledColumn],
|
||||
store: store,
|
||||
clicksToEdit: 2,
|
||||
cm: cm,
|
||||
viewConfig: {forceFit:true},
|
||||
selModel: selModel,
|
||||
tbar: [{
|
||||
tooltip: 'Create a new entry on the server. '+
|
||||
'The new entry is initially disabled so it must be enabled '+
|
||||
'before it start taking effect.',
|
||||
iconCls:'add',
|
||||
text: 'Add service',
|
||||
handler: addRecord
|
||||
}, '-', delButton, '-', saveBtn, rejectBtn]
|
||||
});
|
||||
|
||||
store.on('update', function(s, r, o) {
|
||||
d = s.getModifiedRecords().length == 0
|
||||
saveBtn.setDisabled(d);
|
||||
rejectBtn.setDisabled(d);
|
||||
});
|
||||
|
||||
selModel.on('selectionchange', function(self) {
|
||||
delButton.setDisabled(self.getCount() == 0);
|
||||
});
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tvheadend.v4l_adapter = function(data)
|
||||
{
|
||||
var panel = new Ext.TabPanel({
|
||||
border: false,
|
||||
activeTab:0,
|
||||
autoScroll:true,
|
||||
items: [
|
||||
new tvheadend.v4l_adapter_general(data),
|
||||
new tvheadend.v4l_services(data.identifier)
|
||||
]
|
||||
});
|
||||
return panel;
|
||||
}
|
Loading…
Add table
Reference in a new issue