Project

General

Profile

Feature #2076 » tvheadend-biss.patch

hencha satellite, 2014-06-17 13:33

View differences:

Makefile 2014-06-05 09:31:06.000000000 +0300 → Makefile 2014-06-05 21:34:51.171741269 +0300
275 275
SRCS-${CONFIG_CWC} += \
276 276
	src/descrambler/tvhcsa.c \
277 277
	src/descrambler/cwc.c \
278
	src/descrambler/capmt.c
278
	src/descrambler/capmt.c \
279
       src/descrambler/ccw.c
279 280

  
280 281
# FFdecsa
281 282
ifneq ($(CONFIG_DVBCSA),yes)
src/descrambler/ccw.c 1970-01-01 03:00:00.000000000 +0300 → src/descrambler/ccw.c 2014-06-05 21:33:04.603745866 +0300
1
/*
2
 *  tvheadend, CCW
3
 *  Copyright (C) 2012
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

  
19
#include <pthread.h>
20
#include <assert.h>
21
#include <string.h>
22
#include <stdio.h>
23
#include <unistd.h>
24
#include <stdlib.h>
25
#include <stdarg.h>
26
#include <errno.h>
27
#include <netinet/in.h>
28
#include <arpa/inet.h>
29
#include <ctype.h>
30
#include <signal.h>
31
#include <sys/types.h>
32
#include <sys/socket.h>
33
#include <openssl/des.h>
34

  
35
#include "tvheadend.h"
36
#include "tcp.h"
37
#include "ccw.h"
38
#include "notify.h"
39
#include "atomic.h"
40
#include "dtable.h"
41
#include "subscriptions.h"
42
#include "service.h"
43
#include "input.h"
44
#include "input/mpegts/tsdemux.h"
45
#include "tvhcsa.h"
46
#include "input/mpegts/linuxdvb/linuxdvb_private.h"
47

  
48
/**
49
 *
50
 */
51
TAILQ_HEAD(ccw_queue, ccw);
52
LIST_HEAD(ccw_service_list, ccw_service);
53
static struct ccw_queue ccws;
54
static pthread_cond_t ccw_config_changed;
55

  
56
/**
57
 *
58
 */
59
typedef struct ccw_service {
60
  th_descrambler_t ct_head;
61

  
62
  mpegts_service_t *ct_service;
63

  
64
  struct ccw *ct_ccw;
65

  
66
  LIST_ENTRY(ccw_service) ct_link;
67

  
68
  /**
69
   * Status of the key(s) in ct_keys
70
   */
71
  enum {
72
    CT_UNKNOWN,
73
    CT_RESOLVED,
74
    CT_FORBIDDEN
75
  } ct_keystate;
76
  
77
  tvhcsa_t cs_csa;
78

  
79
  /* buffer for keystruct */
80
  void    *ct_keys;
81

  
82
  /* CSA */
83
  int      ct_cluster_size;
84
  uint8_t *ct_tsbcluster;
85
  int      ct_fill;
86

  
87
  uint8_t ccw_evenkey[8];
88
  uint8_t ccw_oddkey[8];
89

  
90
} ccw_service_t;
91

  
92
/**
93
 *
94
 */
95
typedef struct ccw {
96
  pthread_cond_t ccw_cond;
97

  
98
  TAILQ_ENTRY(ccw) ccw_link; /* Linkage protected via global_lock */
99

  
100
  struct ccw_service_list ccw_services;
101

  
102
  uint16_t ccw_caid;  // CAID
103
  uint16_t ccw_tid;   // Transponder ID
104
  uint16_t ccw_sid;   // Channel ID
105
  uint8_t ccw_confedkey[8];  // Key
106
  char *ccw_comment;
107
  char *ccw_id;
108

  
109
  int   ccw_enabled;
110
  int   ccw_running;
111
  int   ccw_reconfigure;
112

  
113
} ccw_t;
114

  
115
/**
116
 * global_lock is held
117
 * s_stream_mutex is held
118
 */
119
static void 
120
ccw_service_destroy(th_descrambler_t *td)
121
{
122
  tvhlog(LOG_INFO, "ccw", "Removing CCW key from service");
123

  
124
  ccw_service_t *ct = (ccw_service_t *)td;
125

  
126
  LIST_REMOVE(td, td_service_link);
127
  LIST_REMOVE(ct, ct_link);
128

  
129
  free_key_struct(ct->ct_keys);
130
  free(ct->ct_tsbcluster);
131
  
132
  tvhcsa_destroy(&ct->cs_csa);
133
  free(ct);
134
}
135

  
136
/**
137
 *
138
 */
139
static void
140
ccw_table_input(struct th_descrambler *td, service_t *s,
141
    struct elementary_stream *st, const uint8_t *data, int len)
142
{
143
// CCW работает без ECM сюда мы попасть не должны никогда
144
  mpegts_service_t *t = (mpegts_service_t*)s;
145

  
146
  tvhlog(LOG_INFO, "ccw","ECM (PID %d) for service \"%s\"", t->s_dvb_prefcapid, t->s_dvb_svcname);
147
}
148

  
149
/**
150
 *
151
 */
152
static int
153
ccw_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
154
     const uint8_t *tsb)
155
{
156
  ccw_service_t *ct = (ccw_service_t *)td;
157
  int r, i;
158
  unsigned char *vec[3];
159
  uint8_t *t0;
160

  
161
  if(ct->ct_keystate == CT_FORBIDDEN)
162
    return 1;
163

  
164
  if(ct->ct_keystate != CT_RESOLVED)
165
    return -1;
166

  
167
  memcpy(ct->ct_tsbcluster + ct->ct_fill * 188, tsb, 188);
168
  ct->ct_fill++;
169

  
170
  if(ct->ct_fill != ct->ct_cluster_size)
171
    return 0;
172

  
173
  ct->ct_fill = 0;
174

  
175
  vec[0] = ct->ct_tsbcluster;
176
  vec[1] = ct->ct_tsbcluster + ct->ct_cluster_size * 188;
177
  vec[2] = NULL;
178

  
179
  while(1) {
180
    t0 = vec[0];
181
    r = decrypt_packets(ct->ct_keys, vec);
182
    if(r == 0)
183
      break;
184
    for(i = 0; i < r; i++) {
185
      ts_recv_packet2((mpegts_service_t*)t, t0);
186
      t0 += 188;
187
    }
188
  }
189
  return 0;
190
}
191

  
192
/**
193
 *
194
 */
195
static inline elementary_stream_t *
196
ccw_find_stream_by_caid(service_t *t, int caid)
197
{
198
  elementary_stream_t *st;
199
  caid_t *c;
200

  
201
  TAILQ_FOREACH(st, &t->s_components, es_link) {
202
    LIST_FOREACH(c, &st->es_caids, link) {
203
      if(c->caid == caid)
204
    return st;
205
    }
206
  }
207
  return NULL;
208
}
209

  
210
/**
211
 * Check if our CAID's matches, and if so, link
212
 *
213
 * global_lock is held
214
 */
215
void
216
ccw_service_start(service_t *t)
217
{
218
  ccw_t *ccw;
219
  ccw_service_t *ct;
220
  th_descrambler_t *td;
221
  mpegts_service_t *ms = (mpegts_service_t*)t;
222
  linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)ms->s_dvb_active_input;
223
  int adapter_num = lfe->lfe_adapter->la_dvb_number;
224

  
225
  lock_assert(&global_lock);
226
  
227
  extern const idclass_t mpegts_service_class;
228
  if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
229
    return;
230

  
231
  TAILQ_FOREACH(ccw, &ccws, ccw_link) {
232
    if(ccw->ccw_caid == 0)
233
      continue;
234

  
235
    if(ccw_find_stream_by_caid(t, ccw->ccw_caid) == NULL)
236
      continue;
237

  
238
    if(ms->s_dvb_service_id != ccw->ccw_sid)
239
      continue;
240

  
241
    if(ms->s_dvb_mux->mm_tsid != ccw->ccw_tid)
242
      continue;
243

  
244
    tvhlog(LOG_INFO, "ccw","Starting ccw key for service \"%s\" on tuner %d", ms->s_dvb_svcname,adapter_num);
245

  
246
    /* create new ccw service */
247
    ct                  = calloc(1, sizeof(ccw_service_t));
248
    ct->ct_cluster_size = get_suggested_cluster_size();
249
    ct->ct_tsbcluster   = malloc(ct->ct_cluster_size * 188);
250

  
251
    ct->ct_keys       = get_key_struct();
252
    ct->ct_ccw      = ccw;
253
    ct->ct_service  = ms;
254

  
255
    memcpy(ct->ccw_evenkey,ccw->ccw_confedkey,8);
256
    memcpy(ct->ccw_oddkey,ccw->ccw_confedkey,8);
257

  
258
    set_even_control_word(ct->ct_keys, ct->ccw_evenkey);
259
    set_odd_control_word(ct->ct_keys, ct->ccw_oddkey);
260
    if(ct->ct_keystate != CT_RESOLVED)
261
        tvhlog(LOG_INFO, "ccw", "Obtained key for service \"%s\"",ms->s_dvb_svcname);
262

  
263
    tvhlog(LOG_DEBUG, "ccw",
264
       "Key for service \"%s\" "
265
       "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x"
266
       " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x",
267
       ms->s_dvb_svcname,
268
       ct->ccw_evenkey[0], ct->ccw_evenkey[1], ct->ccw_evenkey[2], ct->ccw_evenkey[3],
269
           ct->ccw_evenkey[4], ct->ccw_evenkey[5], ct->ccw_evenkey[6], ct->ccw_evenkey[7],
270
           ct->ccw_oddkey[0], ct->ccw_oddkey[1], ct->ccw_oddkey[2], ct->ccw_oddkey[3],
271
           ct->ccw_oddkey[4], ct->ccw_oddkey[5], ct->ccw_oddkey[6],  ct->ccw_oddkey[7]);
272
    ct->ct_keystate = CT_RESOLVED;
273

  
274
    td = &ct->ct_head;
275
    td->td_stop       = ccw_service_destroy;
276
    td->td_table      = ccw_table_input;
277
    td->td_descramble = ccw_descramble;
278
    LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
279

  
280
    LIST_INSERT_HEAD(&ccw->ccw_services, ct, ct_link);
281
  }
282
}
283

  
284
/**
285
 *
286
 */
287
static void
288
ccw_destroy(ccw_t *ccw)
289
{
290
  lock_assert(&global_lock);
291
  TAILQ_REMOVE(&ccws, ccw, ccw_link);
292
  ccw->ccw_running = 0;
293
  pthread_cond_signal(&ccw->ccw_cond);
294
}
295

  
296
/**
297
 *
298
 */
299
static ccw_t *
300
ccw_entry_find(const char *id, int create)
301
{
302
//  pthread_attr_t attr;
303
//  pthread_t ptid;
304
  char buf[20];
305
  ccw_t *ccw;
306
  static int tally;
307

  
308
  if(id != NULL) {
309
    TAILQ_FOREACH(ccw, &ccws, ccw_link)
310
      if(!strcmp(ccw->ccw_id, id))
311
  return ccw;
312
  }
313
  if(create == 0)
314
    return NULL;
315

  
316
  if(id == NULL) {
317
    tally++;
318
    snprintf(buf, sizeof(buf), "%d", tally);
319
    id = buf;
320
  } else {
321
    tally = MAX(atoi(id), tally);
322
  }
323

  
324
  ccw = calloc(1, sizeof(ccw_t));
325
  pthread_cond_init(&ccw->ccw_cond, NULL);
326
  ccw->ccw_id      = strdup(id);
327
  ccw->ccw_running = 1;
328

  
329
  TAILQ_INSERT_TAIL(&ccws, ccw, ccw_link);
330

  
331
//  pthread_attr_init(&attr);
332
//  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
333
//  pthread_create(&ptid, &attr, ccw_thread, ccw);
334
//  pthread_attr_destroy(&attr);
335

  
336
  return ccw;
337
}
338

  
339
/**
340
 *
341
 */
342
static htsmsg_t *
343
ccw_record_build(ccw_t *ccw)
344
{
345
  htsmsg_t *e = htsmsg_create_map();
346
  char buf[100];
347

  
348
  htsmsg_add_str(e, "id", ccw->ccw_id);
349
  htsmsg_add_u32(e, "enabled",  !!ccw->ccw_enabled);
350

  
351
  htsmsg_add_u32(e, "caid", ccw->ccw_caid);
352
  htsmsg_add_u32(e, "tid", ccw->ccw_tid);
353
  htsmsg_add_u32(e, "sid", ccw->ccw_sid);
354

  
355
  snprintf(buf, sizeof(buf),
356
       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
357
       ccw->ccw_confedkey[0],
358
       ccw->ccw_confedkey[1],
359
       ccw->ccw_confedkey[2],
360
       ccw->ccw_confedkey[3],
361
       ccw->ccw_confedkey[4],
362
       ccw->ccw_confedkey[5],
363
       ccw->ccw_confedkey[6],
364
       ccw->ccw_confedkey[7]);
365

  
366
  htsmsg_add_str(e, "key", buf);
367
  htsmsg_add_str(e, "comment", ccw->ccw_comment ?: "");
368

  
369
  return e;
370
}
371

  
372
/**
373
 *
374
 */
375
static int
376
nibble(char c)
377
{
378
  switch(c) {
379
  case '0' ... '9':
380
    return c - '0';
381
  case 'a' ... 'f':
382
    return c - 'a' + 10;
383
  case 'A' ... 'F':
384
    return c - 'A' + 10;
385
  default:
386
    return 0;
387
  }
388
}
389

  
390
/**
391
 *
392
 */
393
static htsmsg_t *
394
ccw_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate)
395
{
396
  ccw_t *ccw;
397
  const char *s;
398
  uint32_t u32;
399
  uint8_t key[8];
400
  int u, l, i;
401

  
402
  if((ccw = ccw_entry_find(id, maycreate)) == NULL)
403
    return NULL;
404

  
405
  lock_assert(&global_lock);
406

  
407
  if(!htsmsg_get_u32(values, "caid", &u32))
408
    ccw->ccw_caid = u32;
409
  if(!htsmsg_get_u32(values, "tid", &u32))
410
    ccw->ccw_tid = u32;
411
  if(!htsmsg_get_u32(values, "sid", &u32))
412
    ccw->ccw_sid = u32;
413

  
414
  if((s = htsmsg_get_str(values, "key")) != NULL) {
415
    for(i = 0; i < 8; i++) {
416
      while(*s != 0 && !isxdigit(*s)) s++;
417
      if(*s == 0)
418
    break;
419
      u = nibble(*s++);
420
      while(*s != 0 && !isxdigit(*s)) s++;
421
      if(*s == 0)
422
    break;
423
      l = nibble(*s++);
424
      key[i] = (u << 4) | l;
425
    }
426
    memcpy(ccw->ccw_confedkey, key, 8);
427
  }
428

  
429
  if((s = htsmsg_get_str(values, "comment")) != NULL) {
430
    free(ccw->ccw_comment);
431
    ccw->ccw_comment = strdup(s);
432
  }
433

  
434
  if(!htsmsg_get_u32(values, "enabled", &u32)) 
435
    ccw->ccw_enabled = u32;
436

  
437
  ccw->ccw_reconfigure = 1;
438

  
439
  pthread_cond_signal(&ccw->ccw_cond);
440

  
441
  pthread_cond_broadcast(&ccw_config_changed);
442

  
443
  return ccw_record_build(ccw);
444
}
445

  
446
/**
447
 *
448
 */
449
static int
450
ccw_entry_delete(void *opaque, const char *id)
451
{
452
  ccw_t *ccw;
453

  
454
  pthread_cond_broadcast(&ccw_config_changed);
455

  
456
  if((ccw = ccw_entry_find(id, 0)) == NULL)
457
    return -1;
458
  ccw_destroy(ccw);
459
  return 0;
460
}
461

  
462
/**
463
 *
464
 */
465
static htsmsg_t *
466
ccw_entry_get_all(void *opaque)
467
{
468
  htsmsg_t *r = htsmsg_create_list();
469
  ccw_t *ccw;
470

  
471
  TAILQ_FOREACH(ccw, &ccws, ccw_link)
472
    htsmsg_add_msg(r, NULL, ccw_record_build(ccw));
473

  
474
  return r;
475
}
476

  
477
/**
478
 *
479
 */
480
static htsmsg_t *
481
ccw_entry_get(void *opaque, const char *id)
482
{
483
  ccw_t *ccw;
484

  
485

  
486
  if((ccw = ccw_entry_find(id, 0)) == NULL)
487
    return NULL;
488
  return ccw_record_build(ccw);
489
}
490

  
491
/**
492
 *
493
 */
494
static htsmsg_t *
495
ccw_entry_create(void *opaque)
496
{
497
  pthread_cond_broadcast(&ccw_config_changed);
498
  return ccw_record_build(ccw_entry_find(NULL, 1));
499
}
500

  
501
/**
502
 *
503
 */
504
static const dtable_class_t ccw_dtc = {
505
  .dtc_record_get     = ccw_entry_get,
506
  .dtc_record_get_all = ccw_entry_get_all,
507
  .dtc_record_create  = ccw_entry_create,
508
  .dtc_record_update  = ccw_entry_update,
509
  .dtc_record_delete  = ccw_entry_delete,
510
  .dtc_read_access = ACCESS_ADMIN,
511
  .dtc_write_access = ACCESS_ADMIN,
512
  .dtc_mutex = &global_lock,
513
};
514

  
515
/**
516
 *
517
 */
518
void
519
ccw_init(void)
520
{
521
  dtable_t *dt;
522

  
523
  TAILQ_INIT(&ccws);
524

  
525
  pthread_cond_init(&ccw_config_changed, NULL);
526

  
527
  dt = dtable_create(&ccw_dtc, "ccw", NULL);
528
  dtable_load(dt);
529

  
530
}
src/descrambler/ccw.h 1970-01-01 03:00:00.000000000 +0300 → src/descrambler/ccw.h 2014-06-05 21:33:04.603745866 +0300
1
/*
2
 *  tvheadend, CCW
3
 *  Copyright (C) 2012
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

  
19
#ifndef CCW_H_
20
#define CCW_H_
21

  
22
void ccw_init(void);
23

  
24
void ccw_service_start(struct service *t);
25

  
26
#endif /* CCW_H_ */
src/descrambler/descrambler.c 2014-05-25 20:12:36.000000000 +0300 → src/descrambler/descrambler.c 2014-06-05 21:33:04.607745865 +0300
19 19
#include "descrambler.h"
20 20
#include "cwc.h"
21 21
#include "capmt.h"
22
#include "ccw.h"
22 23
#include "ffdecsa/FFdecsa.h"
23 24
#include "service.h"
24 25

  
......
109 110
#if ENABLE_CWC
110 111
  cwc_init();
111 112
  capmt_init();
113
  ccw_init();
112 114
#if !ENABLE_DVBCSA
113 115
  ffdecsa_init();
114 116
#endif
......
130 132
#if ENABLE_CWC
131 133
  cwc_service_start(t);
132 134
  capmt_service_start(t);
135
  ccw_service_start(t);
133 136
#endif
134 137
}
135 138

  
src/input/mpegts/tsdemux.c 2014-05-25 20:12:36.000000000 +0300 → src/input/mpegts/tsdemux.c 2014-06-05 21:33:04.607745865 +0300
218 218

  
219 219
  pid = (tsb[1] & 0x1f) << 8 | tsb[2];
220 220

  
221
  if(pid==0x1FFF){  // ignore NULL stream
222
    pthread_mutex_unlock(&t->s_stream_mutex);
223
    return 0;
224
  }
225

  
221 226
  st = service_stream_find((service_t*)t, pid);
222 227

  
223 228
  /* Extract PCR */
src/webui/extjs.c 2014-06-01 03:47:54.000000000 +0300 → src/webui/extjs.c 2014-06-05 21:33:04.607745865 +0300
147 147
  extjs_load(hq, "static/app/acleditor.js");
148 148
  extjs_load(hq, "static/app/cwceditor.js");
149 149
  extjs_load(hq, "static/app/capmteditor.js");
150
  extjs_load(hq, "static/app/ccweditor.js");
150 151
  extjs_load(hq, "static/app/tvadapters.js");
151 152
  extjs_load(hq, "static/app/idnode.js");
152 153
  extjs_load(hq, "static/app/esfilter.js");
src/webui/static/app/ccweditor.js 1970-01-01 03:00:00.000000000 +0300 → src/webui/static/app/ccweditor.js 2014-06-05 21:33:04.607745865 +0300
1
tvheadend.ccweditor = function() {
2
    var fm = Ext.form;
3

  
4
    var enabledColumn = new Ext.grid.CheckColumn({
5
	header : "Enabled",
6
	dataIndex : 'enabled',
7
	width : 60
8
    });
9

  
10
    function setMetaAttr(meta, record) {
11
	var enabled = record.get('enabled');
12
	if (enabled == 1) {
13
	    meta.attr = 'style="color:green;"';
14
	}
15
	else {
16
	    meta.attr = 'style="color:red;"';
17
	}
18
    }
19

  
20
    var cm = new Ext.grid.ColumnModel({
21
            defaultSortable: true,
22
            columns: [ enabledColumn, {
23
	header: "CAID",
24
	dataIndex: 'caid',
25
	renderer: function(value, metadata, record, row, col, store) {
26
	    setMetaAttr(metadata, record);
27
	    return value;
28
	},
29
	editor: new fm.TextField({allowBlank: false})
30
    }, {
31
	header: "Mux ID",
32
	dataIndex: 'tid',
33
	renderer: function(value, metadata, record, row, col, store) {
34
	    setMetaAttr(metadata, record);
35
	    return value;
36
	},
37
	editor: new fm.TextField({allowBlank: false})
38
    }, {
39
	header: "SID",
40
	dataIndex: 'sid',
41
	renderer: function(value, metadata, record, row, col, store) {
42
	    setMetaAttr(metadata, record);
43
	    return value;
44
	},
45
	editor: new fm.TextField({allowBlank: false})
46
    }, {
47
	header: "Key",
48
	dataIndex: 'key',
49
	width: 200,
50
	renderer: function(value, metadata, record, row, col, store) {
51
	    setMetaAttr(metadata, record);
52
	    return value;
53
	},
54
	editor: new fm.TextField({allowBlank: false})
55
    }, {
56
	header : "Comment",
57
	dataIndex : 'comment',
58
	width : 400,
59
	renderer : function(value, metadata, record, row, col, store) {
60
	    setMetaAttr(metadata, record);
61
	    return value;
62
	},
63
	editor : new fm.TextField()
64
    } ]});
65

  
66
    var rec = Ext.data.Record.create([ 'enabled','caid','tid','sid','key','comment' ]);
67

  
68
    store = new Ext.data.JsonStore({
69
	root: 'entries',
70
	fields: rec,
71
	url: "tablemgr",
72
	autoLoad: true,
73
	id: 'id',
74
	baseParams: {table: 'ccw', op: "get"}
75
    });
76

  
77
    tvheadend.comet.on('ccwStatus', function(server) {
78
	var rec = store.getById(server.id);
79
	if(rec){
80
	    rec.set('connected', server.connected);
81
	}
82
    });
83

  
84
    return new tvheadend.tableEditor('CCW Keys', 'ccw', cm, rec,
85
		     [enabledColumn], store,
86
		     'config_ccw.html', 'key');
87
}
src/webui/static/app/tvheadend.js 2014-06-01 03:47:54.000000000 +0300 → src/webui/static/app/tvheadend.js 2014-06-05 21:38:41.151731348 +0300
286 286
            tabs2 = [new tvheadend.cwceditor];
287 287
            if (tvheadend.capabilities.indexOf('linuxdvb') !== -1)
288 288
              tabs2.push(new tvheadend.capmteditor);
289
            tabs2.push(new tvheadend.ccweditor);
289 290
            tvheadend.conf_csa = new Ext.TabPanel({
290 291
                activeTab: 0,
291 292
                autoScroll: true,
(4-4/6)