]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add HTML/web interface
authorAndreas Öman <andreas@lonelycoder.com>
Tue, 20 Nov 2007 08:04:28 +0000 (08:04 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Tue, 20 Nov 2007 08:04:28 +0000 (08:04 +0000)
Makefile
htmlui.c [new file with mode: 0644]
htmlui.h [new file with mode: 0644]

index 25d3e780dd8c7af4698753c2c1f51d1bb2501c1c..096628c290251164199b6dbb813cf0ae7968e165 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 SRCS = main.c dispatch.c channels.c transports.c teletext.c psi.c \
        subscriptions.c tsmux.c tsdemux.c pes.c buffer.c tcp.c
 
-SRCS += http.c
+SRCS += http.c htmlui.c
 
 SRCS += pvr.c
 
diff --git a/htmlui.c b/htmlui.c
new file mode 100644 (file)
index 0000000..eb65abb
--- /dev/null
+++ b/htmlui.c
@@ -0,0 +1,753 @@
+/*
+ *  tvheadend, HTML user interface
+ *  Copyright (C) 2007 Andreas Öman
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include "tvhead.h"
+#include "htmlui.h"
+#include "channels.h"
+#include "epg.h"
+#include "pvr.h"
+#include "strtab.h"
+
+static struct strtab recstatustxt[] = {
+  { "Scheduled",          HTSTV_PVR_STATUS_SCHEDULED      },
+  { "Recording",          HTSTV_PVR_STATUS_RECORDING      },
+  { "Done",               HTSTV_PVR_STATUS_DONE           },
+
+  { "Recording aborted",  HTSTV_PVR_STATUS_ABORTED        },
+
+  { "No transponder",     HTSTV_PVR_STATUS_NO_TRANSPONDER },
+  { "File error",         HTSTV_PVR_STATUS_FILE_ERROR     },
+  { "Disk full",          HTSTV_PVR_STATUS_DISK_FULL      },
+  { "Buffer error",       HTSTV_PVR_STATUS_BUFFER_ERROR   },
+};
+
+static struct strtab recstatuscolor[] = {
+  { "#3333aa",      HTSTV_PVR_STATUS_SCHEDULED      },
+  { "#aa3333",      HTSTV_PVR_STATUS_RECORDING      },
+  { "#33aa33",      HTSTV_PVR_STATUS_DONE           },
+  { "#aa3333",      HTSTV_PVR_STATUS_ABORTED        },
+  { "#aa3333",      HTSTV_PVR_STATUS_NO_TRANSPONDER },
+  { "#aa3333",      HTSTV_PVR_STATUS_FILE_ERROR     },
+  { "#aa3333",      HTSTV_PVR_STATUS_DISK_FULL      },
+  { "#aa3333",      HTSTV_PVR_STATUS_BUFFER_ERROR   },
+};
+
+
+static int
+pvrstatus_to_html(tv_pvr_status_t pvrstatus, const char **text,
+                 const char **col)
+{
+  *text = val2str(pvrstatus, recstatustxt);
+  if(*text == NULL)
+    return -1;
+
+  *col = val2str(pvrstatus, recstatuscolor);
+  if(*col == NULL)
+    return -1;
+
+  return 0;
+}
+
+
+
+
+
+static void
+html_header(tcp_queue_t *tq, const char *title, int javascript)
+{
+  tcp_qprintf(tq, 
+             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
+             "http://www.w3.org/TR/html4/strict.dtd\">\r\n"
+             "<html><head>\r\n"
+             "<title>%s</title>\r\n"
+             "<meta http-equiv=\"Content-Type\" "
+             "content=\"text/html; charset=utf-8\">\r\n", title);
+
+  tcp_qprintf(tq, 
+             "<style type=\"text/css\">\r\n"
+             "<!--\r\n"
+             "img { border: 0px; }\r\n"
+             "a:link { text-decoration: none}\r\n"
+             "a:visited { text-decoration: none}\r\n"
+             "a:active { text-decoration: none}\r\n"
+             "a:link { text-decoration: none; color: #000000}\r\n"
+             "a:visited { text-decoration: none; color: #000000}\r\n"
+             "a:active { text-decoration: none; color: #000000}\r\n"
+             "a:hover { text-decoration: underline; color: CC3333}\r\n"
+             ""
+             "body {margin: 4px 4px; "
+             "font: 75% Verdana, Arial, Helvetica, sans-serif; "
+             "width: 600px; margin-right: auto; margin-left: auto;}\r\n"
+             ""
+             "#box {background: #cccc99;}\r\n"
+             ".roundtop {background: #ffffff;}\r\n"
+             ".roundbottom {background: #ffffff;}\r\n"
+             ".r1{margin: 0 5px; height: 1px; overflow: hidden; "
+             "background: #000000; border-left: 1px solid #000000; "
+             "border-right: 1px solid #000000;}\r\n"
+             ""
+             ".r2{margin: 0 3px; height: 1px; overflow: hidden; "
+             "background: #cccc99; border-left: 1px solid #000000; "
+             "border-right: 1px solid #000000; border-width: 0 2px;}\r\n"
+             ""
+             ".r3{margin: 0 2px; height: 1px; overflow: hidden; "
+             "background: #cccc99; border-left: 1px solid #000000; "
+             "border-right: 1px solid #000000;}\r\n"
+             ""
+             ".r4{margin: 0 1px; height: 2px; overflow: hidden; "
+             "background: #cccc99; border-left: 1px solid #000000; "
+             "border-right: 1px solid #000000;}\r\n"
+             ""
+             ".content3 {padding-left: 3px; height: 60px; "
+              "border-left: 1px solid #000000; "
+             "border-right: 1px solid #000000;}\r\n"
+             ""
+             ".content {padding-left: 3px; border-left: 1px solid #000000; "
+             "border-right: 1px solid #000000;}\r\n"
+             ""
+             ".logo {padding: 2px; width: 60px; height: 56px; "
+             "float: left};\r\n"
+             ""
+             ".over {float: left}\r\n"
+             ".toptxt {float: left; width: 165px; text-align: center}\r\n"
+             ""
+             "#meny {margin: 0; padding: 0}\r\n"
+             "#meny li{display: inline; list-style-type: none;}\r\n"
+             "#meny a{padding: 1.15em 0.8em; text-decoration: none;}\r\n"
+             "-->\r\n"
+             "</style>");
+
+  if(javascript) {
+    tcp_qprintf(tq, 
+               "<script type=\"text/javascript\" "
+               "src=\"http://www.olebyn.nu/hts/overlib.js\"></script>\r\n");
+
+
+
+    tcp_qprintf(tq,
+               "<script language=\"javascript\">\r\n"
+               "<!-- Begin\r\n"
+               "function epop() {\r\n"
+               "  props=window.open(epop.arguments[0],"
+               "'poppage', 'toolbars=0, scrollbars=0, location=0, "
+               "statusbars=0, menubars=0, resizable=0, "
+               "width=600, height=300 left = 100, top = 100');\r\n}\r\n"
+               "// End -->\r\n"
+               "</script>\r\n");
+  }
+  /* BODY start */
+  
+  tcp_qprintf(tq, "</head><body>\r\n");
+
+  if(javascript)
+    tcp_qprintf(tq, 
+               "<div id=\"overDiv\" style=\"position:absolute; "
+               "visibility:hidden; z-index:1000;\"></div>\r\n");
+
+}
+
+static void
+html_footer(tcp_queue_t *tq)
+{
+  tcp_qprintf(tq, 
+             "</body></html>\r\n\r\n");
+}
+
+
+static void
+box_top(tcp_queue_t *tq, const char *style)
+{
+  tcp_qprintf(tq, "<div id=\"%s\">"
+             "<div class=\"roundtop\">"
+             "<div class=\"r1\"></div>"
+             "<div class=\"r2\"></div>"
+             "<div class=\"r3\"></div>"
+             "<div class=\"r4\"></div>"
+             "</div>", style);
+}
+
+static void
+box_bottom(tcp_queue_t *tq)
+{
+  tcp_qprintf(tq, 
+             "<div class=\"roundbottom\">"
+             "<div class=\"r4\"></div>"
+             "<div class=\"r3\"></div>"
+             "<div class=\"r2\"></div>"
+             "<div class=\"r1\"></div>"
+             "</div></div>");
+}
+
+
+static void
+top_menu(tcp_queue_t *tq)
+{
+  box_top(tq, "box");
+
+  tcp_qprintf(tq, 
+             "<div class=\"content\">"
+             "<ul id=\"meny\">"
+             "<li><a href=\"/\">TV Guide</a></li>"
+             "<li><a href=\"/pvrlog\">Recordings</a></li>"
+             "<li><a href=\"/status\">System Status</a></li>"
+             "</ul></div>");
+
+  box_bottom(tq);
+  tcp_qprintf(tq, "<br>\r\n");
+}
+
+
+
+
+void
+esacpe_char(char *dst, int dstlen, const char *src, char c, 
+           const char *repl)
+{
+  char v;
+  const char *r;
+
+  while((v = *src++) && dstlen > 1) {
+    if(v != c) {
+      *dst++ = v;
+      dstlen--;
+    } else {
+      r = repl;
+      while(dstlen > 1 && *r) {
+       *dst++ = *r++;
+       dstlen--;
+      }
+    }
+  }
+  *dst = 0;
+}
+
+
+
+static int
+is_client_simple(http_connection_t *hc)
+{
+  char *c;
+
+  if((c = http_arg_get(&hc->hc_args, "UA-OS")) != NULL) {
+    if(strstr(c, "Windows CE") || strstr(c, "Pocket PC"))
+      return 1;
+  }
+
+  if((c = http_arg_get(&hc->hc_args, "x-wap-profile")) != NULL) {
+    return 1;
+  }
+  return 0;
+}
+
+
+/*
+ * Output_event
+ */
+
+static void
+output_event(http_connection_t *hc, tcp_queue_t *tq, th_channel_t *ch,
+            event_t *e, int simple)
+{
+  char title[100];
+  char bufa[4000];
+  char overlibstuff[4000];
+  struct tm a, b;
+  time_t stop;
+  event_t *cur;
+  tv_pvr_status_t pvrstatus;
+  const char *pvr_txt, *pvr_color;
+
+  localtime_r(&e->e_start, &a);
+  stop = e->e_start + e->e_duration;
+  localtime_r(&stop, &b);
+
+  pvrstatus = pvr_prog_status(e);
+
+  cur = epg_event_get_current(ch);
+
+  if(!simple && e->e_desc != NULL) {
+    esacpe_char(bufa, sizeof(bufa), e->e_desc, '\'', "");
+       
+    snprintf(overlibstuff, sizeof(overlibstuff), 
+            "onmouseover=\"return overlib('%s')\" "
+            "onmouseout=\"return nd();\"",
+            bufa);
+  } else {
+    overlibstuff[0] = 0;
+  }
+
+  if(1 || simple) {
+    snprintf(bufa, sizeof(bufa),
+            "/event/%d", e->e_tag);
+  } else {
+    snprintf(bufa, sizeof(bufa),
+            "javascript:epop('/event/%d')", e->e_tag);
+  }
+
+  esacpe_char(title, sizeof(title), e->e_title, '"', "'");
+
+  tcp_qprintf(tq, 
+             "<div>"
+             "<a href=\"%s\" %s>"
+             "<span style=\"width: %dpx;float: left%s\">"
+             "%02d:%02d - %02d:%02d</span>"
+             "<span style=\"width: %dpx; float: left%s\">%s</span></a>",
+             bufa,
+             overlibstuff,
+             simple ? 80 : 100,
+             e == cur ? ";font-weight:bold" : "",
+             a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
+             simple ? 100 : 250,
+             e == cur ? ";font-weight:bold" : "",
+             title
+             );
+
+  if(!pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color)) {
+    tcp_qprintf(tq, 
+               "<span style=\"font-style:italic;color:%s;font-weight:bold\">"
+               "%s</span></div><br>",
+               pvr_color, pvr_txt);
+  } else {
+    tcp_qprintf(tq, "</div><br>");
+  } 
+}
+
+/*
+ * Root page
+ */
+static int
+page_root(http_connection_t *hc, const char *remain, void *opaque)
+{
+  tcp_queue_t tq;
+  th_channel_t *ch;
+  event_t *e;
+  int i;
+  int simple = is_client_simple(hc);
+  
+
+  tcp_init_queue(&tq, -1);
+
+  html_header(&tq, "HTS/tvheadend", !simple);
+
+  top_menu(&tq);
+
+  epg_lock();
+  TAILQ_FOREACH(ch, &channels, ch_global_link) {
+    box_top(&tq, "box");
+    tcp_qprintf(&tq, "<div class=\"content3\">");
+
+    if(!simple) {
+      tcp_qprintf(&tq, "<div class=\"logo\">");
+      if(ch->ch_icon) {
+       tcp_qprintf(&tq, "<a href=\"channel/%d\">"
+                   "<img src=\"%s\" height=56px>"
+                   "</a>",
+                   ch->ch_tag,
+                   refstr_get(ch->ch_icon));
+      }
+      tcp_qprintf(&tq, "</div>");
+    }
+
+    tcp_qprintf(&tq, "<div class=\"over\">");
+    tcp_qprintf(&tq, "<strong><a href=\"channel/%d\">%s</a></strong><br>",
+               ch->ch_tag, ch->ch_name);
+
+    e = epg_event_find_current_or_upcoming(ch);
+
+    for(i = 0; i < 3 && e != NULL; i++) {
+      output_event(hc, &tq, ch, e, simple);
+      e = TAILQ_NEXT(e, e_link);
+    }
+
+    tcp_qprintf(&tq, "</div></div>");
+    box_bottom(&tq);
+    tcp_qprintf(&tq, "<br>\r\n");
+  }
+  epg_unlock();
+
+  html_footer(&tq);
+  http_output_queue(hc, &tq, "text/html; charset=UTF-8");
+  return 0;
+}
+
+
+
+/*
+ * Channel page
+ */
+
+
+const char *days[7] = {
+  "Sunday",
+  "Monday",
+  "Tuesday",
+  "Wednesday",
+  "Thursday",
+  "Friday",
+  "Saturday",
+  };
+
+
+static int
+page_channel(http_connection_t *hc, const char *remain, void *opaque)
+{
+  tcp_queue_t tq;
+  th_channel_t *ch = NULL;
+  event_t *e = NULL;
+  int i;
+  int simple = is_client_simple(hc);
+  int channeltag = -1;
+  int w, doff = 0, wday;
+  struct tm a;
+
+  i = sscanf(remain, "%d/%d", &channeltag, &doff);
+  ch = channel_by_tag(channeltag);
+  if(i != 2)
+    doff = 0;
+
+  if(ch == NULL) {
+    http_error(hc, 404);
+    return 0;
+  }
+
+  tcp_init_queue(&tq, -1);
+
+  html_header(&tq, "HTS/tvheadend", !simple);
+
+  top_menu(&tq);
+
+  epg_lock();
+
+  box_top(&tq, "box");
+
+  tcp_qprintf(&tq, "<div class=\"content\">");
+  tcp_qprintf(&tq, "<strong><a href=\"channel/%d\">%s</a></strong><br>",
+             ch->ch_tag, ch->ch_name);
+
+  e = epg_event_find_current_or_upcoming(ch);
+  if(e != NULL) {
+    localtime_r(&e->e_start, &a);
+    wday = a.tm_wday;
+
+
+    for(w = 0; w < 7; w++) {
+
+      tcp_qprintf(&tq, 
+                 "<a href=\"/channel/%d/%d\""
+                 "<u><i>%s</i></u></a><br>",
+                 ch->ch_tag, w,
+                 days[(wday + w) % 7]);
+
+      while(e != NULL) {
+       localtime_r(&e->e_start, &a);
+       if(a.tm_wday != wday + w)
+         break;
+
+       if(a.tm_wday == wday + doff)
+         output_event(hc, &tq, ch, e, simple);
+       e = TAILQ_NEXT(e, e_link);
+      }
+    }
+  }
+
+  tcp_qprintf(&tq, "</div>");
+  box_bottom(&tq);
+  tcp_qprintf(&tq, "<br>\r\n");
+  epg_unlock();
+
+  html_footer(&tq);
+  http_output_queue(hc, &tq, "text/html; charset=UTF-8");
+  return 0;
+}
+
+
+
+
+
+/**
+ * Event page
+ */
+static int
+page_event(http_connection_t *hc, const char *remain, void *opaque)
+{
+  tcp_queue_t tq;
+  int simple = is_client_simple(hc);
+  int eventid = atoi(remain);
+  char title[100];
+  event_t *e;
+  struct tm a, b;
+  time_t stop;
+  char desc[4000];
+  recop_t cmd = 0;
+  tv_pvr_status_t pvrstatus;
+  const char *pvr_txt, *pvr_color;
+  
+  epg_lock();
+  e = epg_event_find_by_tag(eventid);
+  if(e == NULL) {
+    epg_unlock();
+    return 404;
+  }
+
+  remain = strchr(remain, '?');
+  if(remain != NULL) {
+    remain++;
+    if(!strncmp(remain, "rec=", 4))
+      cmd = RECOP_ONCE;
+    if(!strncmp(remain, "cancel=", 7))
+      cmd = RECOP_CANCEL;
+    pvr_event_record_op(e->e_ch, e, cmd);
+
+  }
+
+  pvrstatus = pvr_prog_status(e);
+
+  localtime_r(&e->e_start, &a);
+  stop = e->e_start + e->e_duration;
+  localtime_r(&stop, &b);
+
+  tcp_init_queue(&tq, -1);
+
+  html_header(&tq, "HTS/tvheadend", 0);
+  top_menu(&tq);
+
+  tcp_qprintf(&tq, "<form method=\"get\" action=\"/event/%d\">", eventid);
+
+
+  box_top(&tq, "box");
+
+  tcp_qprintf(&tq, "<div class=\"content\">");
+
+
+  esacpe_char(title, sizeof(title), e->e_title, '"', "'");
+  esacpe_char(desc, sizeof(desc), e->e_desc, '\'', "");
+
+  tcp_qprintf(&tq, 
+             "<div style=\"width: %dpx;float: left;font-weight:bold\">"
+             "%02d:%02d - %02d:%02d</div>"
+             "<div style=\"width: %dpx; float: left;font-weight:bold\">"
+             "%s</div></a>",
+             simple ? 80 : 100,
+             a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
+             simple ? 100 : 250,
+             title);
+
+  if(!pvrstatus_to_html(pvrstatus, &pvr_txt, &pvr_color))
+    tcp_qprintf(&tq, 
+               "<div style=\"font-style:italic;color:%s;font-weight:bold\">"
+               "%s</div>",
+               pvr_color, pvr_txt);
+  else
+    tcp_qprintf(&tq, "<br>");
+    
+
+  tcp_qprintf(&tq, "<br>%s", desc);
+
+  tcp_qprintf(&tq,"<div style=\"text-align: center\">");
+
+  switch(pvrstatus) {
+  case HTSTV_PVR_STATUS_SCHEDULED:
+  case HTSTV_PVR_STATUS_RECORDING:
+    tcp_qprintf(&tq,
+               "<input type=\"submit\" name=\"cancel\" "
+               "value=\"Cancel recording\">");
+    break;
+
+
+  case HTSTV_PVR_STATUS_NONE:
+    tcp_qprintf(&tq,
+               "<input type=\"submit\" name=\"rec\" "
+               "value=\"Record\">");
+    break;
+
+  default:
+    tcp_qprintf(&tq,
+               "<input type=\"submit\" name=\"cancel\" "
+               "value=\"Clear error status\">");
+    break;
+
+  }
+
+  tcp_qprintf(&tq, "</div></div></div>");
+  box_bottom(&tq);
+  tcp_qprintf(&tq, "</form>\r\n");
+  html_footer(&tq);
+  http_output_queue(hc, &tq, "text/html; charset=UTF-8");
+
+  epg_unlock();
+
+  return 0;
+}
+
+
+
+static int
+pvrcmp(const void *A, const void *B)
+{
+  const pvr_rec_t *a = *(const pvr_rec_t **)A;
+  const pvr_rec_t *b = *(const pvr_rec_t **)B;
+
+  return a->pvrr_start - b->pvrr_start;
+}
+
+/**
+ * Event page
+ */
+static int
+page_pvrlog(http_connection_t *hc, const char *remain, void *opaque)
+{
+  tcp_queue_t tq;
+  int simple = is_client_simple(hc);
+  pvr_rec_t *pvrr;
+  event_t *e;
+  char escapebuf[4000], href[4000];
+  char title[100];
+  char channel[100];
+  struct tm a, b, day;
+  const char *pvr_txt, *pvr_color;
+  int c, i;
+  pvr_rec_t **pv;
+
+  tcp_init_queue(&tq, -1);
+  html_header(&tq, "HTS/tvheadend", 0);
+  top_menu(&tq);
+
+  box_top(&tq, "box");
+
+  epg_lock();
+
+  tcp_qprintf(&tq, "<div class=\"content\">");
+
+  c = 0;
+  LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link)
+    c++;
+
+  pv = alloca(c * sizeof(pvr_rec_t *));
+
+  i = 0;
+  LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link)
+    pv[i++] = pvrr;
+
+
+  qsort(pv, i, sizeof(pvr_rec_t *), pvrcmp);
+
+  memset(&day, -1, sizeof(struct tm));
+
+  for(i = 0; i < c; i++) {
+    pvrr = pv[i];
+
+    e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start);
+
+    if(e != NULL && !simple && e->e_desc != NULL) {
+      esacpe_char(escapebuf, sizeof(escapebuf), e->e_desc, '\'', "");
+    
+      snprintf(href, sizeof(href), 
+              "<a href=\"/event/%d\" onmouseover=\"return overlib('%s')\" "
+              "onmouseout=\"return nd();\">",
+              e->e_tag,
+              escapebuf);
+
+    } else {
+      href[0] = 0;
+    }
+
+    esacpe_char(channel, sizeof(channel), 
+               pvrr->pvrr_channel->ch_name, '"', "'");
+
+    esacpe_char(title, sizeof(title), 
+               pvrr->pvrr_title ?: "Unnamed recording", '"', "'");
+
+    localtime_r(&pvrr->pvrr_start, &a);
+    localtime_r(&pvrr->pvrr_stop, &b);
+
+    if(a.tm_wday  != day.tm_wday  ||
+       a.tm_mday  != day.tm_mday  ||
+       a.tm_mon   != day.tm_mon   ||
+       a.tm_year  != day.tm_year) {
+
+      memcpy(&day, &a, sizeof(struct tm));
+
+      tcp_qprintf(&tq, 
+                 "<br><b><i>%s, %d/%d</i></b><br>",
+                 days[day.tm_wday],
+                 day.tm_mday,
+                 day.tm_mon + 1);
+    }
+
+
+    tcp_qprintf(&tq, 
+               "%s"
+               "<div style=\"width: %dpx;float: left\">"
+               "%s</div>"
+               "<div style=\"width: %dpx;float: left\">"
+               "%02d:%02d - %02d:%02d</div>"
+               "<div style=\"width: %dpx; float: left\">%s</div>%s",
+               href,
+               simple ? 80 : 100,
+               channel,
+               simple ? 80 : 100,
+               a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
+               simple ? 100 : 250,
+               title,
+               href[0] ? "</a>" : "");
+
+    if(!pvrstatus_to_html(pvrr->pvrr_status, &pvr_txt, &pvr_color)) {
+      tcp_qprintf(&tq, 
+                 "<div style=\"font-style:italic;color:%s;font-weight:bold\">"
+                 "%s</div>",
+                 pvr_color, pvr_txt);
+    } else {
+      tcp_qprintf(&tq, "<br>");
+    } 
+  }
+
+  epg_unlock();
+  tcp_qprintf(&tq, "<br></div>\r\n");
+
+  box_bottom(&tq);
+  tcp_qprintf(&tq, "</form>\r\n");
+  html_footer(&tq);
+  http_output_queue(hc, &tq, "text/html; charset=UTF-8");
+
+  return 0;
+}
+
+/**
+ * HTML user interface setup code
+ */
+void
+htmlui_start(void)
+{
+  http_path_add("/", NULL, page_root);
+  http_path_add("/event", NULL, page_event);
+  http_path_add("/channel", NULL, page_channel);
+  http_path_add("/pvrlog", NULL, page_pvrlog);
+}
diff --git a/htmlui.h b/htmlui.h
new file mode 100644 (file)
index 0000000..dcf0f57
--- /dev/null
+++ b/htmlui.h
@@ -0,0 +1,26 @@
+/*
+ *  tvheadend, HTML user interface
+ *  Copyright (C) 2007 Andreas Öman
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <htmlui://www.gnu.org/licenses/>.
+ */
+
+#ifndef HTMLUI_H_
+#define HTMLUI_H_
+
+#include "http.h"
+
+void htmlui_start(void);
+
+#endif /* HTMLUI_H_ */