summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2000-02-19 13:36:48 +0100
committerKlaus Schmidinger <vdr@tvdr.de>2000-02-19 13:36:48 +0100
commit4a9d9c5876cde9f21ccd165a7630727e6aca576a (patch)
tree84548734048499e913f200e1359acec4fa441fb0
downloadvdr-4a9d9c5876cde9f21ccd165a7630727e6aca576a.tar.gz
vdr-4a9d9c5876cde9f21ccd165a7630727e6aca576a.tar.bz2
Initial revision0.0.1
-rw-r--r--Makefile37
-rw-r--r--README122
-rw-r--r--TODO13
-rw-r--r--channels.conf109
-rw-r--r--config.c333
-rw-r--r--config.h163
-rw-r--r--dvbapi.c166
-rw-r--r--dvbapi.h65
-rw-r--r--interface.c298
-rw-r--r--interface.h41
-rw-r--r--keys-pc.confbin0 -> 225 bytes
-rw-r--r--keys.conf19
-rw-r--r--menu.c745
-rw-r--r--menu.h21
-rw-r--r--osd.c196
-rw-r--r--osd.h68
-rw-r--r--osm.c119
-rw-r--r--remote.c257
-rw-r--r--remote.h43
-rw-r--r--timers.conf9
-rw-r--r--tools.c124
-rw-r--r--tools.h54
22 files changed, 3002 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..6696dff1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+#
+# Makefile for the On Screen Menu of the Video Disk Recorder
+#
+# See the main source file 'osm.c' for copyright information and
+# how to reach the author.
+#
+# $Id: Makefile 1.1 2000/02/19 13:36:48 kls Exp $
+
+OBJS = config.o dvbapi.o interface.o menu.o osd.o remote.o tools.o osm.o
+
+ifdef DEBUG_REMOTE
+DEFINES += -DDEBUG_REMOTE
+endif
+
+ifdef DEBUG_OSD
+DEFINES += -DDEBUG_OSD
+endif
+
+%.o: %.c
+ g++ -g -O2 -Wall -c $(DEFINES) $<
+
+all: osm
+
+config.o : config.c config.h dvbapi.h interface.h tools.h
+dvbapi.o : dvbapi.c config.h dvbapi.h interface.h tools.h
+interface.o: interface.c config.h dvbapi.h interface.h remote.h tools.h
+menu.o : menu.c config.h dvbapi.h interface.h menu.h osd.h tools.h
+osd.o : osd.c config.h interface.h osd.h tools.h
+osm.o : osm.c config.h dvbapi.h interface.h menu.h osd.h tools.h
+remote.o : remote.c remote.h tools.h
+tools.o : tools.c tools.h
+
+osm: $(OBJS)
+ g++ -g -O2 $(OBJS) -lncurses -o osm
+
+clean:
+ -rm $(OBJS) osm
diff --git a/README b/README
new file mode 100644
index 00000000..e4fa836e
--- /dev/null
+++ b/README
@@ -0,0 +1,122 @@
+On Screen Menu for the Video Disk Recorder
+------------------------------------------
+
+These files contain the source code of an on screen
+menu for a video disk recorder based on the DVB driver
+of the LinuxTV project (http://linuxtv.org).
+For details about the "Video Disk Recorder" project please
+refer to http://www.cadsoft.de/people/kls/vdr.
+
+The author can be contacted at kls@cadsoft.de.
+
+Yet another "set-top-box"?
+--------------------------
+
+The "set-top-boxes" available from commercial companies all have
+one major drawback: they are not "open". This project's goal is
+to build an "open" digital satellite receiver and timer controlled
+video disk recorder, based upon open standards and freely available
+driver software (of course, the hardware still has to be bought).
+
+The on screen menu system is simple, but shall provide all the
+possibilites necessary to perform timer controlled recording,
+file management and, maybe, even "on disk editing". The menus
+of commercial set-top-boxes usually are a lot more fancy than
+the ones in this system, but here we have the full source code
+and can modify the menus in whatever way desired.
+
+Compiling and running the program:
+----------------------------------
+
+Make sure the files from this package are located in a
+directory that is "parallel" to the DVB directory of the
+driver source for the Siemens DVB-S PCI card (refer to
+http://linuxtv.org/dvb/siemens_dvb.html for more information
+about that driver). For example, if the DVB driver was
+extracted into the directory /home/kls/vdr/DVB, then this
+package should be extracted into /home/kls/vdr/OSM.
+
+After extracting the package, change into the OSM directory
+and type 'make'. This should produce an executable file
+named 'osm', which can be run after the DVB driver has been
+installed.
+
+There are two macros you can use to customize the 'osm' program
+at compile time. Adding "DEBUG_REMOTE=1" to the 'make' call
+will use the PC's keyboard as input device instead of the "Remote
+Control Unit" (see http://www.cadsoft.de/people/kls/vdr/remote.htm).
+Adding "DEBUG_OSD=1" will use the PC screen (or current window)
+to display texts instead of the DVB card's on-screen display
+interface. These modes are useful when testing new menus if you
+only have a remote connection to the VDR (which, in my case, is
+located in the living room and has neither a monitor nor a keyboard).
+
+Configuration files:
+--------------------
+
+There are three configuration files that hold information about
+channels, remote control keys and timers. These files are currrently
+assumed to be located in the directory from which the 'osm' program
+was started (this will become configurable later). The configuration
+files can be edited with any text editor, or will be written by the
+'osm' program if any changes are made inside the on-screen menus.
+The meaning of the data entries may still vary in future releases,
+so for the moment please look at the source code (config.c) to see
+the meaning of the various fields.
+
+There is no way of adding or deleting channels or timers yet, this
+will be implemented later.
+
+Learning the remote control keys:
+---------------------------------
+
+The remote control configuration file 'keys.conf' that comes with
+this package contains the codes for the "d-box" remote control unit.
+If you want to use a different remote control unit, simply delete
+the file 'keys.conf' and restart the 'osm' program. The program will
+then start a key learning session in which it first attempts to determine
+the basic data tranfer mode and timing of your remote control unit,
+and then will ask you to press one key after the other so that it can
+learn the various key codes. You will at least need to provide an "Up"
+and a "Down" key, so that you can switch channels. The rest os the key
+definitions is optional, but the more keys you define, the more you
+will be able to navigate through the menus.
+
+If the program has been built with "DEBUG_REMOTE=1", it will use the
+key configuration file 'keys-pc.conf', so that you won't loose data
+when switching between normal and debug mode.
+
+Navigating through the On Screen Menus:
+---------------------------------------
+
+The "Main" menu can be called up with the "Menu" key of your remote
+control unit. The "Up" and "Down" keys are used to select a specific
+item. The "Left" and "Right" keys can be used to change options, and
+the numeric keys allow direct input of numeric data. The "Ok" key
+confirms any changes (or switches to a channel in the "Channels" menu).
+The "Back" key goes back one level in the menu structure, discarding
+any changes that might have been made in the current menu.
+
+In the "Channels" menu, the current channel can be edited by pressing
+the "Right" key.
+
+In the "Timers" menu, the current timer can be enabled or disabled with
+the "Right" or "Left" key, respectively (enabled timers are marked with ">").
+"Ok" here opens the "Edit timer" menu.
+
+Textual options, like channel names or recording file names, can be edited
+by pressing the "Right" button (which puts brackets around the current
+character as in "[R]TL"), selecting the desired character position with
+"Left" and "Right", and changing the character with the "Up" and "Down"
+keys. "Ok" then confirms the changes.
+
+At any point in the menu system, pressing the "Menu" key again will
+immediately leave the menu system.
+
+What do you think?
+------------------
+
+So, what do you think about this project? Does it make sense? Were you
+able to use it? Do you have suggestions on how to improve it?
+Please send email to kls@cadsoft.de if you'd like to comment on this.
+
diff --git a/TODO b/TODO
new file mode 100644
index 00000000..32793469
--- /dev/null
+++ b/TODO
@@ -0,0 +1,13 @@
+TODO list for the Video Disk Recorder project
+---------------------------------------------
+
+* Implement a way to add and delete channels and timers.
+* Implement recording to disk and playback from disk.
+* Implement disk file management (delete old/viewed files to make
+ room for new recordings if necessary).
+* Make it work with two DVB-S PCI cards to allow simultaneous
+ recording of one programme, while replaying another programme
+ (or maybe the same one, but time delayed).
+* Implement "on-disk editing" to allow "cutting out" of certain
+ scenes in order to archive them (or, reversely, cut out
+ commercial breaks).
diff --git a/channels.conf b/channels.conf
new file mode 100644
index 00000000..9daca0af
--- /dev/null
+++ b/channels.conf
@@ -0,0 +1,109 @@
+RTL:12188:h:1:27500:163:104
+Sat.1:12552:v:1:22000:163:104
+Pro 7:12480:v:1:27500:255:256
+RTL2:12188:h:1:27500:166:128
+ARD:11837:h:1:27500:101:102
+BR3:11837:h:1:27500:201:202
+Hessen 3:11837:h:1:27500:301:302
+N3:11837:h:1:27500:401:402
+SR3:11837:h:1:27500:501:502
+WDR:11837:h:1:27500:601:602
+BR alpha:11837:h:1:27500:701:702
+SWR BW:11837:h:1:27500:801:802
+Phoenix:11837:h:1:27500:901:902
+ZDF:11954:h:1:27500:110:120
+3sat:11954:h:1:27500:210:220
+Kinderkanal:11954:h:1:27500:310:320
+arte:11954:h:1:27500:360:370
+phoenix:11954:h:1:27500:410:420
+ORF Sat:11954:h:1:27500:506:507
+ZDF Infobox:11954:h:1:27500:610:620
+CNN:12168:v:1:27500:165:100
+Super RTL:12188:h:1:27500:165:120
+VOX:12188:h:1:27500:167:136
+DW TV:12363:v:1:27500:305:306
+Kabel 1:12480:v:1:27500:511:512
+TM3:12480:v:1:27500:767:768
+DSF:12480:v:1:27500:1023:1024
+HOT:12480:v:1:27500:1279:1280
+BloombergTV:12552:v:1:22000:162:99
+Sky News:12552:v:1:22000:305:306
+KinderNet:12574:h:1:22000:163:92
+Alice:12610:v:1:22000:162:96
+n-tv:12670:v:1:22000:162:96
+Grand Tour.:12670:v:1:22000:289:290
+TW1:12692:h:1:22000:166:167
+Eins Extra:12722:h:1:22000:101:102
+Eins Festival:12722:h:1:22000:201:202
+Eins MuXx:12722:h:1:22000:301:302
+MDR:12722:h:1:22000:401:402
+ORB:12722:h:1:22000:501:502
+B1:12722:h:1:22000:601:602
+ARD Online-Kanal:12722:h:1:22000:8191:701
+Premiere World Promo:11798:h:1:27500:255:256
+TV Niepokalanow:11876:h:1:27500:305:321
+test card:11876:h:1:27500:306:322
+Mosaico:11934:v:1:27500:165:100
+Andalucia TV:11934:v:1:27500:166:104
+TVC Internacional:11934:v:1:27500:167:108
+Nasza TV:11992:h:1:27500:165:98
+WishLine test:12012:v:1:27500:163:90
+Pro 7 Austria:12051:v:1:27500:161:84
+Kabel 1 Schweiz:12051:v:1:27500:162:163
+Kabel 1 Austria:12051:v:1:27500:166:167
+Pro 7 Schweiz:12051:v:1:27500:289:290
+Kiosque:12129:v:1:27500:160:80
+KTO:12129:v:1:27500:170:120
+TCM:12168:v:1:27500:160:80
+Cartoon Network France & Spain:12168:v:1:27500:161:84
+TVBS Europe:12168:v:1:27500:162:88
+TVBS Europe:12168:v:1:27500:162:89
+Travel:12168:v:1:27500:163:92
+TCM Espania:12168:v:1:27500:164:96
+MTV Spain:12168:v:1:27500:167:112
+TCM France:12168:v:1:27500:169:64
+RTL2 CH:12188:h:1:27500:164:112
+La Cinquieme:12207:v:1:27500:160:80
+ARTE:12207:v:1:27500:165:100
+Post Filial TV:12226:h:1:27500:255:256
+Canal Canaris:12246:v:1:27500:160:80
+Canal Canaris:12246:v:1:27500:160:81
+Canal Canaris:12246:v:1:27500:160:82
+Canal Canaris:12246:v:1:27500:160:83
+AB Sat Passion promo:12266:h:1:27500:160:80
+AB Channel 1:12266:h:1:27500:161:84
+Taquilla 0:12285:v:1:27500:165:100
+CSAT:12324:v:1:27500:160:80
+Mosaique:12324:v:1:27500:162:88
+Mosaique 2:12324:v:1:27500:163:92
+Mosaique 3:12324:v:1:27500:164:96
+Le Sesame C+:12324:v:1:27500:165:1965
+FEED:12344:h:1:27500:163:92
+RTM 1:12363:v:1:27500:162:96
+ESC 1:12363:v:1:27500:163:104
+TV5 Europe:12363:v:1:27500:164:112
+TV7 Tunisia:12363:v:1:27500:166:128
+ARTE:12363:v:1:27500:167:137
+RAI Uno:12363:v:1:27500:289:290
+RTP International:12363:v:1:27500:300:301
+Fashion TV:12402:v:1:27500:163:92
+VideoService:12422:h:1:27500:255:256
+Beta Research promo:12422:h:1:27500:1023:1024
+Canal Canarias:12441:v:1:27500:160:80
+TVC International:12441:v:1:27500:512:660
+Fitur:12441:v:1:27500:514:662
+Astra Info 1:12552:v:1:22000:164:112
+Astra Info 2:12552:v:1:22000:165:120
+Astra Vision 1:12552:v:1:22000:168:144
+Astra Vision 1:12552:v:1:22000:168:145
+Astra Vision 1:12552:v:1:22000:168:146
+Astra Vision 1:12552:v:1:22000:168:147
+Astra Vision 1:12552:v:1:22000:168:148
+Astra Vision 1:12552:v:1:22000:168:149
+Astra Vision 1:12552:v:1:22000:168:150
+RTL Tele Letzebuerg:12552:v:1:22000:168:144
+Astra Mosaic:12552:v:1:22000:175:176
+MHP test:12604:h:1:22000:5632:8191
+Bloomberg TV Spain:12610:v:1:22000:45:49
+Video Italia:12610:v:1:22000:121:122
+AC 3 promo:12670:v:1:22000:308:256
diff --git a/config.c b/config.c
new file mode 100644
index 00000000..50315691
--- /dev/null
+++ b/config.c
@@ -0,0 +1,333 @@
+/*
+ * config.c: Configuration file handling
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: config.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "config.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include "dvbapi.h"
+#include "interface.h"
+
+// -- cKeys ------------------------------------------------------------------
+
+tKey keyTable[] = { // "Up" and "Down" must be the first two keys!
+ { kUp, "Up", 0 },
+ { kDown, "Down", 0 },
+ { kMenu, "Menu", 0 },
+ { kOk, "Ok", 0 },
+ { kBack, "Back", 0 },
+ { kLeft, "Left", 0 },
+ { kRight, "Right", 0 },
+ { k0, "0", 0 },
+ { k1, "1", 0 },
+ { k2, "2", 0 },
+ { k3, "3", 0 },
+ { k4, "4", 0 },
+ { k5, "5", 0 },
+ { k6, "6", 0 },
+ { k7, "7", 0 },
+ { k8, "8", 0 },
+ { k9, "9", 0 },
+ { kNone, "", 0 },
+ };
+
+cKeys::cKeys(void)
+{
+ fileName = NULL;
+ code = 0;
+ address = 0;
+ keys = keyTable;
+}
+
+void cKeys::Clear(void)
+{
+ for (tKey *k = keys; k->type != kNone; k++)
+ k->code = 0;
+}
+
+bool cKeys::Load(char *FileName)
+{
+ isyslog(LOG_INFO, "loading %s", FileName);
+ bool result = false;
+ if (FileName)
+ fileName = strdup(FileName);
+ if (fileName) {
+ FILE *f = fopen(fileName, "r");
+ if (f) {
+ int line = 0;
+ char buffer[MaxBuffer];
+ result = true;
+ while (fgets(buffer, sizeof(buffer), f) > 0) {
+ line++;
+ char *Name = buffer;
+ char *p = strpbrk(Name, " \t");
+ if (p) {
+ *p = 0; // terminates 'Name'
+ while (*++p && isspace(*p))
+ ;
+ if (*p) {
+ if (strcasecmp(Name, "Code") == 0)
+ code = *p;
+ else if (strcasecmp(Name, "Address") == 0)
+ address = strtol(p, NULL, 16);
+ else {
+ for (tKey *k = keys; k->type != kNone; k++) {
+ if (strcasecmp(Name, k->name) == 0) {
+ k->code = strtol(p, NULL, 16);
+ Name = NULL; // to indicate that we found it
+ break;
+ }
+ }
+ if (Name) {
+ fprintf(stderr, "unknown key in %s, line %d\n", fileName, line);
+ result = false;
+ break;
+ }
+ }
+ }
+ continue;
+ }
+ fprintf(stderr, "error in %s, line %d\n", fileName, line);
+ result = false;
+ break;
+ }
+ fclose(f);
+ }
+ else
+ fprintf(stderr, "can't open '%s'\n", fileName);
+ }
+ else
+ fprintf(stderr, "no key configuration file name supplied!\n");
+ return result;
+}
+
+bool cKeys::Save(void)
+{
+ //TODO make backup copies???
+ bool result = true;
+ FILE *f = fopen(fileName, "w");
+ if (f) {
+ if (fprintf(f, "Code\t%c\nAddress\t%04X\n", code, address) > 0) {
+ for (tKey *k = keys; k->type != kNone; k++) {
+ if (fprintf(f, "%s\t%08X\n", k->name, k->code) <= 0) {
+ result = false;
+ break;
+ }
+ }
+ }
+ else
+ result = false;
+ fclose(f);
+ }
+ else
+ result = false;
+ return result;
+}
+
+eKeys cKeys::Get(unsigned int Code)
+{
+ if (Code != 0) {
+ tKey *k;
+ for (k = keys; k->type != kNone; k++) {
+ if (k->code == Code)
+ break;
+ }
+ return k->type;
+ }
+ return kNone;
+}
+
+void cKeys::Set(eKeys Key, unsigned int Code)
+{
+ for (tKey *k = keys; k->type != kNone; k++) {
+ if (k->type == Key) {
+ k->code = Code;
+ break;
+ }
+ }
+}
+
+// -- cChannel ---------------------------------------------------------------
+
+cChannel::cChannel(void)
+{
+ *name = 0;
+}
+
+bool cChannel::Parse(char *s)
+{
+ char *buffer = NULL;
+ if (7 == sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid)) {
+ strncpy(name, buffer, MaxChannelName - 1);
+ name[strlen(buffer)] = 0;
+ delete buffer;
+ return true;
+ }
+ return false;
+}
+
+bool cChannel::Save(FILE *f)
+{
+ return fprintf(f, "%s:%d:%c:%d:%d:%d:%d\n", name, frequency, polarization, diseqc, srate, vpid, apid) > 0;
+}
+
+bool cChannel::Switch(void)
+{
+ if (!ChannelLocked) {
+ isyslog(LOG_INFO, "switching to channel %d", Index() + 1);
+ CurrentChannel = Index();
+ Interface.DisplayChannel(CurrentChannel + 1, name);
+ for (int i = 3; --i;) {
+ if (DvbSetChannel(frequency, polarization, diseqc, srate, vpid, apid))
+ return true;
+ esyslog(LOG_ERR, "retrying");
+ }
+ }
+ Interface.Info("Channel locked (recording)!");
+ return false;
+}
+
+bool cChannel::SwitchTo(int i)
+{
+ cChannel *channel = Channels.Get(i);
+ return channel && channel->Switch();
+}
+
+// -- cTimer -----------------------------------------------------------------
+
+cTimer::cTimer(void)
+{
+ *file = 0;
+}
+
+int cTimer::TimeToInt(int t)
+{
+ return (t / 100 * 60 + t % 100) * 60;
+}
+
+int cTimer::ParseDay(char *s)
+{
+ char *tail;
+ int d = strtol(s, &tail, 10);
+ if (tail && *tail) {
+ d = 0;
+ if (tail == s) {
+ if (strlen(s) == 7) {
+ for (char *p = s + 6; p >= s; p--) {
+ d <<= 1;
+ d |= (*p != '-');
+ }
+ d |= 0x80000000;
+ }
+ }
+ }
+ else if (d < 1 || d > 31)
+ d = 0;
+ return d;
+}
+
+char *cTimer::PrintDay(int d)
+{
+ static char buffer[8];
+ if ((d & 0x80000000) != 0) {
+ char *b = buffer;
+ char *w = "MTWTFSS";
+ *b = 0;
+ while (*w) {
+ *b++ = (d & 1) ? *w : '-';
+ d >>= 1;
+ w++;
+ }
+ }
+ else
+ sprintf(buffer, "%d", d);
+ return buffer;
+}
+
+bool cTimer::Parse(char *s)
+{
+ char *buffer1 = NULL;
+ char *buffer2 = NULL;
+ if (9 == sscanf(s, "%d:%d:%a[^:]:%d:%d:%c:%d:%d:%as", &active, &channel, &buffer1, &start, &stop, &quality, &priority, &lifetime, &buffer2)) {
+ day = ParseDay(buffer1);
+ strncpy(file, buffer2, MaxFileName - 1);
+ file[strlen(buffer2)] = 0;
+ delete buffer1;
+ delete buffer2;
+ return day != 0;
+ }
+ return false;
+}
+
+bool cTimer::Save(FILE *f)
+{
+ return fprintf(f, "%d:%d:%s:%d:%d:%c:%d:%d:%s\n", active, channel, PrintDay(day), start, stop, quality, priority, lifetime, file) > 0;
+}
+
+bool cTimer::Matches(void)
+{
+ if (active) {
+ time_t t = time(NULL);
+ struct tm *now = localtime(&t);
+ int weekday = now->tm_wday == 0 ? 6 : now->tm_wday - 1; // we start with monday==0!
+ int current = (now->tm_hour * 60 + now->tm_min) * 60 + now->tm_sec;
+ int begin = TimeToInt(start);
+ int end = TimeToInt(stop);
+ bool twoDays = (end < begin);
+
+ bool todayMatches = false, yesterdayMatches = false;
+ if ((day & 0x80000000) != 0) {
+ if ((day & (1 << weekday)) != 0)
+ todayMatches = true;
+ else if (twoDays) {
+ int yesterday = weekday == 0 ? 6 : weekday - 1;
+ if ((day & (1 << yesterday)) != 0)
+ yesterdayMatches = true;
+ }
+ }
+ else if (day == now->tm_mday)
+ todayMatches = true;
+ else if (twoDays) {
+ t -= 86400;
+ now = localtime(&t);
+ if (day == now->tm_mday)
+ yesterdayMatches = true;
+ }
+ return (todayMatches && current >= begin && (current <= end || twoDays))
+ || (twoDays && yesterdayMatches && current <= end);
+ }
+ return false;
+}
+
+cTimer *cTimer::GetMatch(void)
+{
+ cTimer *t = (cTimer *)Timers.First();
+ while (t) {
+ if (t->Matches())
+ return t;
+ t = (cTimer *)t->Next();
+ }
+ return NULL;
+}
+
+// -- cKeys ------------------------------------------------------------------
+
+cKeys Keys;
+
+// -- cChannels --------------------------------------------------------------
+
+int CurrentChannel = 0;
+bool ChannelLocked = false;
+
+cChannels Channels;
+
+// -- cTimers ----------------------------------------------------------------
+
+cTimers Timers;
+
diff --git a/config.h b/config.h
new file mode 100644
index 00000000..3abb7a1a
--- /dev/null
+++ b/config.h
@@ -0,0 +1,163 @@
+/*
+ * config.h: Configuration file handling
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: config.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef __CONFIG_H
+#define __CONFIG_H
+
+#include <stdio.h>
+#include <string.h>
+#include "tools.h"
+
+#define MaxBuffer 1000
+
+enum eKeys { // "Up" and "Down" must be the first two keys!
+ kUp,
+ kDown,
+ kMenu,
+ kOk,
+ kBack,
+ kLeft,
+ kRight,
+ k0, k1, k2, k3, k4, k5, k6, k7, k8, k9,
+ kNone
+ };
+
+struct tKey {
+ eKeys type;
+ char *name;
+ unsigned int code;
+ };
+
+class cKeys {
+private:
+ char *fileName;
+public:
+ unsigned char code;
+ unsigned short address;
+ tKey *keys;
+ cKeys(void);
+ void Clear(void);
+ bool Load(char *FileName = NULL);
+ bool Save(void);
+ eKeys Get(unsigned int Code);
+ void Set(eKeys Key, unsigned int Code);
+ };
+
+class cChannel : public cListObject {
+public:
+ enum { MaxChannelName = 32 }; // 31 chars + terminating 0!
+ char name[MaxChannelName];
+ int frequency; // MHz
+ char polarization;
+ int diseqc;
+ int srate;
+ int vpid;
+ int apid;
+ cChannel(void);
+ bool Parse(char *s);
+ bool Save(FILE *f);
+ bool Switch(void);
+ static bool SwitchTo(int i);
+ };
+
+class cTimer : public cListObject {
+public:
+ enum { MaxFileName = 256 };
+ int active;
+ int channel;
+ int day;
+ int start;
+ int stop;
+//TODO VPS???
+ char quality;
+ int priority;
+ int lifetime;
+ char file[MaxFileName];
+ cTimer(void);
+ bool Parse(char *s);
+ bool Save(FILE *f);
+ bool Matches(void);
+ static cTimer *GetMatch(void);
+ static int TimeToInt(int t);
+ static int ParseDay(char *s);
+ static char *PrintDay(int d);
+ };
+
+template<class T> class cConfig : public cList<T> {
+private:
+ char *fileName;
+ void Clear(void)
+ {
+ delete fileName;
+ cList<T>::Clear();
+ }
+public:
+ bool Load(char *FileName)
+ {
+ isyslog(LOG_INFO, "loading %s", FileName);
+ bool result = true;
+ Clear();
+ fileName = strdup(FileName);
+ FILE *f = fopen(fileName, "r");
+ if (f) {
+ int line = 0;
+ char buffer[MaxBuffer];
+ while (fgets(buffer, sizeof(buffer), f) > 0) {
+ line++;
+ T *l = new T;
+ if (l->Parse(buffer))
+ Add(l);
+ else {
+ fprintf(stderr, "error in %s, line %d\n", fileName, line);
+ delete l;
+ result = false;
+ break;
+ }
+ }
+ fclose(f);
+ }
+ else {
+ fprintf(stderr, "can't open '%s'\n", fileName);
+ result = false;
+ }
+ return result;
+ }
+ bool Save(void)
+ {
+ //TODO make backup copies???
+ bool result = true;
+ T *l = (T *)First();
+ FILE *f = fopen(fileName, "w");
+ if (f) {
+ while (l) {
+ if (!l->Save(f)) {
+ result = false;
+ break;
+ }
+ l = (T *)l->Next();
+ }
+ fclose(f);
+ }
+ else
+ result = false;
+ return result;
+ }
+ };
+
+class cChannels : public cConfig<cChannel> {};
+class cTimers : public cConfig<cTimer> {};
+
+extern int CurrentChannel;
+extern bool ChannelLocked;
+
+extern cChannels Channels;
+extern cTimers Timers;
+extern cKeys Keys;
+
+#endif //__CONFIG_H
diff --git a/dvbapi.c b/dvbapi.c
new file mode 100644
index 00000000..5597fbd5
--- /dev/null
+++ b/dvbapi.c
@@ -0,0 +1,166 @@
+/*
+ * dvbapi.c: Interface to the DVB driver
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: dvbapi.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+// FIXME: these should be defined in ../DVB/driver/dvb.h!!!
+typedef unsigned int u32;
+typedef unsigned short u16;
+typedef unsigned char u8;
+
+#include "dvbapi.h"
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "../DVB/driver/dvb.h"
+#include "interface.h"
+#include "tools.h"
+
+#define VIDEODEVICE "/dev/video"
+
+const char *DvbQuality = "LMH"; // Low, Medium, High
+
+bool DvbSetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid)
+{
+ int v = open(VIDEODEVICE, O_RDWR);
+
+ if (v >= 0) {
+ struct frontend front;
+ ioctl(v, VIDIOCGFRONTEND, &front);
+ unsigned int freq = FrequencyMHz;
+ front.ttk = (freq < 11800UL) ? 0 : 1;
+ if (freq < 11800UL)
+ freq -= 9750UL;
+ else
+ freq -= 10600UL;
+ front.freq = freq * 1000000UL;
+ front.diseqc = Diseqc;
+ front.srate = Srate * 1000;
+ front.volt = (Polarization == 'v') ? 0 : 1;
+ front.video_pid = Vpid;
+ front.audio_pid = Apid;
+ front.AFC = 1;
+ ioctl(v, VIDIOCSFRONTEND, &front);
+ close(v);
+ if (front.sync & 0x1F == 0x1F)
+ return true;
+ esyslog(LOG_ERR, "channel not sync'ed (front.sync=%X)!", front.sync);
+ }
+ else
+ Interface.Error("can't open VIDEODEVICE");//XXX
+ return false;
+}
+
+// -- cDvbRecorder -----------------------------------------------------------
+
+cDvbRecorder::cDvbRecorder(void)
+{
+}
+
+cDvbRecorder::~cDvbRecorder()
+{
+ Stop();
+}
+
+bool cDvbRecorder::Record(const char *FileName, char Quality)
+{
+ isyslog(LOG_INFO, "record %s (%c)", FileName, Quality);
+ return true;
+ // TODO
+ return false;
+}
+
+bool cDvbRecorder::Play(const char *FileName, int Frame)
+{
+ isyslog(LOG_INFO, "play %s (%d)", FileName, Frame);
+ // TODO
+ return false;
+}
+
+bool cDvbRecorder::FastForward(void)
+{
+ isyslog(LOG_INFO, "fast forward");
+ // TODO
+ return false;
+}
+
+bool cDvbRecorder::FastRewind(void)
+{
+ isyslog(LOG_INFO, "fast rewind");
+ // TODO
+ return false;
+}
+
+bool cDvbRecorder::Pause(void)
+{
+ isyslog(LOG_INFO, "pause");
+ // TODO
+ return false;
+}
+
+void cDvbRecorder::Stop(void)
+{
+ isyslog(LOG_INFO, "stop");
+ // TODO
+}
+
+int cDvbRecorder::Frame(void)
+{
+ isyslog(LOG_INFO, "frame");
+ // TODO
+ return 0;
+}
+
+// ---------------------------------------------------------------------------
+
+static void DvbOsdCmd(OSD_Command cmd, int color = 0, int x0 = 0, int y0 = 0, int x1 = 0, int y1 = 0, void *data = NULL)
+{
+ int v = open(VIDEODEVICE, O_RDWR);
+
+ if (v >= 0) {
+ struct drawcmd dc;
+ dc.cmd = cmd;
+ dc.color = color;
+ dc.x0 = x0;
+ dc.y0 = y0;
+ dc.x1 = x1;
+ dc.y1 = y1;
+ dc.data = data;
+ ioctl(v, VIDIOCSOSDCOMMAND, &dc);
+ close(v);
+ }
+ else
+ Interface.Error("can't open VIDEODEVICE");//XXX
+}
+
+void DvbOsdOpen(int x, int y, int w, int h)
+{
+ DvbOsdCmd(OSD_Open, 1, x, y, x + w - 1, y + h - 1);
+ DvbOsdCmd(OSD_SetColor, 0, 0, 0, 0, 127); // background 50% gray
+ DvbOsdCmd(OSD_SetColor, 1, 255, 255, 255, 255); // text white
+}
+
+void DvbOsdClose(void)
+{
+ DvbOsdCmd(OSD_Close);
+}
+
+void DvbOsdClear(void)
+{
+ DvbOsdCmd(OSD_Clear);
+}
+
+void DvbOsdClrEol(int x, int y)
+{
+ DvbOsdCmd(OSD_FillBlock, 0, x, y * DvbOsdLineHeight, x + 490, (y + 1) * DvbOsdLineHeight);//XXX
+}
+
+void DvbOsdText(int x, int y, char *s)
+{
+ DvbOsdCmd(OSD_Text, 1, x, y, 1, 0, s);
+}
diff --git a/dvbapi.h b/dvbapi.h
new file mode 100644
index 00000000..6a8f0400
--- /dev/null
+++ b/dvbapi.h
@@ -0,0 +1,65 @@
+/*
+ * dvbapi.h: Interface to the DVB driver
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: dvbapi.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef __DVBAPI_H
+#define __DVBAPI_H
+
+const int DvbOsdCharWidth = 12; //XXX
+const int DvbOsdLineHeight = 25;
+
+extern const char *DvbQuality; // Low, Medium, High
+
+bool DvbSetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid);
+
+class cDvbRecorder {
+public:
+ cDvbRecorder(void);
+ ~cDvbRecorder();
+ bool Record(const char *FileName, char Quality);
+ // Starts recording the current channel into the given file, with the
+ // given quality level. Any existing file will be overwritten.
+ // Returns true if recording was started successfully.
+ // If there is already a recording session active, false will be
+ // returned.
+ bool Play(const char *FileName, int Frame = 0);
+ // Starts playback of the given file, at the optional Frame (default
+ // is the beginning of the file). If Frame is beyond the last recorded
+ // frame in the file, or if it is negative, playback will be positioned
+ // to the last frame in the file and will do an implicit Pause() there.
+ // If there is already a playback session active, it will be stopped
+ // and the new file or frame (which may be in the same file) will
+ // be played back.
+ bool FastForward(void);
+ // Runs the current playback session forward at a higher speed.
+ // TODO allow different fast forward speeds???
+ bool FastRewind(void);
+ // Runs the current playback session backwards forward at a higher speed.
+ // TODO allow different fast rewind speeds???
+ bool Pause(void);
+ // Pauses the current recording or playback session, or resumes a paused
+ // session.
+ // Returns true if there is actually a recording or playback session
+ // active that was paused/resumed.
+ void Stop(void);
+ // Stops the current recording or playback session.
+ int Frame(void);
+ // Returns the number of the current frame in the current recording or
+ // playback session, which can be used to start playback at a given position.
+ // The number returned is the actual number of frames counted from the
+ // beginning of the current file.
+ // The very first frame has the number 1.
+ };
+
+void DvbOsdOpen(int x, int y, int w, int h);
+void DvbOsdClose(void);
+void DvbOsdClear(void);
+void DvbOsdClrEol(int x, int y);
+void DvbOsdText(int x, int y, char *s);
+
+#endif //__DVBAPI_H
diff --git a/interface.c b/interface.c
new file mode 100644
index 00000000..58c28f1f
--- /dev/null
+++ b/interface.c
@@ -0,0 +1,298 @@
+/*
+ * interface.c: Abstract user interface layer
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: interface.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "interface.h"
+#include <ncurses.h>
+#include <unistd.h>
+#include "dvbapi.h"
+#include "remote.h"
+
+#ifndef DEBUG_REMOTE
+cRcIo RcIo("/dev/ttyS1");//XXX
+#endif
+
+WINDOW *window;
+
+cInterface Interface;
+
+cInterface::cInterface(void)
+{
+ open = 0;
+ cols[0] = 0;
+#ifdef DEBUG_OSD
+ initscr();
+ keypad(stdscr, TRUE);
+ nonl();
+ cbreak();
+ noecho();
+ leaveok(stdscr, TRUE);
+ window = stdscr;
+#else
+#endif
+}
+
+void cInterface::Init(void)
+{
+#ifndef DEBUG_REMOTE
+ RcIo.SetCode(Keys.code, Keys.address);
+#endif
+}
+
+void cInterface::Open(void)
+{
+ if (!open++) {
+#ifdef DEBUG_OSD
+#else
+//TODO
+ DvbOsdOpen(100, 100, 500, 400);
+#endif
+ }
+}
+
+void cInterface::Close(void)
+{
+ if (!--open) {
+#ifdef DEBUG_OSD
+#else
+//TODO
+ DvbOsdClose();
+#endif
+ }
+}
+
+unsigned int cInterface::GetCh(void)
+{
+#ifdef DEBUG_REMOTE
+ return getch();
+#else
+#ifdef DEBUG_OSD
+ wrefresh(window);//XXX
+#endif
+ unsigned int Command;
+ return RcIo.GetCommand(&Command) ? Command : 0;
+#endif
+}
+
+eKeys cInterface::GetKey(void)
+{
+ return Keys.Get(GetCh());
+}
+
+void cInterface::Clear(void)
+{
+ if (open) {
+#ifdef DEBUG_OSD
+ wclear(window);
+#else
+//TODO
+ DvbOsdClear();
+#endif
+ }
+}
+
+void cInterface::SetCols(int *c)
+{
+ for (int i = 0; i < MaxCols; i++) {
+ cols[i] = *c++;
+ if (cols[i] == 0)
+ break;
+ }
+}
+
+void cInterface::Write(int x, int y, char *s)
+{
+ if (open) {
+#ifdef DEBUG_OSD
+ wmove(window, y, x); // ncurses wants 'y' before 'x'!
+ waddstr(window, s);
+#else
+ DvbOsdText(x * DvbOsdCharWidth, y * DvbOsdLineHeight, s);
+#endif
+ }
+}
+
+void cInterface::WriteText(int x, int y, char *s, bool Current)
+{
+ if (open) {
+#ifdef DEBUG_OSD
+ wmove(window, y, x); // ncurses wants 'y' before 'x'!
+ wclrtoeol(window);//XXX
+#else
+//TODO
+ DvbOsdClrEol(x * DvbOsdCharWidth, y);//XXX
+#endif
+ Write(x, y, Current ? "*" : " ");
+ x++;
+ int col = 0;
+ for (;;) {
+ char *t = strchr(s, '\t');
+ char *p = s;
+ char buf[1000];
+ if (t && col < MaxCols && cols[col] > 0) {
+ unsigned int n = t - s;
+ if (n >= sizeof(buf))
+ n = sizeof(buf) - 1;
+ strncpy(buf, s, n);
+ buf[n] = 0;
+ p = buf;
+ s = t + 1;
+ }
+ Write(x, y, p);
+ if (p == s)
+ break;
+ x += cols[col++];
+ }
+ }
+}
+
+void cInterface::Info(char *s)
+{
+ Open();
+ isyslog(LOG_ERR, s);
+ WriteText(0, 11, s);//TODO
+#ifdef DEBUG_OSD
+ wrefresh(window);//XXX
+#endif
+ sleep(1);
+ WriteText(0, 11, "");//TODO
+#ifdef DEBUG_OSD
+ wrefresh(window);//XXX
+#endif
+ Close();
+}
+
+void cInterface::Error(char *s)
+{
+ Open();
+ esyslog(LOG_ERR, s);
+ WriteText(0, 12, s);//TODO
+#ifdef DEBUG_OSD
+ wrefresh(window);//XXX
+#endif
+ sleep(1);
+ WriteText(0, 12, "");//TODO
+#ifdef DEBUG_OSD
+ wrefresh(window);//XXX
+#endif
+ Close();
+}
+
+void cInterface::QueryKeys(void)
+{
+ Keys.Clear();
+ WriteText(1, 1, "Learning Remote Control Keys");
+ WriteText(1, 3, "Phase 1: Detecting RC code type");
+ WriteText(1, 5, "Press any key on the RC unit");
+#ifndef DEBUG_REMOTE
+ unsigned char Code = 0;
+ unsigned short Address;
+#endif
+ for (;;) {
+#ifdef DEBUG_REMOTE
+ if (GetCh())
+ break;
+#else
+ //TODO on screen display...
+ if (RcIo.DetectCode(&Code, &Address)) {
+ Keys.code = Code;
+ Keys.address = Address;
+ WriteText(1, 5, "RC code detected!");
+ WriteText(1, 6, "Do not press any key...");
+ RcIo.Flush(3);
+ WriteText(1, 5, "");
+ WriteText(1, 6, "");
+ break;
+ }
+#endif
+ }
+ WriteText(1, 3, "Phase 2: Learning specific key codes");
+ tKey *k = Keys.keys;
+ while (k->type != kNone) {
+ char *Prompt;
+ asprintf(&Prompt, "Press key for '%s'", k->name);
+ WriteText(1, 5, Prompt);
+ delete Prompt;
+ for (;;) {
+ unsigned int ch = GetCh();
+ if (ch != 0) {
+ switch (Keys.Get(ch)) {
+ case kUp: if (k > Keys.keys) {
+ k--;
+ break;
+ }
+ case kDown: if (k > Keys.keys + 1) {
+ WriteText(1, 5, "Press 'Up' to confirm");
+ WriteText(1, 6, "Press 'Down' to continue");
+ WriteText(1, 7, "");
+ WriteText(1, 8, "");
+ for (;;) {
+ eKeys key = GetKey();
+ if (key == kUp) {
+ Clear();
+ return;
+ }
+ else if (key == kDown) {
+ WriteText(1, 6, "");
+ break;
+ }
+ }
+ break;
+ }
+ case kNone: k->code = ch;
+ k++;
+ break;
+ default: break;
+ }
+ break;
+ }
+ }
+ if (k > Keys.keys)
+ WriteText(1, 7, "(press 'Up' to go back)");
+ else
+ WriteText(1, 7, "");
+ if (k > Keys.keys + 1)
+ WriteText(1, 8, "(press 'Down' to end key definition)");
+ else
+ WriteText(1, 8, "");
+ }
+}
+
+void cInterface::LearnKeys(void)
+{
+ isyslog(LOG_INFO, "learning keys");
+ for (;;) {
+ Clear();
+ QueryKeys();
+ Clear();
+ WriteText(1, 1, "Learning Remote Control Keys");
+ WriteText(1, 3, "Phase 3: Saving key codes");
+ WriteText(1, 5, "Press 'Up' to save, 'Down' to cancel");
+ for (;;) {
+ eKeys key = GetKey();
+ if (key == kUp) {
+ Keys.Save();
+ Clear();
+ return;
+ }
+ else if (key == kDown) {
+ Keys.Load();
+ Clear();
+ return;
+ }
+ }
+ }
+}
+
+void cInterface::DisplayChannel(int Number, char *Name)
+{
+//TODO
+#ifndef DEBUG_REMOTE
+ RcIo.Number(Number);
+#endif
+}
diff --git a/interface.h b/interface.h
new file mode 100644
index 00000000..e387ab91
--- /dev/null
+++ b/interface.h
@@ -0,0 +1,41 @@
+/*
+ * interface.h: Abstract user interface layer
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: interface.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef __INTERFACE_H
+#define __INTERFACE_H
+
+#include "config.h"
+
+class cInterface {
+public:
+ enum { MaxCols = 5 };
+private:
+ int open;
+ int cols[MaxCols];
+ unsigned int GetCh(void);
+ void QueryKeys(void);
+ void Write(int x, int y, char *s);
+public:
+ cInterface(void);
+ void Init(void);
+ void Open(void);
+ void Close(void);
+ eKeys GetKey(void);
+ void Clear(void);
+ void SetCols(int *c);
+ void WriteText(int x, int y, char *s, bool Current = false);
+ void Info(char *s);
+ void Error(char *s);
+ void LearnKeys(void);
+ void DisplayChannel(int Number, char *Name);
+ };
+
+extern cInterface Interface;
+
+#endif //__INTERFACE_H
diff --git a/keys-pc.conf b/keys-pc.conf
new file mode 100644
index 00000000..c57613f0
--- /dev/null
+++ b/keys-pc.conf
Binary files differ
diff --git a/keys.conf b/keys.conf
new file mode 100644
index 00000000..f56222c3
--- /dev/null
+++ b/keys.conf
@@ -0,0 +1,19 @@
+Code B
+Address 0000
+Up 000047E2
+Down 000007E2
+Menu 000011E2
+Ok 000079E2
+Back 00001AE2
+Left 000005E2
+Right 000045E2
+0 00007FE2
+1 00003FE2
+2 00005FE2
+3 00001FE2
+4 00006FE2
+5 00002FE2
+6 00004FE2
+7 00000FE2
+8 000077E2
+9 000037E2
diff --git a/menu.c b/menu.c
new file mode 100644
index 00000000..4f525ff7
--- /dev/null
+++ b/menu.c
@@ -0,0 +1,745 @@
+/*
+ * menu.c: The actual menu implementations
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: menu.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "menu.h"
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+#include "dvbapi.h"
+
+const char *FileNameChars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789/-.# ";//TODO more?
+
+// --- cMenuEditItem ---------------------------------------------------------
+
+class cMenuEditItem : public cOsdItem {
+private:
+ const char *name;
+ const char *value;
+public:
+ cMenuEditItem(const char *Name);
+ ~cMenuEditItem();
+ void SetValue(const char *Value);
+ };
+
+cMenuEditItem::cMenuEditItem(const char *Name)
+{
+ name = strdup(Name);
+ value = NULL;
+}
+
+cMenuEditItem::~cMenuEditItem()
+{
+ delete name;
+ delete value;
+}
+
+void cMenuEditItem::SetValue(const char *Value)
+{
+ delete value;
+ value = strdup(Value);
+ char *buffer = NULL;
+ asprintf(&buffer, "%s:\t%s", name, value);
+ SetText(buffer, false);
+ Display();
+}
+
+// --- cMenuEditIntItem ------------------------------------------------------
+
+class cMenuEditIntItem : public cMenuEditItem {
+protected:
+ int *value;
+ int min, max;
+ virtual void Set(void);
+public:
+ cMenuEditIntItem(const char *Name, int *Value, int Min = 0, int Max = INT_MAX);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuEditIntItem::cMenuEditIntItem(const char *Name, int *Value, int Min, int Max)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ min = Min;
+ max = Max;
+ Set();
+}
+
+void cMenuEditIntItem::Set(void)
+{
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%d", *value);
+ SetValue(buf);
+}
+
+eOSStatus cMenuEditIntItem::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cMenuEditItem::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ int newValue;
+ if (k0 <= Key && Key <= k9) {
+ if (fresh) {
+ *value = 0;
+ fresh = false;
+ }
+ newValue = *value * 10 + (Key - k0);
+ }
+ else if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
+ newValue = *value - 1;
+ fresh = true;
+ }
+ else if (Key == kRight) {
+ newValue = *value + 1;
+ fresh = true;
+ }
+ else
+ return status;
+ if ((!fresh || min <= newValue) && newValue <= max) {
+ *value = newValue;
+ Set();
+ }
+ status = osContinue;
+ }
+ return status;
+}
+
+// --- cMenuEditBoolItem -----------------------------------------------------
+
+class cMenuEditBoolItem : public cMenuEditIntItem {
+protected:
+ virtual void Set(void);
+public:
+ cMenuEditBoolItem(const char *Name, int *Value);
+ };
+
+cMenuEditBoolItem::cMenuEditBoolItem(const char *Name, int *Value)
+:cMenuEditIntItem(Name, Value, 0, 1)
+{
+ Set();
+}
+
+void cMenuEditBoolItem::Set(void)
+{
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%s", *value ? "yes" : "no");
+ SetValue(buf);
+}
+
+// --- cMenuEditChanItem -----------------------------------------------------
+
+class cMenuEditChanItem : public cMenuEditIntItem {
+protected:
+ virtual void Set(void);
+public:
+ cMenuEditChanItem(const char *Name, int *Value);
+ };
+
+cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value)
+:cMenuEditIntItem(Name, Value, 1, Channels.Count())
+{
+ Set();
+}
+
+void cMenuEditChanItem::Set(void)
+{
+ char buf[255];
+ cChannel *channel = Channels.Get(*value - 1);
+ if (channel)
+ snprintf(buf, sizeof(buf), "%d %s", *value, channel->name);
+ else
+ *buf = 0;
+ SetValue(buf);
+}
+
+// --- cMenuEditDayItem ------------------------------------------------------
+
+class cMenuEditDayItem : public cMenuEditIntItem {
+protected:
+ static int days[];
+ int d;
+ virtual void Set(void);
+public:
+ cMenuEditDayItem(const char *Name, int *Value);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+int cMenuEditDayItem::days[] ={ cTimer::ParseDay("M------"),
+ cTimer::ParseDay("-T-----"),
+ cTimer::ParseDay("--W----"),
+ cTimer::ParseDay("---T---"),
+ cTimer::ParseDay("----F--"),
+ cTimer::ParseDay("-----S-"),
+ cTimer::ParseDay("------S"),
+ cTimer::ParseDay("MTWTF--"),
+ cTimer::ParseDay("MTWTFS-"),
+ cTimer::ParseDay("MTWTFSS"),
+ cTimer::ParseDay("-----SS"),
+ 0 };
+
+cMenuEditDayItem::cMenuEditDayItem(const char *Name, int *Value)
+:cMenuEditIntItem(Name, Value, -INT_MAX, 31)
+{
+ d = -1;
+ if (*value < 0) {
+ int n = 0;
+ while (days[n]) {
+ if (days[n] == *value) {
+ d = n;
+ break;
+ }
+ n++;
+ }
+ }
+ Set();
+}
+
+void cMenuEditDayItem::Set(void)
+{
+ SetValue(cTimer::PrintDay(*value));
+}
+
+eOSStatus cMenuEditDayItem::ProcessKey(eKeys Key)
+{
+ switch (Key) {
+ case kLeft: if (d > 0)
+ *value = days[--d];
+ else if (d == 0) {
+ *value = 31;
+ d = -1;
+ }
+ else if (*value == 1) {
+ d = sizeof(days) / sizeof(int) - 2;
+ *value = days[d];
+ }
+ else
+ return cMenuEditIntItem::ProcessKey(Key);
+ Set();
+ break;
+ case kRight: if (d >= 0) {
+ *value = days[++d];
+ if (*value == 0) {
+ *value = 1;
+ d = -1;
+ }
+ }
+ else if (*value == 31) {
+ d = 0;
+ *value = days[d];
+ }
+ else
+ return cMenuEditIntItem::ProcessKey(Key);
+ Set();
+ break;
+ default : return cMenuEditIntItem::ProcessKey(Key);
+ }
+ return osContinue;
+}
+
+// --- cMenuEditTimeItem -----------------------------------------------------
+
+class cMenuEditTimeItem : public cMenuEditItem {
+protected:
+ int *value;
+ int hh, mm;
+ int pos;
+ virtual void Set(void);
+public:
+ cMenuEditTimeItem(const char *Name, int *Value);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuEditTimeItem::cMenuEditTimeItem(const char *Name, int *Value)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ hh = *value / 100;
+ mm = *value % 100;
+ pos = 0;
+ Set();
+}
+
+void cMenuEditTimeItem::Set(void)
+{
+ char buf[10];
+ snprintf(buf, sizeof(buf), "%02d:%02d", hh, mm);
+ SetValue(buf);
+}
+
+eOSStatus cMenuEditTimeItem::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cMenuEditItem::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ if (k0 <= Key && Key <= k9) {
+ if (fresh || pos > 3) {
+ pos = 0;
+ fresh = false;
+ }
+ int n = Key - k0;
+ switch (pos) {
+ case 0: if (n <= 2) {
+ hh = n * 10;
+ mm = 0;
+ pos++;
+ }
+ break;
+ case 1: if (hh + n <= 23) {
+ hh += n;
+ pos++;
+ }
+ break;
+ case 2: if (n <= 5) {
+ mm += n * 10;
+ pos++;
+ }
+ break;
+ case 3: if (mm + n <= 59) {
+ mm += n;
+ pos++;
+ }
+ break;
+ }
+ }
+ else if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
+ if (--mm < 0) {
+ mm = 59;
+ if (--hh < 0)
+ hh = 23;
+ }
+ fresh = true;
+ }
+ else if (Key == kRight) {
+ if (++mm > 59) {
+ mm = 0;
+ if (++hh > 23)
+ hh = 0;
+ }
+ fresh = true;
+ }
+ else
+ return status;
+ *value = hh * 100 + mm;
+ Set();
+ status = osContinue;
+ }
+ return status;
+}
+
+// --- cMenuEditChrItem ------------------------------------------------------
+
+class cMenuEditChrItem : public cMenuEditItem {
+private:
+ char *value;
+ const char *allowed;
+ const char *current;
+ virtual void Set(void);
+public:
+ cMenuEditChrItem(const char *Name, char *Value, const char *Allowed);
+ ~cMenuEditChrItem();
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuEditChrItem::cMenuEditChrItem(const char *Name, char *Value, const char *Allowed)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ allowed = strdup(Allowed);
+ current = strchr(allowed, *Value);
+ if (!current)
+ current = allowed;
+ Set();
+}
+
+cMenuEditChrItem::~cMenuEditChrItem()
+{
+ delete allowed;
+}
+
+void cMenuEditChrItem::Set(void)
+{
+ char buf[2];
+ snprintf(buf, sizeof(buf), "%c", *value);
+ SetValue(buf);
+}
+
+eOSStatus cMenuEditChrItem::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cMenuEditItem::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ if (Key == kLeft) {
+ if (current > allowed)
+ current--;
+ }
+ else if (Key == kRight) {
+ if (*(current + 1))
+ current++;
+ }
+ else
+ return status;
+ *value = *current;
+ Set();
+ status = osContinue;
+ }
+ return status;
+}
+
+// --- cMenuEditStrItem ------------------------------------------------------
+
+class cMenuEditStrItem : public cMenuEditItem {
+private:
+ char *value;
+ int length;
+ const char *allowed;
+ int pos;
+ virtual void Set(void);
+ char Inc(char c, bool Up);
+public:
+ cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed);
+ ~cMenuEditStrItem();
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuEditStrItem::cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ length = Length;
+ allowed = strdup(Allowed);
+ pos = -1;
+ Set();
+}
+
+cMenuEditStrItem::~cMenuEditStrItem()
+{
+ delete allowed;
+}
+
+void cMenuEditStrItem::Set(void)
+{
+ char buf[1000];
+ if (pos >= 0) {
+ strncpy(buf, value, pos);
+ char *s = value[pos] != ' ' ? value + pos + 1 : "";
+ snprintf(buf + pos, sizeof(buf) - pos - 2, "[%c]%s", *(value + pos), s);
+ SetValue(buf);
+ }
+ else
+ SetValue(value);
+}
+
+char cMenuEditStrItem::Inc(char c, bool Up)
+{
+ const char *p = strchr(allowed, c);
+ if (!p)
+ p = allowed;
+ if (Up) {
+ if (!*++p)
+ p = allowed;
+ }
+ else if (--p < allowed)
+ p = allowed + strlen(allowed) - 1;
+ return *p;
+}
+
+eOSStatus cMenuEditStrItem::ProcessKey(eKeys Key)
+{
+ switch (Key) {
+ case kLeft: if (pos > 0) {
+ if (value[pos] == ' ')
+ value[pos] = 0;
+ pos--;
+ }
+ break;
+ case kRight: if (pos < length && value[pos] != ' ') {
+ if (++pos >= int(strlen(value))) {
+ value[pos] = ' ';
+ value[pos + 1] = 0;
+ }
+ }
+ break;
+ case kUp:
+ case kDown: if (pos >= 0)
+ value[pos] = Inc(value[pos], Key == kUp);
+ else
+ return cMenuEditItem::ProcessKey(Key);
+ break;
+ case kOk: if (pos >= 0) {
+ if (value[pos] == ' ')
+ value[pos] = 0;
+ pos = -1;
+ break;
+ }
+ // run into default
+ default: return cMenuEditItem::ProcessKey(Key);
+ }
+ Set();
+ return osContinue;
+}
+
+// --- cMenuEditChannel ------------------------------------------------------
+
+class cMenuEditChannel : public cOsdMenu {
+private:
+ cChannel *channel;
+ cChannel data;
+public:
+ cMenuEditChannel(int Index);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuEditChannel::cMenuEditChannel(int Index)
+:cOsdMenu("Edit channel", 14)
+{
+ channel = Channels.Get(Index);
+ if (channel) {
+ data = *channel;
+ Add(new cMenuEditStrItem("Name", data.name, sizeof(data.name), FileNameChars));
+ Add(new cMenuEditIntItem("Frequency", &data.frequency, 10000, 13000)); //TODO exact limits???
+ Add(new cMenuEditChrItem("Polarization", &data.polarization, "hv"));
+ Add(new cMenuEditIntItem("Diseqc", &data.diseqc, 0, 10)); //TODO exact limits???
+ Add(new cMenuEditIntItem("Srate", &data.srate, 22000, 27500)); //TODO exact limits - toggle???
+ Add(new cMenuEditIntItem("Vpid", &data.vpid, 0, 10000)); //TODO exact limits???
+ Add(new cMenuEditIntItem("Apid", &data.apid, 0, 10000)); //TODO exact limits???
+ }
+}
+
+eOSStatus cMenuEditChannel::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cOsdMenu::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ if (Key == kOk) {
+ if (channel)
+ *channel = data;
+ Channels.Save();
+ status = osBack;
+ }
+ }
+ return status;
+}
+
+// --- cMenuChannelItem ------------------------------------------------------
+
+class cMenuChannelItem : public cOsdItem {
+private:
+ int index;
+ cChannel *channel;
+public:
+ cMenuChannelItem(int Index, cChannel *Channel);
+ virtual void Set(void);
+ };
+
+cMenuChannelItem::cMenuChannelItem(int Index, cChannel *Channel)
+{
+ index = Index;
+ channel = Channel;
+ Set();
+}
+
+void cMenuChannelItem::Set(void)
+{
+ char *buffer = NULL;
+ asprintf(&buffer, "%d\t%s", index + 1, channel->name); // user visible channel numbers start with '1'
+ SetText(buffer, false);
+}
+
+// --- cMenuChannels ---------------------------------------------------------
+
+class cMenuChannels : public cOsdMenu {
+public:
+ cMenuChannels(void);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuChannels::cMenuChannels(void)
+:cOsdMenu("Channels", 4)
+{
+ //TODO
+ int i = 0;
+ cChannel *channel;
+
+ while ((channel = Channels.Get(i)) != NULL) {
+ Add(new cMenuChannelItem(i, channel), i == CurrentChannel);
+ i++;
+ }
+}
+
+eOSStatus cMenuChannels::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cOsdMenu::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ switch (Key) {
+ //TODO need to block this if we are already editing a channel!
+ case kRight: return AddSubMenu(new cMenuEditChannel(Current()));
+ case kOk: {
+ cChannel *ch = Channels.Get(Current());
+ if (ch)
+ ch->Switch();
+ return osEnd;
+ }
+ default: break;
+ }
+ }
+ return status;
+}
+
+// --- cMenuEditTimer --------------------------------------------------------
+
+class cMenuEditTimer : public cOsdMenu {
+private:
+ cTimer *timer;
+ cTimer data;
+public:
+ cMenuEditTimer(int Index);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuEditTimer::cMenuEditTimer(int Index)
+:cOsdMenu("Edit timer", 10)
+{
+ timer = Timers.Get(Index);
+ if (timer) {
+ data = *timer;
+ Add(new cMenuEditBoolItem("Active", &data.active));
+ Add(new cMenuEditChanItem("Channel", &data.channel));
+ Add(new cMenuEditDayItem( "Day", &data.day));
+ Add(new cMenuEditTimeItem("Start", &data.start));
+ Add(new cMenuEditTimeItem("Stop", &data.stop));
+//TODO VPS???
+ Add(new cMenuEditChrItem( "Quality", &data.quality, DvbQuality));
+ Add(new cMenuEditIntItem( "Priority", &data.priority, 0, 99));
+ Add(new cMenuEditIntItem( "Lifetime", &data.lifetime, 0, 99));
+ Add(new cMenuEditStrItem( "File", data.file, sizeof(data.file), FileNameChars));
+ }
+}
+
+eOSStatus cMenuEditTimer::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cOsdMenu::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ if (Key == kOk) {
+ if (timer && memcmp(timer, &data, sizeof(data)) != 0) {
+ *timer = data;
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %d modified (%s)", timer->Index() + 1, timer->active ? "active" : "inactive");
+ }
+ status = osBack;
+ }
+ }
+ return status;
+}
+
+// --- cMenuTimerItem --------------------------------------------------------
+
+class cMenuTimerItem : public cOsdItem {
+private:
+ int index;
+ cTimer *timer;
+public:
+ cMenuTimerItem(int Index, cTimer *Timer);
+ virtual void Set(void);
+ };
+
+cMenuTimerItem::cMenuTimerItem(int Index, cTimer *Timer)
+{
+ index = Index;
+ timer = Timer;
+ Set();
+}
+
+void cMenuTimerItem::Set(void)
+{
+ char *buffer = NULL;
+ asprintf(&buffer, "%d\t%c\t%d\t%s\t%02d:%02d\t%02d:%02d", index + 1,
+ timer->active ? '>' : ' ',
+ timer->channel,
+ timer->PrintDay(timer->day),
+ timer->start / 100,
+ timer->start % 100,
+ timer->stop / 100,
+ timer->stop % 100); // user visible timer numbers start with '1'
+ SetText(buffer, false);
+}
+
+// --- cMenuTimer ------------------------------------------------------------
+
+class cMenuTimer : public cOsdMenu {
+public:
+ cMenuTimer(void);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+cMenuTimer::cMenuTimer(void)
+:cOsdMenu("Timer", 3, 2, 4, 10, 6)
+{
+ int i = 0;
+ cTimer *timer;
+
+ while ((timer = Timers.Get(i)) != NULL) {
+ Add(new cMenuTimerItem(i, timer));
+ i++;
+ }
+}
+
+eOSStatus cMenuTimer::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cOsdMenu::ProcessKey(Key);
+
+ if (status == osUnknown) {
+ switch (Key) {
+ //TODO need to block this if we are already editing a channel!
+ case kOk: return AddSubMenu(new cMenuEditTimer(Current()));
+ //TODO new timer
+ //TODO delete timer
+ case kLeft:
+ case kRight:
+ {
+ cTimer *timer = Timers.Get(Current());
+ if (timer) {
+ timer->active = (Key == kRight);
+ isyslog(LOG_INFO, "timer %d %sactivated", timer->Index() + 1, timer->active ? "" : "de");
+ RefreshCurrent();
+ DisplayCurrent(true);
+ Timers.Save();
+ }
+ }
+ default: break;
+ }
+ }
+ return status;
+}
+
+// --- cMenuMain -------------------------------------------------------------
+
+cMenuMain::cMenuMain(void)
+:cOsdMenu("Main")
+{
+ //TODO
+ Add(new cOsdItem("Channels", osChannels));
+ Add(new cOsdItem("Timer", osTimer));
+ Add(new cOsdItem("Recordings", osRecordings));
+}
+
+eOSStatus cMenuMain::ProcessKey(eKeys Key)
+{
+ eOSStatus status = cOsdMenu::ProcessKey(Key);
+
+ switch (status) {
+ case osChannels: return AddSubMenu(new cMenuChannels);
+ case osTimer: return AddSubMenu(new cMenuTimer);
+ //TODO Replay
+ default: break;
+ }
+ return status;
+}
+
diff --git a/menu.h b/menu.h
new file mode 100644
index 00000000..849d8ee3
--- /dev/null
+++ b/menu.h
@@ -0,0 +1,21 @@
+/*
+ * menu.h: The actual menu implementations
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: menu.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef _MENU_H
+#define _MENU_H
+
+#include "osd.h"
+
+class cMenuMain : public cOsdMenu {
+public:
+ cMenuMain(void);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+#endif //_MENU_H
diff --git a/osd.c b/osd.c
new file mode 100644
index 00000000..d7ab6993
--- /dev/null
+++ b/osd.c
@@ -0,0 +1,196 @@
+/*
+ * osd.c: Abstract On Screen Display layer
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: osd.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "osd.h"
+#include <assert.h>
+#include <string.h>
+
+// --- cOsdItem --------------------------------------------------------------
+
+cOsdItem::cOsdItem(eOSStatus Status)
+{
+ text = NULL;
+ offset = -1;
+ status = Status;
+ fresh = false;
+}
+
+cOsdItem::cOsdItem(char *Text, eOSStatus Status)
+{
+ text = NULL;
+ offset = -1;
+ status = Status;
+ fresh = false;
+ SetText(Text);
+}
+
+cOsdItem::~cOsdItem()
+{
+ delete text;
+}
+
+void cOsdItem::SetText(char *Text, bool Copy)
+{
+ delete text;
+ text = Copy ? strdup(Text) : Text;
+}
+
+void cOsdItem::Display(int Offset, bool Current)
+{
+ fresh |= Offset >= 0;
+ Current |= Offset < 0;
+ if (Offset >= 0)
+ offset = Offset;
+ //TODO current if Offset == -1 ???
+ if (offset >= 0)
+ Interface.WriteText(0, offset + 2, text, Current);
+}
+
+eOSStatus cOsdItem::ProcessKey(eKeys Key)
+{
+ return Key == kOk ? status : osUnknown;
+}
+
+// --- cOsdMenu --------------------------------------------------------------
+
+cOsdMenu::cOsdMenu(char *Title, int c0, int c1, int c2, int c3, int c4)
+{
+ title = strdup(Title);
+ cols[0] = c0;
+ cols[1] = c1;
+ cols[2] = c2;
+ cols[3] = c3;
+ cols[4] = c4;
+ first = count = 0;
+ current = -1;
+ subMenu = NULL;
+ Interface.Open();
+}
+
+cOsdMenu::~cOsdMenu()
+{
+ delete title;
+ delete subMenu;
+ Interface.Clear();
+ Interface.Close();
+}
+
+void cOsdMenu::Add(cOsdItem *Item, bool Current)
+{
+ cList<cOsdItem>::Add(Item);
+ count++;
+ if (Current && current < 0)
+ current = Item->Index();
+}
+
+void cOsdMenu::Display(void)
+{
+ Interface.Clear();
+ Interface.SetCols(cols);
+ Interface.WriteText(0, 0, title);
+ if (current < 0 && count)
+ current = 0; // just for safety - there HAS to be a current item!
+ int n = 0;
+ if (current - first >= MAXOSDITEMS) {
+ first = current - MAXOSDITEMS / 2;
+ if (first + MAXOSDITEMS > count)
+ first = count - MAXOSDITEMS;
+ if (first < 0)
+ first = 0;
+ }
+ for (int i = first; i < count; i++) {
+ cOsdItem *item = Get(i);
+ if (item)
+ item->Display(i - first, i == current);
+ if (++n == MAXOSDITEMS) //TODO get this from Interface!!!
+ break;
+ }
+}
+
+void cOsdMenu::RefreshCurrent(void)
+{
+ cOsdItem *item = Get(current);
+ if (item)
+ item->Set();
+}
+
+void cOsdMenu::DisplayCurrent(bool Current)
+{
+ cOsdItem *item = Get(current);
+ if (item)
+ item->Display(current - first, Current);
+}
+
+void cOsdMenu::CursorUp(void)
+{
+ if (current > 0) {
+ DisplayCurrent(false);
+ if (--current < first) {
+ first -= MAXOSDITEMS;
+ if (first < 0)
+ first = 0;
+ Display();
+ }
+ else
+ DisplayCurrent(true);
+ }
+}
+
+void cOsdMenu::CursorDown(void)
+{
+ if (current < count - 1) {
+ DisplayCurrent(false);
+ if (++current >= first + MAXOSDITEMS) {
+ first += MAXOSDITEMS;
+ if (first > count - MAXOSDITEMS)
+ first = count - MAXOSDITEMS;
+ Display();
+ }
+ else
+ DisplayCurrent(true);
+ }
+}
+
+eOSStatus cOsdMenu::AddSubMenu(cOsdMenu *SubMenu)
+{
+ delete subMenu;
+ subMenu = SubMenu;
+ subMenu->Display();
+ return osContinue; // convenience return value (see cMenuMain)
+}
+
+eOSStatus cOsdMenu::ProcessKey(eKeys Key)
+{
+ if (subMenu) {
+ eOSStatus status = subMenu->ProcessKey(Key);
+ if (status == osBack) {
+ delete subMenu;
+ subMenu = NULL;
+ RefreshCurrent();
+ Display();
+ status = osContinue;
+ }
+ return status;
+ }
+
+ cOsdItem *item = Get(current);
+ if (item) {
+ eOSStatus status = item->ProcessKey(Key);
+ if (status != osUnknown)
+ return status;
+ }
+ switch (Key) {
+ case kUp: CursorUp(); break;
+ case kDown: CursorDown(); break;
+ case kBack: return osBack;
+ default: return osUnknown;
+ }
+ return osContinue;
+}
+
diff --git a/osd.h b/osd.h
new file mode 100644
index 00000000..392f1a75
--- /dev/null
+++ b/osd.h
@@ -0,0 +1,68 @@
+/*
+ * osd.h: Abstract On Screen Display layer
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: osd.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef __OSD_H
+#define __OSD_H
+
+#include "config.h"
+#include "interface.h"
+#include "tools.h"
+
+#define MAXOSDITEMS 9
+
+enum eOSStatus { osUnknown,
+ osContinue,
+ osProcessed,
+ osChannels,
+ osTimer,
+ osRecordings,
+ osBack,
+ osEnd,
+ };
+
+class cOsdItem : public cListObject {
+private:
+ char *text;
+ int offset;
+ eOSStatus status;
+protected:
+ bool fresh;
+public:
+ cOsdItem(eOSStatus Status = osUnknown);
+ cOsdItem(char *Text, eOSStatus Status = osUnknown);
+ virtual ~cOsdItem();
+ void SetText(char *Text, bool Copy = true);
+ char *Text(void) { return text; }
+ void Display(int Offset = -1, bool Current = false);
+ virtual void Set(void) {}
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+class cOsdMenu : public cList<cOsdItem> {
+private:
+ char *title;
+ int cols[cInterface::MaxCols];
+ int first, current, count;
+ cOsdMenu *subMenu;
+protected:
+ void RefreshCurrent(void);
+ void DisplayCurrent(bool Current);
+ void CursorUp(void);
+ void CursorDown(void);
+ eOSStatus AddSubMenu(cOsdMenu *SubMenu);
+public:
+ cOsdMenu(char *Title, int c0 = 0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0);
+ virtual ~cOsdMenu();
+ int Current(void) { return current; }
+ void Add(cOsdItem *Item, bool Current = false);
+ void Display(void);
+ virtual eOSStatus ProcessKey(eKeys Key);
+ };
+
+#endif //__OSD_H
diff --git a/osm.c b/osm.c
new file mode 100644
index 00000000..cc017451
--- /dev/null
+++ b/osm.c
@@ -0,0 +1,119 @@
+/*
+ * osm.c: On Screen Menu for the Video Disk Recorder
+ *
+ * Copyright (C) 2000 Klaus Schmidinger
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ *
+ * The author can be reached at kls@cadsoft.de
+ *
+ * The project's page is at http://www.cadsoft.de/people/kls/vdr
+ *
+ * $Id: osm.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "config.h"
+#include "dvbapi.h"
+#include "interface.h"
+#include "menu.h"
+#include "tools.h"
+
+#ifdef DEBUG_REMOTE
+#define KEYS_CONF "keys-pc.conf"
+#else
+#define KEYS_CONF "keys.conf"
+#endif
+
+int main(int argc, char *argv[])
+{
+ openlog("vdr", LOG_PID | LOG_CONS, LOG_USER);
+ isyslog(LOG_INFO, "started");
+
+ Channels.Load("channels.conf");
+ Timers.Load("timers.conf");
+ if (!Keys.Load(KEYS_CONF))
+ Interface.LearnKeys();
+ Interface.Init();
+
+ cChannel::SwitchTo(CurrentChannel);
+
+ cMenuMain *Menu = NULL;
+ cTimer *Timer = NULL;
+ cDvbRecorder *Recorder = NULL;
+
+ for (;;) {
+ //TODO check for free disk space and delete files if necessary/possible
+ // in case there is an ongoing recording
+ if (!Timer && (Timer = cTimer::GetMatch()) != NULL) {
+ // switch to channel:
+ isyslog(LOG_INFO, "timer %d start", Timer->Index() + 1);
+ delete Menu;
+ Menu = NULL;
+ cChannel::SwitchTo(Timer->channel - 1);
+ ChannelLocked = true;
+ // start recording:
+ delete Recorder;
+ Recorder = new cDvbRecorder;
+ //TODO special filename handling!!!
+ if (!Recorder->Record(Timer->file, Timer->quality)) {
+ delete Recorder;
+ Recorder = NULL;
+ }
+ }
+ if (Timer) {
+ if (!Timer->Matches()) {
+ // stop recording:
+ if (Recorder)
+ Recorder->Stop();
+ // end timer:
+ ChannelLocked = false;
+ isyslog(LOG_INFO, "timer %d stop", Timer->Index() + 1);
+ Timer = NULL;
+ //TODO switch back to the previous channel???
+ //TODO clear single event timer???
+ }
+ }
+ eKeys key = Interface.GetKey();
+ if (Menu) {
+ switch (Menu->ProcessKey(key)) {
+ default: if (key != kMenu)
+ break;
+ case osBack:
+ case osEnd: delete Menu;
+ Menu = NULL;
+ break;
+ }
+ }
+ else {
+ switch (key) {
+ case kMenu: Menu = new cMenuMain;
+ Menu->Display();
+ break;
+ case kUp:
+ case kDown: {
+ int n = CurrentChannel + (key == kUp ? 1 : -1);
+ cChannel *channel = Channels.Get(n);
+ if (channel)
+ channel->Switch();
+ }
+ break;
+ default: break;
+ }
+ }
+ }
+ closelog();
+ return 1;
+}
diff --git a/remote.c b/remote.c
new file mode 100644
index 00000000..c2fb36f6
--- /dev/null
+++ b/remote.c
@@ -0,0 +1,257 @@
+/*
+ * remote.c: Interface to the Remote Control Unit
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: remote.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "remote.h"
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+#include "tools.h"
+
+cRcIo::cRcIo(char *DeviceName)
+{
+ dp = 0;
+ mode = modeB;
+ code = 0;
+ address = 0xFFFF;
+ t = 0;
+ firstTime = lastTime = 0;
+ minDelta = 0;
+ lastCommand = 0;
+ if ((f = open(DeviceName, O_RDWR | O_NONBLOCK)) >= 0) {
+ struct termios t;
+ if (tcgetattr(f, &t) == 0) {
+ cfsetspeed(&t, B9600);
+ cfmakeraw(&t);
+ if (tcsetattr(f, TCSAFLUSH, &t) == 0)
+ return;
+ }
+ close(f);
+ }
+ f = -1;
+}
+
+cRcIo::~cRcIo()
+{
+ if (f >= 0)
+ close(f);
+}
+
+int cRcIo::ReceiveByte(bool Wait)
+{
+ // Returns the byte if one was received within 1 second, -1 otherwise
+ if (f >= 0) {
+ fd_set set;
+ struct timeval timeout;
+ timeout.tv_sec = Wait ? 1 : 0;
+ timeout.tv_usec = Wait ? 0 : 10000;
+ FD_ZERO(&set);
+ FD_SET(f, &set);
+ if (select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0) {
+ if (FD_ISSET(f, &set)) {
+ unsigned char b;
+ if (read(f, &b, 1) == 1)
+ return b;
+ }
+ }
+ }
+ return -1;
+}
+
+bool cRcIo::SendByteHandshake(unsigned char c)
+{
+ if (f >= 0 && write(f, &c, 1) == 1) {
+ for (int reply = ReceiveByte(); reply >= 0;) {
+ if (reply == c)
+ return true;
+ else if (reply == 'X') {
+ // skip any incoming RC code - it will come again
+ for (int i = 6; i--;) {
+ if (ReceiveByte(false) < 0)
+ return false;
+ }
+ }
+ else
+ return false;
+ }
+ }
+ return false;
+}
+
+bool cRcIo::SendByte(unsigned char c)
+{
+ for (int retry = 5; retry--;) {
+ if (SendByteHandshake(c))
+ return true;
+ }
+ return false;
+}
+
+void cRcIo::Flush(int WaitSeconds)
+{
+ time_t t0 = time(NULL);
+
+ for (;;) {
+ while (ReceiveByte(false) >= 0)
+ t0 = time(NULL);
+ if (time(NULL) - t0 >= WaitSeconds)
+ break;
+ }
+}
+
+bool cRcIo::SetCode(unsigned char Code, unsigned short Address)
+{
+ code = Code;
+ address = Address;
+ minDelta = 200;
+ return SendCommand(code);
+}
+
+bool cRcIo::SetMode(unsigned char Mode)
+{
+ mode = Mode;
+ return SendCommand(mode);
+}
+
+bool cRcIo::GetCommand(unsigned int *Command, unsigned short *Address)
+{
+#pragma pack(1)
+ union {
+ struct {
+ unsigned short address;
+ unsigned int command;
+ } data;
+ unsigned char raw[6];
+ } buffer;
+#pragma pack()
+
+ Flush();
+ if (Command && ReceiveByte() == 'X') {
+ for (int i = 0; i < 6; i++) {
+ int b = ReceiveByte(false);
+ if (b >= 0)
+ buffer.raw[i] = b;
+ else
+ return false;
+ }
+ if (Address)
+ *Address = ntohs(buffer.data.address); // the PIC sends bytes in "network order"
+ else if (address != ntohs(buffer.data.address))
+ return false;
+ *Command = ntohl(buffer.data.command);
+ if (code == 'B' && address == 0x0000 && *Command == 0x00004000)
+ // Well, well, if it isn't the "d-box"...
+ // This remote control sends the above command before and after
+ // each keypress - let's just drop this:
+ return false;
+ if (*Command == lastCommand) {
+ // let's have a timeout to avoid getting overrun by commands
+ int now = time_ms();
+ int delta = now - lastTime;
+ if (delta < minDelta)
+ minDelta = delta; // dynamically adjust to the smallest delta
+ lastTime = now;
+ if (delta < minDelta * 1.3) { // if commands come in rapidly...
+ if (now - firstTime < 250)
+ return false; // ...repeat function kicks in after 250ms
+ return true;
+ }
+ }
+ lastTime = firstTime = time_ms();
+ lastCommand = *Command;
+ return true;
+ }
+ if (time(NULL) - t > 60) {
+ SendCommand(code); // in case the PIC listens to the wrong code
+ t = time(NULL);
+ }
+ return false;
+}
+
+bool cRcIo::SendCommand(unsigned char Cmd)
+{
+ return SendByte(Cmd | 0x80);
+}
+
+bool cRcIo::Digit(int n, int v)
+{
+ return SendByte(((n & 0x03) << 5) | (v & 0x0F) | (((dp >> n) & 0x01) << 4));
+}
+
+bool cRcIo::Number(int n, bool Hex)
+{
+ if (!Hex) {
+ char buf[8];
+ sprintf(buf, "%4d", n & 0xFFFF);
+ n = 0;
+ for (char *d = buf; *d; d++) {
+ if (*d == ' ')
+ *d = 0xF;
+ n = (n << 4) | ((*d - '0') & 0x0F);
+ }
+ }
+ for (int i = 0; i < 4; i++) {
+ if (!Digit(i, n))
+ return false;
+ n >>= 4;
+ }
+ return SendCommand(mode);
+}
+
+bool cRcIo::String(char *s)
+{
+ char *chars = mode == modeH ? "0123456789ABCDEF" : "0123456789-EHLP ";
+ int n = 0;
+
+ for (int i = 0; *s && i < 4; s++, i++) {
+ n <<= 4;
+ for (char *c = chars; *c; c++) {
+ if (*c == *s) {
+ n |= c - chars;
+ break;
+ }
+ }
+ }
+ return Number(n, mode == modeH);
+}
+
+bool cRcIo::DetectCode(unsigned char *Code, unsigned short *Address)
+{
+ // Caller should initialize 'Code' to 0 and call DetectCode()
+ // until it returns true. Whenever DetectCode() returns false
+ // and 'Code' is not 0, the caller can use 'Code' to display
+ // a message like "Trying code '%c'". If false is returned and
+ // 'Code' is 0, all possible codes have been tried and the caller
+ // can either stop calling DetectCode() (and give some error
+ // message), or start all over again.
+ if (*Code < 'A' || *Code > 'D') {
+ *Code = 'A';
+ return false;
+ }
+ if (*Code <= 'D') {
+ SetMode(modeH);
+ char buf[5];
+ sprintf(buf, "C0D%c", *Code);
+ String(buf);
+ SetCode(*Code, 0);
+ unsigned int Command;
+ if (GetCommand(&Command, Address))
+ return true;
+ if (*Code < 'D') {
+ (*Code)++;
+ return false;
+ }
+ }
+ *Code = 0;
+ return false;
+}
+
diff --git a/remote.h b/remote.h
new file mode 100644
index 00000000..68279291
--- /dev/null
+++ b/remote.h
@@ -0,0 +1,43 @@
+/*
+ * remote.h: Interface to the Remote Control Unit
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: remote.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef __REMOTE_H
+#define __REMOTE_H
+
+#include <stdio.h>
+#include <time.h>
+
+class cRcIo {
+private:
+ int f;
+ unsigned char dp, code, mode;
+ unsigned short address;
+ time_t t;
+ int firstTime, lastTime, minDelta;
+ unsigned int lastCommand;
+ bool SendCommand(unsigned char Cmd);
+ int ReceiveByte(bool Wait = true);
+ bool SendByteHandshake(unsigned char c);
+ bool SendByte(unsigned char c);
+public:
+ enum { modeH = 'h', modeB = 'b', modeS = 's' };
+ cRcIo(char *DeviceName);
+ ~cRcIo();
+ void Flush(int WaitSeconds = 0);
+ bool SetCode(unsigned char Code, unsigned short Address);
+ bool SetMode(unsigned char Mode);
+ bool GetCommand(unsigned int *Command, unsigned short *Address = NULL);
+ bool Digit(int n, int v);
+ bool Number(int n, bool Hex = false);
+ void Points(unsigned char Dp) { dp = Dp; }
+ bool String(char *s);
+ bool DetectCode(unsigned char *Code, unsigned short *Address);
+ };
+
+#endif //__REMOTE_H
diff --git a/timers.conf b/timers.conf
new file mode 100644
index 00000000..ac002a99
--- /dev/null
+++ b/timers.conf
@@ -0,0 +1,9 @@
+1:2:-----S-:2210:2320:H:99:99:Wochenshow
+1:3:M------:2125:2205:H:99:99:Neues
+1:15:MTWTF--:1828:1901:M:10:5:nano
+1:2:-----S-:1737:1827:H:99:99:kls/StarTrek/DS9
+1:3:M------:2110:2230:H:99:99:SevenDays
+1:3:---T---:2215:2300:H:99:99:SwItch
+0:1:1:0:0:H:99:99:#
+0:1:1:0:0:H:99:99:#
+0:1:1:0:0:L:0:5:#
diff --git a/tools.c b/tools.c
new file mode 100644
index 00000000..e0eeaefd
--- /dev/null
+++ b/tools.c
@@ -0,0 +1,124 @@
+/*
+ * tools.c: Various tools
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: tools.c 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#include "tools.h"
+#include <stdlib.h>
+#include <sys/time.h>
+
+int time_ms(void)
+{
+ struct timeval t;
+ if (gettimeofday(&t, NULL) == 0)
+ return t.tv_sec * 1000 + t.tv_usec / 1000;
+ return 0;
+}
+
+// --- cListObject -----------------------------------------------------------
+
+cListObject::cListObject(void)
+{
+ prev = next = NULL;
+}
+
+cListObject::~cListObject()
+{
+}
+
+void cListObject::Append(cListObject *Object)
+{
+ next = Object;
+ Object->prev = this;
+}
+
+void cListObject::Unlink(void)
+{
+ if (next)
+ next->prev = prev;
+ if (prev)
+ prev->next = next;
+}
+
+int cListObject::Index(void)
+{
+ cListObject *p = prev;
+ int i = 0;
+
+ while (p) {
+ i++;
+ p = p->prev;
+ }
+ return i;
+}
+
+// --- cListBase -------------------------------------------------------------
+
+cListBase::cListBase(void)
+{
+ objects = lastObject = NULL;
+}
+
+cListBase::~cListBase()
+{
+ Clear();
+ while (objects) {
+ cListObject *object = objects->Next();
+ delete objects;
+ objects = object;
+ }
+}
+
+void cListBase::Add(cListObject *Object)
+{
+ if (lastObject)
+ lastObject->Append(Object);
+ else
+ objects = Object;
+ lastObject = Object;
+}
+
+void cListBase::Del(cListObject *Object)
+{
+ if (Object == objects)
+ objects = Object->Next();
+ if (Object == lastObject)
+ lastObject = Object->Prev();
+ Object->Unlink();
+ delete Object;
+}
+
+void cListBase::Clear(void)
+{
+ while (objects) {
+ cListObject *object = objects->Next();
+ delete objects;
+ objects = object;
+ }
+ objects = lastObject = NULL;
+}
+
+cListObject *cListBase::Get(int Index)
+{
+ cListObject *object = objects;
+ while (object && Index-- > 0)
+ object = object->Next();
+ return object;
+}
+
+int cListBase::Count(void)
+{
+ int n = 0;
+ cListObject *object = objects;
+
+ while (object) {
+ n++;
+ object = object->Next();
+ }
+ return n;
+}
+
diff --git a/tools.h b/tools.h
new file mode 100644
index 00000000..43f85254
--- /dev/null
+++ b/tools.h
@@ -0,0 +1,54 @@
+/*
+ * tools.h: Various tools
+ *
+ * See the main source file 'osm.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: tools.h 1.1 2000/02/19 13:36:48 kls Exp $
+ */
+
+#ifndef __TOOLS_H
+#define __TOOLS_H
+
+#include <syslog.h>
+
+//TODO
+#define dsyslog syslog
+#define esyslog syslog
+#define isyslog syslog
+
+class cListObject {
+private:
+ cListObject *prev, *next;
+public:
+ cListObject(void);
+ virtual ~cListObject();
+ void Append(cListObject *Object);
+ void Unlink(void);
+ int Index(void);
+ cListObject *Prev(void) { return prev; }
+ cListObject *Next(void) { return next; }
+ };
+
+class cListBase {
+protected:
+ cListObject *objects, *lastObject;
+ cListBase(void);
+public:
+ virtual ~cListBase();
+ void Add(cListObject *Object);
+ void Del(cListObject *Object);
+ void Clear(void);
+ cListObject *Get(int Index);
+ int Count(void);
+ };
+
+template<class T> class cList : public cListBase {
+public:
+ T *Get(int Index) { return (T *)cListBase::Get(Index); }
+ T *First(void) { return (T *)objects; }
+ };
+
+int time_ms(void);
+
+#endif //__TOOLS_H