Commit c475c637 authored by Klaus Schmidinger's avatar Klaus Schmidinger

Version 0.01 (Initial revision).

# 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
%.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
-rm $(OBJS) osm
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 (
For details about the "Video Disk Recorder" project please
refer to
The author can be contacted at
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 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
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
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 if you'd like to comment on this.
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).
Pro 7:12480:v:1:27500:255:256
Hessen 3:11837:h:1:27500:301:302
BR alpha:11837:h:1:27500:701:702
SWR BW:11837:h:1:27500:801:802
ORF Sat:11954:h:1:27500:506:507
ZDF Infobox:11954:h:1:27500:610:620
Super RTL:12188:h:1:27500:165:120
DW TV:12363:v:1:27500:305:306
Kabel 1:12480:v:1:27500:511:512
Sky News:12552:v:1:22000:305:306
Grand Tour.:12670:v:1:22000:289:290
Eins Extra:12722:h:1:22000:101:102
Eins Festival:12722:h:1:22000:201:202
Eins MuXx:12722:h:1:22000:301:302
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
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
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
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
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
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
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
RAI Uno:12363:v:1:27500:289:290
RTP International:12363:v:1:27500:300:301
Fashion TV:12402:v:1:27500:163:92
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
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
* 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 },
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) {
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
if (Name) {
fprintf(stderr, "unknown key in %s, line %d\n", fileName, line);
result = false;
fprintf(stderr, "error in %s, line %d\n", fileName, line);
result = false;
fprintf(stderr, "can't open '%s'\n", fileName);
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;
result = false;
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)
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;
// -- cChannel ---------------------------------------------------------------
*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 -----------------------------------------------------------------
*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;
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;
* 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!
k0, k1, k2, k3, k4, k5, k6, k7, k8, k9,
struct tKey {
eKeys type;
char *name;
unsigned int code;
class cKeys {
char *fileName;
unsigned char code;
unsigned short address;
tKey *keys;
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 {
enum { MaxChannelName = 32 }; // 31 chars + terminating 0!
char name[MaxChannelName];
int frequency; // MHz
char polarization;
int diseqc;
int srate;
int vpid;
int apid;
bool Parse(char *s);
bool Save(FILE *f);
bool Switch(void);
static bool SwitchTo(int i);
class cTimer : public cListObject {
enum { MaxFileName = 256 };
int active;
int channel;
int day;
int start;
int stop;
char quality;
int priority;
int lifetime;
char file[MaxFileName];
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> {
char *fileName;
void Clear(void)
delete fileName;
bool Load(char *FileName)
isyslog(LOG_INFO, "loading %s", FileName);
bool result = true;
fileName = strdup(FileName);
FILE *f = fopen(fileName, "r");
if (f) {
int line = 0;
char buffer[MaxBuffer];
while (fgets(buffer, sizeof(buffer), f) > 0) {
T *l = new T;
if (l->Parse(buffer))
else {
fprintf(stderr, "error in %s, line %d\n", fileName, line);
delete l;
result = false;