diff --git a/HISTORY b/HISTORY index 884fe241..24b23c97 100644 --- a/HISTORY +++ b/HISTORY @@ -3934,7 +3934,7 @@ Video Disk Recorder Revision History layers of subdirectories. - Removed EPG bugfix #0, because it removed actually important data. -2005-11-26: Version 1.3.37 +2005-11-27: Version 1.3.37 - Added compiler options "-fPIC -g" to all plugins (thanks to Rolf Ahrenberg). - Fixed initializing the day index when editing the weekday parameter of a @@ -3957,3 +3957,8 @@ Video Disk Recorder Revision History by Stefan Huelswitt). - Added a copy constructor to cString and fixed its assignment operator (thanks to Holger Brunn). +- The new function Skins.QueueMessage() can be called from a background thread + to queue a message for display. See VDR/skins.h for details. +- The SVDRP command MESG uses the new message queueing facility, so MESG + commands may now be executed at any time, and the message will be displayed + (no more "pending message"). diff --git a/PLUGINS.html b/PLUGINS.html index d1c787b3..bdaf1efb 100644 --- a/PLUGINS.html +++ b/PLUGINS.html @@ -14,18 +14,18 @@ Copyright © 2005 Klaus Schmidinger
www.cadsoft.de/vdr

-
  -Important modifications introduced in version 1.3.20 are marked like this. -
-
  +
  Important modifications introduced in version 1.3.21 are marked like this.
-
  +
  Important modifications introduced in version 1.3.30 are marked like this.
-
  +
  Important modifications introduced in version 1.3.31 are marked like this.
+
  +Important modifications introduced in version 1.3.37 are marked like this. +

VDR provides an easy to use plugin interface that allows additional functionality to be added to the program by implementing a dynamically loadable library file. @@ -58,9 +58,7 @@ structures and allows it to hook itself into specific areas to perform special a

  • Command line arguments
  • Command line help
  • Getting started -
     
  • Shutting down -
  • Main menu entry
  • User interaction
  • Housekeeping @@ -68,10 +66,10 @@ structures and allows it to hook itself into specific areas to perform special a
  • The Setup menu
  • Configuration files
  • Internationalization -
      +
     
  • Custom services
  • -
      +
     
  • SVDRP commands
  • Loading plugins into VDR @@ -87,7 +85,7 @@ structures and allows it to hook itself into specific areas to perform special a
  • Skins
  • Themes
  • Devices -
      +
     
  • Audio
  • Remote Control @@ -314,10 +312,8 @@ since VDR, for instance, has to create the plugin objects in order to get their command line help - and after that immediately destroys them again.

    The destructor has to clean up any data created by the plugin. -
      Any threads the plugin may have created shall be stopped in the Stop() function. -

    Of course, if your plugin doesn't define any member variables that need to be initialized (and deleted), you don't need to implement either of these functions. @@ -512,7 +508,6 @@ VDR to exit. If the plugin doesn't implement any background functionality or internationalized texts, it doesn't need to implement either of these functions. -
     

    Shutting down

    Stop it, right there!

    @@ -529,7 +524,6 @@ The Stop() function will only be called if a previous call to the Start() function of that plugin has returned true. The Stop() functions are called in the reverse order as the Start() functions were called. -


    Main menu entry

    @@ -872,7 +866,7 @@ Texts are first searched for in the Phrases registered for this plugin (i and then in the global VDR texts. So a plugin can make use of texts defined by the core VDR code. -
      +
     

    Custom services

    What can I do for you?

    @@ -943,7 +937,7 @@ any plugin handled the request, or false if no plugin handled the reque

    -
      +
     

    SVDRP commands

    Infinite Diversity in Infinite Combinations

    @@ -1521,6 +1515,22 @@ with the full required resolution. Only if this fails shall it use alternate areas. Drawing areas are always rectangular and may not overlap (but do not need to be adjacent). +
      +

    +Directly accessing the OSD is only allowed from the foreground thread, which +restricts this to a cOsdObject returned from the plugin's MainMenuAction() +function, or any of the skin classes a plugin might implement. +

    +If a plugin runs a separate thread and wants to issue a message directly from +within that tread, it can call + +

    +int cSkins::QueueMessage(eMessageType Type, const char *s, int Seconds = 0, int Timeout = 0);
    +

    + +to queue that message for display. See VDR/skins.h for details. +

    +


    Skins

    The emperor's new clothes

    @@ -1830,7 +1840,7 @@ private: virtual void Action(void); public: cMyAudio(void); -
      +
      virtual void Play(const uchar *Data, int Length, uchar Id);
    virtual void Mute(bool On); diff --git a/interface.c b/interface.c index 4f18ddda..56084784 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.69 2005/09/03 09:07:23 kls Exp $ + * $Id: interface.c 1.70 2005/11/27 15:31:06 kls Exp $ */ #include "interface.h" @@ -36,13 +36,6 @@ eKeys cInterface::GetKey(bool Wait) if (SVDRP) { if (SVDRP->Process()) Wait = false; - if (!Skins.IsOpen()) { - char *message = SVDRP->GetMessage(); - if (message) { - Skins.Message(mtInfo, message); - free(message); - } - } } return cRemote::Get(Wait ? 1000 : 10); } diff --git a/skins.c b/skins.c index e81e8825..c6213cf1 100644 --- a/skins.c +++ b/skins.c @@ -4,13 +4,49 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: skins.c 1.5 2005/10/02 10:12:10 kls Exp $ + * $Id: skins.c 1.6 2005/11/27 15:52:25 kls Exp $ */ #include "skins.h" #include "interface.h" #include "status.h" -#include "tools.h" + +// --- cSkinQueuedMessage ---------------------------------------------------- + +class cSkinQueuedMessage : public cListObject { + friend class cSkins; +private: + eMessageType type; + char *message; + int seconds; + int timeout; + tThreadId threadId; + eKeys key; + int state; + cMutex mutex; + cCondVar condVar; +public: + cSkinQueuedMessage(eMessageType Type, const char *s, int Seconds, int Timeout); + virtual ~cSkinQueuedMessage(); + }; + +cSkinQueuedMessage::cSkinQueuedMessage(eMessageType Type, const char *s, int Seconds, int Timeout) +{ + type = Type; + message = s ? strdup(s) : NULL; + seconds = Seconds; + timeout = Timeout; + threadId = cThread::ThreadId(); + key = kNone; + state = 0; // waiting +} + +cSkinQueuedMessage::~cSkinQueuedMessage() +{ + free(message); +} + +cList SkinQueuedMessages; // --- cSkinDisplay ---------------------------------------------------------- @@ -202,6 +238,95 @@ eKeys cSkins::Message(eMessageType Type, const char *s, int Seconds) return k; } +int cSkins::QueueMessage(eMessageType Type, const char *s, int Seconds, int Timeout) +{ + if (Type == mtStatus) { + dsyslog("cSkins::QueueMessage() called with mtStatus - ignored!"); + return kNone; + } + if (isempty(s)) { + dsyslog("cSkins::QueueMessage() called with empty message - ignored!"); + return kNone; + } + int k = kNone; + if (Timeout > 0) { + if (cThread::IsMainThread()) { + dsyslog("cSkins::QueueMessage() called from main thread with Timeout = %d - ignored!", Timeout); + return k; + } + cSkinQueuedMessage *m = new cSkinQueuedMessage(Type, s, Seconds, Timeout); + queueMessageMutex.Lock(); + SkinQueuedMessages.Add(m); + m->mutex.Lock(); + queueMessageMutex.Unlock(); + if (m->condVar.TimedWait(m->mutex, Timeout * 1000)) + k = m->key; + else + k = -1; // timeout, nothing has been displayed + m->state = 2; // done + m->mutex.Unlock(); + } + else { + queueMessageMutex.Lock(); + // Check if there is a waiting message w/o timeout for this thread: + if (Timeout == -1) { + for (cSkinQueuedMessage *m = SkinQueuedMessages.Last(); m; m = SkinQueuedMessages.Prev(m)) { + if (m->threadId == cThread::ThreadId()) { + if (m->state == 0 && m->timeout == -1) + m->state = 2; // done + break; + } + } + } + // Add the new message: + SkinQueuedMessages.Add(new cSkinQueuedMessage(Type, s, Seconds, Timeout)); + queueMessageMutex.Unlock(); + } + return k; +} + +void cSkins::ProcessQueuedMessages(void) +{ + if (!cThread::IsMainThread()) { + dsyslog("cSkins::ProcessQueuedMessages() called from background thread - ignored!"); + return; + } + cSkinQueuedMessage *msg = NULL; + // Get the first waiting message: + queueMessageMutex.Lock(); + for (cSkinQueuedMessage *m = SkinQueuedMessages.First(); m; m = SkinQueuedMessages.Next(m)) { + if (m->state == 0) { // waiting + m->state = 1; // active + msg = m; + break; + } + } + queueMessageMutex.Unlock(); + // Display the message: + if (msg) { + msg->mutex.Lock(); + if (msg->state == 1) { // might have changed since we got it + msg->key = Skins.Message(msg->type, msg->message, msg->seconds); + if (msg->timeout == 0) + msg->state = 2; // done + else + msg->condVar.Broadcast(); + } + msg->mutex.Unlock(); + } + // Remove done messages from the queue: + queueMessageMutex.Lock(); + for (;;) { + cSkinQueuedMessage *m = SkinQueuedMessages.First(); + if (m && m->state == 2) { // done + SkinQueuedMessages.Del(m); + } + else + break; + } + queueMessageMutex.Unlock(); +} + void cSkins::Flush(void) { if (cSkinDisplay::Current()) diff --git a/skins.h b/skins.h index 5fc63443..90ed0c3a 100644 --- a/skins.h +++ b/skins.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: skins.h 1.8 2005/05/15 14:41:41 kls Exp $ + * $Id: skins.h 1.9 2005/11/27 15:41:44 kls Exp $ */ #ifndef __SKINS_H @@ -16,6 +16,7 @@ #include "osd.h" #include "recording.h" #include "themes.h" +#include "thread.h" #include "tools.h" enum eMessageType { mtStatus = 0, mtInfo, mtWarning, mtError }; // will be used to calculate color offsets! @@ -298,6 +299,7 @@ class cSkins : public cList { private: cSkin *current; cSkinDisplayMessage *displayMessage; + cMutex queueMessageMutex; public: cSkins(void); ~cSkins(); @@ -312,6 +314,35 @@ public: ///< Displays the given message, either through a currently visible ///< display object that is capable of doing so, or by creating a ///< temporary cSkinDisplayMessage object. + ///< The return value is the key pressed by the user. If no user input + ///< has been received within Seconds (the default value of 0 results + ///< in the ///< value defined for "Message time" in the setup), kNone + ///< will be returned. + int QueueMessage(eMessageType Type, const char *s, int Seconds = 0, int Timeout = 0); + ///< Like Message(), but this function may be called from a background + ///< thread. The given message is put into a queue and the main program + ///< loop will display it as soon as this is suitable. If Timeout is 0, + ///< QueueMessage() returns immediately and the return value will be kNone. + ///< If a positive Timeout is given, the thread will wait at most the given + ///< number of seconds for the message to be actually displayed (note that + ///< the user may currently be doing something that doesn't allow for + ///< queued messages to be displayed immediately). If the timeout expires + ///< and the message hasn't been displayed yet, the return value is -1 + ///< and the message will be removed from the queue without being displayed. + ///< Positive values of Timeout are only allowed for background threads. + ///< If QueueMessage() is called from the foreground thread with a Timeout + ///< greater than 0, the call is ignored and nothing is displayed. + ///< Queued messages will be displayed in the sequence they have been + ///< put into the queue, so messages from different threads may appear + ///< mingled. If a particular thread queues a message with a Timeout of + ///< -1, and the previous message from the same thread also had a Timeout + ///< of -1, only the last message will be displayed. This can be used for + ///< progress displays, where only the most recent message is actually + ///< important. + ///< Type may only be mtInfo, mtWarning or mtError. A call with mtStatus + ///< will be ignored, as will be one with an empty message. + void ProcessQueuedMessages(void); + ///< Processes the first queued message, if any. void Flush(void); ///< Flushes the currently active cSkinDisplay, if any. }; diff --git a/svdrp.c b/svdrp.c index 8e09d41c..78b759aa 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.83 2005/11/05 11:21:38 kls Exp $ + * $Id: svdrp.c 1.84 2005/11/27 15:29:28 kls Exp $ */ #include "svdrp.h" @@ -35,6 +35,7 @@ #include "menu.h" #include "plugin.h" #include "remote.h" +#include "skins.h" #include "timers.h" #include "tools.h" #include "videodir.h" @@ -225,12 +226,9 @@ const char *HelpPages[] = { "LSTT [ ]\n" " List timers. Without option, all timers are listed. Otherwise\n" " only the given timer is listed.", - "MESG [ ]\n" - " Displays the given message on the OSD. If message is omitted, the\n" - " currently pending message (if any) will be returned. The message\n" - " will be displayed for a few seconds as soon as the OSD has become\n" - " idle. If a new MESG command is entered while the previous message\n" - " has not yet been displayed, the old message will be overwritten.", + "MESG \n" + " Displays the given message on the OSD. The message will be queued\n" + " and displayed whenever this is suitable.\n", "MODC \n" " Modify a channel. Settings must be in the same format as returned\n" " by the LSTC command.", @@ -363,7 +361,6 @@ cSVDRP::cSVDRP(int Port) numChars = 0; length = BUFSIZ; cmdLine = MALLOC(char, length); - message = NULL; lastActivity = 0; isyslog("SVDRP listening on port %d", Port); } @@ -371,7 +368,6 @@ cSVDRP::cSVDRP(int Port) cSVDRP::~cSVDRP() { Close(); - free(message); free(cmdLine); } @@ -954,15 +950,12 @@ void cSVDRP::CmdLSTT(const char *Option) void cSVDRP::CmdMESG(const char *Option) { if (*Option) { - free(message); - message = strdup(Option); - isyslog("SVDRP message: '%s'", message); - Reply(250, "Message stored"); + isyslog("SVDRP message: '%s'", Option); + Skins.QueueMessage(mtInfo, Option); + Reply(250, "Message queued"); } - else if (message) - Reply(250, "%s", message); else - Reply(550, "No pending message"); + Reply(501, "Missing message"); } void cSVDRP::CmdMODC(const char *Option) @@ -1489,11 +1482,4 @@ bool cSVDRP::Process(void) return false; } -char *cSVDRP::GetMessage(void) -{ - char *s = message; - message = NULL; - return s; -} - //TODO more than one connection??? diff --git a/svdrp.h b/svdrp.h index 2e182f21..469c47a5 100644 --- a/svdrp.h +++ b/svdrp.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: svdrp.h 1.25 2005/11/05 10:54:22 kls Exp $ + * $Id: svdrp.h 1.26 2005/11/27 15:26:42 kls Exp $ */ #ifndef __SVDRP_H @@ -48,7 +48,6 @@ private: int numChars; int length; char *cmdLine; - char *message; time_t lastActivity; void Close(bool Timeout = false); bool Send(const char *s, int length = -1); @@ -88,7 +87,6 @@ public: ~cSVDRP(); bool HasConnection(void) { return file.IsOpen(); } bool Process(void); - char *GetMessage(void); }; #endif //__SVDRP_H diff --git a/thread.c b/thread.c index 3fa41006..52747730 100644 --- a/thread.c +++ b/thread.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 1.45 2005/08/14 11:15:42 kls Exp $ + * $Id: thread.c 1.46 2005/11/27 15:15:53 kls Exp $ */ #include "thread.h" @@ -193,6 +193,7 @@ void cMutex::Unlock(void) // --- cThread --------------------------------------------------------------- +tThreadId cThread::mainThreadId = cThread::ThreadId(); bool cThread::emergencyExitRequested = false; cThread::cThread(const char *Description) diff --git a/thread.h b/thread.h index e72677f3..1b6200ce 100644 --- a/thread.h +++ b/thread.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 1.31 2005/10/09 11:12:32 kls Exp $ + * $Id: thread.h 1.32 2005/11/27 15:16:50 kls Exp $ */ #ifndef __THREAD_H @@ -72,6 +72,8 @@ public: void Unlock(void); }; +typedef pthread_t tThreadId; + class cThread { friend class cThreadLock; private: @@ -80,6 +82,7 @@ private: pthread_t childTid; cMutex mutex; char *description; + static tThreadId mainThreadId; static bool emergencyExitRequested; static void *StartThread(cThread *Thread); protected: @@ -112,6 +115,8 @@ public: bool Active(void); ///< Checks whether the thread is still alive. static bool EmergencyExit(bool Request = false); + static tThreadId ThreadId(void) { return pthread_self(); } + static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; } }; // cMutexLock can be used to easily set a lock on mutex and make absolutely diff --git a/vdr.c b/vdr.c index 5cf73751..8b206f97 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/vdr * - * $Id: vdr.c 1.219 2005/11/04 13:48:39 kls Exp $ + * $Id: vdr.c 1.220 2005/11/27 15:56:18 kls Exp $ */ #include @@ -677,6 +677,9 @@ int main(int argc, char *argv[]) else if (!LastCamMenu) LastCamMenu = time(NULL); } + // Queued messages: + if (!Skins.IsOpen()) + Skins.ProcessQueuedMessages(); // User Input: cOsdObject *Interact = Menu ? Menu : cControl::Control(); eKeys key = Interface->GetKey((!Interact || !Interact->NeedsFastResponse()) && time(NULL) - LastCamMenu > LASTCAMMENUTIMEOUT);