From 7e4b4d290570aee1d24241b0e0ac10e7c8148a36 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Wed, 20 Sep 2000 18:00:00 +0200 Subject: Version 0.64 - NOTE: If you are using DVB driver version 0.7 you need to load the dvb.o module with option outstream=0, so your insmod statement should read 'insmod dvb.o outstream=0'. This is currently necessary because 'vdr' still works with AV_PES data. - Video files now have the 'group read' bit set. - Fixed handling errors in 'readstring()'. - Handling SIGPIPE and re-establishing handler after intercepting a signal. - The configuration files are now by default read from the video directory. This can be changed by using the new '-c' option. Make sure you copy your current '*.conf' files to your video directory ('/video' by default), or use "-c ." to get the old behaviour of loading the configuration files from the current directory. - Waiting for input is now handled by a common function, which improves response time on user actions. As a consequence the EIT data may sometimes not be displayed, but this will change later when cEIT runs as a separate thread. - The new SVDRP command 'HITK' (thanks to Guido Fiala!) can be used to 'hit' a remote control key. Establish an SVDRP connection and enter HITK without a parameter for a list of all valid key names. - The new SVDRP command 'GRAB' (thanks to Guido Fiala!) can be used to grab the current frame and save it to a file. - The new SVDRP commands 'OVL*' can be used to control video overlays (thanks to Guido Fiala!). This is mainly for use in the 'kvdr' tool (see the 'kvdr' page at http://www.s.netic.de/gfiala). - If the name of the video directory used with the '-v' option had trailing slashes, the recording file names have been damaged. Trailing slashes are now silently removed. - Fixed a buffer overflow in EIT parsing. - Added a security warning regarding SVDRP to the INSTALL file. - Fixed 'confirm' dialog. - The daemon mode (option '-d') now no longer works with REMOTE=KBD (there is no stdin in daemon mode, so KBD makes no sense - plus it sometimes crashed). --- CONTRIBUTORS | 3 + HISTORY | 37 +++++++- INSTALL | 32 ++++--- Makefile | 24 ++--- TODO | 1 - config.c | 18 ++-- config.h | 5 +- dvbapi.c | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- dvbapi.h | 27 +++++- eit.c | 19 ++-- interface.c | 35 +++++--- interface.h | 8 +- menu.c | 4 +- remote.c | 60 ++++++------- remote.h | 17 ++-- svdrp.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- svdrp.h | 41 +++++---- timers.conf | 12 +-- tools.c | 157 +++++++++++++++++++++++++-------- tools.h | 26 +++++- vdr.c | 44 ++++++---- videodir.c | 4 +- 22 files changed, 890 insertions(+), 232 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9f6aa1b..03fd4bc 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -18,6 +18,9 @@ Heino Goldenstein Guido Fiala for implementing slow forward/back + for implementing the SVDRP command 'HITK' + for implementing image grabbing + for implementing overlay capabilities (see his 'kvdr' tool at http://www.s.netic.de/gfiala) Robert Schneider for implementing EIT support for displaying the current/next info diff --git a/HISTORY b/HISTORY index dafab3b..f8a1007 100644 --- a/HISTORY +++ b/HISTORY @@ -167,4 +167,39 @@ Video Disk Recorder Revision History - When directly selecting a channel by entering the channel number, the digits entered so far together with the name of that channel are displayed on the OSD (suggested by Martin Hammerschmid). - + +2000-09-20: Version 0.64 + +- NOTE: If you are using DVB driver version 0.7 you need to load the dvb.o + module with option outstream=0, so your insmod statement should read + 'insmod dvb.o outstream=0'. This is currently necessary because 'vdr' + still works with AV_PES data. +- Video files now have the 'group read' bit set. +- Fixed handling errors in 'readstring()'. +- Handling SIGPIPE and re-establishing handler after intercepting a signal. +- The configuration files are now by default read from the video directory. + This can be changed by using the new '-c' option. Make sure you copy your + current '*.conf' files to your video directory ('/video' by default), or + use "-c ." to get the old behaviour of loading the configuration files + from the current directory. +- Waiting for input is now handled by a common function, which improves + response time on user actions. As a consequence the EIT data may sometimes + not be displayed, but this will change later when cEIT runs as a separate + thread. +- The new SVDRP command 'HITK' (thanks to Guido Fiala!) can be used to 'hit' + a remote control key. Establish an SVDRP connection and enter HITK without + a parameter for a list of all valid key names. +- The new SVDRP command 'GRAB' (thanks to Guido Fiala!) can be used to grab + the current frame and save it to a file. +- The new SVDRP commands 'OVL*' can be used to control video overlays (thanks + to Guido Fiala!). This is mainly for use in the 'kvdr' tool (see the 'kvdr' + page at http://www.s.netic.de/gfiala). +- If the name of the video directory used with the '-v' option had trailing + slashes, the recording file names have been damaged. Trailing slashes are + now silently removed. +- Fixed a buffer overflow in EIT parsing. +- Added a security warning regarding SVDRP to the INSTALL file. +- Fixed 'confirm' dialog. +- The daemon mode (option '-d') now no longer works with REMOTE=KBD (there + is no stdin in daemon mode, so KBD makes no sense - plus it sometimes + crashed). diff --git a/INSTALL b/INSTALL index 1ebffa1..7b953be 100644 --- a/INSTALL +++ b/INSTALL @@ -16,7 +16,11 @@ you will have to change the definition of DVBDIR in the Makefile. This program requires the card driver version 0.05 or higher -to work properly. +to work properly. If you are using driver version 0.7 you need +to load the dvb.o module with option outstream=0, so your insmod +statement should read 'insmod dvb.o outstream=0'. This is currently +necessary because 'vdr' works with AV_PES data and will change +once it has been modified to work directly with MPEG2. After extracting the package, change into the VDR directory and type 'make'. This should produce an executable file @@ -48,6 +52,11 @@ port ("Simple Video Disk Recorder Protocol"). By default, it listens on port 2001 (use the --port=PORT option to change this). For details about the SVDRP syntax see the source file 'svdrp.c'. +WARNING: DUE TO THE OPEN SVDRP PORT THIS PROGRAM MAY CONSTITUTE A +======= POTENTIAL SECURITY HAZARD! IF YOU ARE NOT RUNNING VDR IN + A CONTROLLED ENVIRONMENT, YOU MAY WANT TO DISABLE SVDRP + BY USING '--port=0'! + If the program shall run as a daemon, use the --daemon option. This will completely detach it from the terminal and will continue as a background process. @@ -64,7 +73,9 @@ All recordings are written into directories below "/video". Please make sure this directory exists, and that the user who runs the 'vdr' program has read and write access to that directory. If you prefer a different location for your video files, you can use -the '-v' option to change that. +the '-v' option to change that. Please make sure that the directory +name you use with '-v' is a clean and absolute path name (no '..' or +multiple slashes). Note that the file system need not be 64-bit proof, since the 'vdr' program splits video files into chunks of about 1GB. You should use @@ -102,14 +113,15 @@ 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 'vdr' program -was started (this will become configurable later). The configuration -files can be edited with any text editor, or will be written by the -'vdr' 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. +channels, remote control keys and timers. By default these files are +assumed to be located in the video directory, but a different directory +can be used with the '-c' option. + +The configuration files can be edited with any text editor, or will be written +by the 'vdr' 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. The files that come with this package contain the author's selections, so please make sure you adapt these to your personal taste. Also make sure diff --git a/Makefile b/Makefile index 2f00459..fef7a6f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.9 2000/09/10 08:55:45 kls Exp $ +# $Id: Makefile 1.11 2000/09/20 17:01:57 kls Exp $ DVBDIR = ../DVB @@ -26,21 +26,21 @@ endif all: vdr -config.o : config.c config.h dvbapi.h eit.h interface.h tools.h -dvbapi.o : dvbapi.c config.h dvbapi.h interface.h tools.h videodir.h -eit.o : eit.c eit.h -interface.o: interface.c config.h dvbapi.h eit.h interface.h remote.h tools.h -menu.o : menu.c config.h dvbapi.h interface.h menu.h osd.h recording.h tools.h -osd.o : osd.c config.h dvbapi.h interface.h osd.h tools.h -vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h videodir.h -recording.o: recording.c config.h dvbapi.h interface.h recording.h tools.h videodir.h -remote.o : remote.c remote.h tools.h -svdrp.o : svdrp.c svdrp.h config.h interface.h tools.h +config.o : config.c config.h dvbapi.h eit.h interface.h svdrp.h tools.h +dvbapi.o : dvbapi.c config.h dvbapi.h interface.h svdrp.h tools.h videodir.h +eit.o : eit.c eit.h tools.h +interface.o: interface.c config.h dvbapi.h eit.h interface.h remote.h svdrp.h tools.h +menu.o : menu.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h +osd.o : osd.c config.h dvbapi.h interface.h osd.h svdrp.h tools.h +recording.o: recording.c config.h dvbapi.h interface.h recording.h svdrp.h tools.h videodir.h +remote.o : remote.c config.h dvbapi.h remote.h tools.h +svdrp.o : svdrp.c config.h dvbapi.h interface.h svdrp.h tools.h tools.o : tools.c tools.h +vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h videodir.h videodir.o : videodir.c tools.h videodir.h vdr: $(OBJS) - g++ -g -O2 $(OBJS) -lncurses -o vdr + g++ -g -O2 $(OBJS) -lncurses -ljpeg -o vdr clean: -rm $(OBJS) vdr diff --git a/TODO b/TODO index 09e9a10..f2fb5ea 100644 --- a/TODO +++ b/TODO @@ -7,5 +7,4 @@ TODO list for the Video Disk Recorder project scenes in order to archive them (or, reversely, cut out commercial breaks). * Implement channel scanning. -* Better support for encrypted channels. * Implement remaining commands in SVDRP. diff --git a/config.c b/config.c index 0a965ee..e161efd 100644 --- a/config.c +++ b/config.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.22 2000/09/10 15:07:15 kls Exp $ + * $Id: config.c 1.23 2000/09/17 09:11:59 kls Exp $ */ #include "config.h" @@ -155,14 +155,22 @@ eKeys cKeys::Get(unsigned int Code) return kNone; } -unsigned int cKeys::Encode(const char *Command) +eKeys cKeys::Translate(const char *Command) { - if (Command != NULL) { + if (Command) { const tKey *k = keys; - while ((k->type != kNone) && strcmp(k->name, Command) != 0) + while ((k->type != kNone) && strcasecmp(k->name, Command) != 0) k++; - return k->code; + return k->type; } + return kNone; +} + +unsigned int cKeys::Encode(const char *Command) +{ + eKeys k = Translate(Command); + if (k != kNone) + return keys[k].code; return 0; } diff --git a/config.h b/config.h index 30dd9ba..418fa63 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.19 2000/09/10 15:05:08 kls Exp $ + * $Id: config.h 1.21 2000/09/17 09:08:13 kls Exp $ */ #ifndef __CONFIG_H @@ -17,7 +17,7 @@ #include "dvbapi.h" #include "tools.h" -#define VDRVERSION "0.63" +#define VDRVERSION "0.64" #define MaxBuffer 10000 @@ -55,6 +55,7 @@ public: void SetDummyValues(void); bool Load(const char *FileName = NULL); bool Save(void); + eKeys Translate(const char *Command); unsigned int Encode(const char *Command); eKeys Get(unsigned int Code); void Set(eKeys Key, unsigned int Code); diff --git a/dvbapi.c b/dvbapi.c index 9f9cbab..1468de6 100644 --- a/dvbapi.c +++ b/dvbapi.c @@ -4,14 +4,18 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.24 2000/09/10 10:25:09 kls Exp $ + * $Id: dvbapi.c 1.27 2000/09/17 12:45:55 kls Exp $ */ #include "dvbapi.h" #include #include +extern "C" { +#include +} #include #include +#include #include #include #include @@ -139,7 +143,7 @@ cIndexFile::cIndexFile(const char *FileName, bool Record) LOG_ERROR; } if (Record) { - if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) >= 0) { + if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP)) >= 0) { if (delta) { esyslog(LOG_ERR, "ERROR: padding index file with %d '0' bytes", delta); while (delta--) @@ -297,7 +301,7 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset) bool cIndexFile::StoreResume(int Index) { if (fileName) { - int resumeFile = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + int resumeFile = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP); if (resumeFile >= 0) { if (write(resumeFile, &Index, sizeof(Index)) != sizeof(Index)) LOG_ERROR_STR(fileName); @@ -315,8 +319,8 @@ char *cIndexFile::Str(int Index, bool WithFrame) static char buffer[16]; int f = (Index % FRAMESPERSEC) + 1; int s = (Index / FRAMESPERSEC); - int m = s / 60 % 60; - int h = s / 3600; + int m = s / 60 % 60; + int h = s / 3600; s %= 60; snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f); return buffer; @@ -511,7 +515,7 @@ protected: char *fileName, *pFileNumber; bool stop; int GetAvPesLength(void) - { + { if (Byte(0) == 'A' && Byte(1) == 'V' && Byte(4) == 'U') return (Byte(6) << 8) + Byte(7) + AV_PES_HEADER_LEN; return 0; @@ -751,7 +755,7 @@ int cRecordBuffer::Write(int Max) if (n) { if (stop && pictureType == I_FRAME) { ok = false; - return -1; // finish the recording before the next 'I' frame + return -1; // finish the recording before the next 'I' frame } if (NextFile()) { if (index && pictureType != NO_PICTURE) @@ -801,7 +805,7 @@ private: void Close(void); public: cReplayBuffer(int *OutFile, const char *FileName); - virtual ~cReplayBuffer(); + virtual ~cReplayBuffer(); virtual int Read(int Max = -1); virtual int Write(int Max = -1); void SetMode(eReplayMode Mode); @@ -1065,9 +1069,14 @@ cDvbApi::cDvbApi(const char *FileName) if (videoDev < 0) LOG_ERROR; cols = rows = 0; + + ovlGeoSet = ovlStat = ovlFbSet = false; + ovlBrightness = ovlColour = ovlHue = ovlContrast = 32768; + ovlClipCount = 0; + #if defined(DEBUG_OSD) || defined(REMOTE_KBD) initscr(); - keypad(stdscr, TRUE); + keypad(stdscr, true); nonl(); cbreak(); noecho(); @@ -1076,7 +1085,7 @@ cDvbApi::cDvbApi(const char *FileName) #if defined(DEBUG_OSD) memset(&colorPairs, 0, sizeof(colorPairs)); start_color(); - leaveok(stdscr, TRUE); + leaveok(stdscr, true); window = NULL; #endif lastProgress = lastTotal = -1; @@ -1089,6 +1098,7 @@ cDvbApi::~cDvbApi() Close(); Stop(); StopRecord(); + OvlO(false); //Overlay off! close(videoDev); } #if defined(DEBUG_OSD) || defined(REMOTE_KBD) @@ -1187,6 +1197,242 @@ void cDvbApi::Cleanup(void) PrimaryDvbApi = NULL; } +bool cDvbApi::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY) +{ + int result = 0; + // just do this once? + struct video_mbuf mbuf; + result |= ioctl(videoDev, VIDIOCGMBUF, &mbuf); + int msize = mbuf.size; + // gf: this needs to be a protected member of cDvbApi! //XXX kls: WHY??? + unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0); + if (!mem || mem == (unsigned char *)-1) + return false; + // set up the size and RGB + struct video_capability vc; + result |= ioctl(videoDev, VIDIOCGCAP, &vc); + struct video_mmap vm; + vm.frame = 0; + if ((SizeX > 0) && (SizeX <= vc.maxwidth) && + (SizeY > 0) && (SizeY <= vc.maxheight)) { + vm.width = SizeX; + vm.height = SizeY; + } + else { + vm.width = vc.maxwidth; + vm.height = vc.maxheight; + } + vm.format = VIDEO_PALETTE_RGB24; + // this needs to be done every time: + result |= ioctl(videoDev, VIDIOCMCAPTURE, &vm); + result |= ioctl(videoDev, VIDIOCSYNC, &vm.frame); + // make RGB out of BGR: + int memsize = vm.width * vm.height; + unsigned char *mem1 = mem; + for (int i = 0; i < memsize; i++) { + unsigned char tmp = mem1[2]; + mem1[2] = mem1[0]; + mem1[0] = tmp; + mem1 += 3; + } + + if (Quality < 0) + Quality = 255; //XXX is this 'best'??? + + isyslog(LOG_INFO, "grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height); + FILE *f = fopen(FileName, "wb"); + if (f) { + if (Jpeg) { + // write JPEG file: + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, f); + cinfo.image_width = vm.width; + cinfo.image_height = vm.height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, Quality, true); + jpeg_start_compress(&cinfo, true); + + int rs = vm.width * 3; + JSAMPROW rp[vm.height]; + for (int k = 0; k < vm.height; k++) + rp[k] = &mem[rs * k]; + jpeg_write_scanlines(&cinfo, rp, vm.height); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + } + else { + // write PNM file: + if (fprintf(f, "P6\n%d\n%d\n255\n", vm.width, vm.height) < 0 || + fwrite(mem, vm.width * vm.height * 3, 1, f) < 0) { + LOG_ERROR_STR(FileName); + result |= 1; + } + } + fclose(f); + } + else { + LOG_ERROR_STR(FileName); + result |= 1; + } + + if (ovlStat && ovlGeoSet) { + // switch the Overlay on again (gf: why have i to do anything again?) + OvlG(ovlSizeX, ovlSizeY, ovlPosX, ovlPosY); + } + if (ovlFbSet) + OvlP(ovlBrightness, ovlColour, ovlHue, ovlContrast); + + munmap(mem, msize); + return result == 0; +} + +bool cDvbApi::OvlF(int SizeX, int SizeY, int FbAddr, int Bpp, int Palette) +{ + int result = 0; + // get the actual X-Server settings??? + // plausibility-check problem: can't be verified w/o X-server!!! + if (SizeX <= 0 || SizeY <= 0 || FbAddr == 0 || Bpp / 8 > 4 || + Bpp / 8 <= 0 || Palette <= 0 || Palette > 13 || ovlClipCount < 0 || + SizeX > 4096 || SizeY > 4096) { + ovlFbSet = ovlGeoSet = false; + OvlO(false); + return false; + } + else { + dsyslog(LOG_INFO, "OvlF: %d %d %x %d %d", SizeX, SizeY, FbAddr, Bpp, Palette); + // this is the problematic part! + struct video_buffer vb; + result |= ioctl(videoDev, VIDIOCGFBUF, &vb); + vb.base = (void*)FbAddr; + vb.depth = Bpp; + vb.height = SizeY; + vb.width = SizeX; + vb.bytesperline = ((vb.depth + 1) / 8) * vb.width; + //now the real thing: setting the framebuffer + result |= ioctl(videoDev, VIDIOCSFBUF, &vb); + if (result) { + ovlFbSet = ovlGeoSet = false; + ovlClipCount = 0; + OvlO(false); + return false; + } + else { + ovlFbSizeX = SizeX; + ovlFbSizeY = SizeY; + ovlBpp = Bpp; + ovlPalette = Palette; + ovlFbSet = true; + return true; + } + } +} + +bool cDvbApi::OvlG(int SizeX, int SizeY, int PosX, int PosY) +{ + int result = 0; + // get the actual X-Server settings??? + struct video_capability vc; + result |= ioctl(videoDev, VIDIOCGCAP, &vc); + if (!ovlFbSet) + return false; + if (SizeX < vc.minwidth || SizeY < vc.minheight || + SizeX > vc.maxwidth || SizeY>vc.maxheight +// || PosX > FbSizeX || PosY > FbSizeY +// PosX < -SizeX || PosY < -SizeY || + ) { + ovlGeoSet = false; + OvlO(false); + return false; + } + else { + struct video_window vw; + result |= ioctl(videoDev, VIDIOCGWIN, &vw); + vw.x = PosX; + vw.y = PosY; + vw.width = SizeX; + vw.height = SizeY; + vw.chromakey = ovlPalette; + vw.flags = VIDEO_WINDOW_CHROMAKEY; // VIDEO_WINDOW_INTERLACE; //VIDEO_CLIP_BITMAP; + vw.clips = ovlClipRects; + vw.clipcount = ovlClipCount; + result |= ioctl(videoDev, VIDIOCSWIN, &vw); + if (result) { + ovlGeoSet = false; + ovlClipCount = 0; + return false; + } + else { + ovlSizeX = SizeX; + ovlSizeY = SizeY; + ovlPosX = PosX; + ovlPosY = PosY; + ovlGeoSet = true; + ovlStat = true; + return true; + } + } +} + +bool cDvbApi::OvlC(int ClipCount, CRect *cr) +{ + if (ovlGeoSet && ovlFbSet) { + for (int i = 0; i < ClipCount; i++) { + ovlClipRects[i].x = cr[i].x; + ovlClipRects[i].y = cr[i].y; + ovlClipRects[i].width = cr[i].width; + ovlClipRects[i].height = cr[i].height; + ovlClipRects[i].next = &(ovlClipRects[i + 1]); + } + ovlClipCount = ClipCount; + //use it: + return OvlG(ovlSizeX, ovlSizeY, ovlPosX, ovlPosY); + } + return false; +} + +bool cDvbApi::OvlP(__u16 Brightness, __u16 Colour, __u16 Hue, __u16 Contrast) +{ + int result = 0; + ovlBrightness = Brightness; + ovlColour = Colour; + ovlHue = Hue; + ovlContrast = Contrast; + struct video_picture vp; + if (!ovlFbSet) + return false; + result |= ioctl(videoDev, VIDIOCGPICT, &vp); + vp.brightness = Brightness; + vp.colour = Colour; + vp.hue = Hue; + vp.contrast = Contrast; + vp.depth = ovlBpp; + vp.palette = ovlPalette; // gf: is this always ok? VIDEO_PALETTE_RGB565; + result |= ioctl(videoDev, VIDIOCSPICT, &vp); + return result == 0; +} + +bool cDvbApi::OvlO(bool Value) +{ + int result = 0; + if (!ovlGeoSet && Value) + return false; + int one = 1; + int zero = 0; + result |= ioctl(videoDev, VIDIOCCAPTURE, Value ? &one : &zero); + ovlStat = Value; + if (result) { + ovlStat = false; + return false; + } + return true; +} + #ifdef DEBUG_OSD void cDvbApi::SetColor(eDvbColor colorFg, eDvbColor colorBg) { @@ -1233,7 +1479,7 @@ void cDvbApi::Open(int w, int h) rows = h; #ifdef DEBUG_OSD window = subwin(stdscr, h, w, d, 0); - syncok(window, TRUE); + syncok(window, true); #define B2C(b) (((b) * 1000) / 255) #define SETCOLOR(n, r, g, b, o) init_color(n, B2C(r), B2C(g), B2C(b)) #else diff --git a/dvbapi.h b/dvbapi.h index a43e707..08ba4bf 100644 --- a/dvbapi.h +++ b/dvbapi.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.h 1.14 2000/09/10 10:03:29 kls Exp $ + * $Id: dvbapi.h 1.16 2000/09/17 12:15:05 kls Exp $ */ #ifndef __DVBAPI_H @@ -21,6 +21,12 @@ typedef unsigned char __u8; #include #include +// Overlay facilities +#define MAXCLIPRECTS 100 +typedef struct CRect { + signed short x, y, width, height; + }; + #define MenuLines 15 #define MenuColumns 40 @@ -70,6 +76,25 @@ public: // Closes down all DVB devices. // Must be called at the end of the program. + // Image Grab facilities + + bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1); + + // Overlay facilities + +private: + bool ovlStat, ovlGeoSet, ovlFbSet; + int ovlSizeX, ovlSizeY, ovlPosX, ovlPosY, ovlBpp, ovlPalette, ovlClips, ovlClipCount; + int ovlFbSizeX, ovlFbSizeY; + __u16 ovlBrightness, ovlColour, ovlHue, ovlContrast; + struct video_clip ovlClipRects[MAXCLIPRECTS]; +public: + bool OvlF(int SizeX, int SizeY, int FbAddr, int Bpp, int Palette); + bool OvlG(int SizeX, int SizeY, int PosX, int PosY); + bool OvlC(int ClipCount, CRect *Cr); + bool OvlP(__u16 Brightness, __u16 Color, __u16 Hue, __u16 Contrast); + bool OvlO(bool Value); + // On Screen Display facilities private: diff --git a/eit.c b/eit.c index c4c1200..d62c902 100644 --- a/eit.c +++ b/eit.c @@ -13,7 +13,7 @@ * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * - * $Id: eit.c 1.1 2000/09/03 10:22:25 kls Exp $ + * $Id: eit.c 1.3 2000/09/17 15:23:05 kls Exp $ ***************************************************************************/ #include "eit.h" @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include "tools.h" @@ -253,15 +252,12 @@ int cEIT::GetSection(unsigned char *buf, ushort PID, unsigned char sec) int seclen=0; unsigned short handle, pid; unsigned char section, sectionnum=0xff, maxsec=0; - struct pollfd pfd; if ((handle = SetBitFilter(PID, (sec<<8)|0x00ff, SECTION_CONTINUOS))==0xffff) return -1; seclen=0; - pfd.fd=fsvbi; - pfd.events=POLLIN; - if (poll(&pfd, 1, 20000)==0) + if (!cFile::AnyFileReady(fsvbi, 20000)) { //cerr << "Timeout\n"; return -1; @@ -312,13 +308,12 @@ char * cEIT::mjd2string(unsigned short mjd) /** */ int cEIT::GetEIT() { - unsigned char buf[1024]; + unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-) eit_t *eit; struct eit_loop_struct1 *eitloop; struct eit_short_event_descriptor_struct *eitevt; - int seclen; + unsigned int seclen; unsigned short handle, pid; - struct pollfd pfd; eit_event * pevt = (eit_event *)0; time_t tstart; @@ -344,9 +339,7 @@ int cEIT::GetEIT() tstart = time(NULL); while ((!evtRunning.bIsValid || !evtNext.bIsValid) && nReceivedEITs < 20 && difftime(time(NULL), tstart) < 4) { - pfd.fd=fsvbi; - pfd.events=POLLIN; - if (poll(&pfd, 1, 5000)==0) + if (!cFile::AnyFileReady(fsvbi, 5000)) { //cerr << "Timeout\n"; CloseFilter(handle); @@ -357,6 +350,8 @@ int cEIT::GetEIT() seclen=(buf[6]<<8)|buf[7]; pid=(buf[4]<<8)|buf[5]; + if (seclen >= sizeof(buf)) + seclen = sizeof(buf) - 1; read(fsvbi, buf, seclen); if (seclen < (int)(sizeof(eit_t) diff --git a/interface.c b/interface.c index 5633f86..3d01238 100644 --- a/interface.c +++ b/interface.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.c 1.15 2000/09/10 16:04:14 kls Exp $ + * $Id: interface.c 1.19 2000/09/19 17:41:23 kls Exp $ */ #include "interface.h" @@ -29,11 +29,19 @@ cInterface::cInterface(void) open = 0; cols[0] = 0; keyFromWait = kNone; + SVDRP = NULL; } -void cInterface::Init(void) +void cInterface::Init(int SVDRPport) { RcIo.SetCode(Keys.code, Keys.address); + if (SVDRPport) + SVDRP = new cSVDRP(SVDRPport); +} + +void cInterface::Cleanup(void) +{ + delete SVDRP; } void cInterface::Open(int NumCols, int NumLines) @@ -52,10 +60,6 @@ void cInterface::Close(void) unsigned int cInterface::GetCh(bool Wait) { -#ifdef DEBUG_OSD - timeout(0); - getch(); // just to make 'ncurses' display the window: -#endif if (RcIo.InputAvailable(Wait)) { unsigned int Command; return RcIo.GetCommand(&Command, NULL) ? Command : 0; @@ -65,21 +69,24 @@ unsigned int cInterface::GetCh(bool Wait) eKeys cInterface::GetKey(bool Wait) { + if (SVDRP) + SVDRP->Process(); eKeys Key = keyFromWait != kNone ? keyFromWait : Keys.Get(GetCh(Wait)); keyFromWait = kNone; return Key; } +void cInterface::PutKey(eKeys Key) +{ + keyFromWait = Key; +} + eKeys cInterface::Wait(int Seconds, bool KeepChar) { - int t0 = time_ms() + Seconds * 1000; eKeys Key = kNone; - - while (time_ms() < t0) { - Key = GetKey(); - if (Key != kNone) - break; - } + RcIo.Flush(500); + if (cFile::AnyFileReady(-1, Seconds * 1000)) + Key = GetKey(); if (KeepChar) keyFromWait = Key; return Key; @@ -227,7 +234,7 @@ void cInterface::QueryKeys(void) Keys.address = Address; WriteText(1, 5, "RC code detected!"); WriteText(1, 6, "Do not press any key..."); - RcIo.Flush(3); + RcIo.Flush(3000); ClearEol(0, 5); ClearEol(0, 6); break; diff --git a/interface.h b/interface.h index 4503dc6..8f7f1b8 100644 --- a/interface.h +++ b/interface.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.h 1.11 2000/09/10 10:35:46 kls Exp $ + * $Id: interface.h 1.13 2000/09/18 22:29:31 kls Exp $ */ #ifndef __INTERFACE_H @@ -12,6 +12,7 @@ #include "config.h" #include "dvbapi.h" +#include "svdrp.h" class cInterface { public: @@ -20,16 +21,19 @@ private: int open; int cols[MaxCols]; eKeys keyFromWait; + cSVDRP *SVDRP; unsigned int GetCh(bool Wait = true); void QueryKeys(void); void HelpButton(int Index, const char *Text, eDvbColor FgColor, eDvbColor BgColor); eKeys Wait(int Seconds = 1, bool KeepChar = false); public: cInterface(void); - void Init(void); + void Init(int SVDRPport = 0); + void Cleanup(void); void Open(int NumCols = MenuColumns, int NumLines = MenuLines); void Close(void); eKeys GetKey(bool Wait = true); + void PutKey(eKeys Key); void Clear(void); void ClearEol(int x, int y, eDvbColor Color = clrBackground); void SetCols(int *c); diff --git a/menu.c b/menu.c index ad803cf..114ea7e 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.26 2000/09/10 15:06:15 kls Exp $ + * $Id: menu.c 1.27 2000/09/11 21:13:46 kls Exp $ */ #include "menu.h" @@ -1190,7 +1190,7 @@ eOSState cDirectChannelSelect::ProcessKey(eKeys Key) if (number >= 0) { number = number * 10 + Key - k0; cChannel *channel = Channels.GetByNumber(number); - char *Name = channel ? channel->name : "*** Invalid Channel ***"; + const char *Name = channel ? channel->name : "*** Invalid Channel ***"; int BufSize = MenuColumns + 1; char buffer[BufSize]; snprintf(buffer, BufSize, "%d %s", number, Name); diff --git a/remote.c b/remote.c index 0476962..46565a8 100644 --- a/remote.c +++ b/remote.c @@ -6,7 +6,7 @@ * * Ported to LIRC by Carsten Koch 2000-06-16. * - * $Id: remote.c 1.11 2000/07/29 16:23:47 kls Exp $ + * $Id: remote.c 1.13 2000/09/19 17:40:52 kls Exp $ */ #include "remote.h" @@ -49,33 +49,29 @@ cRcIoBase::~cRcIoBase() cRcIoKBD::cRcIoKBD(void) { + f.Open(0); // stdin } cRcIoKBD::~cRcIoKBD() { } -void cRcIoKBD::Flush(int WaitSeconds) +void cRcIoKBD::Flush(int WaitMs) { - time_t t0 = time(NULL); + int t0 = time_ms(); timeout(10); for (;;) { while (getch() > 0) - t0 = time(NULL); - if (time(NULL) - t0 >= WaitSeconds) + t0 = time_ms(); + if (time_ms() - t0 >= WaitMs) break; } } bool cRcIoKBD::InputAvailable(bool Wait) { - timeout(Wait ? 1000 : 10); - int ch = getch(); - if (ch == ERR) - return false; - ungetch(ch); - return true; + return f.Ready(Wait); } bool cRcIoKBD::GetCommand(unsigned int *Command, unsigned short *) @@ -98,7 +94,7 @@ cRcIoRCU::cRcIoRCU(char *DeviceName) code = 0; address = 0xFFFF; lastNumber = 0; - if ((f = open(DeviceName, O_RDWR | O_NONBLOCK)) >= 0) { + if (f.Open(DeviceName, O_RDWR | O_NONBLOCK)) { struct termios t; if (tcgetattr(f, &t) == 0) { cfsetspeed(&t, B9600); @@ -107,17 +103,14 @@ cRcIoRCU::cRcIoRCU(char *DeviceName) return; } LOG_ERROR_STR(DeviceName); - close(f); + f.Close(); } else LOG_ERROR_STR(DeviceName); - f = -1; } cRcIoRCU::~cRcIoRCU() { - if (f >= 0) - close(f); } int cRcIoRCU::ReceiveByte(bool Wait) @@ -135,7 +128,7 @@ int cRcIoRCU::ReceiveByte(bool Wait) bool cRcIoRCU::SendByteHandshake(unsigned char c) { - if (f >= 0) { + if (f.IsOpen()) { int w = write(f, &c, 1); if (w == 1) { for (int reply = ReceiveByte(); reply >= 0;) { @@ -179,21 +172,21 @@ bool cRcIoRCU::SetMode(unsigned char Mode) return SendCommand(mode); } -void cRcIoRCU::Flush(int WaitSeconds) +void cRcIoRCU::Flush(int WaitMs) { - time_t t0 = time(NULL); + int t0 = time_ms(); for (;;) { while (ReceiveByte(false) >= 0) - t0 = time(NULL); - if (time(NULL) - t0 >= WaitSeconds) + t0 = time_ms(); + if (time_ms() - t0 >= WaitMs) break; } } bool cRcIoRCU::InputAvailable(bool Wait) { - return DataAvailable(f, Wait); + return f.Ready(Wait); } bool cRcIoRCU::GetCommand(unsigned int *Command, unsigned short *Address) @@ -349,22 +342,21 @@ cRcIoLIRC::cRcIoLIRC(char *DeviceName) struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, DeviceName); - f = socket(AF_UNIX, SOCK_STREAM, 0); - if (f >= 0) { - if (connect(f, (struct sockaddr *)&addr, sizeof(addr)) >= 0) + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock >= 0) { + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) >= 0) { + f.Open(sock); return; + } LOG_ERROR_STR(DeviceName); - close(f); + close(sock); } else LOG_ERROR_STR(DeviceName); - f = -1; } cRcIoLIRC::~cRcIoLIRC() { - if (f >= 0) - close(f); } const char *cRcIoLIRC::ReceiveString(void) @@ -389,24 +381,24 @@ const char *cRcIoLIRC::ReceiveString(void) return NULL; } -void cRcIoLIRC::Flush(int WaitSeconds) +void cRcIoLIRC::Flush(int WaitMs) { char buf[LIRC_BUFFER_SIZE]; - time_t t0 = time(NULL); + int t0 = time_ms(); for (;;) { while (InputAvailable(false)) { read(f, buf, sizeof(buf)); - t0 = time(NULL); + t0 = time_ms(); } - if (time(NULL) - t0 >= WaitSeconds) + if (time_ms() - t0 >= WaitMs) break; } } bool cRcIoLIRC::InputAvailable(bool Wait) { - return DataAvailable(f, Wait); + return f.Ready(Wait); } bool cRcIoLIRC::GetCommand(unsigned int *Command, unsigned short *) diff --git a/remote.h b/remote.h index 7b94ac7..03f9155 100644 --- a/remote.h +++ b/remote.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remote.h 1.7 2000/07/15 16:32:43 kls Exp $ + * $Id: remote.h 1.9 2000/09/19 17:39:36 kls Exp $ */ #ifndef __REMOTE_H @@ -12,6 +12,7 @@ #include #include +#include "tools.h" class cRcIoBase { protected: @@ -28,7 +29,7 @@ public: virtual void SetPoints(unsigned char Dp, bool On) {} virtual bool String(char *s) { return true; } virtual bool DetectCode(unsigned char *Code, unsigned short *Address) { return true; } - virtual void Flush(int WaitSeconds = 0) {} + virtual void Flush(int WaitMs = 0) {} virtual bool InputAvailable(bool Wait = false) = 0; virtual bool GetCommand(unsigned int *Command, unsigned short *Address = NULL) = 0; }; @@ -36,10 +37,12 @@ public: #if defined REMOTE_KBD class cRcIoKBD : public cRcIoBase { +private: + cFile f; public: cRcIoKBD(void); virtual ~cRcIoKBD(); - virtual void Flush(int WaitSeconds = 0); + virtual void Flush(int WaitMs = 0); virtual bool InputAvailable(bool Wait = false); virtual bool GetCommand(unsigned int *Command, unsigned short *Address = NULL); }; @@ -48,7 +51,7 @@ public: class cRcIoRCU : public cRcIoBase { private: - int f; + cFile f; unsigned char dp, code, mode; unsigned short address; int lastNumber; @@ -66,7 +69,7 @@ public: virtual void SetPoints(unsigned char Dp, bool On); virtual bool String(char *s); virtual bool DetectCode(unsigned char *Code, unsigned short *Address); - virtual void Flush(int WaitSeconds = 0); + virtual void Flush(int WaitMs = 0); virtual bool InputAvailable(bool Wait = false); virtual bool GetCommand(unsigned int *Command, unsigned short *Address = NULL); }; @@ -76,13 +79,13 @@ public: class cRcIoLIRC : public cRcIoBase { private: enum { LIRC_KEY_BUF = 8, LIRC_BUFFER_SIZE = 128 }; - int f; + cFile f; char keyName[LIRC_KEY_BUF]; const char *ReceiveString(void); public: cRcIoLIRC(char *DeviceName); virtual ~cRcIoLIRC(); - virtual void Flush(int WaitSeconds = 0); + virtual void Flush(int WaitMs = 0); virtual bool InputAvailable(bool Wait = false); virtual bool GetCommand(unsigned int *Command, unsigned short *Address = NULL); }; diff --git a/svdrp.c b/svdrp.c index 2af3ee5..c9ea7c3 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 1.6 2000/09/09 10:51:21 kls Exp $ + * $Id: svdrp.c 1.10 2000/09/17 13:39:37 kls Exp $ */ #define _GNU_SOURCE @@ -120,8 +120,15 @@ const char *HelpPages[] = { " Delete channel.", "DELT \n" " Delete timer.", + "GRAB [ jpeg | pnm [ [ ] ] ]\n" + " Grab the current frame and save it to the given file. Images can\n" + " be stored as JPEG (default) or PNM, at the given quality (default\n" + " is 'maximum', only applies to JPEG) and size (default is full screen).", "HELP [ ]\n" " The HELP command gives help info.", + "HITK [ ]\n" + " Hit the given remote control key. Without option a list of all\n" + " valid key names is given.", "LSTC [ | ]\n" " List channels. Without option, all channels are listed. Otherwise\n" " only the given channel is listed. If a name is given, all channels\n" @@ -147,6 +154,16 @@ const char *HelpPages[] = { " Create a new timer. Settings must be in the same format as returned\n" " by the LSTT command. It is an error if a timer with the same channel,\n" " day, start and stop time already exists.", + "OVLF \n" + " Set the size, address depth and palette of the overlay.", + "OVLG \n" + " Set the size and position of the overlay.", + "OVLC \n" + " Set the overlay clipping rectangles.", + "OVLP \n" + " Set the picture parameters for the overlay.", + "OVLO 0 | 1\n" + " Switch the overlay on or off.", "UPDT \n" " Updates a timer. Settings must be in the same format as returned\n" " by the LSTT command. If a timer with the same channel, day, start\n" @@ -206,7 +223,6 @@ const char *GetHelpPage(const char *Cmd) cSVDRP::cSVDRP(int Port) :socket(Port) { - filedes = -1; isyslog(LOG_INFO, "SVDRP listening on port %d", Port); } @@ -217,14 +233,13 @@ cSVDRP::~cSVDRP() void cSVDRP::Close(void) { - if (filedes >= 0) { + if (file.IsOpen()) { //TODO how can we get the *full* hostname? char buffer[MAXCMDBUFFER]; gethostname(buffer, sizeof(buffer)); Reply(221, "%s closing connection", buffer); isyslog(LOG_INFO, "closing connection"); //TODO store IP#??? - close(filedes); - filedes = -1; + file.Close(); } } @@ -232,11 +247,14 @@ bool cSVDRP::Send(const char *s, int length) { if (length < 0) length = strlen(s); - int wbytes = write(filedes, s, length); + int wbytes = write(file, s, length); if (wbytes == length) return true; - if (wbytes < 0) + if (wbytes < 0) { LOG_ERROR; + file.Close(); + cDvbApi::PrimaryDvbApi->OvlO(false); + } else //XXX while...??? esyslog(LOG_ERR, "Wrote %d bytes to client while expecting %d\n", wbytes, length); return false; @@ -244,7 +262,7 @@ bool cSVDRP::Send(const char *s, int length) void cSVDRP::Reply(int Code, const char *fmt, ...) { - if (filedes >= 0) { + if (file.IsOpen()) { if (Code != 0) { va_list ap; va_start(ap, fmt); @@ -274,7 +292,7 @@ void cSVDRP::Reply(int Code, const char *fmt, ...) } } -void cSVDRP::CmdChan(const char *Option) +void cSVDRP::CmdCHAN(const char *Option) { if (*Option) { int n = -1; @@ -331,13 +349,13 @@ void cSVDRP::CmdChan(const char *Option) Reply(550, "Unable to find channel \"%d\"", CurrentChannel); } -void cSVDRP::CmdDelc(const char *Option) +void cSVDRP::CmdDELC(const char *Option) { //TODO combine this with menu action (timers must be updated) Reply(502, "DELC not yet implemented"); } -void cSVDRP::CmdDelt(const char *Option) +void cSVDRP::CmdDELT(const char *Option) { if (*Option) { if (isnumber(Option)) { @@ -362,7 +380,68 @@ void cSVDRP::CmdDelt(const char *Option) Reply(501, "Missing timer number"); } -void cSVDRP::CmdHelp(const char *Option) +void cSVDRP::CmdGRAB(const char *Option) +{ + char *FileName = NULL; + bool Jpeg = true; + int Quality = -1, SizeX = -1, SizeY = -1; + if (*Option) { + char buf[strlen(Option) + 1]; + char *p = strcpy(buf, Option); + const char *delim = " \t"; + FileName = strtok(p, delim); + if ((p = strtok(NULL, delim)) != NULL) { + if (strcasecmp(p, "JPEG") == 0) + Jpeg = true; + else if (strcasecmp(p, "PNM") == 0) + Jpeg = false; + else { + Reply(501, "Unknown image type \"%s\"", p); + return; + } + } + if ((p = strtok(NULL, delim)) != NULL) { + if (isnumber(p)) + Quality = atoi(p); + else { + Reply(501, "Illegal quality \"%s\"", p); + return; + } + } + if ((p = strtok(NULL, delim)) != NULL) { + if (isnumber(p)) + SizeX = atoi(p); + else { + Reply(501, "Illegal sizex \"%s\"", p); + return; + } + if ((p = strtok(NULL, delim)) != NULL) { + if (isnumber(p)) + SizeY = atoi(p); + else { + Reply(501, "Illegal sizey \"%s\"", p); + return; + } + } + else { + Reply(501, "Missing sizey"); + return; + } + } + if ((p = strtok(NULL, delim)) != NULL) { + Reply(501, "Unexpected parameter \"%s\"", p); + return; + } + if (cDvbApi::PrimaryDvbApi->GrabImage(FileName, Jpeg, Quality, SizeX, SizeY)) + Reply(250, "Grabbed image %s", Option); + else + Reply(451, "Grab image failed"); + } + else + Reply(501, "Missing filename"); +} + +void cSVDRP::CmdHELP(const char *Option) { if (*Option) { const char *hp = GetHelpPage(Option); @@ -390,7 +469,27 @@ void cSVDRP::CmdHelp(const char *Option) Reply(214, "End of HELP info"); } -void cSVDRP::CmdLstc(const char *Option) +void cSVDRP::CmdHITK(const char *Option) +{ + if (*Option) { + eKeys k = Keys.Translate(Option); + if (k != kNone) { + Interface.PutKey(k); + Reply(250, "Key \"%s\" accepted", Option); + } + else + Reply(504, "Unknown key: \"%s\"", Option); + } + else { + Reply(-214, "Valid names for the HITK command:"); + for (int i = 0; i < kNone; i++) { + Reply(-214, " %s", Keys.keys[i].name); + } + Reply(214, "End of key list"); + } +} + +void cSVDRP::CmdLSTC(const char *Option) { if (*Option) { if (isnumber(Option)) { @@ -433,7 +532,7 @@ void cSVDRP::CmdLstc(const char *Option) } } -void cSVDRP::CmdLstt(const char *Option) +void cSVDRP::CmdLSTT(const char *Option) { if (*Option) { if (isnumber(Option)) { @@ -457,7 +556,7 @@ void cSVDRP::CmdLstt(const char *Option) } } -void cSVDRP::CmdModc(const char *Option) +void cSVDRP::CmdMODC(const char *Option) { if (*Option) { char *tail; @@ -486,7 +585,7 @@ void cSVDRP::CmdModc(const char *Option) Reply(501, "Missing channel settings"); } -void cSVDRP::CmdModt(const char *Option) +void cSVDRP::CmdMODT(const char *Option) { if (*Option) { char *tail; @@ -519,19 +618,19 @@ void cSVDRP::CmdModt(const char *Option) Reply(501, "Missing timer settings"); } -void cSVDRP::CmdMovc(const char *Option) +void cSVDRP::CmdMOVC(const char *Option) { //TODO combine this with menu action (timers must be updated) Reply(502, "MOVC not yet implemented"); } -void cSVDRP::CmdMovt(const char *Option) +void cSVDRP::CmdMOVT(const char *Option) { //TODO combine this with menu action Reply(502, "MOVT not yet implemented"); } -void cSVDRP::CmdNewc(const char *Option) +void cSVDRP::CmdNEWC(const char *Option) { if (*Option) { cChannel *channel = new cChannel; @@ -549,7 +648,7 @@ void cSVDRP::CmdNewc(const char *Option) Reply(501, "Missing channel settings"); } -void cSVDRP::CmdNewt(const char *Option) +void cSVDRP::CmdNEWT(const char *Option) { if (*Option) { cTimer *timer = new cTimer; @@ -573,7 +672,107 @@ void cSVDRP::CmdNewt(const char *Option) Reply(501, "Missing timer settings"); } -void cSVDRP::CmdUpdt(const char *Option) +void cSVDRP::CmdOVLF(const char *Option) +{ + if (*Option) { + int SizeX = 0, SizeY = 0, Bpp = 0, Palette = 0, FbAddr = 0; + if (5 == sscanf(Option, "%d %d %x %d %d", &SizeX, &SizeY, &FbAddr, &Bpp, &Palette)) { + //somehow_set_overlay_geometry; + if (cDvbApi::PrimaryDvbApi->OvlF(SizeX, SizeY, FbAddr, Bpp, Palette)) + Reply(250, "Overlay framebuffer set"); + else + Reply(451, "Illegal overlay framebuffer settings"); + } + else + Reply(501, "Could not parse overlay framebuffer settings"); + } + else + Reply(501, "Missing overlay framebuffer settings"); +} + +void cSVDRP::CmdOVLG(const char *Option) +{ + if (*Option) { + int SizeX = 0, SizeY = 0, PosX = 0, PosY = 0; + if (4 == sscanf(Option, "%d %d %d %d", &SizeX, &SizeY, &PosX, &PosY)) { + //somehow_set_overlay_geometry; + if (cDvbApi::PrimaryDvbApi->OvlG(SizeX, SizeY, PosX, PosY)) + Reply(250, "Overlay geometry set"); + else + Reply(451, "Illegal overlay geometry settings"); + } + else + Reply(501, "Could not parse overlay geometry settings"); + } + else + Reply(501, "Missing overlay geometry settings"); +} + +void cSVDRP::CmdOVLC(const char *Option) +{ + if (*Option) { + int ClipCount = 0; + unsigned char s[2 * MAXCLIPRECTS * sizeof(CRect) + 2]; + if (2 == sscanf(Option, "%d %s", &ClipCount, s)) { + // Base16-decoding of CRect-array: + unsigned char *p = (unsigned char*)ovlClipRects; + int i = 0, size = sizeof(CRect)*ClipCount; + for (int j = 0; i < size; i++) { + p[i] = (s[j++] - 65); + p[i] += (s[j++] - 65) << 4; + } + if (((unsigned)ClipCount == (i / sizeof(CRect))) && (ClipCount >= 0)) { + // apply it: + if (cDvbApi::PrimaryDvbApi->OvlC(ClipCount, ovlClipRects)) + Reply(250, "Overlay-Clipping set"); + else + Reply(451, "Illegal overlay clipping settings"); + return; + } + } + Reply(501, "Error parsing Overlay-Clipping settings"); + } + else + Reply(501, "Missing Clipping settings"); +} + +void cSVDRP::CmdOVLP(const char *Option) +{ + if (*Option) { + int Brightness = 0, Colour = 0, Hue = 0, Contrast = 0; + if (4 == sscanf(Option, "%d %d %d %d", &Brightness, &Colour, &Hue, &Contrast)) { + //somehow_set_overlay_picture_settings; + if (cDvbApi::PrimaryDvbApi->OvlP(Brightness, Colour, Hue, Contrast)) + Reply(250, "Overlay picture settings set"); + else + Reply(451, "Illegal overlay picture settings"); + } + else + Reply(501, "Could not parse overlay picture settings"); + } + else + Reply(501, "Missing overlay picture settings"); +} + +void cSVDRP::CmdOVLO(const char *Option) +{ + if (*Option) { + int Value; + if (1 == sscanf(Option, "%d", &Value)) { + //somehow_set_overlay_picture_settings; + if (cDvbApi::PrimaryDvbApi->OvlO(Value)) + Reply(250, "Overlay capture set"); + else + Reply(451, "Error setting overlay capture"); + } + else + Reply(501, "Could not parse status"); + } + else + Reply(501, "Missing overlay capture status"); +} + +void cSVDRP::CmdUPDT(const char *Option) { if (*Option) { cTimer *timer = new cTimer; @@ -612,19 +811,26 @@ void cSVDRP::Execute(char *Cmd) while (*s && !isspace(*s)) s++; *s++ = 0; - if (CMD("CHAN")) CmdChan(s); - else if (CMD("DELC")) CmdDelc(s); - else if (CMD("DELT")) CmdDelt(s); - else if (CMD("HELP")) CmdHelp(s); - else if (CMD("LSTC")) CmdLstc(s); - else if (CMD("LSTT")) CmdLstt(s); - else if (CMD("MODC")) CmdModc(s); - else if (CMD("MODT")) CmdModt(s); - else if (CMD("MOVC")) CmdMovc(s); - else if (CMD("MOVT")) CmdMovt(s); - else if (CMD("NEWC")) CmdNewc(s); - else if (CMD("NEWT")) CmdNewt(s); - else if (CMD("UPDT")) CmdUpdt(s); + if (CMD("CHAN")) CmdCHAN(s); + else if (CMD("DELC")) CmdDELC(s); + else if (CMD("DELT")) CmdDELT(s); + else if (CMD("GRAB")) CmdGRAB(s); + else if (CMD("HELP")) CmdHELP(s); + else if (CMD("HITK")) CmdHITK(s); + else if (CMD("LSTC")) CmdLSTC(s); + else if (CMD("LSTT")) CmdLSTT(s); + else if (CMD("MODC")) CmdMODC(s); + else if (CMD("MODT")) CmdMODT(s); + else if (CMD("MOVC")) CmdMOVC(s); + else if (CMD("MOVT")) CmdMOVT(s); + else if (CMD("NEWC")) CmdNEWC(s); + else if (CMD("NEWT")) CmdNEWT(s); + else if (CMD("OVLF")) CmdOVLF(s); + else if (CMD("OVLG")) CmdOVLG(s); + else if (CMD("OVLC")) CmdOVLC(s); + else if (CMD("OVLP")) CmdOVLP(s); + else if (CMD("OVLO")) CmdOVLO(s); + else if (CMD("UPDT")) CmdUPDT(s); else if (CMD("QUIT") || CMD("\x04")) Close(); else Reply(500, "Command unrecognized: \"%s\"", Cmd); @@ -632,9 +838,9 @@ void cSVDRP::Execute(char *Cmd) void cSVDRP::Process(void) { - bool SendGreeting = filedes < 0; + bool SendGreeting = !file.IsOpen(); - if (filedes >= 0 || (filedes = socket.Accept()) >= 0) { + if (file.IsOpen() || file.Open(socket.Accept())) { char buffer[MAXCMDBUFFER]; if (SendGreeting) { //TODO how can we get the *full* hostname? @@ -642,7 +848,7 @@ void cSVDRP::Process(void) time_t now = time(NULL); Reply(220, "%s SVDRP VideoDiskRecorder %s; %s", buffer, VDRVERSION, ctime(&now)); } - int rbytes = readstring(filedes, buffer, sizeof(buffer) - 1); + int rbytes = file.ReadString(buffer, sizeof(buffer) - 1); if (rbytes > 0) { //XXX overflow check??? // strip trailing whitespace: diff --git a/svdrp.h b/svdrp.h index 3c1cafa..12eb11e 100644 --- a/svdrp.h +++ b/svdrp.h @@ -4,12 +4,15 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: svdrp.h 1.2 2000/08/06 12:45:28 kls Exp $ + * $Id: svdrp.h 1.6 2000/09/17 13:22:04 kls Exp $ */ #ifndef __SVDRP_H #define __SVDRP_H +#include "dvbapi.h" +#include "tools.h" + class cSocket { private: int port; @@ -26,23 +29,31 @@ public: class cSVDRP { private: cSocket socket; - int filedes; + cFile file; + CRect ovlClipRects[MAXCLIPRECTS]; void Close(void); bool Send(const char *s, int length = -1); void Reply(int Code, const char *fmt, ...); - void CmdChan(const char *Option); - void CmdDelc(const char *Option); - void CmdDelt(const char *Option); - void CmdHelp(const char *Option); - void CmdLstc(const char *Option); - void CmdLstt(const char *Option); - void CmdModc(const char *Option); - void CmdModt(const char *Option); - void CmdMovc(const char *Option); - void CmdMovt(const char *Option); - void CmdNewc(const char *Option); - void CmdNewt(const char *Option); - void CmdUpdt(const char *Option); + void CmdCHAN(const char *Option); + void CmdDELC(const char *Option); + void CmdDELT(const char *Option); + void CmdGRAB(const char *Option); + void CmdHELP(const char *Option); + void CmdHITK(const char *Option); + void CmdLSTC(const char *Option); + void CmdLSTT(const char *Option); + void CmdMODC(const char *Option); + void CmdMODT(const char *Option); + void CmdMOVC(const char *Option); + void CmdMOVT(const char *Option); + void CmdNEWC(const char *Option); + void CmdNEWT(const char *Option); + void CmdOVLF(const char *Option); + void CmdOVLG(const char *Option); + void CmdOVLC(const char *Option); + void CmdOVLP(const char *Option); + void CmdOVLO(const char *Option); + void CmdUPDT(const char *Option); void Execute(char *Cmd); public: cSVDRP(int Port); diff --git a/timers.conf b/timers.conf index 9031d35..a625527 100644 --- a/timers.conf +++ b/timers.conf @@ -1,12 +1,14 @@ 1:15:M------:2128:2205:99:7:Neues: -1:3:-T-----:2013:2125:99:99:SevenDays -1:10:-T-----:2058:2202:99:10:Quarks: -1:26:-T-----:2255:0005:99:99:UFO: -0:3:---T---:2211:2300:99:10:Switch: +1:3:-T-----:2013:2125:99:99:SevenDays: +0:10:-T-----:2058:2202:99:10:Quarks: +1:26:-T-----:2255:0015:99:99:UFO: +1:3:---T---:2215:2315:99:10:IngoAppelt: 1:2:----F--:2140:2225:10:10:WWW: +1:1:----F--:2212:2325:99:99:7Tage7Koepfe: 1:11:-----S-:2158:2235:99:99:Computer: 1:2:-----S-:2213:2320:99:30:Wochenshow: -1:11:------S:2058:2120:99:10:Centauri: +1:11:------S:2013:2035:99:10:Centauri: +1:14:------S:2158:2235:99:14:MaxUndLisa: 1:15:MTWTF--:1828:1901:10:5:nano: 1:1:-TWTF--:0955:1040:99:99:Ellen: 1:1:MTWTF--:1553:1710:99:99:Hammerman: diff --git a/tools.c b/tools.c index e081ee7..79d2ee6 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.14 2000/09/09 12:53:34 kls Exp $ + * $Id: tools.c 1.19 2000/09/19 17:55:09 kls Exp $ */ #define _GNU_SOURCE @@ -12,31 +12,20 @@ #include #include #include +#if defined(DEBUG_OSD) +#include +#endif #include #include #include -#include #include +#include #include #define MaxBuffer 1000 int SysLogLevel = 3; -bool DataAvailable(int filedes, bool wait) -{ - if (filedes >= 0) { - fd_set set; - FD_ZERO(&set); - FD_SET(filedes, &set); - struct timeval timeout; - timeout.tv_sec = wait ? 1 : 0; - timeout.tv_usec = wait ? 0 : 10000; - return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && FD_ISSET(filedes, &set); - } - return false; -} - void writechar(int filedes, char c) { write(filedes, &c, sizeof(c)); @@ -56,32 +45,12 @@ char readchar(int filedes) bool readint(int filedes, int &n) { - return DataAvailable(filedes) && read(filedes, &n, sizeof(n)) == sizeof(n); -} - -int readstring(int filedes, char *buffer, int size, bool wait = false) -{ - int rbytes = 0; - - while (DataAvailable(filedes, wait)) { - int n = read(filedes, buffer + rbytes, size - rbytes); - if (n == 0) - break; // EOF - if (n < 0) { - LOG_ERROR; - break; - } - rbytes += n; - if (rbytes == size) - break; - wait = false; - } - return rbytes; + return cFile::AnyFileReady(filedes, 0) && read(filedes, &n, sizeof(n)) == sizeof(n); } void purge(int filedes) { - while (DataAvailable(filedes)) + while (cFile::AnyFileReady(filedes, 0)) readchar(filedes); } @@ -153,6 +122,14 @@ bool isnumber(const char *s) return true; } +const char *AddDirectory(const char *DirName, const char *FileName) +{ + static char *buf = NULL; + delete buf; + asprintf(&buf, "%s/%s", DirName && *DirName ? DirName : ".", FileName); + return buf; +} + #define DFCMD "df -m %s" uint FreeDiskSpaceMB(const char *Directory) @@ -313,6 +290,110 @@ void KillProcess(pid_t pid, int Timeout) } } +// --- cFile ----------------------------------------------------------------- + +bool cFile::files[FD_SETSIZE] = { false }; +int cFile::maxFiles = 0; + +cFile::cFile(void) +{ + f = -1; +} + +cFile::~cFile() +{ + Close(); +} + +bool cFile::Open(const char *FileName, int Flags, mode_t Mode) +{ + if (!IsOpen()) + return Open(open(FileName, Flags, Mode)); + esyslog(LOG_ERR, "ERROR: attempt to re-open %s", FileName); + return false; +} + +bool cFile::Open(int FileDes) +{ + if (FileDes >= 0) { + if (!IsOpen()) { + f = FileDes; + if (f >= 0) { + if (f < FD_SETSIZE) { + if (f >= maxFiles) + maxFiles = f + 1; + if (!files[f]) + files[f] = true; + else + esyslog(LOG_ERR, "ERROR: file descriptor %d already in files[]", f); + return true; + } + else + esyslog(LOG_ERR, "ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE); + } + } + else + esyslog(LOG_ERR, "ERROR: attempt to re-open file descriptor %d", FileDes); + } + return false; +} + +void cFile::Close(void) +{ + if (f >= 0) { + close(f); + files[f] = false; + f = -1; + } +} + +int cFile::ReadString(char *Buffer, int Size) +{ + int rbytes = 0; + bool wait = true; + + while (Ready(wait)) { + int n = read(f, Buffer + rbytes, 1); + if (n == 0) + break; // EOF + if (n < 0) { + LOG_ERROR; + return -1; + } + rbytes += n; + if (rbytes == Size || Buffer[rbytes - 1] == '\n') + break; + wait = false; + } + return rbytes; +} + +bool cFile::Ready(bool Wait) +{ + return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0); +} + +bool cFile::AnyFileReady(int FileDes, int TimeoutMs) +{ +#ifdef DEBUG_OSD + refresh(); +#endif + fd_set set; + FD_ZERO(&set); + for (int i = 0; i < maxFiles; i++) { + if (files[i]) + FD_SET(i, &set); + } + if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes]) + FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor + if (TimeoutMs == 0) + TimeoutMs = 10; // load gets too heavy with 0 + struct timeval timeout; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set)); +} + // --- cListObject ----------------------------------------------------------- cListObject::cListObject(void) diff --git a/tools.h b/tools.h index 1ef955c..5c749a7 100644 --- a/tools.h +++ b/tools.h @@ -4,16 +4,17 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.13 2000/09/09 12:53:10 kls Exp $ + * $Id: tools.h 1.15 2000/09/17 07:58:19 kls Exp $ */ #ifndef __TOOLS_H #define __TOOLS_H #include +#include #include #include -#include +#include #include extern int SysLogLevel; @@ -30,12 +31,10 @@ extern int SysLogLevel; #define DELETENULL(p) (delete (p), p = NULL) -bool DataAvailable(int filedes, bool wait = false); void writechar(int filedes, char c); void writeint(int filedes, int n); char readchar(int filedes); bool readint(int filedes, int &n); -int readstring(int filedes, char *buffer, int size, bool wait = false); void purge(int filedes); char *readline(FILE *f); char *strn0cpy(char *dest, const char *src, size_t n); @@ -44,6 +43,7 @@ char *skipspace(char *s); int time_ms(void); void delay_ms(int ms); bool isnumber(const char *s); +const char *AddDirectory(const char *DirName, const char *FileName); uint FreeDiskSpaceMB(const char *Directory); bool DirectoryOk(const char *DirName, bool LogErrors = false); bool MakeDirs(const char *FileName, bool IsDirectory = false); @@ -51,6 +51,24 @@ bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks = false); bool CheckProcess(pid_t pid); void KillProcess(pid_t pid, int Timeout = MAXPROCESSTIMEOUT); +class cFile { +private: + static bool files[]; + static int maxFiles; + int f; +public: + cFile(void); + ~cFile(); + operator int () { return f; } + bool Open(const char *FileName, int Flags, mode_t Mode = S_IRUSR | S_IWUSR | S_IRGRP); + bool Open(int FileDes); + void Close(void); + bool IsOpen(void) { return f >= 0; } + int ReadString(char *Buffer, int Size); + bool Ready(bool Wait = true); + static bool AnyFileReady(int FileDes = -1, int TimeoutMs = 1000); + }; + class cListObject { private: cListObject *prev, *next; diff --git a/vdr.c b/vdr.c index 4932bf2..91643c2 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/people/kls/vdr * - * $Id: vdr.c 1.30 2000/09/10 14:33:09 kls Exp $ + * $Id: vdr.c 1.35 2000/09/20 16:45:01 kls Exp $ */ #include @@ -34,7 +34,6 @@ #include "interface.h" #include "menu.h" #include "recording.h" -#include "svdrp.h" #include "tools.h" #include "videodir.h" @@ -46,9 +45,11 @@ static int Interrupted = 0; -void SignalHandler(int signum) +static void SignalHandler(int signum) { - Interrupted = signum; + if (signum != SIGPIPE) + Interrupted = signum; + signal(signum, SignalHandler); } int main(int argc, char *argv[]) @@ -58,9 +59,11 @@ int main(int argc, char *argv[]) #define DEFAULTSVDRPPORT 2001 int SVDRPport = DEFAULTSVDRPPORT; + const char *ConfigDirectory = NULL; bool DaemonMode = false; static struct option long_options[] = { + { "config", required_argument, NULL, 'c' }, { "daemon", no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "log", required_argument, NULL, 'l' }, @@ -71,10 +74,14 @@ int main(int argc, char *argv[]) int c; int option_index = 0; - while ((c = getopt_long(argc, argv, "dhl:p:v:", long_options, &option_index)) != -1) { + while ((c = getopt_long(argc, argv, "c:dhl:p:v:", long_options, &option_index)) != -1) { switch (c) { + case 'c': ConfigDirectory = optarg; + break; case 'd': DaemonMode = true; break; - case 'h': printf("Usage: vdr [OPTION]\n\n" + case 'h': printf("Usage: vdr [OPTION]\n\n" // for easier orientation, this is column 80| + " -c DIR, --config=DIR read config files from DIR (default is to read them\n" + " from the video directory)\n" " -h, --help display this help and exit\n" " -d, --daemon run in daemon mode\n" " -l LEVEL, --log=LEVEL set log level (default: 3)\n" @@ -108,6 +115,8 @@ int main(int argc, char *argv[]) } break; case 'v': VideoDirectory = optarg; + while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/') + optarg[strlen(optarg) - 1] = 0; break; default: abort(); } @@ -128,7 +137,7 @@ int main(int argc, char *argv[]) // Daemon mode: if (DaemonMode) { -#ifndef DEBUG_OSD +#if !defined(DEBUG_OSD) && !defined(REMOTE_KBD) pid_t pid = fork(); if (pid < 0) { fprintf(stderr, "%s\n", strerror(errno)); @@ -141,7 +150,7 @@ int main(int argc, char *argv[]) fclose(stdout); fclose(stderr); #else - fprintf(stderr, "vdr: can't run in daemon mode with DEBUG_OSD on!\n"); + fprintf(stderr, "vdr: can't run in daemon mode with DEBUG_OSD or REMOTE_KBD on!\n"); abort(); #endif } @@ -154,16 +163,19 @@ int main(int argc, char *argv[]) // Configuration data: - Setup.Load("setup.conf"); - Channels.Load("channels.conf"); - Timers.Load("timers.conf"); + if (!ConfigDirectory) + ConfigDirectory = VideoDirectory; + + Setup.Load(AddDirectory(ConfigDirectory, "setup.conf")); + Channels.Load(AddDirectory(ConfigDirectory, "channels.conf")); + Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")); #ifdef REMOTE_LIRC Keys.SetDummyValues(); #else - if (!Keys.Load(KEYS_CONF)) + if (!Keys.Load(AddDirectory(ConfigDirectory, KEYS_CONF))) Interface.LearnKeys(); #endif - Interface.Init(); + Interface.Init(SVDRPport); cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB); @@ -174,10 +186,10 @@ int main(int argc, char *argv[]) if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN); if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); + if (signal(SIGPIPE, SignalHandler) == SIG_IGN) signal(SIGPIPE, SIG_IGN); // Main program loop: - cSVDRP *SVDRP = SVDRPport ? new cSVDRP(SVDRPport) : NULL; cOsdBase *Menu = NULL; cReplayControl *ReplayControl = NULL; int LastChannel = -1; @@ -267,13 +279,11 @@ int main(int argc, char *argv[]) default: break; } } - if (SVDRP) - SVDRP->Process();//TODO lock menu vs. SVDRP? } isyslog(LOG_INFO, "caught signal %d", Interrupted); delete Menu; delete ReplayControl; - delete SVDRP; + Interface.Cleanup(); cDvbApi::Cleanup(); isyslog(LOG_INFO, "exiting"); if (SysLogLevel > 0) diff --git a/videodir.c b/videodir.c index 7bd6299..91d362d 100644 --- a/videodir.c +++ b/videodir.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: videodir.c 1.1 2000/07/29 15:21:42 kls Exp $ + * $Id: videodir.c 1.2 2000/09/15 13:23:47 kls Exp $ */ #include "videodir.h" @@ -137,7 +137,7 @@ int OpenVideoFile(const char *FileName, int Flags) } } } - int Result = open(ActualFileName, Flags, S_IRUSR | S_IWUSR); + int Result = open(ActualFileName, Flags, S_IRUSR | S_IWUSR | S_IRGRP); if (ActualFileName != FileName) delete ActualFileName; return Result; -- cgit v1.2.3